¿Cómo puedo usar variables de entorno en Nginx.conf

183

[Publicado en forma cruzada y editado desde https://stackoverflow.com/questions/21933955 ya que se consideró demasiado parecido a un administrador de sistemas para StackOverflow.]

Tengo un contenedor acoplable que ejecuta Nginx, que se vincula a otro contenedor acoplable. El nombre de host y la dirección IP del segundo contenedor se cargan en el contenedor Nginx como variables de entorno en el inicio, pero no se conocen antes (es dinámico). Quiero nginx.confque use estos valores, p. Ej.

upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

¿Cómo puedo obtener variables de entorno en la configuración de Nginx al inicio?

EDITAR 1

Este es el archivo completo, después de la respuesta sugerida a continuación:

env APP_WEB_1_PORT_5000_TCP_ADDR;
# Nginx host configuration for django_app

# Django app is served by Gunicorn, running under port 5000 (via Foreman)
upstream gunicorn {
    server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000;
}

server {
    listen 80;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location /static/ {
        alias /app/static/;
    }
    location /media/ {
        alias /app/media/;
    }
    location / {
        proxy_pass http://gunicorn;
    }
}

Recargando nginx luego errores:

$ nginx -s reload
nginx: [emerg] unknown directive "env" in /etc/nginx/sites-enabled/default:1

EDIT 2: más detalles

Variables de entorno actuales

root@87ede56e0b11:/# env | grep APP_WEB_1
APP_WEB_1_NAME=/furious_turing/app_web_1
APP_WEB_1_PORT=tcp://172.17.0.63:5000
APP_WEB_1_PORT_5000_TCP=tcp://172.17.0.63:5000
APP_WEB_1_PORT_5000_TCP_PROTO=tcp
APP_WEB_1_PORT_5000_TCP_PORT=5000
APP_WEB_1_PORT_5000_TCP_ADDR=172.17.0.63

Root nginx.conf:

root@87ede56e0b11:/# head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env APP_WEB_1_PORT_5000_TCP_ADDR;

Configuración del sitio nginx:

root@87ede56e0b11:/# head /etc/nginx/sites-available/default
# Django app is served by Gunicorn, running under port 5000 (via Foreman)
upstream gunicorn {
    server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000;
}

server {
    listen 80;

Recargar la configuración de nginx:

root@87ede56e0b11:/# nginx -s reload
nginx: [emerg] directive "server" is not terminated by ";" in /etc/nginx/sites-enabled/default:3
Hugo Rodger-Brown
fuente
8
Esta no es una solución genérica para las variables de entorno, pero si desea utilizar variables de entorno para los nombres de host / direcciones IP de servidores ascendentes, tenga en cuenta que Docker (al menos en versiones recientes) modifica / etc / hosts por usted. Consulte docs.docker.com/userguide/dockerlinks. Esto significa que si su contenedor vinculado se llama 'app_web_1', docker creará una línea en / etc / hosts en su contenedor Nginx. Así que puede reemplazar server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000; con server app_web_1:5000;
mozz100
1
Gracias @ mozz100 - eso es increíblemente útil - las entradas / etc / hosts son mucho más efectivas que las de env en este caso. Lo único que falta es lo que sucede si el contenedor ascendente se reinicia y adquiere una nueva IP. Supongo que los contenedores secundarios seguirán apuntando a la IP original, no a la nueva.
Hugo Rodger-Brown
1
Sí, si reiniciara app_web_1, obtendría una nueva dirección IP, por lo que también necesitaría reiniciar su contenedor nginx. Docker lo reiniciará con una actualización /etc/hostspara que no tenga que alterar los archivos de configuración nginx.
mozz100

Respuestas:

100

Del archivo docker oficial de Nginx:

Uso de variables de entorno en la configuración de nginx:

De fábrica, Nginx no admite el uso de variables de entorno dentro de la mayoría de los bloques de configuración.

Pero envsubstpuede usarse como una solución alternativa si necesita generar su configuración nginx dinámicamente antes de que comience nginx.

Aquí hay un ejemplo usando docker-compose.yml:

image: nginx
volumes:
 - ./mysite.template:/etc/nginx/conf.d/mysite.template
ports:
 - "8080:80"
environment:
 - NGINX_HOST=foobar.com
 - NGINX_PORT=80
command: /bin/bash -c "envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" 

El archivo mysite.template puede contener referencias variables como esta:

listen ${NGINX_PORT};

Actualizar:

Pero sabes que esto causó a sus variables Nginx así:

proxy_set_header        X-Forwarded-Host $host;

dañado a:

proxy_set_header        X-Forwarded-Host ;

Entonces, para evitar eso, uso este truco:

Tengo un script para ejecutar Nginx, que se usa en el docker-composearchivo como opción de comando para el servidor Nginx, lo llamé run_nginx.sh:

#!/usr/bin/env bash
export DOLLAR='$'
envsubst < nginx.conf.template > /etc/nginx/nginx.conf
nginx -g "daemon off;"

Y debido a la nueva DOLLARvariable definida en el run_nginx.shscript, ahora el contenido de mi nginx.conf.templatearchivo para la variable Nginx es así:

proxy_set_header        X-Forwarded-Host ${DOLLAR}host;

Y para mi variable definida es así:

server_name  ${WEB_DOMAIN} www.${WEB_DOMAIN};

También aquí , está mi caso de uso real para eso.

Omid Raha
fuente
2
Eso mata las confesiones de nginx comoproxy_set_header Host $http_host;
triturar
22
@shredding puede pasar nombres de variables para ser reemplazados - otros no se tocan: command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"funciona para mí b / c Sé cómo se llaman ...
pkyeck
44
ok, olvidé escapar de la $, debería sercommand: /bin/bash -c "envsubst '\$VAR1 \$VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
pkyeck
2
Con consideración para escapar, esto funciona en su propio Dockerfile:CMD ["/bin/sh","-c", "if [ -n \"${SOME_ENV}\" ]; then echo envsubst '${SOME_ENV}' with ${SOME_ENV} && envsubst '${SOME_ENV}' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf; fi ; nginx -g 'daemon off;'"]
KCD
1
Esta es una gran solución. Gracias. También el enlace mencionado anteriormente github.com/docker-library/docs/issues/496 tiene una gran solución para el problema.
kabirbaidhya
34

Hacer esto con Lua es sustancialmente más fácil de lo que parece:

server {
    set_by_lua $server_name 'return os.getenv("NGINX_SERVERNAME")';
}

Encontré eso aquí:

https://docs.apitools.com/blog/2014/07/02/using-environment-variables-in-nginx-conf.html

Editar:

Aparentemente, esto requiere instalar el módulo lua: https://github.com/openresty/lua-nginx-module

Edición 2:

Tenga en cuenta que con este enfoque debe definir la envvariable en Nginx:

env ENVIRONMENT_VARIABLE_NAME

¡Tienes que hacer esto en contexto de nivel superior nginx.confo no funcionará! No en el bloque de servidor o en la configuración de algún sitio en /etc/nginx/sites-available, porque está incluido nginx.confen httpcontexto (que no es el contexto de nivel superior).

También tenga en cuenta que con este enfoque si intenta hacer una redirección, por ejemplo:

server {
    listen 80;
    server_name $server_name;
    return 301 https://$server_name$request_uri;
}

no funcionará tan bien:

2016/08/30 14:49:35 [emerg] 1#0: the duplicate "server_name" variable in /etc/nginx/sites-enabled/default:8

Y si le das un nombre de variable por separado:

set_by_lua $server_name_from_env 'return os.getenv("NGINX_SERVERNAME")';

server {
    listen 80;
    server_name $server_name_from_env;
    return 301 https://$server_name$request_uri;
}

nginx no lo interpretará y lo redireccionará a https://%24server_name_from_env/.

Colton Leekly-Winslow
fuente
3
Es posible que necesite env NGINX_SERVERNAMEalgún lugar en su nginx.conf.
hiroshi
Esto no funcionó para mí, aunque tengo el módulo lua en mi imagen docker nginx. ¿Podría esto estar relacionado con el hecho de que incluyo un archivo de configuración dentro de mi nginx.conf? Estaba intentando set_by_luala variable en el archivo de configuración incluido, mientras que la env MY_VARdeclaración estaba en el nginx.conf principal, como se sugirió. ¡Qué pena, esta hubiera sido la solución más limpia!
pederpansen
¿Alguien sabe las ventajas y desventajas de utilizar este método envsubst? Supongo que el profesional es que no necesita ejecutar el envsubstrcomando antes de iniciar el servidor y la desventaja es que necesita instalar el módulo lua. Me pregunto si hay alguna implicación de seguridad en cualquiera de los enfoques.
Mark Winterbottom
@MarkWinterbottom aún no probó esto, pero parece que no tendría que otorgar acceso de escritura a los archivos de configuración nginx, que tiene que usar envsubsty que en mi caso es un no-go
Griddo
14

Escribí algo que puede o no ser útil: https://github.com/yawn/envplate

Edita en línea los archivos de configuración con referencias $ {key} a variables de entorno, opcionalmente crea copias de seguridad / registra lo que hace. Está escrito en Go y el binario estático resultante se puede descargar simplemente desde la pestaña de lanzamiento para Linux y MacOS.

También puede ejecutar procesos (), sustituir valores predeterminados, registros y tiene una semántica de fallas sensible.

bostezo
fuente
10

La imagen oficial de nginx recomienda usarenvsubst , pero como lo señalaron otros , también reemplazará a $hostotras variables, lo que no es deseable. Pero afortunadamente envsubstpuede tomar como parámetro los nombres de las variables a reemplazar .

Para evitar un parámetro de comando muy complejo en el contenedor (como en el ejemplo vinculado), puede escribir un script de punto de entrada de Docker que completará las variables de entorno antes de ejecutar el comando. El script de punto de entrada también es un buen lugar para validar los parámetros y establecer valores predeterminados.

Aquí hay un ejemplo de un contenedor nginx que toma API_HOSTy API_PORTparámetros como variables de entorno.

nginx-default.conf.template

resolver  127.0.0.11 valid=10s;  # recover from the backend's IP changing

server {
  listen  80;

  location / {
    root  /usr/share/nginx/html;
  }

  location /api {
    proxy_pass  http://${API_HOST}:${API_PORT};
    proxy_set_header  Host $http_host;
  }
}

docker-entrypoint.sh

#!/usr/bin/env sh
set -eu

envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf

exec "$@"

Dockerfile

FROM nginx:1.15-alpine

COPY nginx-default.conf.template /etc/nginx/conf.d/default.conf.template

COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
Esko Luontola
fuente
5

¡Lo que hice fue usar el erb !

cat nginx.conf  | grep -i error_log

error_log <%= ENV["APP_ROOT"] %>/nginx/logs/error.log;

--Después de usar erb

export APP_ROOT=/tmp

erb nginx.conf  | grep -i error_log

error_log /tmp/nginx/logs/error.log;

Esto se usa en Cloudfoundry staticfile-buildpack

Ejemplo de configuración de nginx: https://github.com/cloudfoundry/staticfile-buildpack/blob/master/conf/nginx.conf

En tu caso

head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env APP_WEB_1_PORT_5000_TCP_ADDR;
upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

volverse

head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env <%= ENV["APP_WEB_1_PORT_5000_TCP_ADDR"] %>
upstream gunicorn {
    server <%= ENV["APP_HOST_NAME"] %>:<%= ENV["APP_HOST_PORT"] %>
}

#After applying erb

export APP_WEB_1_PORT_5000_TCP_ADDR=12.12.12.12
export APP_HOST_NAME=test
export APP_HOST_PORT=7089 

erb /etc/nginx/nginx.conf

head /etc/nginx/nginx.conf
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;
env 12.12.12.12
upstream gunicorn {
    server test: 7089
}
Sabith KS
fuente
4

Refiriéndose a la respuesta sobre el uso de erb, se puede hacer de la siguiente manera.

Escriba el archivo de configuración NGINX como un archivo erb que contiene la variable de entorno y evalúelo utilizando el comando erb en un archivo de configuración normal.

erb nginx.conf.erb > nginx.conf

Dentro del bloque del servidor del archivo nginx.conf.erb podría haber

listen <%= ENV["PORT"] %>;
Ruifeng Ma
fuente
1
¿Necesito instalar Ruby entonces, dentro del contenedor? (Si no, ¿cómo es erbcapaz de hacer una sustitución de variables ... después de que se inició el contenedor, ¿verdad?) - ¿ erbEs correcto este material de Ruby: stuartellis.name/articles/erb
KajMagnus
1
Es una herramienta de línea de comandos que viene con la instalación estándar de Ruby. Pruebe otras opciones si no lo tiene en el contenedor.
Ruifeng Ma
3

Sé que esta es una vieja pregunta, pero en caso de que alguien se encuentre con esto (como ahora lo hago), hay una manera mucho mejor de hacerlo. Debido a que Docker inserta el alias del contenedor vinculado en / etc / hosts, simplemente puede hacer

upstream upstream_name {
    server docker_link_alias;
}

suponiendo que su comando docker es algo así docker run --link othercontainer:docker_link_alias nginx_container.

cderwin
fuente
1
Puede obtener el host utilizando este método pero no el puerto. Tienes que codificar el puerto o asumir que está usando el puerto 80.
Ben Whaley
2

Otra opción ... Acabo de encontrar esta herramienta hoy: https://github.com/kreuzwerker/envplate ... Escrita en Go, se puede instalar muy fácilmente. Usarlo es bastante simple. Aunque necesitará colocar variables de plantilla en su nginx.conf.

Por ejemplo ${SOME_ENV_VAR}, se reemplazará cuando se invoque el epcomando envplate en el archivo. Entonces, si su Dockerfile no puede obtener ese binario o si no se ejecuta por alguna razón, dejaría su configuración inválida. Solo una pequeña nota en comparación con otras soluciones como el uso de extensiones perl o lua.

Realmente me gusta cómo puede establecer valores predeterminados también para cuando la variable de entorno no está configurada. Ex. ${SOME_ENV_VAR:default-value}(y puedes escapar de los valores). Nuevamente, envplate aún debe ejecutarse con éxito.

Una ventaja de usar un enfoque como este es que no terminas con una imagen de Docker que es más grande de lo necesario porque dejaste de instalar todo tipo de módulos adicionales que de otro modo no necesitarías. También puede ser más fácil que usar sed si las cosas comienzan a complicarse y contiene esa funcionalidad de valor predeterminado.

Tom
fuente
Aconsejo a github.com/gliderlabs/sigil ya que me encontré con muchos errores y problemas con envplate.
Nick
2

Lo logro usando un script de shell.

Aquí está la plantilla nginx:

server {

    listen 80;
    server_name ___MY_DOMAIN_NAME___;
    charset utf-8;

    location /proxy {
        proxy_pass http://___PROXY_IP___:___PROXY_PORT___;
        proxy_set_header Host            $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }

    location / {
        return         200;
    }

}

Y el script para reemplazar las variables de entorno está aquí:

echo sleep 3
sleep 3

echo build starting nginx config


echo replacing ___MY_DOMAIN_NAME___/$MY_DOMAIN_NAME
echo replacing ___PROXY_IP___/$LETSENCRYPT_IP
echo replacing ___PROXY_PORT___/$PROXY_PORT

sed -i "s/___MY_DOMAIN_NAME___/$MY_DOMAIN_NAME/g" /etc/nginx/nginx.conf
sed -i "s/___PROXY_IP___/$PROXY_IP/g" /etc/nginx/nginx.conf
sed -i "s/___PROXY_PORT___/$PROXY_PORT/g" /etc/nginx/nginx.conf

cat /etc/nginx/nginx.conf

if [ -z "$MY_DOMAIN_NAME" ]; then
    echo "Need to set MY_DOMAIN_NAME"
    exit 1
fi  
if [ -z "$LETSENCRYPT_IP" ]; then
    echo "Need to set LETSENCRYPT_IP"
    exit 1
fi  
if [ -z "$LETSENCRYPT_PORT" ]; then
    echo "Need to set LETSENCRYPT_PORT"
    exit 1
fi
if [ -z "$LETSENCRYPT_HTTPS_IP" ]; then
    echo "Need to set LETSENCRYPT_HTTPS_IP"
    exit 1
fi 
if [ -z "$LETSENCRYPT_HTTPS_PORT" ]; then
    echo "Need to set LETSENCRYPT_HTTPS_PORT"
    exit 1
fi

nginx -g 'daemon off;'
Geige V
fuente
1

Otra posibilidad es usar el comando 'sed' con expresiones regulares, ¡entonces no tendrá que meterse con sus archivos de configuración! De esa manera, puede usar sus archivos de configuración normalmente, pero cuando ejecuta Docker, intercambiará los valores con las variables env. Nada de esto "agrega una cadena de texto a los archivos de configuración que busca y reemplaza".

Puede crear un archivo run.sh con valores de reemplazo utilizando sus variables de entorno.

Para cambiar los "7" en esta línea:

client_body_timeout 7s;         #Default 60s

Comando Sed usando client_body_timeout como línea de búsqueda y $ client_body_timeout como variable de reemplazo de env:

sed -i "s/\(client_body_timeout\).*\?\;/\1 $client_body_timeout;/" /usr/local/nginx/conf/nginx.conf

Copie / pegue esta línea para cada parámetro que desee establecer y cambie client_body_timeout con la opción de configuración y $ client_body_timeout con la variable env con la que está asociado. Úselo con el archivo de configuración existente y simplemente funcionará.

Jeff
fuente
0

Aquí hay un ejemplo del uso del sedenfoque, no necesariamente mejor, pero puede ser útil para algunos. Primero, agregue una palabra clave personalizada para ser reemplazada dentro del archivo conf. En segundo lugar, cree un dockerfile que declare una ENVvariable y luego una CMDque use sedpara editar la configuración antes de ejecutar nginx explícitamente.

Supongamos que su default.conf contiene esto con la palabra clave docker_host:

location /api { proxy_pass http://docker_host:9000/api; }

Y escriba su Dockerfile similar a:

ENV docker_host localhost
ADD default.conf /etc/nginx/conf.d/default.conf
CMD sed -i.bak s/docker_host/$docker_host/g /etc/nginx/conf.d/default.conf &&   nginx -g "daemon off;"

Luego construye la imagen y ejecuta el contenedor usando

docker run -d -p 80:80 -e "docker_host=${env:COMPUTERNAME}" imagename
Steven
fuente
0

La forma correcta de hacer esto con lua, ya que la respuesta anterior es obsoleta:

server {
    set_by_lua $curr_server_name 'return os.getenv("NGINX_SERVERNAME")';
    server_name = $curr_server_name;
}

Eric Meadows
fuente
-2

Debería poder hacer lo que quiera con las plantillas de Dockerize .

Claude
fuente
77
¿Puedes agregar más detalles?
Pierre.Vriens