Jak korzystać ze zmiennych środowiskowych w Nginx.conf

183

[Przekazano i poddano edycji z https://stackoverflow.com/questions/21933955, ponieważ uznano go za zbyt podobny do sysadmin dla StackOverflow.]

Mam kontener dokerów z systemem Nginx, który łączy się z innym kontenerem dokowania. Nazwa hosta i adres IP drugiego kontenera są ładowane do kontenera Nginx jako zmienne środowiskowe podczas uruchamiania, ale przedtem nie są znane (jest dynamiczne). Chcę nginx.confużywać tych wartości - np

upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

Jak mogę wprowadzić zmienne środowiskowe do konfiguracji Nginx podczas uruchamiania?

EDYCJA 1

To jest cały plik, po sugerowanej odpowiedzi poniżej:

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

Przeładowanie nginx następnie błędy:

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

EDYCJA 2: więcej szczegółów

Bieżące zmienne środowiskowe

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

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

Konfiguracja strony 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;

Załaduj ponownie konfigurację nginx:

root@87ede56e0b11:/# nginx -s reload
nginx: [emerg] directive "server" is not terminated by ";" in /etc/nginx/sites-enabled/default:3
Hugo Rodger-Brown
źródło
8
Nie jest to ogólne rozwiązanie dla zmiennych środowiskowych, ale jeśli chcesz używać zmiennych środowiskowych dla nazw hostów / adresów IP serwerów nadrzędnych, zauważ, że Docker (przynajmniej w najnowszych wersjach) modyfikuje / etc / hosts. Zobacz docs.docker.com/userguide/dockerlinks Oznacza to, że jeśli twój połączony kontener nosi nazwę „app_web_1”, doker utworzy linię w / etc / hosts w twoim kontenerze Nginx. Więc można po prostu zastąpić server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000; z server app_web_1:5000;
mozz100
1
Dzięki @ mozz100 - to niezwykle przydatne - wpisy / etc / hosts są w tym przypadku znacznie bardziej skuteczne niż zmienne env. Jedynym brakującym bitem jest to, co się stanie, jeśli kontener nadrzędny zostanie zrestartowany i uzyska nowy adres IP. Zakładam, że kontenery dziecięce nadal będą wskazywały pierwotny adres IP, a nie nowy?
Hugo Rodger-Brown
1
Tak, jeśli zrestartujesz app_web_1, otrzyma nowy adres IP, więc musisz zrestartować również swój kontener nginx. Docker uruchomiłby go ponownie po zaktualizowaniu /etc/hosts, abyś nie musiał zmieniać plików konfiguracyjnych nginx.
mozz100

Odpowiedzi:

100

Z oficjalnego pliku dokera Nginx:

Używanie zmiennych środowiskowych w konfiguracji nginx:

Standardowo Nginx nie obsługuje używania zmiennych środowiskowych w większości bloków konfiguracyjnych.

Ale envsubstmoże być użyty jako obejście, jeśli musisz dynamicznie wygenerować konfigurację nginx przed uruchomieniem nginx.

Oto przykład z użyciem 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;'" 

Plik mysite.template może wówczas zawierać odwołania do zmiennych, takie jak to:

listen ${NGINX_PORT};

Aktualizacja:

Ale wiesz, że spowodowało to takie zmienne Nginx:

proxy_set_header        X-Forwarded-Host $host;

uszkodzony do:

proxy_set_header        X-Forwarded-Host ;

Aby temu zapobiec, używam tej sztuczki:

Mam skrypt do uruchomienia Nginx, użyty w docker-composepliku jako opcja polecenia dla serwera Nginx, nazwałem go run_nginx.sh:

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

Z powodu zdefiniowanej nowej DOLLARzmiennej w run_nginx.shskrypcie zawartość mojego nginx.conf.templatepliku dla samej zmiennej Nginx wygląda następująco:

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

A dla mojej zdefiniowanej zmiennej jest to tak:

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

Również tutaj jest mój prawdziwy przypadek użycia.

Omid Raha
źródło
2
To zabija nginx confs jakproxy_set_header Host $http_host;
niszczenie
22
@ shredding możesz podać nazwy zmiennych, które chcesz wymienić - inne nie są dotykane: command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"działa dla mnie b / c Wiem, jak się je nazywa ...
pkyeck
4
ok, zapomniałem uciec $, powinno byćcommand: /bin/bash -c "envsubst '\$VAR1 \$VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
pkyeck
2
Z uwzględnieniem ucieczce działa w swoim własnym 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
To świetne rozwiązanie. Dzięki. Również wspomniany powyżej link github.com/docker-library/docs/issues/496 ma świetne rozwiązanie tego problemu.
kabirbaidhya
34

Robienie tego z Luą jest znacznie łatwiejsze niż się wydaje:

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

Znalazłem to tutaj:

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

Edytować:

Najwyraźniej wymaga to instalacji modułu lua: https://github.com/openresty/lua-nginx-module

Edycja 2:

Zauważ, że przy takim podejściu musisz zdefiniować envzmienną w Nginx:

env ENVIRONMENT_VARIABLE_NAME

Musisz to zrobić w kontekście najwyższego poziomu, w nginx.confprzeciwnym razie nie zadziała! Nie w bloku serwera ani w konfiguracji niektórych witryn /etc/nginx/sites-available, ponieważ jest on uwzględniony nginx.confw httpkontekście (który nie jest kontekstem najwyższego poziomu).

Pamiętaj również, że przy takim podejściu, jeśli próbujesz dokonać przekierowania, np .:

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

to też nie zadziała:

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

A jeśli nadasz jej osobną nazwę zmiennej:

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 nie zinterpretuje tego i przekieruje cię do https://%24server_name_from_env/.

Colton Leekly-Winslow
źródło
3
możesz potrzebować env NGINX_SERVERNAMEgdzieś w swoim pliku nginx.conf.
hiroshi
Nie działało to dla mnie, chociaż mam moduł lua na obrazie dokera nginx. Czy może to być związane z faktem, że dołączam plik konfiguracyjny do mojego pliku nginx.conf? Próbowałem użyć set_by_luazmiennej w dołączonym pliku konfiguracyjnym, podczas gdy env MY_VARdeklaracja znajdowała się w głównym pliku nginx.conf, jak sugerowano. Szkoda, że ​​byłoby to najczystsze rozwiązanie!
pederpansen
Czy ktoś zna zalety / wady korzystania z tej metody envsubst? Myślę, że pro jest to, że nie musisz uruchamiać envsubstrpolecenia przed uruchomieniem serwera, a wada polega na tym, że musisz zainstalować moduł lua? Zastanawiam się, czy są jakieś konsekwencje dla bezpieczeństwa w obu podejściach?
Mark Winterbottom,
@MarkWinterbottom jeszcze tego nie przetestował, ale wygląda na to, że nie musisz przyznawać dostępu do zapisu do plików konfiguracyjnych nginx, których musisz użyć envsubsti które w moim przypadku są nie do
przyjęcia
14

Napisałem coś, co może, ale nie musi być pomocne: https://github.com/yawn/envplate

Inline edytuje pliki konfiguracyjne z odwołaniami $ {key} do zmiennych środowiskowych, opcjonalnie tworząc kopie zapasowe / rejestrując, co robi. Jest napisany w Go, a wynikowy statyczny plik binarny można po prostu pobrać z karty wersji dla systemów Linux i MacOS.

Może także wykonywać procesy (), zastępować wartości domyślne, logować i ma sensowną semantykę awarii.

ziewać
źródło
10

Oficjalny obraz nginx zaleca używanieenvsubst , ale jak zauważyli inni , zastąpi także $hostinne zmienne, co nie jest pożądane. Ale na szczęście jako parametrenvsubst można przyjąć nazwy zmiennych do zamiany .

Aby uniknąć bardzo złożonego parametru polecenia dla kontenera (jak w przykładzie z linkiem), możesz napisać skrypt punktu wejścia Docker, który wypełni zmienne środowiskowe przed wykonaniem polecenia. Skrypt punktu wejścia jest również dobrym miejscem do sprawdzania poprawności parametrów i ustawiania wartości domyślnych.

Oto przykład pojemnika nginx który pobiera API_HOSTi API_PORTparametry zmienne środowiska.

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 "$@"

Plik Docker

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
źródło
5

To, co zrobiłem, to użycie erbu !

cat nginx.conf  | grep -i error_log

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

- Po użyciu erb

export APP_ROOT=/tmp

erb nginx.conf  | grep -i error_log

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

Jest to używane w pakiecie staticfile-buildpack Cloudfoundry

Przykładowa konfiguracja nginx: https://github.com/cloudfoundry/staticfile-buildpack/blob/master/conf/nginx.conf

W Twoim przypadku

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

stają się

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
źródło
4

Odnosząc się do odpowiedzi dotyczącej używania erb, można to zrobić jak poniżej.

Zapisz plik konfiguracyjny NGINX jako plik erb zawierający zmienną środowiskową i oceń go za pomocą polecenia erb do normalnego pliku konfiguracyjnego.

erb nginx.conf.erb > nginx.conf

Wewnątrz bloku serwera pliku nginx.conf.erb może być

listen <%= ENV["PORT"] %>;
Ruifeng Ma
źródło
1
Czy muszę zainstalować Ruby wewnątrz kontenera? (Jeśli nie, to w jaki sposób erbmożna dokonać podstawienia zmiennych ... po uruchomieniu kontenera, prawda?) - erbczy te ruby ​​są prawidłowe: stuartellis.name/articles/erb
KajMagnus
1
To narzędzie wiersza polecenia, które jest dostarczane ze standardową instalacją Ruby. Wypróbuj inne opcje, jeśli nie masz go w pojemniku.
Ruifeng Ma
3

Wiem, że to stare pytanie, ale na wypadek, gdyby ktoś natknął się na to (tak jak teraz), jest na to znacznie lepszy sposób. Ponieważ okno dokowane wstawia połączony alias kontenera do / etc / hosts, możesz po prostu to zrobić

upstream upstream_name {
    server docker_link_alias;
}

zakładając, że polecenie dokera jest podobne docker run --link othercontainer:docker_link_alias nginx_container.

cderwin
źródło
1
Możesz uzyskać hosta za pomocą tej metody, ale nie portu. Musisz albo na stałe zakodować port, albo założyć, że używa on portu 80.
Ben Whaley
2

Inna opcja ... Właśnie znalazłem to narzędzie dzisiaj: https://github.com/kreuzwerker/envplate ... Napisane w Go, można je bardzo łatwo zainstalować. Korzystanie z niego jest dość proste. Chociaż będziesz musiał umieścić zmienne szablonu w swoim pliku nginx.conf.

Na przykład ${SOME_ENV_VAR}zostanie zastąpiony, gdy w pliku zostanie wywołane eppolecenie envplate . Więc jeśli plik Docker nie otrzyma tego pliku binarnego lub jeśli z jakiegoś powodu nie zostanie uruchomiony, spowoduje to, że twoja konfiguracja będzie nieważna. Tylko mała uwaga w porównaniu do innych rozwiązań, takich jak używanie rozszerzeń perla lub lua.

Bardzo podoba mi się to, jak można ustawić wartości domyślne, gdy zmienna środowiskowa nie jest ustawiona. Dawny. ${SOME_ENV_VAR:default-value}(i możesz uciec od wartości). Znów envplate musi nadal działać poprawnie.

Jedną z zalet takiego podejścia jest to, że nie kończy się obrazem Dockera, który jest większy niż to konieczne, ponieważ odszedłeś instalując wszelkiego rodzaju dodatkowe moduły, których inaczej nie potrzebujesz. Może być również łatwiejsze niż używanie sed, jeśli sprawy zaczynają się komplikować i zawiera tę funkcję wartości domyślnej.

Tomek
źródło
Radzę github.com/gliderlabs/sigil, ponieważ napotkałem wiele błędów i problemów z envplate.
Nick
2

Osiągam to za pomocą skryptu powłoki.

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

}

