Як я можу використовувати змінні середовища в Nginx.conf


183

[ Перекладено та відредаговано з https://stackoverflow.com/questions/21933955, оскільки воно вважалося занадто систематичним для StackOverflow.]

У мене є контейнер докера під управлінням Nginx, який посилається на інший контейнер докера. Ім'я хоста та IP-адреса другого контейнера завантажуються в контейнер Nginx як змінні середовища при запуску, але до цього невідомо (це динамічно). Я хочу, щоб мої nginx.confвикористовували ці значення - наприклад

upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

Як я можу отримати змінні середовища в конфігурацію Nginx при запуску?

ЗРІД 1

Це весь файл після запропонованої відповіді нижче:

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 та помилок:

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

EDIT 2: детальніше

Поточні змінні середовища

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

Корінь 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;

Конфігурація 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;

Перезавантажте конфігурацію nginx:

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

8
Це не загальне рішення для змінних оточуючих середовищ, але якщо ви хочете використовувати змінні середовища для імен хостів / IP-адрес серверів висхідного потоку, зауважте, що Docker (принаймні в останніх версіях) змінює / etc / hosts для вас. Див. Docs.docker.com/userguide/dockerlinks Це означає, що якщо ваш зв’язаний контейнер називається "app_web_1", докер створить рядок у / etc / hosts у вашому контейнері Nginx. Тож ви можете просто замінити server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000; на server app_web_1:5000;
mozz100

1
Дякуємо @ mozz100 - це неймовірно корисно - / etc / hosts в цьому випадку набагато ефективніші, ніж env vars. Єдиний біт, який не вистачає, - це те, що відбувається, якщо контейнер вище за течією буде перезапущений та отримає новий IP. Я припускаю, що дочірні контейнери все одно вказуватимуть на початковий IP, а не на новий?
Х'юго Роджер-Браун

1
Так, якщо ви перезапустили, app_web_1він отримає нову IP-адресу, тож вам доведеться також перезапустити контейнер nginx. Docker перезапустить його оновленим, /etc/hostsтому вам не потрібно буде змінювати конфігураційні файли nginx.
mozz100

Відповіді:


100

З офіційного файлу докерів Nginx:

Використання змінних середовища в конфігурації nginx:

Nginx не підтримує використання змінних середовища в більшості блоків конфігурації.

Але envsubstможе бути використаний як вирішення, якщо вам потрібно буде динамічно генерувати конфігурацію nginx до запуску nginx.

Ось приклад використання 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;'" 

Файл mysite.template може містити подібні посилання:

listen ${NGINX_PORT};

Оновлення:

Але ви знаєте, що це спричинило такі його змінні Nginx:

proxy_set_header        X-Forwarded-Host $host;

пошкоджено:

proxy_set_header        X-Forwarded-Host ;

Отже, щоб запобігти цьому, я використовую цю хитрість:

У мене є сценарій для запуску Nginx, який використовується у docker-composeфайлі як командна опція для сервера Nginx, я назвав його run_nginx.sh:

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

І тому, що визначена нова DOLLARзмінна на run_nginx.shскрипті, тепер вміст мого nginx.conf.templateфайлу для самої змінної Nginx виглядає так:

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

А для моєї визначеної змінної є так:

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

Також тут є мій реальний варіант використання для цього.


2
Це вбиває nginx confs на кшталтproxy_set_header Host $http_host;
подрібнення

22
@shredding ви можете передати імена змінних, які потрібно замінити - інших не чіпати: command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"працює для мене б / с Я знаю, як їх називають ...
pkyeck

4
ок, забув уникнути $, повинен бути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
Зважаючи на те, щоб уникнути цієї роботи у вашому власному 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
Це чудове рішення. Дякую. Також посилання, згадане вище, github.com/docker-library/docs/isissue/496 має чудове рішення проблеми.
kabirbaidhya

34

Робити це з Lua істотно простіше, ніж це звучить:

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

Я виявив, що тут:

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

Редагувати:

Мабуть, для цього потрібна установка модуля lua: https://github.com/openresty/lua-nginx-module

Редагувати 2:

Зауважте, що при такому підході ви повинні визначити envзмінну в Nginx:

env ENVIRONMENT_VARIABLE_NAME

Це потрібно робити в контексті топлеву версії, nginx.confабо це не спрацює! Не в блоці сервера або в конфігурації якогось сайту в /etc/nginx/sites-available, тому що він включений nginx.confу httpконтекст (що не є контекстом верхнього рівня).

Також зауважте, що при такому підході ви намагаєтеся зробити переадресацію, наприклад:

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

він також не працюватиме:

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

І якщо ви дасте окреме ім'я змінної:

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 не інтерпретує його і перенаправить вас на https://%24server_name_from_env/.


3
вам може знадобитися env NGINX_SERVERNAMEдесь у вашому nginx.conf.
hiroshi

Це не спрацювало для мене, хоча в моєму зображенні докера nginx є модуль lua. Чи може це бути пов’язано з тим, що я включаю конфігураційний файл у свій nginx.conf? Я намагався set_by_luaзмінну у включеному конфігураційному файлі, тоді як env MY_VARдекларація була в головному nginx.conf, як було запропоновано. Яка ганьба, це було б найчистішим рішенням!
pederpansen

Хтось знає плюси та мінуси використання цього методу envsubst? Я думаю, що професіонал - вам не потрібно запускати envsubstrкоманду перед запуском сервера, і це означає, що вам потрібно встановити модуль lua? Цікаво, чи є якісь наслідки для безпеки для будь-якого підходу?
Марк Вінтерботтом

@MarkWinterbottom ще не перевіряв це, але, схоже, вам не доведеться надавати доступ для запису до файлів конфігурації nginx, якими ви користуєтесь, envsubstі які в моєму випадку не мають потреби
Griddo

14

Я написав щось, що може бути або не корисним: https://github.com/yawn/envplate

Він вбудовує редагування файлів конфігурації з посиланнями $ {key} на змінні середовища, необов'язково створюючи резервні копії / реєстрацію того, що він робить. Він написаний на Go, і отриманий статичний двійковий файл можна просто завантажити з вкладки випуску для Linux та MacOS.

Він також може виконувати () процеси, замінювати параметри за замовчуванням, журнали та має розумну семантику відмов.


10

Офіційне зображення nginx рекомендує використовуватиenvsubst , але, як вказують інші, воно замінить також $hostі інші змінні, що не бажано. Але, на щастя, envsubstможна взяти за параметр імена змінних, які потрібно замінити .

Щоб уникнути дуже складного параметру команди для контейнера (як у зв'язаному прикладі), ви можете написати скрипт вхідної точки Docker, який заповнить змінні середовища перед виконанням команди. Сценарій вхідної точки також є хорошим місцем для перевірки параметрів та встановлення значень за замовчуванням.

Ось приклад контейнера nginx, який приймає API_HOSTта API_PORTпараметри як змінні середовища.

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

Докерфайл

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

Що я зробив, це використовувати erb !

cat nginx.conf  | grep -i error_log

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

- Після використання erb

export APP_ROOT=/tmp

erb nginx.conf  | grep -i error_log

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

Це використовується в Cloudfoundry staticfile-buildpack

Зразок конфігурації nginx: https://github.com/cloudfoundry/staticfile-buildpack/blob/master/conf/nginx.conf

У вашому випадку

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

стати

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

Посилаючись на відповідь про використання erb, це можна зробити як нижче.

Запишіть конфігураційний файл NGINX як файл erb, що містить змінну середовища, і оцініть його за допомогою команди erb у звичайний файл конфігурації.

erb nginx.conf.erb > nginx.conf

Всередині серверного блоку файлу nginx.conf.erb може бути

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

1
Чи потрібно мені встановлювати Ruby тоді, всередині контейнера? (Якщо ні, то як можна erbзробити заміну змінної ... після запуску контейнера, правда?) - erbчи правильно це Ruby: stuartellis.name/articles/erb
KajMagnus

1
Це інструмент командного рядка, який постачається зі стандартною установкою Ruby. Спробуйте інші варіанти, якщо у вас немає цього контейнера.
Ruifeng Ma

3

Я знаю, що це старе питання, але на всякий випадок, якщо хтось натрапить на це (як я зараз), є набагато кращий спосіб зробити це. Оскільки докер вставляє псевдонім пов'язаного контейнера в / etc / hosts, ви можете просто зробити

upstream upstream_name {
    server docker_link_alias;
}

якщо припустити, що ваша команда docker є чимось на зразок docker run --link othercontainer:docker_link_alias nginx_container.


1
Ви можете отримати хост за допомогою цього методу, але не порт. Вам доведеться або жорсткий код порту, або припустити, що він використовує порт 80.
Бен Уейлі

2

Ще один варіант ... Я щойно знайшов цей інструмент сьогодні: https://github.com/kreuzwerker/envplate ... Написаний на Go, його можна встановити дуже легко. Використовувати його досить просто. Хоча вам потрібно буде розмістити змінні шаблону у вашому nginx.conf.

Наприклад, ${SOME_ENV_VAR}буде замінено, коли epкоманда envplate буде викликана у файл. Тож у випадку, якщо ваш Dockerfile не зможе отримати цей бінарний файл або якщо він з якихось причин не запуститься, він визнає ваш конфігурацію недійсним. Лише невелика примітка порівняно з іншими рішеннями, такими як розширення Perl або Lua.

Мені дуже подобається, як ви також можете встановити значення за замовчуванням, коли змінна середовища не встановлена. Вих. ${SOME_ENV_VAR:default-value}(і ви можете уникнути значень). Знову ж, envplate все ще має успішно працювати.

Однією з переваг використання такого підходу є те, що ви не закінчуєте зображення Docker, яке перевищує необхідне, тому що ви перестали встановлювати всілякі додаткові модулі, які вам інакше не потрібні. Це також може бути простіше, ніж використовувати sed, якщо речі починають ускладнюватися, і він містить функціональне значення за замовчуванням.


Я раджу github.com/gliderlabs/sigil, оскільки я натрапив на багато помилок і проблем із envplate.
Нік

2

Я досягаю цього за допомогою сценарію оболонки.

Ось шаблон 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;
    }

}

