Как настроить сопоставление портов Docker для использования Nginx в качестве восходящего прокси?

86

Обновление II

Сейчас 16 июля 2015 года, и все снова изменилось. Я обнаружил этот автомагический контейнер от Джейсона Уайлдера : https://github.com/jwilder/nginx-proxyи он решает эту проблему примерно столько же, сколько требуется для docker runконтейнера. Теперь это решение, которое я использую для решения этой проблемы.

Обновить

Сейчас июль 2015 года, и ситуация кардинально изменилась в отношении сетевых контейнеров Docker. Сейчас существует множество различных предложений, которые решают эту проблему (разными способами).

Вы должны использовать этот пост, чтобы получить общее представление о docker --linkподходе к обнаружению сервисов, который настолько прост, насколько это возможно, работает очень хорошо и на самом деле требует меньше фантазий, чем большинство других решений. Он ограничен тем, что довольно сложно объединить контейнеры на отдельные хосты в любом данном кластере, и контейнеры не могут быть перезапущены после подключения к сети, но предлагают быстрый и относительно простой способ объединения контейнеров в сеть на одном и том же хосте. Это хороший способ получить представление о том, что программное обеспечение, которое вы, вероятно, будете использовать для решения этой проблемы, на самом деле делает под капотом.

Кроме того, вы , вероятно , хотите , чтобы также проверить зарождающейся Docker в network, Hashicorp - х consul, Weaveworks weave, Джефф Линдсей progrium/consulиgliderlabs/registrator , и компании Google Kubernetes.

Там также CoreOS предложения , которые используют etcd, fleetи flannel.

И если вы действительно хотите устроить вечеринку, вы можете запустить кластер Mesosphere, или Deis, или Flynn.

Если вы новичок в нетворкинге (как и я), то вам следует достать очки для чтения, включить «Раскрась небо звездами - лучшее из Энии» по Wi-Fi и выпить пива - это будет некоторое время, прежде чем вы действительно поймете, что именно вы пытаетесь сделать. Подсказка: вы пытаетесь реализовать Service Discovery Layerв своем Cluster Control Plane. Это очень хороший способ провести субботний вечер.

Это очень весело, но мне жаль, что я не нашел время, чтобы лучше узнать о сетях в целом, прежде чем сразу погрузиться в них. В конце концов я нашел пару сообщений от доброжелательных богов Digital Ocean Tutorial: Introduction to Networking Terminologyи Understanding ... Networking. Я предлагаю сначала прочитать их несколько раз, прежде чем погрузиться в них.

Радоваться, веселиться!



Исходный пост

Кажется, я не могу понять отображение портов для Dockerконтейнеров. В частности, как передавать запросы от Nginx в другой контейнер, прослушивая другой порт на том же сервере.

У меня есть Dockerfile для контейнера Nginx:

FROM ubuntu:14.04
MAINTAINER Me <me@myapp.com>

RUN apt-get update && apt-get install -y htop git nginx

ADD sites-enabled/api.myapp.com /etc/nginx/sites-enabled/api.myapp.com
ADD sites-enabled/app.myapp.com /etc/nginx/sites-enabled/app.myapp.com
ADD nginx.conf /etc/nginx/nginx.conf

RUN echo "daemon off;" >> /etc/nginx/nginx.conf

EXPOSE 80 443

CMD ["service", "nginx", "start"]



И тогда api.myapp.comконфигурационный файл выглядит так:

upstream api_upstream{

    server 0.0.0.0:3333;

}


server {

    listen 80;
    server_name api.myapp.com;
    return 301 https://api.myapp.com/$request_uri;

}


server {

    listen 443;
    server_name api.mypp.com;

    location / {

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://api_upstream;

    }

}

А потом еще один app.myapp.com.

А потом бегу:

sudo docker run -p 80:80 -p 443:443 -d --name Nginx myusername/nginx


И все это работает нормально, но запросы не проходят через другие контейнеры / порты. И когда я ssh в контейнер Nginx и просматриваю журналы, я не вижу ошибок.

Любая помощь?

AJB
источник
1
Пожалуйста, включите материал для ответа в свой ответ, а не в текст вопроса.
jscs 07

Ответы:

56

Ответ @T0xicCode правильный, но я подумал, что расскажу подробнее, поскольку на самом деле мне потребовалось около 20 часов, чтобы наконец реализовать рабочее решение.

Если вы хотите запустить Nginx в собственном контейнере и использовать его в качестве обратного прокси для балансировки нагрузки нескольких приложений на одном экземпляре сервера, вам необходимо выполнить следующие шаги:

