Wie kann ich Umgebungsvariablen in Nginx.conf verwenden?

183

[Cross-posted und bearbeitet von https://stackoverflow.com/questions/21933955, da dies für StackOverflow als zu sysadmin-artig angesehen wurde.]

Ich habe einen Docker-Container mit Nginx, der mit einem anderen Docker-Container verknüpft ist. Der Hostname und die IP-Adresse des zweiten Containers werden beim Start als Umgebungsvariablen in den Nginx-Container geladen, sind aber bis dahin nicht bekannt (dynamisch). Ich möchte, dass ich nginx.confdiese Werte verwende - z

upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

Wie kann ich beim Start Umgebungsvariablen in die Nginx-Konfiguration übernehmen?

EDIT 1

Dies ist die gesamte Datei nach der vorgeschlagenen Antwort unten:

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

Nginx neu laden, dann Fehler:

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

EDIT 2: Weitere Details

Aktuelle Umgebungsvariablen

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;

Site nginx Konfiguration:

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;

Nginx-Konfiguration neu laden:

root@87ede56e0b11:/# nginx -s reload
nginx: [emerg] directive "server" is not terminated by ";" in /etc/nginx/sites-enabled/default:3
Hugo Rodger-Brown
quelle
8
Dies ist keine generische Lösung für Umgebungsvariablen. Wenn Sie jedoch Umgebungsvariablen für die Hostnamen / IP-Adressen von Upstream-Servern verwenden möchten, beachten Sie, dass Docker (zumindest in neueren Versionen) / etc / hosts für Sie ändert. Siehe docs.docker.com/userguide/dockerlinks. Dies bedeutet, wenn Ihr verknüpfter Container 'app_web_1' heißt, erstellt Docker eine Zeile in / etc / hosts in Ihrem Nginx-Container. So können Sie nur server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000; mit server app_web_1:5000;
Mozz100
1
Danke @ mozz100 - das ist unglaublich nützlich - / etc / hosts-Einträge sind in diesem Fall viel effektiver als env vars. Das einzige fehlende Bit ist, was passiert, wenn der Upstream-Container neu gestartet wird und eine neue IP erhält. Ich gehe davon aus, dass die untergeordneten Container weiterhin auf die ursprüngliche IP verweisen, nicht auf die neue.
Hugo Rodger-Brown
1
Ja, wenn Sie einen Neustart durchführen app_web_1, wird eine neue IP-Adresse zugewiesen, sodass Sie auch Ihren Nginx-Container neu starten müssen. Docker würde es mit einer aktualisierten Version neu starten, /etc/hostssodass Sie die nginx-Konfigurationsdatei (en) nicht ändern müssen.
Mozz100

Antworten:

100

Aus der offiziellen Nginx Docker-Datei:

Umgebungsvariablen in der Nginx-Konfiguration verwenden:

Standardmäßig unterstützt Nginx die Verwendung von Umgebungsvariablen in den meisten Konfigurationsblöcken nicht.

Aber envsubstkann als Behelfslösung verwendet werden , wenn Sie Ihre nginx Konfiguration erzeugen müssen , um dynamisch vor nginx beginnt.

Hier ist ein Beispiel mit 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;'" 

Die mysite.template-Datei kann dann Variablenreferenzen wie diese enthalten:

listen ${NGINX_PORT};

Aktualisieren:

Aber Sie wissen, dass dies zu seinen Nginx-Variablen wie folgt geführt hat:

proxy_set_header        X-Forwarded-Host $host;

beschädigt zu:

proxy_set_header        X-Forwarded-Host ;

Um dies zu verhindern, benutze ich diesen Trick:

Ich habe ein Skript , Nginx zu laufen, die auf der verwendeten docker-composefür Nginx - Server - Datei als Befehlsoption, ich nannte es run_nginx.sh:

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

Und aufgrund der definierten neuen DOLLARVariablen im run_nginx.shSkript sieht der Inhalt meiner nginx.conf.templateDatei für die Nginx-Variable folgendermaßen aus:

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

Und für meine definierte Variable ist das so:

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

Auch hier gibt es meinen eigentlichen Anwendungsfall.

Omid Raha
quelle
2
Das tötet Nginx wieproxy_set_header Host $http_host;
Zerkleinern
22
@shredding Sie können Variablen Namen übergeben, die ersetzt werden sollen - andere werden nicht berührt: command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"funktioniert bei mir b / c Ich weiß, wie sie heißen ...
pkyeck
4
ok, vergessen zu entkommen $, sollte seincommand: /bin/bash -c "envsubst '\$VAR1 \$VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
pkyeck
2
Mit Rücksicht auf die Flucht funktioniert dies in Ihrem eigenen 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
Dies ist eine großartige Lösung. Vielen Dank. Auch der oben erwähnte Link github.com/docker-library/docs/issues/496 hat eine großartige Lösung für das Problem.
Kabirbaidhya
34

Dies mit Lua zu tun ist wesentlich einfacher als es sich anhört:

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

Das habe ich hier gefunden:

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

Bearbeiten:

Dazu muss anscheinend das Lua-Modul installiert werden: https://github.com/openresty/lua-nginx-module

Bearbeiten 2:

Beachten Sie, dass Sie bei diesem Ansatz eine envVariable in Nginx definieren müssen :

env ENVIRONMENT_VARIABLE_NAME

Sie müssen dies im Toplevel-Kontext tun, sonst funktioniert nginx.confes nicht! Nicht im Serverblock oder in der Konfiguration einer Site in /etc/nginx/sites-available, da sie nginx.confim httpKontext enthalten ist (was kein Kontext der obersten Ebene ist).

Beachten Sie auch, dass Sie bei diesem Ansatz versuchen, eine Umleitung durchzuführen, z.

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

es wird nicht so gut funktionieren:

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

Und wenn Sie einen eigenen Variablennamen vergeben:

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 interpretiert es nicht und leitet Sie weiter zu https://%24server_name_from_env/.

Colton Leekly-Winslow
quelle
3
du brauchst vielleicht env NGINX_SERVERNAMEirgendwo in deiner nginx.conf.
Hiroshi
Dies hat bei mir nicht funktioniert, obwohl ich das Lua-Modul in meinem Nginx Docker-Image habe. Könnte dies damit zusammenhängen, dass ich eine Konfigurationsdatei in meine nginx.conf einbinde? Ich habe versucht, set_by_luadie Variable in der enthaltenen Konfigurationsdatei, während die env MY_VARDeklaration in der Hauptdatei nginx.conf war, wie vorgeschlagen. Was für eine Schande, das wäre die sauberste Lösung gewesen!
pederpansen
Kennt jemand die Vor- / Nachteile dieser Methode envsubst? Ich vermute, der Vorteil ist, dass Sie den envsubstrBefehl nicht ausführen müssen, bevor Sie den Server starten, und der Nachteil ist, dass Sie das Lua-Modul installieren müssen? Ich frage mich, ob beide Ansätze Auswirkungen auf die Sicherheit haben.
Mark Winterbottom
@ MarkWinterbottom hat dies noch nicht getestet, aber es sieht so aus, als müssten Sie keinen Schreibzugriff auf die Nginx-Konfigurationsdateien gewähren, die Sie verwenden müssen envsubstund die in meinem Fall ein No-Go sind
Griddo
14

Ich habe etwas geschrieben, das hilfreich sein kann oder nicht: https://github.com/yawn/envplate

Es bearbeitet Konfigurationsdateien inline mit $ {key} -Verweisen auf Umgebungsvariablen und erstellt optional Backups / protokolliert, was es tut. Es ist in Go geschrieben und die resultierende statische Binärdatei kann einfach von der Registerkarte "Release" für Linux und MacOS heruntergeladen werden.

Es kann auch exec () -Prozesse ausführen, Standardeinstellungen ersetzen, Protokolle erstellen und hat eine sinnvolle Fehlersemantik.

gähnen
quelle
10

Das offizielle Nginx-Image empfiehlt die Verwendungenvsubst , aber wie von anderen hervorgehoben, ersetzt es auch $hostund andere Variablen, was nicht wünschenswert ist. Aber zum Glück envsubstkann man als Parameter die Namen der zu ersetzenden Variablen nehmen .

Um einen sehr komplexen Befehlsparameter für den Container zu vermeiden (wie im verknüpften Beispiel), können Sie ein Docker-Entypoint-Skript schreiben, das die Umgebungsvariablen vor der Ausführung des Befehls ausfüllt. Das Entrypoint-Skript ist auch ein guter Ort, um die Parameter zu validieren und Standardwerte festzulegen.

Hier ist ein Beispiel für einen Nginx-Container, der API_HOSTund API_PORTParameter als Umgebungsvariablen akzeptiert.

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
quelle
5

Was ich getan habe, war das erb zu benutzen !

cat nginx.conf  | grep -i error_log

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

--Nach der Verwendung von erb

export APP_ROOT=/tmp

erb nginx.conf  | grep -i error_log

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

Dies wird im Cloudfoundry-Buildpack für statische Dateien verwendet

Nginx-Beispielkonfiguration: https://github.com/cloudfoundry/staticfile-buildpack/blob/master/conf/nginx.conf

In deinem Fall

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

werden

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
quelle
4

Bezüglich der Antwort auf die Verwendung von erb kann wie folgt vorgegangen werden.

Schreiben Sie die NGINX-Konfigurationsdatei als erb-Datei, die die Umgebungsvariable enthält, und werten Sie sie mit dem Befehl erb in eine normale Konfigurationsdatei aus.

erb nginx.conf.erb > nginx.conf

Innerhalb des Serverblocks der Datei nginx.conf.erb könnte es sein

listen <%= ENV["PORT"] %>;
Ruifeng Ma
quelle
1
Muss ich Ruby dann im Container installieren? (Wenn nicht, wie erbkann man eine variable Substitution durchführen ... nachdem der Container gestartet wurde, oder?) - stimmterb das mit Ruby: stuartellis.name/articles/erb
KajMagnus
1
Es ist ein Kommandozeilen-Tool, das mit der Standard-Ruby-Installation geliefert wird. Probieren Sie andere Optionen aus, wenn Sie sie nicht im Container haben.
Ruifeng Ma
3

Ich weiß, dass dies eine alte Frage ist, aber für den Fall, dass jemand darauf stößt (wie ich es jetzt habe), gibt es einen viel besseren Weg, dies zu tun. Da docker den Alias ​​des verknüpften Containers in / etc / hosts einfügt, können Sie dies einfach tun

upstream upstream_name {
    server docker_link_alias;
}

vorausgesetzt, Ihr Docker-Befehl ist so etwas wie docker run --link othercontainer:docker_link_alias nginx_container.

cderwin
quelle
1
Sie können den Host mit dieser Methode abrufen, aber nicht den Port. Sie müssen entweder den Port fest codieren oder davon ausgehen, dass er Port 80 verwendet.
Ben Whaley,
2

Eine weitere Option ... Ich habe dieses Tool heute gerade gefunden: https://github.com/kreuzwerker/envplate ... Geschrieben in Go, kann es sehr einfach installiert werden. Die Benutzung ist ganz einfach. Sie müssen jedoch Vorlagenvariablen in Ihre nginx.conf einfügen.

Beispielsweise ${SOME_ENV_VAR}wird ersetzt, wenn der epBefehl envplate für die Datei aufgerufen wird. Sollte Ihre Docker-Datei diese Binärdatei nicht erhalten oder aus irgendeinem Grund nicht ausgeführt werden, ist Ihre Konfiguration ungültig. Nur ein kleiner Hinweis im Vergleich zu anderen Lösungen wie der Verwendung von Perl- oder Lua-Erweiterungen.

Mir gefällt sehr gut, wie Sie auch Standardwerte festlegen können, wenn die Umgebungsvariable nicht festgelegt ist. Ex. ${SOME_ENV_VAR:default-value}(und Sie können Werte entkommen). Auch hier muss envplate noch erfolgreich ausgeführt werden.

Ein Vorteil eines solchen Ansatzes besteht darin, dass Sie kein Docker-Image haben, das größer als erforderlich ist, da Sie alle Arten von zusätzlichen Modulen installiert haben, die Sie ansonsten nicht benötigen. Es kann auch einfacher sein als sed zu verwenden, wenn die Dinge komplexer werden und diese Standardwertfunktionalität enthalten ist.

Tom
quelle
Ich rate github.com/gliderlabs/sigil, da ich auf viele Bugs und Probleme mit Envplate gestoßen bin.
Nick
2

Ich bewerkstellige dies mit einem Shell-Skript.

Hier ist die Nginx-Vorlage:

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

}

