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