Свяжите свои контейнеры

Когда вы используете docker runсвои контейнеры, обычно путем ввода сценария оболочки User Data, вы можете объявлять ссылки на любые другие запущенные контейнеры. Это означает, что вам нужно запускать свои контейнеры по порядку, и только последние контейнеры могут связываться с первыми. Вот так:

#!/bin/bash
sudo docker run -p 3000:3000 --name API mydockerhub/api
sudo docker run -p 3001:3001 --link API:API --name App mydockerhub/app
sudo docker run -p 80:80 -p 443:443 --link API:API --link App:App --name Nginx mydockerhub/nginx

Таким образом , в этом примере, APIконтейнер не связан ни с какими другими, но Appконтейнер связан APIи Nginxсвязан с как APIи App.

Результатом этого является изменения в envВарс и /etc/hostsфайлы , которые находятся в пределах APIи Appконтейнеров. Результаты выглядят так:

/ etc / hosts

Запуск cat /etc/hostsв вашем Nginxконтейнере приведет к следующему:

172.17.0.5  0fd9a40ab5ec
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3  App
172.17.0.2  API



ENV Vars

Запуск envв вашем Nginxконтейнере приведет к следующему:

API_PORT=tcp://172.17.0.2:3000
API_PORT_3000_TCP_PROTO=tcp
API_PORT_3000_TCP_PORT=3000
API_PORT_3000_TCP_ADDR=172.17.0.2

APP_PORT=tcp://172.17.0.3:3001
APP_PORT_3001_TCP_PROTO=tcp
APP_PORT_3001_TCP_PORT=3001
APP_PORT_3001_TCP_ADDR=172.17.0.3

Я усек многие фактические вары, но это ключевые значения, необходимые для прокси-трафика в ваши контейнеры.

Чтобы получить оболочку для выполнения вышеуказанных команд в работающем контейнере, используйте следующее:

sudo docker exec -i -t Nginx bash

Вы можете видеть, что теперь у вас есть как /etc/hostsзаписи файлов, так и envвары, содержащие локальный IP-адрес для любого из связанных контейнеров. Насколько я могу судить, это все, что происходит, когда вы запускаете контейнеры с объявленными параметрами ссылки. Но теперь вы можете использовать эту информацию для настройки nginxв своем Nginxконтейнере.



Настройка Nginx

Здесь все становится немного сложнее, и есть несколько вариантов. Вы можете настроить свои сайты так, чтобы они указывали на запись в созданном /etc/hostsфайле docker, или вы можете использовать ENVvars и запустить замену строки (я использовал sed) в вашем nginx.confи любых других файлах conf, которые могут быть в вашей /etc/nginx/sites-enabledпапке, чтобы вставить IP ценности.



ВАРИАНТ А. Настройка Nginx с использованием переменных ENV

Это вариант, который я выбрал, потому что я не мог заставить /etc/hostsработать параметр файла. Вскоре я попробую вариант Б и дополню этот пост любыми выводами.

Ключевое различие между этой опцией и использованием /etc/hostsопции файла заключается в том, как вы пишете, Dockerfileчтобы использовать сценарий оболочки в качестве CMDаргумента, который, в свою очередь, обрабатывает замену строки для копирования значений IP ENVв ваш файл (ы) conf.

Вот набор файлов конфигурации, которые у меня остались:

Dockerfile

FROM ubuntu:14.04
MAINTAINER Your Name <you@myapp.com>

RUN apt-get update && apt-get install -y nano htop git nginx

ADD nginx.conf /etc/nginx/nginx.conf
ADD api.myapp.conf /etc/nginx/sites-enabled/api.myapp.conf
ADD app.myapp.conf /etc/nginx/sites-enabled/app.myapp.conf
ADD Nginx-Startup.sh /etc/nginx/Nginx-Startup.sh

EXPOSE 80 443

CMD ["/bin/bash","/etc/nginx/Nginx-Startup.sh"]

nginx.conf

daemon off;
user www-data;
pid /var/run/nginx.pid;
worker_processes 1;


events {
    worker_connections 1024;
}


