¿Cómo configurar la asignación de puertos de Docker para usar Nginx como un proxy ascendente?

86

Actualización II

Ahora es el 16 de julio de 2015 y las cosas han vuelto a cambiar. Descubrí este contenedor automágico de Jason Wilder : https://github.com/jwilder/nginx-proxyy resuelve este problema en el tiempo que lleva docker runel contenedor. Esta es ahora la solución que estoy usando para resolver este problema.

Actualizar

Ahora es julio de 2015 y las cosas han cambiado drásticamente con respecto a la red de contenedores Docker. En la actualidad, hay muchas ofertas diferentes que resuelven este problema (de diversas formas).

Debe usar esta publicación para obtener una comprensión básica del docker --linkenfoque para el descubrimiento de servicios, que es tan básico como parece, funciona muy bien y, en realidad, requiere menos bailes elegantes que la mayoría de las otras soluciones. Tiene la limitación de que es bastante difícil conectar contenedores en red en hosts separados en un clúster determinado, y los contenedores no se pueden reiniciar una vez conectados en red, pero ofrece una forma rápida y relativamente fácil de conectar contenedores en red en el mismo host. Es una buena manera de tener una idea de lo que está haciendo bajo el capó el software que probablemente usará para resolver este problema.

Además, probablemente también querrá ver los networknuevos Docker , Hashicorp's consul, Weaveworks weave, Jeff Lindsay's progrium/consul&gliderlabs/registrator , y Google's Kubernetes.

Hay también los CoreOS ofertas que utilizan etcd, fleety flannel.

Y si realmente quieres tener una fiesta, puedes armar un grupo para ejecutar Mesosphere, o Deis, o Flynn.

Si eres nuevo en el mundo de las redes (como yo), entonces deberías sacar tus lentes para leer, poner "Paint The Sky With Stars - The Best of Enya" en el Wi-Hi-Fi y tomar una cerveza. Va a ser un tiempo antes de que realmente comprenda exactamente qué es lo que está tratando de hacer. Sugerencia: está intentando implementar un Service Discovery Layeren su Cluster Control Plane. Es una forma muy bonita de pasar la noche del sábado.

Es muy divertido, pero desearía haberme tomado el tiempo para educarme mejor sobre las redes en general antes de sumergirme de lleno. Finalmente encontré un par de publicaciones de los benevolentes dioses del Tutorial Digital Ocean: Introduction to Networking Terminologyy Understanding ... Networking. Sugiero leerlos un par de veces antes de sumergirnos.

¡Que te diviertas!



Publicación original

Parece que no puedo comprender la asignación de puertos para Dockercontenedores. Específicamente cómo pasar solicitudes de Nginx a otro contenedor, escuchando en otro puerto, en el mismo servidor.

Tengo un Dockerfile para un contenedor Nginx como este:

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



Y luego el api.myapp.comarchivo de configuración se ve así:

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;

    }

}

Y luego otro para app.myapp.comtambién.

Y luego corro:

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


Y todo está bien, pero las solicitudes no se transfieren a los otros contenedores / puertos. Y cuando entro al contenedor de Nginx e inspecciono los registros, no veo errores.

¿Alguna ayuda?

AJB
fuente
1
Incluya material de respuesta en su respuesta, no en el cuerpo de la pregunta.
jscs

Respuestas:

56

La respuesta de @ T0xicCode es correcta, pero pensé que ampliaría los detalles, ya que en realidad me tomó alrededor de 20 horas implementar finalmente una solución funcional.

Si está buscando ejecutar Nginx en su propio contenedor y usarlo como un proxy inverso para equilibrar la carga de varias aplicaciones en la misma instancia de servidor, los pasos que debe seguir son los siguientes:

Vincula tus contenedores

Cuando utiliza docker runsus contenedores, generalmente ingresando un script de shell User Data, puede declarar enlaces a cualquier otro contenedor en ejecución . Esto significa que debe iniciar sus contenedores en orden y solo los últimos contenedores pueden vincularse a los primeros. Al igual que:

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

Entonces, en este ejemplo, el APIcontenedor no está vinculado a ningún otro, pero el Appcontenedor está vinculado APIy Nginxestá vinculado a ambos APIy App.

El resultado de esto son cambios en las envvars y los /etc/hostsarchivos que residen dentro de los contenedores APIy App. Los resultados se ven así:

/ etc / hosts

Ejecutar cat /etc/hostsdentro de su Nginxcontenedor producirá lo siguiente:

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

