web-dev-qa-db-fra.com

Comment configurer le mappage de port Docker pour utiliser Nginx en tant que proxy en amont?

Mise à jour II

Nous sommes le 16 juillet 2015 et les choses ont encore changé. J'ai découvert ce conteneur automagique à partir de Jason Wilder : https://github.com/jwilder/nginx-proxy et le problème est résolu aussi longtemps qu'il faut jusqu'à _docker run_ le conteneur. . C'est maintenant la solution que j'utilise pour résoudre ce problème.

Mise à jour

Nous sommes en juillet 2015 et les choses ont radicalement changé en ce qui concerne la mise en réseau des conteneurs Docker. Il existe maintenant de nombreuses offres différentes qui résolvent ce problème (de différentes manières).

Vous devriez utiliser cet article pour acquérir une compréhension de base de l’approche docker --link de la découverte de service, qui est aussi élémentaire que possible, fonctionne très bien et nécessite en fait moins de danse de fantaisie que la plupart des autres solutions. Il est limité en ce sens qu'il est assez difficile de mettre en réseau des conteneurs sur des hôtes distincts dans un cluster donné. Les conteneurs ne peuvent pas être redémarrés une fois en réseau, mais offrent un moyen rapide et relativement simple de mettre en réseau des conteneurs sur le même hôte. C'est un bon moyen d'avoir une idée de ce que le logiciel que vous utiliserez probablement pour résoudre ce problème est en train de fonctionner sous le capot.

En outre, vous voudrez probablement également jeter un œil à la nouvelle version de Docker network , celle d'Hashicorp consul , Weaveworks weave , de Jeff Lindsay progrium/consul_ & _gliderlabs/registrator , et celle de Google Kubernetes .

Il existe également les offres CoreOS qui utilisent etcd , fleet et flannel .

Et si vous voulez vraiment organiser une fête, vous pouvez créer un cluster à exécuter Mesosphere , ou Deis , ou Flynn .

Si vous débutez dans le réseautage (comme moi), vous devriez alors sortir vos lunettes de lecture, pop "Peignez le ciel avec des étoiles - Le meilleur d’Enya" sur le Wi-Hi-Fi, et craquez une bière - il faudra un certain temps avant que vous compreniez vraiment ce que vous essayez de faire. Astuce: vous essayez d'implémenter un _Service Discovery Layer_ dans votre _Cluster Control Plane_. C'est une très bonne façon de passer un samedi soir.

C'est très amusant, mais j'aurais bien aimé prendre le temps de mieux comprendre le réseautage en général avant de plonger directement. J'ai finalement trouvé quelques messages des dieux bienveillants de Digital Ocean Tutorial: Introduction to Networking Terminology et Understanding ... Networking . Je suggère de les lire quelques fois avant de plonger.

S'amuser!



Poste originale

Je n'arrive pas à saisir le mappage de port pour les conteneurs Docker. Spécifiquement, comment transmettre les requêtes de Nginx à un autre conteneur, en écoutant sur un autre port, sur le même serveur.

J'ai un fichier Docker pour un conteneur Nginx comme ceci:

_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"]
_



Ensuite, le fichier de configuration _api.myapp.com_ se présente comme suit:

_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;

    }

}
_

Et puis un autre pour _app.myapp.com_ également.

Et puis je cours:

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


Et tout se passe bien, mais les demandes ne sont pas transmises aux autres conteneurs/ports. Et quand je ssh dans le conteneur Nginx et inspecte les journaux, je ne vois aucune erreur.

De l'aide?

81
AJB

La réponse de @ T0xicCode est correcte, mais je pensais développer davantage les détails, car il m'a fallu environ 20 heures pour enfin mettre en œuvre une solution opérationnelle.

Si vous souhaitez exécuter Nginx dans son propre conteneur et l'utiliser comme proxy inverse pour équilibrer la charge de plusieurs applications sur la même instance de serveur, procédez comme suit:

Liez vos conteneurs

Lorsque vous _docker run_ vos conteneurs, généralement en entrant un script Shell dans _User Data_, vous pouvez déclarer des liens vers n’importe quel autre conteneur en cours d’exécution . Cela signifie que vous devez démarrer vos conteneurs dans l'ordre et seuls ces derniers conteneurs peuvent être liés aux anciens. Ainsi:

_#!/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
_

Ainsi, dans cet exemple, le conteneur API n'est lié à aucun autre, mais le conteneur App est lié à API et Nginx est lié à la fois à API et à App.

Il en résulte des modifications apportées aux fichiers env et aux fichiers _/etc/hosts_ qui résident dans les conteneurs API et App. Les résultats ressemblent à ceci:

/ etc/hosts

Lancer _cat /etc/hosts_ dans votre conteneur Nginx produira les éléments suivants:

_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

Exécuter env dans votre conteneur Nginx produira les éléments suivants:

_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
_

J'ai tronqué un grand nombre de vars réels, mais les valeurs ci-dessus sont la clé dont vous avez besoin pour proxy le trafic sur vos conteneurs.

Pour obtenir un shell pour exécuter les commandes ci-dessus dans un conteneur en cours d'exécution, utilisez les éléments suivants:

_Sudo docker exec -i -t Nginx bash_

Vous pouvez constater que vous avez maintenant les deux entrées de fichier _/etc/hosts_ et les variables env qui contiennent l'adresse IP locale de tous les conteneurs liés. Pour autant que je sache, c'est tout ce qui se passe lorsque vous exécutez des conteneurs avec des options de lien déclarées. Mais vous pouvez maintenant utiliser ces informations pour configurer nginx dans votre conteneur Nginx.



Configuration de Nginx

C’est là que les choses se compliquent un peu et qu’il ya plusieurs options. Vous pouvez choisir de configurer vos sites pour qu'ils pointent vers une entrée du fichier _/etc/hosts_ créée par docker, ou vous pouvez utiliser les variables ENV et exécuter une chaîne de remplacement (j'ai utilisé sed) sur votre _nginx.conf_. d’autres fichiers de configuration susceptibles d’être dans votre dossier _/etc/nginx/sites-enabled_ pour insérer les valeurs IP.



OPTION A: Configurer Nginx avec ENV Vars

C'est l'option que j'ai choisie car je ne pouvais pas utiliser l'option de fichier _/etc/hosts_ pour fonctionner. Je vais essayer l'option B assez tôt et mettre à jour ce post avec tous les résultats.

La principale différence entre cette option et l'utilisation de l'option de fichier _/etc/hosts_ réside dans la manière dont vous écrivez votre Dockerfile pour utiliser un script Shell comme argument CMD, qui à son tour gère le remplacement de la chaîne pour copier les valeurs IP de ENV dans votre fichier de configuration. (s).

Voici l'ensemble des fichiers de configuration que j'ai obtenus:

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;


}
_

REMARQUE: Il est important d'inclure _daemon off;_ dans votre fichier _nginx.conf_ pour vous assurer que votre conteneur ne se ferme pas immédiatement après son lancement.

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
_

Je vous laisse le soin de faire vos devoirs sur la plupart des contenus de _nginx.conf_ et _api.myapp.conf_.

La magie se passe dans _Nginx-Startup.sh_ où nous utilisons sed pour remplacer les chaînes dans l'espace réservé _APP_IP_ que nous avons écrit dans le bloc upstream de nos fichiers _api.myapp.conf_ et _app.myapp.conf_.

Cette question ask.ubuntu.com l'explique très bien: Rechercher et remplacer du texte dans un fichier à l'aide de commandes

GOTCHA Sous OSX, sed traite les options différemment, l'indicateur _-i_ de manière spécifique. Sur Ubuntu, le drapeau _-i_ gérera le remplacement "en place"; il ouvrira le fichier, changera le texte, puis "sauvegardera" le même fichier. Sur OSX, l'indicateur _-i_ requiert l'extension de fichier que vous souhaitez que le fichier résultant ait. Si vous travaillez avec un fichier sans extension, vous devez entrer "" comme valeur pour l'indicateur _-i_.

GOTCHA Pour utiliser les variables ENV dans les expressions rationnelles utilisées par sed pour rechercher la chaîne que vous souhaitez remplacer, vous devez placer la variable var entre guillemets. Donc, la syntaxe correcte, quoique stupide, est la suivante.