І сценарій для заміни змінних оточення є тут:

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

Інша можливість - використовувати команду 'sed' з регулярними виразами, тоді вам взагалі не доведеться возитися з конфігураційними файлами! Таким чином ви можете нормально використовувати свої конфігураційні файли, але коли ви запустите docker, він замінить значення змінними env. Нічого з цього "додайте рядок тексту у свої конфігураційні файли, які ви шукаєте та заміняєте."

Ви можете створити файл run.sh із значеннями заміни, використовуючи змінні середовища.

Щоб змінити "7" у цьому рядку:

client_body_timeout 7s;         #Default 60s

Команда Sed, використовуючи client_body_timeout як рядок пошуку та $ client_body_timeout в якості змінної env змінної:

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

Скопіюйте / вставте цей рядок для кожного параметра, який ви хочете встановити, і змініть client_body_timeout за допомогою параметра config та $ client_body_timeout зі змінною env, з якою вона пов’язана. Використовуйте з існуючим конфігураційним файлом, і він просто працюватиме.


0

Ось приклад використання sedпідходу, не обов’язково кращого, але він може бути корисним для деяких. Спочатку додайте спеціальне ключове слово, яке потрібно замінити у конф-файлі. По-друге, створіть dockerfile, який оголошує ENVзмінну, а потім a, CMDякий використовує sedдля редагування конфігурації перед явним запуском nginx.

Тож припустимо, що default.conf містить це ключове слово docker_host:

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

І напишіть свій Dockerfile схожий на:

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

Потім побудуйте зображення та запустіть контейнер за допомогою

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

0

Правильний спосіб зробити це з луєю, оскільки відповідь вище несвіжий:

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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.