Ejecutar envdentro de su Nginxcontenedor producirá lo siguiente:

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

He truncado muchas de las variables reales, pero los anteriores son los valores clave que necesita para el tráfico proxy a sus contenedores.

Para obtener un shell para ejecutar los comandos anteriores dentro de un contenedor en ejecución, use lo siguiente:

sudo docker exec -i -t Nginx bash

Puede ver que ahora tiene /etc/hostsentradas de archivo y envvariables que contienen la dirección IP local para cualquiera de los contenedores que estaban vinculados. Por lo que puedo decir, esto es todo lo que sucede cuando ejecuta contenedores con opciones de enlace declaradas. Pero ahora puede usar esta información para configurar nginxdentro de su Nginxcontenedor.



Configurando Nginx

Aquí es donde se pone un poco complicado y hay un par de opciones. Puede elegir configurar sus sitios para que apunten a una entrada en el /etc/hostsarchivo que dockercreó, o puede utilizar las ENVvars y ejecutar un reemplazo de cadena (yo usé sed) en su nginx.confy cualquier otro archivo conf que pueda estar en su /etc/nginx/sites-enabledcarpeta para insertar la IP valores.



OPCIÓN A: Configurar Nginx usando ENV Vars

Esta es la opción que elegí porque no pude hacer que la /etc/hostsopción de archivo funcionara. Pronto intentaré la Opción B y actualizaré esta publicación con cualquier hallazgo.

La diferencia clave entre esta opción y el uso de la /etc/hostsopción de archivo es cómo escribe su Dockerfilepara usar un script de shell como CMDargumento, que a su vez maneja el reemplazo de la cadena para copiar los valores de IP ENVa sus archivos conf.

Aquí está el conjunto de archivos de configuración con los que terminé:

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;


}

NOTA: Es importante incluirlo daemon off;en su nginx.confarchivo para asegurarse de que su contenedor no salga inmediatamente después del lanzamiento.

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

Dejaré que usted haga su tarea sobre la mayoría de los contenidos de nginx.confy api.myapp.conf.

La magia ocurre Nginx-Startup.shcuando usamos sedpara hacer el reemplazo de cadenas en el APP_IPmarcador de posición que hemos escrito en el upstreambloque de nuestros archivos api.myapp.confy app.myapp.conf.

Esta pregunta de ask.ubuntu.com lo explica muy bien: busque y reemplace texto dentro de un archivo usando comandos

GOTCHA En OSX, sedmaneja las opciones de manera diferente, -iespecíficamente la bandera. En Ubuntu, la -ibandera manejará el reemplazo 'en su lugar'; abrirá el archivo, cambiará el texto y luego 'guardará' el mismo archivo. En OSX, la -ibandera requiere la extensión de archivo que le gustaría que tuviera el archivo resultante. Si está trabajando con un archivo que no tiene extensión, debe ingresar "" como el valor de la -ibandera.

GOTCHA Para usar ENV vars dentro de la expresión regular que sedusa para encontrar la cadena que desea reemplazar, debe envolver la var entre comillas dobles. Entonces, la sintaxis correcta, aunque de aspecto extraño, es la anterior.

Entonces, Docker ha lanzado nuestro contenedor y ha activado la Nginx-Startup.shsecuencia de comandos para que se ejecute, que ha utilizado sedpara cambiar el valor APP_IPa la ENVvariable correspondiente que proporcionamos en el sedcomando. Ahora tenemos archivos conf dentro de nuestro /etc/nginx/sites-enableddirectorio que tienen las direcciones IP de las ENVvars que Docker configuró al iniciar el contenedor. Dentro de su api.myapp.confarchivo, verá que el upstreambloque ha cambiado a esto:

upstream api_upstream{
    server 172.0.0.2:3000;
}

La dirección IP que ve puede ser diferente, pero he notado que normalmente lo es 172.0.0.x.

Ahora debería tener todo enrutado correctamente.

GOTCHA No puede reiniciar / volver a ejecutar ningún contenedor una vez que haya ejecutado el lanzamiento inicial de la instancia. Docker proporciona a cada contenedor una nueva IP al iniciarse y no parece reutilizar ninguna de las que se usó antes. Entonces api.myapp.comobtendrá 172.0.0.2 la primera vez, pero luego obtendrá 172.0.0.4 la próxima vez. Pero Nginxya habrá configurado la primera IP en sus archivos conf, o en su /etc/hostsarchivo, por lo que no podrá determinar la nueva IP para api.myapp.com. Es probable que se utilice la solución a esto CoreOSy su etcdservicio que, según mi entendimiento limitado, actúa como compartido ENVpara todas las máquinas registradas en el mismo CoreOSclúster. Este es el próximo juguete con el que voy a jugar.