Skrypt zastępujący zmienne środowiskowe znajduje się tutaj:

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.
źródło
1

Inną możliwością jest użycie polecenia „sed” z wyrażeniami regularnymi, wtedy nie będziesz musiał w ogóle zadzierać z plikami konfiguracyjnymi! W ten sposób możesz normalnie korzystać z plików konfiguracyjnych, ale po uruchomieniu dokera zmieni on wartości na zmienne env. Nic z tego „nie dodaje ciągu tekstowego do plików konfiguracyjnych, które wyszukujesz i zastępujesz”.

Możesz utworzyć plik run.sh z wartościami zastępczymi, używając zmiennych środowiskowych.

Aby zmienić „7” w tym wierszu:

client_body_timeout 7s;         #Default 60s

Sed Command, używając client_body_timeout jako linii wyszukiwania i $ client_body_timeout jako zastępczą zmienną env:

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

Skopiuj / wklej ten wiersz dla każdego parametru, który chcesz ustawić i zmień time_body_body_timeout za pomocą opcji config i $ client_body_timeout za pomocą zmiennej env, z którą jest on powiązany. Użyj z istniejącym plikiem konfiguracyjnym i to po prostu będzie działać.

Jeff
źródło
0

Oto przykład zastosowania tego sedpodejścia, niekoniecznie lepszego, ale może być przydatne dla niektórych. Najpierw dodaj niestandardowe słowo kluczowe, które ma zostać zastąpione w pliku conf. Po drugie, utwórz plik docker, który deklaruje ENVzmienną, a następnie plik, CMDktóry używa seddo edycji konfiguracji przed jawnym uruchomieniem nginx.

Załóżmy więc, że plik default.conf zawiera to słowo kluczowe docker_host:

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

I napisz plik Docker podobny do:

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

Następnie zbuduj obraz i uruchom kontener za pomocą

docker run -d -p 80:80 -e "docker_host=${env:COMPUTERNAME}" imagename
Steven
źródło
0

Właściwy sposób to zrobić za pomocą lua, ponieważ powyższa odpowiedź jest nieaktualna:

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

Eric Meadows
źródło
-2

Powinieneś móc robić, co chcesz, korzystając z szablonów Dockerize .

Claude
źródło
7
Czy możesz dodać więcej szczegółów?
Pierre.Vriens