Docker a donc lancé notre conteneur et déclenché l'exécution du script _Nginx-Startup.sh_, qui a utilisé sed pour remplacer la valeur _APP_IP_ par la variable ENV correspondante fournie dans la commande sed. Nous avons maintenant des fichiers de configuration dans notre répertoire _/etc/nginx/sites-enabled_ qui ont les adresses IP des vars ENV que ce docker a définies lors du démarrage du conteneur. Dans votre fichier _api.myapp.conf_, vous verrez que le bloc upstream a été remplacé par le texte suivant:

_upstream api_upstream{
    server 172.0.0.2:3000;
}
_

L'adresse IP que vous voyez peut être différente, mais j'ai remarqué qu'il s'agit généralement de _172.0.0.x_.

Vous devriez maintenant avoir tout le routage approprié.

GOTCHA Vous ne pouvez pas relancer/réexécuter les conteneurs une fois que vous avez lancé le lancement initial de l'instance. Docker attribue une nouvelle adresse IP à chaque conteneur lors de son lancement et ne semble pas réutiliser celles déjà utilisées auparavant. Donc _api.myapp.com_ obtiendra 172.0.0.2 la première fois, puis 172.0.0.4 la prochaine fois. Mais Nginx aura déjà défini la première adresse IP dans ses fichiers de configuration, ou dans son fichier _/etc/hosts_, de sorte qu'il ne sera pas en mesure de déterminer la nouvelle adresse IP pour _api.myapp.com_. La solution à ce problème est susceptible d'utiliser CoreOS et son service etcd qui, selon ma compréhension limitée, agit comme un ENV partagé pour toutes les machines inscrites dans le même cluster CoreOS. Ceci est le prochain jouet que je vais jouer avec la mise en place.



OPTION B: Utiliser _/etc/hosts_ Entrées de fichier

Ceci devrait être le moyen le plus rapide et le plus simple de le faire, mais je ne pouvais pas le faire fonctionner. Apparemment, vous venez d'entrer la valeur de l'entrée _/etc/hosts_ dans vos fichiers _api.myapp.conf_ et _app.myapp.conf_, mais cette méthode n'a pas fonctionné.

UPDATE: Voir réponse de @ Wes Tod pour obtenir des instructions sur la manière de faire fonctionner cette méthode.

Voici la tentative que j'ai faite dans _api.myapp.conf_:

_upstream api_upstream{
    server API:3000;
}
_

Considérant qu'il y a une entrée dans mon fichier _/etc/hosts_ comme ceci: _172.0.0.2 API_ J'ai pensé que cela attirerait simplement la valeur, mais cela ne semble pas être le cas.

J'ai également eu quelques problèmes auxiliaires liés à mon source _Elastic Load Balancer_ auprès de tous les AZ, de sorte que le problème peut avoir été rencontré lorsque j'ai essayé cette voie. Au lieu de cela, j'ai dû apprendre à gérer le remplacement de chaînes dans Linux, donc c'était amusant. Je vais essayer cela dans un moment et voir comment ça se passe.

55
AJB

J'ai essayé d'utiliser le populaire proxy inverse de Jason Wilder, qui fonctionne comme par magie pour tout le monde, et j'ai appris que cela ne fonctionnait pas pour tout le monde (c'est-à-dire: moi). Et je suis tout nouveau chez NGINX, et je n’ai pas aimé que je ne comprenne pas les technologies que j’essayais d’utiliser.

Je voulais ajouter mes 2 centimes, car la discussion ci-dessus concernant linking conteneurs est maintenant dépassée car il s'agit d'une fonctionnalité obsolète. Alors, voici une explication sur la façon de le faire en utilisant networks. Cette réponse est un exemple complet de configuration de nginx en tant que proxy inverse sur un site Web paginé de manière statique à l'aide de Docker Compose et de la configuration de nginx.

TL; DR;

Ajoutez les services qui doivent communiquer entre eux sur un réseau prédéfini. Pour une discussion étape par étape sur les réseaux Docker, j’ai appris certaines choses ici: https://technologyconversations.com/2016/04/25/docker-networking-and-dns-the-good-the- mauvais-et-le-laid /

Définir le réseau