OPCIÓN B: Usar /etc/hostsentradas de archivo

Esta debería ser la forma más rápida y fácil de hacer esto, pero no pude hacer que funcione. Aparentemente, solo /etc/hostsingresó el valor de la entrada en sus archivos api.myapp.confy app.myapp.conf, pero no pude hacer que este método funcione.

ACTUALIZACIÓN: consulte la respuesta de @Wes Tod para obtener instrucciones sobre cómo hacer que este método funcione.

Aquí está el intento que hice en api.myapp.conf:

upstream api_upstream{
    server API:3000;
}

Teniendo en cuenta que hay una entrada en mi /etc/hostsarchivo como esa: 172.0.0.2 APIpensé que solo obtendría el valor, pero no parece serlo.

También tuve un par de problemas auxiliares con mi Elastic Load Balancerabastecimiento de todas las AZ, por lo que ese pudo haber sido el problema cuando probé esta ruta. En su lugar, tuve que aprender a manejar la sustitución de cadenas en Linux, así que fue divertido. Lo intentaré en un momento y veré cómo funciona.

AJB
fuente
2
Otro problema al usar enlaces es que si reinicia el contenedor API, lo más probable es que obtenga una nueva IP. Esto no se refleja en el archivo nginx containers / etc / hosts, que continuará usando la IP anterior y, por lo tanto, también debe reiniciarse.
judoole
13

Intenté usar el popular proxy inverso de Jason Wilder que funciona mágicamente para todos, y aprendí que no funciona para todos (es decir, yo). Y soy nuevo en NGINX, y no me gustó que no entendiera las tecnologías que estaba tratando de usar.

Quería agregar mis 2 centavos, porque la discusión anterior sobre los linkingcontenedores juntos ahora está fechada ya que es una característica obsoleta. Así que aquí hay una explicación sobre cómo hacerlo usando networks. Esta respuesta es un ejemplo completo de cómo configurar nginx como un proxy inverso para un sitio web paginado estáticamente usando una Docker Composeconfiguración de nginx.

TL; DR;

Agregue los servicios que necesitan comunicarse entre sí en una red predefinida. Para una discusión paso a paso sobre las redes Docker, aprendí algunas cosas aquí: https://technologyconversations.com/2016/04/25/docker-networking-and-dns-the-good-the-bad-and- el feo/

Definir la red

En primer lugar, necesitamos una red en la que todos sus servicios de backend puedan hablar. Llamé al mío webpero puede ser lo que quieras.

docker network create web

Crea la aplicación

Simplemente haremos una aplicación de sitio web simple. El sitio web es una página index.html simple servida por un contenedor nginx. El contenido es un volumen montado en el host en una carpeta.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

Tenga en cuenta que aquí ya no necesitamos mapeo de puertos. Simplemente exponemos el puerto 80. Esto es útil para evitar colisiones de puertos.

Ejecute la aplicación

Encienda este sitio web con

docker-compose up -d

Algunas comprobaciones divertidas con respecto a las asignaciones de dns para su contenedor:

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

Este ping debería funcionar, dentro de su contenedor.

Construye el Proxy

Proxy inverso de Nginx:

Dockerfile

FROM nginx

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

Reiniciamos toda la configuración del host virtual, ya que la vamos a personalizar.

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

Ejecutar el proxy

Encienda el proxy usando nuestro confiable

docker-compose up -d

Suponiendo que no haya problemas, entonces tiene dos contenedores en ejecución que pueden comunicarse entre sí usando sus nombres. Probémoslo.

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

Configurar host virtual

El último detalle es configurar el archivo de alojamiento virtual para que el proxy pueda dirigir el tráfico en función de cómo desee configurar su coincidencia:

sample-site.conf para nuestra configuración de alojamiento virtual:

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

    server_name my.domain.com;

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

  }

Según cómo se configuró el proxy, necesitará este archivo almacenado en su conf.dcarpeta local que montamos a través de la volumesdeclaración en el docker-composearchivo.

Por último, pero no menos importante, dígale a nginx que vuelva a cargar su configuración.

docker exec nginx-proxy service nginx reload