http {

    # Basic Settings

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 33;
    types_hash_max_size 2048;

    server_tokens off;
    server_names_hash_bucket_size 64;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;


    # Logging Settings
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;


    # Gzip Settings

gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 3;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/xml text/css application/x-javascript application/json;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";

    # Virtual Host Configs  
    include /etc/nginx/sites-enabled/*;

    # Error Page Config
    #error_page 403 404 500 502 /srv/Splash;


}

ПРИМЕЧАНИЕ. Важно включить его daemon off;в nginx.confфайл, чтобы убедиться, что ваш контейнер не выйдет сразу после запуска.

api.myapp.conf

upstream api_upstream{
    server APP_IP:3000;
}

server {
    listen 80;
    server_name api.myapp.com;
    return 301 https://api.myapp.com/$request_uri;
}

server {
    listen 443;
    server_name api.myapp.com;

    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://api_upstream;
    }

}

Nginx-Startup.sh

#!/bin/bash
sed -i 's/APP_IP/'"$API_PORT_3000_TCP_ADDR"'/g' /etc/nginx/sites-enabled/api.myapp.com
sed -i 's/APP_IP/'"$APP_PORT_3001_TCP_ADDR"'/g' /etc/nginx/sites-enabled/app.myapp.com

service nginx start

Я оставлю вам делать домашнюю работу по большей части содержимого nginx.confи api.myapp.conf.

Магия происходит в Nginx-Startup.shкоторой мы используем , sedчтобы сделать замену строки на APP_IPзаполнитель , который мы написали в upstreamблок наших api.myapp.confи app.myapp.confфайлов.

Этот вопрос на ask.ubuntu.com очень хорошо объясняет: поиск и замена текста в файле с помощью команд

GOTCHA В OSX sedпараметры обрабатываются по-другому, особенно -iфлаг. В Ubuntu -iфлаг будет обрабатывать замену «на месте»; он откроет файл, изменит текст, а затем «сохранит» тот же файл. В OSX для -iфлага требуется расширение файла, который должен иметь полученный файл. Если вы работаете с файлом без расширения, вы должны ввести '' в качестве значения -iфлага.

GOTCHA Чтобы использовать переменные ENV в регулярном выражении, которое sedиспользуется для поиска строки, которую вы хотите заменить, вам необходимо заключить переменную в двойные кавычки. Таким образом, правильный, хотя и нечеткий синтаксис такой, как указано выше.

Итак, докер запустил наш контейнер и запустил Nginx-Startup.shскрипт для запуска, который использовался sedдля изменения значения APP_IPсоответствующей ENVпеременной, которую мы указали в sedкоманде. Теперь у нас есть файлы conf в нашем /etc/nginx/sites-enabledкаталоге, которые имеют IP-адреса из ENVпеременных, которые докер установил при запуске контейнера. В вашем api.myapp.confфайле вы увидите, что upstreamблок изменился на этот:

upstream api_upstream{
    server 172.0.0.2:3000;
}

Видимый вами IP-адрес может быть другим, но я заметил, что это обычно 172.0.0.x.

Теперь у вас должна быть правильная маршрутизация.

GOTCHA Вы не можете перезапустить / повторно запустить какие-либо контейнеры после запуска первого экземпляра. Docker предоставляет каждому контейнеру новый IP-адрес при запуске и, похоже, не использует повторно тот, который использовался ранее. Таким api.myapp.comобразом, в первый раз будет получено 172.0.0.2, а в следующий раз - 172.0.0.4. Но Nginxон уже установил первый IP-адрес в своих файлах conf или в своем /etc/hostsфайле, поэтому он не сможет определить новый IP-адрес для api.myapp.com. Решение этой проблемы, скорее всего, будет использовать CoreOSи его etcdсервис, который, в моем ограниченном понимании, действует как общий ENVдля всех машин, зарегистрированных в одном CoreOSкластере. Это следующая игрушка, с которой я собираюсь поиграться.



ВАРИАНТ Б: Использование /etc/hostsфайловых записей

Это должен быть более быстрый и простой способ сделать это, но я не мог заставить его работать. Якобы вы просто ввести значение /etc/hostsвступления в ваших api.myapp.confи app.myapp.confфайлов, но я не мог получить этот метод работы.

ОБНОВЛЕНИЕ: см. Ответ @Wes Tod, чтобы узнать, как заставить этот метод работать.

Вот попытка, которую я предпринял api.myapp.conf:

upstream api_upstream{
    server API:3000;
}

Учитывая, что в моем /etc/hostsфайле есть такая запись : 172.0.0.2 APIя подумал, что она просто получит значение, но, похоже, это не так.

У меня также была пара дополнительных проблем с моими Elastic Load Balancerисточниками из всех АЗ, так что это могло быть проблемой, когда я пробовал этот маршрут. Вместо этого мне пришлось научиться обрабатывать замену строк в Linux, так что это было весело. Я попробую через некоторое время и посмотрю, как это пойдет.

AJB
источник
2
Еще одна проблема с использованием ссылок заключается в том, что если вы перезапустите контейнер API, он, скорее всего, получит новый IP-адрес. Это не отражено в файле / etc / hosts контейнера nginx, который будет продолжать использовать старый IP-адрес и, следовательно, также должен быть перезапущен.
judoole
13

Я попытался использовать популярный обратный прокси-сервер Джейсона Уайлдера, который волшебным образом работает для всех, и узнал, что он работает не для всех (то есть для меня). И я новичок в NGINX, и мне не понравилось, что я не разбирался в технологиях, которые пытался использовать.

Хотел добавить свои 2 цента, потому что приведенное выше обсуждение linkingконтейнеров вместе теперь устарело, поскольку это устаревшая функция. Итак, вот объяснение того, как это сделать с помощью networks. Этот ответ является полным примером настройки nginx в качестве обратного прокси для статически выгружаемого веб-сайта с использованием Docker Composeконфигурации nginx.

TL; DR;

Добавьте службы, которым необходимо общаться друг с другом, в заранее заданную сеть. Для пошагового обсуждения сетей Docker я кое-что узнал здесь: https://technologyconversations.com/2016/04/25/docker-networking-and-dns-the-good-the-bad-and- уродливый /

Определите сеть

Прежде всего, нам нужна сеть, по которой могут общаться все ваши серверные службы. Я позвонил в свой, webно это может быть что угодно.

docker network create web

Создайте приложение

Мы просто сделаем простое приложение для веб-сайта. Веб-сайт представляет собой простую страницу index.html, обслуживаемую контейнером nginx. Содержимое - это подключенный том к хосту в папкеcontent

DockerFile:

FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf

default.conf

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

docker-compose.yml

version: "2"

networks:
  mynetwork:
    external:
      name: web

services:
  nginx:
    container_name: sample-site
    build: .
    expose:
      - "80"
    volumes:
      - "./content/:/var/www/html/"
    networks:
      default: {}
      mynetwork:
        aliases:
          - sample-site

Обратите внимание, что здесь нам больше не нужно сопоставление портов. Мы просто открываем порт 80. Это удобно, чтобы избежать конфликтов портов.

Запустите приложение

Запустите этот сайт с помощью

docker-compose up -d

Несколько забавных проверок сопоставлений DNS для вашего контейнера:

docker exec -it sample-site bash
ping sample-site

Этот пинг должен работать внутри вашего контейнера.

Создайте прокси

Обратный прокси Nginx:

Dockerfile

FROM nginx

RUN rm /etc/nginx/conf.d/*

Мы сбрасываем всю конфигурацию виртуального хоста, так как собираемся настроить ее.

docker-compose.yml

version: "2"

networks:
  mynetwork:
    external:
      name: web


services:
  nginx:
    container_name: nginx-proxy
    build: .
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf.d/:/etc/nginx/conf.d/:ro
      - ./sites/:/var/www/
    networks:
      default: {}
      mynetwork:
        aliases:
          - nginx-proxy

Запустите прокси

Запустите прокси с помощью наших надежных

docker-compose up -d

Предполагая, что проблем нет, у вас есть два запущенных контейнера, которые могут общаться друг с другом, используя свои имена. Давайте проверим это.

docker exec -it nginx-proxy bash
ping sample-site
ping nginx-proxy

Настроить виртуальный хост

Последняя деталь - настроить файл виртуального хостинга, чтобы прокси мог направлять трафик в зависимости от того, что вы хотите настроить:

sample-site.conf для конфигурации нашего виртуального хостинга:

  server {
    listen 80;
    listen [::]:80;

    server_name my.domain.com;

    location / {
      proxy_pass http://sample-site;
    }

  }

В зависимости от того, как был настроен прокси, вам понадобится этот файл, хранящийся в вашей локальной conf.dпапке, которую мы смонтировали с помощью volumesобъявления в docker-composeфайле.

И последнее, но не менее важное: скажите nginx перезагрузить его конфигурацию.

docker exec nginx-proxy service nginx reload

Эта последовательность шагов - кульминация многих часов головной боли, когда я боролся с болезненной ошибкой 502 Bad Gateway и впервые изучал nginx, поскольку большую часть моего опыта я получил с Apache.

Этот ответ демонстрирует, как устранить ошибку 502 Bad Gateway, которая возникает из-за того, что контейнеры не могут взаимодействовать друг с другом.

Я надеюсь, что этот ответ избавит кого-то от нескольких часов боли, поскольку по какой-то причине было действительно сложно понять, как контейнеры разговаривают друг с другом, несмотря на то, что я ожидал очевидного варианта использования. Но опять же, я тупой. И, пожалуйста, дайте мне знать, как я могу улучшить этот подход.

gdbj
источник
Ах! Оле 502 Gateway Error, печально известная классика наших дней. Спасибо @gdbj за потраченное время на продвижение разговора и предоставление такого подробного решения.
AJB
Просто хотел поблагодарить за то, что нашли для этого время, это избавило меня от лишних хлопот. Спасибо.
Single Entity
10

Используя ссылки докеров , вы можете связать восходящий контейнер с контейнером nginx. Дополнительная функция заключается в том, что докер управляет файлом хоста, что означает, что вы сможете ссылаться на связанный контейнер, используя имя, а не потенциально случайный IP-адрес.

T0xicCode
источник
7

«Вариант B» AJB можно заставить работать, используя базовый образ Ubuntu и настраивая nginx самостоятельно. (Это не сработало, когда я использовал образ Nginx из Docker Hub.)

Вот файл Docker, который я использовал:

FROM ubuntu
RUN apt-get update && apt-get install -y nginx
RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log
RUN rm -rf /etc/nginx/sites-enabled/default
EXPOSE 80 443
COPY conf/mysite.com /etc/nginx/sites-enabled/mysite.com
CMD ["nginx", "-g", "daemon off;"]

Моя конфигурация nginx (также известная как conf / mysite.com):

server {
    listen 80 default;
    server_name mysite.com;

    location / {
        proxy_pass http://website;
    }
}

upstream website {
    server website:3000;
}

И наконец, как я запускаю свои контейнеры:

$ docker run -dP --name website website
$ docker run -dP --name nginx --link website:website nginx

Это заставило меня работать, поэтому мой nginx направил восходящий поток на второй контейнер докеров, который открыл порт 3000.

Уэс Тодд
источник
Спасибо за помощь, Уэс! Я попробую, когда вернусь из отпуска.
AJB,
У меня было несколько подобных проблем с официальными изображениями. Я обнаружил, что гораздо лучше базироваться на поле ubuntu, а затем напрямую извлекать строки из файлов докеров. Не то чтобы это было необходимо, но увы ....
Уэс Тодд
1
Это здорово, но я не понимаю, как конфигурация nginx просто знает значение «веб-сайт». Магия Nginx или магия докеров, или что-то еще?
Кайл Чадха
1
Строка upstream website {определяет ценность сайта для nginx. Это то, что вы затем используете в своем proxy_pass. Часть этого докера просто использует то же имя для согласованности, но не имеет ничего общего с настройкой nginx. Чтобы было немного понятнее, прокси-пропуск должен читать:upstream website { server localhost:3000; }
Уэс Тодд
2
К вашему сведению, я использую последний официальный образ nginx (1.9.2), и, похоже, он у меня работает. Так что, возможно, они исправили проблему.
Pejvan
6

Ответ @gdbj - отличное объяснение и самый последний ответ. Однако есть более простой подход.

Поэтому, если вы хотите перенаправить весь трафик с nginx, прослушивающего 80другой контейнер 8080, минимальная конфигурация может быть всего:

nginx.conf:

server {
    listen 80;

    location / {
        proxy_pass http://client:8080; # this one here
        proxy_redirect off;
    }

}

docker-compose.yml

version: "2"
services:
  entrypoint:
    image: some-image-with-nginx
    ports:
      - "80:80"
    links:
      - client  # will use this one here

  client:
    image: some-image-with-api
    ports:
      - "8080:8080"

Документы Docker

Диолор
источник
Сначала я подумал, что у вас проблемы с конфликтом портов.
gdbj 09
@gdbj Моя проблема заключалась в разрешении URL / IP между контейнерами. Думаю, у нас было то же самое. В вашем случае вы используете сети, которые тоже работают нормально, в моем случае я просто связываю контейнеры
Diolor
Отлично, это все, что мне нужно! Спасибо за ответ.
Флоран Гмелин
2

Только что нашел статью от Ананда Мани Санкара, в которой показан простой способ использования прокси-сервера nginx с docker composer.

По сути, необходимо настроить связывание экземпляров и порты в файле docker-compose и соответственно обновить восходящий поток в nginx.conf.

Исборг
источник
1
В статье используются linksустаревшие. Используйте сети прямо сейчас: docs.docker.com/engine/userguide/networking
gdbj
Будет ли работать волшебная подстановка nginx conf и в этом случае?
Соня