Comment utiliser les variables d'environnement dans Nginx.conf


184

[ Rédigé à la baisse et édité à partir de https://stackoverflow.com/questions/21933955 car il a été jugé trop sysadmin-like pour StackOverflow.]

J'ai un conteneur Docker sous Nginx, qui est lié à un autre conteneur Docker. Le nom d'hôte et l'adresse IP du deuxième conteneur sont chargés dans le conteneur Nginx en tant que variables d'environnement au démarrage, mais ils ne sont pas connus auparavant (dynamique). Je veux que nginx.confj'utilise ces valeurs - par exemple

upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

Comment obtenir des variables d'environnement dans la configuration de Nginx au démarrage?

EDIT 1

Ceci est le fichier entier, après la réponse suggérée ci-dessous:

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

Le rechargement de nginx alors des erreurs:

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

EDIT 2: plus de détails

Variables d'environnement actuelles

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

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

Configuration du site 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;

Rechargez la configuration de nginx:

root@87ede56e0b11:/# nginx -s reload
nginx: [emerg] directive "server" is not terminated by ";" in /etc/nginx/sites-enabled/default:3

8
Ce n'est pas une solution générique pour les variables d'environnement, mais si vous souhaitez utiliser des variables pour les noms d'hôte / adresses IP des serveurs en amont, notez que Docker (au moins dans les versions récentes) modifie / etc / hosts pour vous. Voir docs.docker.com/userguide/dockerlinks Cela signifie que si votre conteneur lié s'appelle 'app_web_1', docker créera une ligne dans / etc / hosts dans votre conteneur Nginx. Donc, vous pouvez simplement remplacer server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000; par server app_web_1:5000;
mozz100

1
Merci @ mozz100 - c'est incroyablement utile - les entrées / etc / hosts sont beaucoup plus efficaces que env vars dans ce cas. Le seul élément manquant est ce qui se passe si le conteneur en amont est redémarré et acquiert une nouvelle adresse IP. Je suppose que les conteneurs enfants indiqueront toujours l'adresse IP d'origine, pas la nouvelle?
Hugo Rodger-Brown

1
Oui, si vous redémarrez app_web_1, une nouvelle adresse IP sera obtenue. Vous devrez donc également redémarrer votre conteneur nginx. Docker le relancerait avec une mise à jour /etc/hostsafin que vous n'ayez pas besoin de modifier le (s) fichier (s) de configuration de nginx.
mozz100

Réponses:


101

À partir du fichier officiel de docker Nginx:

Utilisation de variables d’environnement dans la configuration de nginx:

En sortie d'usine, Nginx ne prend pas en charge l'utilisation de variables d'environnement dans la plupart des blocs de configuration.

Mais envsubstpeut être utilisé comme solution de contournement si vous devez générer votre configuration nginx de manière dynamique avant le démarrage de nginx.

Voici un exemple utilisant 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;'" 

Le fichier mysite.template peut alors contenir des références de variable telles que:

listen ${NGINX_PORT};

Mise à jour:

Mais vous savez que cela a causé à ses variables Nginx comme ceci:

proxy_set_header        X-Forwarded-Host $host;

endommagé à:

proxy_set_header        X-Forwarded-Host ;

Donc, pour éviter cela, j'utilise cette astuce:

J'ai un script pour exécuter Nginx, utilisé sur le docker-composefichier en tant qu'option de commande pour le serveur Nginx, je l'ai nommé run_nginx.sh:

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

Et à cause de la nouvelle DOLLARvariable définie dans le run_nginx.shscript, le contenu de mon nginx.conf.templatefichier pour la variable elle-même est comme ceci:

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

Et pour ma variable définie, c'est comme ça:

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

Aussi ici , il y a mon cas d'utilisation réel pour cela.


2
Cela tue nginx confs commeproxy_set_header Host $http_host;
déchiquetage

22
@shredding vous pouvez passer des noms de variables à remplacer - les autres ne sont pas touchés: command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"fonctionne pour moi car je sais comment ils s'appellent ...
pkyeck

4
ok, j'ai oublié d'échapper à $, devrait êtrecommand: /bin/bash -c "envsubst '\$VAR1 \$VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
pkyeck

2
Avec la considération pour échapper cela fonctionne dans votre propre 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
C'est une excellente solution. Merci. Le lien mentionné ci-dessus, github.com/docker-library/docs/issues/496, constitue également une excellente solution.
Kabirbaidhya

34

Faire cela avec Lua est nettement plus facile qu'il n'y paraît:

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

J'ai trouvé ça ici:

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

Modifier:

Apparemment, cela nécessite l'installation du module lua: https://github.com/openresty/lua-nginx-module

Edit 2:

Notez qu'avec cette approche, vous devez définir une envvariable dans Nginx:

env ENVIRONMENT_VARIABLE_NAME

Vous devez le faire dans un contexte de niveau supérieur nginx.confou cela ne fonctionnera pas! Pas dans le bloc serveur ou dans la configuration de certains sites /etc/nginx/sites-available, car il est inclus nginx.confdans le httpcontexte (qui n'est pas le contexte de niveau supérieur).

Notez également qu'avec cette approche, si vous essayez de faire une redirection, par exemple:

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

ça ne marchera pas aussi bien:

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

Et si vous lui attribuez un nom de variable distinct:

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 ne l'interprète pas et vous redirigera vers https://%24server_name_from_env/.


3
vous aurez peut-être besoin de env NGINX_SERVERNAMEquelque part dans votre nginx.conf.
Hiroshi

Cela n'a pas fonctionné pour moi, bien que j'ai le module lua dans mon image de docker nginx. Cela est-il lié au fait que j'inclus un fichier de configuration dans mon nginx.conf? J'essayais de set_by_luala variable dans le fichier de configuration inclus, alors que la env MY_VARdéclaration était dans le fichier principal nginx.conf, comme suggéré. Quel dommage, cela aurait été la solution la plus propre!
Pederpansen

Est-ce que quelqu'un connaît les avantages / inconvénients d'utiliser cette méthode envsubst? Je suppose que le pro est que vous n’avez pas besoin d’exécuter la envsubstrcommande avant de démarrer le serveur et que vous avez besoin d’installer le module Lua? Je me demande s'il y a des implications pour la sécurité de l'une ou l'autre approche?
Mark Winterbottom

@MarkWinterbottom n'a pas encore testé cela, mais il semblerait que vous n'auriez pas à accorder un accès en écriture aux fichiers de configuration nginx, que vous devez utiliser envsubstet qui sont
inefficaces

14

J'ai écrit quelque chose qui peut ou peut ne pas être utile: https://github.com/yawn/envplate

Il édite en ligne les fichiers de configuration avec des références $ {clé} aux variables d’environnement, en créant éventuellement des sauvegardes / journaux. Il est écrit en Go et le binaire statique résultant peut être simplement téléchargé à partir de l'onglet de publication pour Linux et MacOS.

Il peut également exécuter des processus exec (), remplacer les valeurs par défaut, les journaux et présenter une sémantique des échecs sensible.


10

L'image officielle de nginx recommande l'utilisationenvsubst , mais comme l'ont souligné d'autres personnes, elle remplacera également d' $hostautres variables, ce qui n'est pas souhaitable. Mais heureusement, envsubston peut prendre comme paramètre les noms des variables à remplacer .

Pour éviter un paramètre de commande très complexe sur le conteneur (comme dans l'exemple lié), vous pouvez écrire un script de point d'entrée Docker qui remplira les variables d'environnement avant d'exécuter la commande. Le script entrypoint est également un bon endroit pour valider les paramètres et définir les valeurs par défaut.

Voici un exemple de conteneur nginx qui inclut des paramètres API_HOSTet API_PORTdes variables d’environnement.

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

5

Ce que j'ai fait était d'utiliser le erb !

cat nginx.conf  | grep -i error_log

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

--Après l'utilisation de erb

export APP_ROOT=/tmp

erb nginx.conf  | grep -i error_log

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

Ceci est utilisé dans le cloudfoundry staticfile-buildpack

Exemple de configuration de nginx: https://github.com/cloudfoundry/staticfile-buildpack/blob/master/conf/nginx.conf

Dans ton cas

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

devenir

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
}

4

En vous référant à la réponse sur l'utilisation de erb, cela peut être fait comme ci-dessous.

Ecrivez le fichier de configuration NGINX en tant que fichier erb contenant la variable d’environnement et évaluez-le à l’aide de la commande erb dans un fichier de configuration normal.

erb nginx.conf.erb > nginx.conf

Dans le bloc serveur du fichier nginx.conf.erb, il pourrait y avoir

listen <%= ENV["PORT"] %>;

1
Dois-je installer Ruby dans le conteneur? (Si non, comment erbpeut-on effectuer une substitution de variable ... après le démarrage du conteneur, n'est-ce pas?) - erbce truc en Ruby est-il correct: stuartellis.name/articles/erb
KajMagnus Le

1
C'est un outil de ligne de commande fourni avec l'installation standard de Ruby. Essayez d'autres options si vous ne l'avez pas dans le conteneur.
Ruifeng Ma

3

Je sais que c'est une vieille question, mais juste au cas où quelqu'un tomberait (comme je l'ai maintenant), il y a une bien meilleure façon de le faire. Comme docker insère l’alias du conteneur lié dans / etc / hosts, vous pouvez simplement le faire.

upstream upstream_name {
    server docker_link_alias;
}

en supposant que votre commande docker est quelque chose comme docker run --link othercontainer:docker_link_alias nginx_container.


1
Vous pouvez obtenir l'hôte en utilisant cette méthode, mais pas le port. Vous devez soit coder en dur le port, soit supposer qu'il utilise le port 80.
Ben Whaley

2

Une autre option ... Je viens de trouver cet outil aujourd'hui: https://github.com/kreuzwerker/envplate ... Écrit en Go, il peut être installé très facilement. Son utilisation est assez simple. Bien que vous deviez placer des variables de modèle dans votre nginx.conf.

Par exemple, ${SOME_ENV_VAR}sera remplacé lorsque la epcommande de envplate est appelée sur le fichier. Donc, si votre fichier Docker ne parvient pas à obtenir ce fichier binaire ou s’il ne s’exécute pas pour une raison quelconque, votre configuration serait invalide. Juste une petite note comparée à d’autres solutions comme l’utilisation d’extensions perl ou lua.

J'aime beaucoup la façon dont vous pouvez définir les valeurs par défaut lorsque la variable d'environnement n'est pas définie. Ex. ${SOME_ENV_VAR:default-value}(et vous pouvez échapper aux valeurs). Encore une fois, envplate doit toujours être exécuté avec succès.

L'un des avantages de cette approche est que vous ne vous retrouvez pas avec une image Docker plus grande que nécessaire, car vous avez commencé à installer toutes sortes de modules supplémentaires dont vous n'auriez pas besoin autrement. Cela peut aussi être plus facile que d’utiliser sed si la situation commence à devenir complexe et si elle contient la fonctionnalité de valeur par défaut


Je conseille github.com/gliderlabs/sigil car j'ai rencontré beaucoup de bugs et de problèmes avec envplate.
Nick

2

Je réalise cela en utilisant un script shell.

Voici le modèle 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;
    }

}

Et le script pour remplacer les variables d'environnement est ici:

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

1

Une autre possibilité consiste à utiliser la commande 'sed' avec des expressions régulières pour ne pas avoir à manipuler vos fichiers de configuration du tout! De cette façon, vous pouvez utiliser vos fichiers de configuration normalement, mais lorsque vous exécutez docker, les valeurs sont échangées avec les variables env. Rien de tout cela "ajoute une chaîne de texte à vos fichiers de configuration que vous recherchez et remplacez par".

Vous pouvez créer un fichier run.sh avec des valeurs de remplacement à l'aide de vos variables d'environnement.

Pour changer les "7" sur cette ligne:

client_body_timeout 7s;         #Default 60s

Commande Sed utilisant client_body_timeout comme ligne de recherche et $ client_body_timeout comme variable env. De remplacement:

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

Copiez / collez cette ligne pour chaque paramètre que vous souhaitez définir et modifiez le paramètre client_body_timeout avec l’option config et $ client_body_timeout avec la variable env à laquelle il est associé. Utilisez le fichier de configuration existant et cela fonctionnera simplement.


0

Voici un exemple d'utilisation de l' sedapproche, pas nécessairement meilleure, mais elle peut être utile à certains. Tout d’abord, ajoutez un mot-clé personnalisé à remplacer dans le fichier conf. En second lieu , créer une dockerfile qui déclare une ENVvariable, puis un CMDqui utilise sedpour modifier la configuration avant d' exécuter nginx explicitement.

Supposons donc que votre default.conf contient ceci avec le mot clé docker_host:

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

Et écrivez votre Dockerfile similaire à:

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

Construisez ensuite l'image et exécutez le conteneur à l'aide de

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

0

Manière correcte de faire ceci avec lua, puisque la réponse ci-dessus est périmée:

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


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.