Как я могу использовать переменные окружения в Nginx.conf

184

[Перемещено и отредактировано вниз по адресу https://stackoverflow.com/questions/21933955, поскольку оно считалось слишком похожим на sysadmin для 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

РЕДАКТИРОВАТЬ 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

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;

Конфигурация сайта 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, docker создаст строку в / 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

Ответы:

101

Из официального файла докера 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 как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/issues/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.
Хироши
Это не сработало для меня, хотя у меня есть модуль lua в образе докера nginx. Может ли это быть связано с тем, что я включил файл конфигурации в свой nginx.conf? Я пытался set_by_luaпеременную во включенном файле конфигурации, в то время как env MY_VARобъявление было в основном nginx.conf, как предложено. Какой позор, это было бы самое чистое решение!
pederpansen
Кто-нибудь знает плюсы / минусы использования этого метода envsubst? Я полагаю, что вы не должны запускать envsubstrкоманду перед запуском сервера, а дело в том, что вам нужно установить модуль lua? Интересно, есть ли какие-либо последствия для безопасности любого из этих подходов?
Марк Уинтерботтом
@MarkWinterbottom еще не проверял это, но, похоже, вам не нужно было бы предоставлять доступ на запись к файлам конфигурации nginx, которые вы должны использовать envsubstи которые в моем случае
запрещены
Когда вы говорите, что envдиректива должна быть выполнена "в контексте верхнего уровня в nginx.conf", что именно означает "nginx.conf"? Где этот файл должен быть?
Джек М
14

Я написал что-то, что может или не может быть полезным: https://github.com/yawn/envplate

Он встраивает файлы конфигурации со ссылками $ {key} на переменные среды, при необходимости создавая резервные копии и записывая, что он делает. Он написан на Go, и полученный статический двоичный файл можно просто загрузить с вкладки релиза для Linux и MacOS.

Он также может выполнять процессы exec (), заменять значения по умолчанию, протоколировать журналы и имеет разумную семантику сбоев.

зевать
источник
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 "$@"

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

Что я сделал, так это использовал 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

Я знаю, что это старый вопрос, но на случай, если кто-то наткнется на это (как у меня сейчас), есть гораздо лучший способ сделать это. Поскольку docker вставляет псевдоним связанного контейнера в / etc / hosts, вы можете просто сделать

upstream upstream_name {
    server docker_link_alias;
}

Предполагая, что ваша команда Docker что-то вроде docker run --link othercontainer:docker_link_alias nginx_container.

cderwin
источник
1
Вы можете получить хост, используя этот метод, но не порт. Вы должны либо жестко закодировать порт, либо предположить, что он использует порт 80.
Бен Уэйли,
2

Другой вариант ... Я только что нашел этот инструмент сегодня: https://github.com/kreuzwerker/envplate ... Написанный на Go, его можно очень легко установить. Используя это довольно просто. Хотя вам нужно будет разместить переменные шаблона в вашем nginx.conf.

Например, ${SOME_ENV_VAR}будет заменено при вызове epкоманды envplate для файла. Поэтому, если ваш Dockerfile не сможет получить этот двоичный файл или он по какой-то причине не запустится, он сделает вашу конфигурацию недействительной. Небольшое замечание по сравнению с другими решениями, такими как использование расширений perl или lua.

Мне очень нравится, как вы можете установить значения по умолчанию, когда переменная окружения не установлена. Ex. ${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;'
Geige V
источник
1

Другая возможность - использовать команду «sed» с регулярными выражениями, тогда вам вообще не придется возиться с файлами конфигурации! Таким образом, вы можете использовать ваши конфигурационные файлы как обычно, но когда вы запустите docker, он поменяет значения с переменными env. Ничто из этого «не добавляет строку текста в ваши файлы конфигурации, которые вы ищете и заменяете».

Вы можете создать файл run.sh со значениями замены, используя переменные среды.

Чтобы изменить «7s» в этой строке:

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подхода, не обязательно лучшего, но он может быть полезен для некоторых. Сначала добавьте пользовательское ключевое слово для замены в файле conf. Во-вторых, создайте 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;
}

Эрик Медоуз
источник
-2

Вы должны иметь возможность делать то, что вы хотите с шаблонами Dockerize .

Клод
источник
7
Можете ли вы добавить больше деталей?
Pierre.Vriens