Esta secuencia de pasos es la culminación de horas de fuertes dolores de cabeza mientras luchaba con el siempre doloroso error 502 Bad Gateway y aprendí nginx por primera vez, ya que la mayor parte de mi experiencia fue con Apache.

Esta respuesta es para demostrar cómo eliminar el error 502 Bad Gateway que resulta de que los contenedores no pueden comunicarse entre sí.

Espero que esta respuesta le ahorre a alguien horas de dolor, ya que lograr que los contenedores se hablen entre sí fue realmente difícil de entender por alguna razón, a pesar de ser lo que esperaba que fuera un caso de uso obvio. Pero, de nuevo, soy tonto. Y hágame saber cómo puedo mejorar este enfoque.

gdbj
fuente
¡Ah! El ole 502 Gateway Error, un clásico infame en estos días. Gracias @gdbj por perder el tiempo para avanzar en la conversación y brindar una solución tan detallada.
AJB
Solo quería dar las gracias por tomarse el tiempo para esto, me ahorró un poco de molestias. Gracias.
Entidad única
10

Con los enlaces de la ventana acoplable , puede vincular el contenedor ascendente al contenedor nginx. Una característica adicional es que la ventana acoplable administra el archivo de host, lo que significa que podrá hacer referencia al contenedor vinculado usando un nombre en lugar de la IP potencialmente aleatoria.

T0xicCode
fuente
7

Se puede hacer que la "Opción B" de AJB funcione usando la imagen base de Ubuntu y configurando nginx por su cuenta. (No funcionó cuando usé la imagen de Nginx de Docker Hub).

Aquí está el archivo de Docker que utilicé:

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

Mi configuración de nginx (también conocida como: conf / mysite.com):

server {
    listen 80 default;
    server_name mysite.com;

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

upstream website {
    server website:3000;
}

Y finalmente, cómo comienzo mis contenedores:

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

Esto me puso en funcionamiento, por lo que mi nginx apuntó el flujo ascendente al segundo contenedor de la ventana acoplable que expuso el puerto 3000.

Wes Todd
fuente
¡Gracias por la ayuda Wes! Lo intentaré cuando vuelva de vacaciones.
AJB
He tenido algunos problemas como este con las imágenes oficiales. Me ha parecido mucho mejor basarme en el cuadro de ubuntu y luego extraer las líneas de los archivos de la ventana acoplable directamente. No es que esto deba ser necesario, pero lamentablemente ....
Wes Todd
1
Esto es genial, pero lo que no entiendo es cómo la configuración de nginx solo conoce el valor de 'sitio web'. ¿Magia Nginx o magia Docker, o algo más?
Kyle Chadha
1
La línea upstream website {define el valor del sitio web para nginx. Eso es lo que luego usa en su proxy_pass. La parte de la ventana acoplable de esto solo usa el mismo nombre por coherencia, pero no tiene nada que ver con la configuración de nginx. Para que quede un poco más claro, el pase de proxy debe leer:upstream website { server localhost:3000; }
Wes Todd
2
Para su información, estoy usando la última imagen oficial de nginx (1.9.2) y parece que me está funcionando. Así que tal vez solucionaron el problema.
Pejvan
6

La respuesta de @ gdbj es una gran explicación y la respuesta más actualizada. Sin embargo, aquí hay un enfoque más simple.

Entonces, si desea redirigir todo el tráfico de nginx que escucha 80a otro contenedor que expone 8080, la configuración mínima puede ser tan pequeña como:

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

Diolor
fuente
Al principio pensé que tendrías problemas con la colisión de puertos.
gdbj
@gdbj Mi problema fue la resolución de URL / ip entre contenedores. Supongo que tuvimos lo mismo. En su caso usa redes, que también está funcionando bien, en mi caso simplemente vinculo los contenedores
Diolor
¡Genial, eso es todo lo que necesitaba! Gracias por esa respuesta.
Floran Gmehlin
2

Acabo de encontrar un artículo de Anand Mani Sankar que muestra una forma sencilla de usar el proxy ascendente de nginx con docker composer.

Básicamente, uno debe configurar el enlace de instancias y los puertos en el archivo docker-compose y actualizar en sentido ascendente en nginx.conf.

lsborg
fuente
1
El artículo utiliza linksque están en desuso. Utilice las redes ahora: docs.docker.com/engine/userguide/networking
gdbj
¿Funcionará la sustitución mágica de la configuración de nginx en este caso también?
Lirón