Як налаштувати відображення портів 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.

Якщо ви новачок у роботі з мережами (як я), то вам слід дістати окуляри для читання, випустити "Розфарбуй небо зірками - найкраще від Enya" у Wi-Hi-Fi та розбити пиво - це буде деякий час, перш ніж ви дійсно зрозумієте, що саме ви намагаєтесь зробити. Підказка: Ви намагаєтесь застосувати a Service Discovery Layerу своєму Cluster Control Plane. Це дуже приємний спосіб провести суботню ніч.

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

Веселіться!



Оригінальна публікація

Здається, я не можу зрозуміти відображення портів для Dockerконтейнерів. Зокрема, як передавати запити від Nginx до іншого контейнера, слухаючи на іншому порту, на тому ж сервері.

У мене є файл Docker для контейнера 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


І все це чудово встає, але запити не передаються в інші контейнери / порти. І коли я потрапляю в контейнер Nginx і перевіряю журнали, я не бачу помилок.

Будь-яка допомога?


1
Будь ласка, розмістіть матеріал відповіді у своїй відповіді, а не в тілі питання.
— jscs

Відповіді:


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.

Результатом цього є зміни до файлів envvars та /etc/hostsфайлів, що знаходяться в контейнерах APIand 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та будь-яких інших конфігураційних файлах, які можуть бути у вашій /etc/nginx/sites-enabledпапці, щоб вставити IP значення.



ВАРІАНТ A: Налаштуйте Nginx, використовуючи ENV Vars

Це варіант, з яким я пішов, оскільки не зміг змусити /etc/hostsфайл працювати. Я спробую варіант В досить скоро і оновлю цю публікацію з будь-якими висновками.

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

Ось набір файлів конфігурації, з якими я опинився:

Докерфайл

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 vars у регулярному виразі, який sedвикористовує для пошуку рядка, який потрібно замінити, потрібно обернути var у подвійні лапки. Тож правильний, хоч і хиткий на вигляд, синтаксис такий, як вище.

Отже, докер запустив наш контейнер і запустив Nginx-Startup.shсценарій для запуску, який використав sedдля зміни значення APP_IPна відповідну ENVзмінну, яку ми вказали в sedкоманді. Тепер у нашому /etc/nginx/sites-enabledкаталозі є файли конф., Які мають IP-адреси з файлів ENVvars, встановлених при запуску контейнера. У вашому 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файли, але я не міг змусити цей метод працювати.

ОНОВЛЕННЯ: Дивіться відповідь @Wes Tod, щоб отримати вказівки щодо того, як змусити цей метод працювати.

Ось спроба, яку я зробив у api.myapp.conf:

upstream api_upstream{
    server API:3000;
}

Враховуючи, що у моєму /etc/hostsфайлі є запис приблизно такий: 172.0.0.2 APIя думав, це просто втягне значення, але, схоже, це не так.

У мене також були кілька допоміжних проблем із моїми Elastic Load Balancerджерелами з усіх AZ, так що, можливо, це було проблемою, коли я пробував цей маршрут. Натомість мені довелося навчитися керувати заміною рядків у Linux, так що це було цікаво. Я спробую це через деякий час і подивлюсь, як це буде.


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

Файл Docker:

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:

Докерфайл

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, яка виникає внаслідок неможливості розмови контейнерів між собою.

Я сподіваюся, ця відповідь позбавляє когось там години болю, оскільки отримати контейнери для спілкування між собою було чомусь важко зрозуміти, незважаючи на те, що я очікував, що це буде очевидним варіантом використання. Але знову ж таки, я німий. І, будь ласка, дайте мені знати, як я можу вдосконалити цей підхід.


Ах! Оле 502 Gateway Error, ганебний класик у наші дні. Дякую @gdbj за те, що ви витратили час на просування розмови та надали таке детальне рішення.
— AJB

Просто хотів подякувати, що знайшов час на це, це врятувало мене чимало клопоту. Дякую.
— Single Entity

10

Використовуючи посилання докера , ви можете зв’язати висхідний контейнер із контейнером nginx. Додатковою особливістю є те, що докер керує файлом хосту, а це означає, що ви зможете посилатися на пов'язаний контейнер, використовуючи ім'я, а не потенційно випадковий ip.


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
FYI Я використовую останнє офіційне зображення nginx (1.9.2), і воно, здається, працює для мене. Тож, можливо, вони вирішили проблему.
— Пейван,

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"

Докер-документи


Спочатку я думав, що у вас будуть проблеми зі зіткненням портів.
— gdbj

@gdbj Моя проблема полягала в роздільній здатності url / ip між контейнерами. Думаю, у нас було те саме. У вашому випадку ви використовуєте мережі, які також працюють нормально, у моєму випадку я просто пов'язую контейнери
— Diolor

Чудово, це все, що мені потрібно! Дякую за цю відповідь.
— Флоран Гмелін

2

Щойно знайдена стаття від Anand Mani Sankar, яка показує простий спосіб використання проксі-сервера nginx вгору за допомогою докер-композитора.

В основному потрібно налаштувати прив'язку екземпляра та порти у файлі складання докерів та відповідно оновити вгору на nginx.conf.


1
Стаття використовує linksзастарілі. Використовуйте мережі зараз: docs.docker.com/engine/userguide/networking
— gdbj

Чи спрацює магічна заміна nginx conf і в цьому випадку?
— Соня
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.