Как настроить сопоставление портов 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 соответственно.