Und das Skript zum Ersetzen der Umgebungsvariablen ist hier:

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
quelle
1

Eine andere Möglichkeit ist, den Befehl 'sed' mit regulären Ausdrücken zu verwenden, dann müssen Sie sich überhaupt nicht mit Ihren Konfigurationsdateien herumschlagen! Auf diese Weise können Sie Ihre Konfigurationsdateien normal verwenden. Wenn Sie jedoch docker ausführen, werden die Werte durch die env-Variablen ersetzt. "Fügen Sie Ihren Konfigurationsdateien, die Sie suchen und durch die Sie ersetzen, keine Textzeichenfolge hinzu."

Sie können mithilfe Ihrer Umgebungsvariablen eine run.sh-Datei mit Ersatzwerten erstellen.

So ändern Sie die "7" in dieser Zeile:

client_body_timeout 7s;         #Default 60s

Sed-Befehl mit client_body_timeout als Suchzeile und $ client_body_timeout als Ersatz- Umgebungsvariable :

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

Kopieren Sie diese Zeile für jeden Parameter, den Sie festlegen möchten, und ändern Sie client_body_timeout mit der Option config und $ client_body_timeout mit der Umgebungsvariablen, der sie zugeordnet ist. Verwenden Sie mit vorhandenen Konfigurationsdatei und es wird nur funktionieren.

Jeff
quelle
0

Hier ist ein Beispiel für die Verwendung des sedAnsatzes, nicht unbedingt besser, aber es kann für einige nützlich sein. Fügen Sie zunächst ein benutzerdefiniertes Schlüsselwort hinzu, das in der conf-Datei ersetzt werden soll. Zweitens erstellen Sie eine Docker-Datei, die eine ENVVariable deklariert , und anschließend eine CMD, mit sedder die Konfiguration bearbeitet wird, bevor nginx explizit ausgeführt wird.

Angenommen, Ihre default.conf enthält Folgendes mit dem Schlüsselwort docker_host:

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

Und schreiben Sie Ihre Dockerfile ähnlich wie:

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

Erstellen Sie dann das Image und führen Sie den Container mit aus

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

Richtiger Weg, dies mit lua zu tun, da die Antwort oben abgestanden ist:

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

Eric Meadows
quelle
-2

Sie sollten in der Lage sein, mit den Vorlagen von Dockerize das zu tun, was Sie wollen .

Claude
quelle
7
Können Sie weitere Details hinzufügen?
Pierre.Vriens