Tout d’abord, nous avons besoin d’un réseau sur lequel tous vos services backend peuvent parler. J'ai appelé le mien web mais cela peut être ce que vous voulez.

docker network create web

Construire l'application

Nous allons simplement faire une application de site Web simple. Le site Web est une simple page index.html servie par un conteneur nginx. Le contenu est un volume monté sur l'hôte sous un dossier 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

Notez que nous n’avons plus besoin de mappage de port ici. Nous exposons simplement le port 80. Ceci est pratique pour éviter les collisions de ports.

Lancer l'application

Lancez ce site avec

docker-compose up -d

Quelques vérifications amusantes concernant les mappages DNS pour votre conteneur:

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

Ce ping devrait fonctionner dans votre conteneur.

Construire le proxy

Proxy inverse Nginx:

Dockerfile

FROM nginx

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

Nous réinitialisons toute la configuration de l'hôte virtuel, car nous allons la personnaliser.

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

Exécuter le proxy

Lancez le proxy en utilisant notre fidèle

docker-compose up -d

En supposant qu'il n'y ait pas de problème, deux conteneurs en cours d'exécution peuvent se parler en utilisant leurs noms. Testons-le.

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

Configurer l'hôte virtuel

Le dernier détail est de configurer le fichier d'hébergement virtuel afin que le proxy puisse diriger le trafic en fonction de la façon dont vous souhaitez configurer votre correspondance:

sample-site.conf pour notre configuration d'hébergement virtuel:

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

    server_name my.domain.com;

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

  }

Selon la configuration du proxy, vous aurez besoin de ce fichier stocké dans votre dossier local conf.d que nous avons monté via la déclaration volumes dans le fichier docker-compose.

Dernier point mais non le moindre, dites à nginx de recharger sa configuration.

docker exec nginx-proxy service nginx reload

Cette séquence d'étapes est l'aboutissement de nombreuses heures de douleur à la tête alors que je luttais contre l'erreur toujours douloureuse 502 Bad Gateway et que j'apprenais nginx pour la première fois, car la majeure partie de mon expérience était avec Apache.

Cette réponse montre comment supprimer l'erreur 502 Bad Gateway résultant de l'impossibilité pour les conteneurs de communiquer entre eux.

J'espère que cette réponse permettra à quelqu'un d'économiser des heures de douleur, car il était très difficile de comprendre les contenants les uns avec les autres pour une raison quelconque, même si c'était ce que je pensais être un cas d'utilisation évident. Mais là encore, je suis idiot. Et s'il vous plaît laissez-moi savoir comment je peux améliorer cette approche.

11
gdbj

En utilisant docker links , vous pouvez lier le conteneur en amont au conteneur nginx. Une fonctionnalité supplémentaire est que docker gère le fichier Host, ce qui signifie que vous pourrez faire référence au conteneur lié en utilisant un nom plutôt que l'adresse IP potentiellement aléatoire.

9
T0xicCode

"Option B" d'AJB peut fonctionner en utilisant l'image de base Ubuntu et en configurant vous-même nginx. (Cela n'a pas fonctionné lorsque j'ai utilisé l'image Nginx de Docker Hub.)

Voici le fichier Docker que j'ai utilisé:

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;"]

Mon config nginx (alias: conf/mysite.com):

server {
    listen 80 default;
    server_name mysite.com;

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

upstream website {
    server website:3000;
}

Et enfin, comment je commence mes conteneurs:

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

Cela m’a permis de me lancer et de fonctionner de sorte que mon nginx pointe l’amont vers le deuxième conteneur docker qui expose le port 3000.

7
Wes Todd

La réponse de @ gdbj est une excellente explication et la réponse la plus récente. Voici cependant une approche plus simple.

Donc, si vous souhaitez rediriger tout le trafic de nginx écoutant _80_ vers un autre conteneur exposant _8080_, la configuration minimale peut être aussi petite que:

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 docs

6
Diolor

Je viens de trouver un article de Anand Mani Sankar qui montre un moyen simple d’utiliser le proxy en amont de nginx avec docker composer.

Fondamentalement, il faut configurer la liaison d’instance et les ports dans le fichier docker-compose et mettre à jour le fichier en amont dans nginx.conf en conséquence.

2
lsborg