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

Обновление II

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

Update

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

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

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

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

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

Если вы новичок в сети (например, я), тогда вы должны получить свои очки для чтения, поп "Paint The Sky With Stars - The Best of Enya" на Wi-Hi-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 <[email protected]>

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 и проверяю журналы, я не вижу ошибок.

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

Ответ 1

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

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

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

Когда вы docker run ваши контейнеры, как правило, вводя оболочку script в 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. Результаты выглядят так:

/и т.д./хосты

Запуск 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

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

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

sudo docker exec -i -t Nginx bash

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



Настройка Nginx

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



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

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

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

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

Dockerfile

FROM ubuntu:14.04
MAINTAINER Your Name <[email protected]>

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;


}

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

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 использует для поиска строки, которую вы хотите заменить, необходимо обернуть var в двойные кавычки. Таким образом, правильный, хотя и привлекательный, синтаксис, как указано выше.

Итак, docker запустил наш контейнер и запустил запуск Nginx-Startup.sh script, который использовал sed, чтобы изменить значение APP_IP на соответствующую переменную env, указанную в команде sed. Теперь у нас есть файлы conf в нашем каталоге /etc/nginx/sites-enabled, которые имеют IP-адреса из env vars, которые устанавливаются при загрузке контейнера. В вашем файле 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. Это следующая игрушка, которую я собираюсь играть с настройкой.



ВАРИАНТ B: Используйте /etc/hosts Записи файла

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

UPDATE: См. @Wes Tod answer для получения инструкций о том, как заставить этот метод работать.

Здесь попытка, которую я сделал в api.myapp.conf:

upstream api_upstream{
    server API:3000;
}

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

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

Ответ 2

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

Ответ 3

AJB "Вариант B" можно заставить работать, используя базовое изображение 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;"]

My nginx config (aka: 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.

Ответ 4

Я попытался использовать популярный обратный прокси Джейсона Уайдера, который кодовым образом работает для всех, и узнал, что он не работает для всех (то есть: меня). И я новичок в 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-the-ugly/

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

Прежде всего, нам нужна сеть, на которой могут разговаривать все ваши бэкэнд-сервисы. Я назвал мой 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;
    }
}

Докер-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/*

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

Докер-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, которая возникает из-за того, что контейнеры не могут разговаривать друг с другом.

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

Ответ 5

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

В основном необходимо настроить привязку экземпляров и порты в файле компоновки докере и обновить вверх по потоку в nginx.conf соответственно.