Como posso usar variáveis ​​de ambiente no Nginx.conf

183

[Postado e editado em https://stackoverflow.com/questions/21933955 , pois foi considerado muito semelhante ao sysadmin para o StackOverflow.]

Eu tenho um contêiner de docker executando o Nginx, vinculado a outro contêiner de docker. O nome do host e o endereço IP do segundo contêiner são carregados no contêiner Nginx como variáveis ​​de ambiente na inicialização, mas não são conhecidos antes (é dinâmico). Eu quero que eu nginx.confuse esses valores - por exemplo,

upstream gunicorn {
    server $APP_HOST_NAME:$APP_HOST_PORT;
}

Como posso obter variáveis ​​de ambiente na configuração do Nginx na inicialização?

EDIT 1

Este é o arquivo inteiro, após a resposta sugerida abaixo:

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

Recarregando nginx e erros:

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

EDIT 2: mais detalhes

Variáveis ​​de ambiente atuais

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;

Configuração do 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;

Recarregue a configuração do nginx:

root@87ede56e0b11:/# nginx -s reload
nginx: [emerg] directive "server" is not terminated by ";" in /etc/nginx/sites-enabled/default:3
Hugo Rodger-Brown
fonte
8
Esta não é uma solução genérica para variáveis ​​de ambiente, mas se você deseja usar variáveis ​​de ambiente para os nomes de host / endereços IP de servidores upstream, observe que o Docker (pelo menos nas versões recentes) modifica o / etc / hosts para você. Consulte docs.docker.com/userguide/dockerlinks Isso significa que, se o contêiner vinculado for chamado 'app_web_1', o docker criará uma linha em / etc / hosts no contêiner Nginx. Então você pode simplesmente substituir server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000; por server app_web_1:5000;
mozz100
1
Obrigado @ mozz100 - que é incrivelmente útil - as entradas / etc / hosts são muito mais eficazes do que env vars neste caso. O único bit que falta é o que acontece se o contêiner upstream for reiniciado e adquirir um novo IP. Estou presumindo que os contêineres filhos ainda apontarão para o IP original, não o novo?
Hugo Rodger-Brown
1
Sim, se você reiniciar app_web_1, obterá um novo endereço IP, portanto, você também precisará reiniciar o contêiner nginx. O Docker reinicia-o com uma atualização, /etc/hostspara que você não precise alterar o (s) arquivo (s) de configuração do nginx.
mozz100

Respostas:

100

A partir do arquivo oficial estivador Nginx:

Usando variáveis ​​de ambiente na configuração do nginx:

Pronto para uso, o Nginx não suporta o uso de variáveis ​​de ambiente na maioria dos blocos de configuração.

Mas envsubstpode ser usado como solução alternativa se você precisar gerar sua configuração do nginx dinamicamente antes do nginx iniciar.

Aqui está um exemplo usando o 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;'" 

O arquivo mysite.template pode conter referências de variáveis ​​como esta:

listen ${NGINX_PORT};

Atualizar:

Mas você sabe que isso causou às suas variáveis ​​Nginx assim:

proxy_set_header        X-Forwarded-Host $host;

danificado para:

proxy_set_header        X-Forwarded-Host ;

Então, para evitar isso, eu uso este truque:

Eu tenho um script para executar o Nginx, usado no docker-composearquivo como opção de comando para o servidor Nginx, que o nomeei run_nginx.sh:

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

E por causa da nova DOLLARvariável definida no run_nginx.shscript, agora o conteúdo do meu nginx.conf.templatearquivo para a variável Nginx é assim:

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

E para a minha variável definida é assim:

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

Também aqui , há o meu caso de uso real para isso.

Omid Raha
fonte
2
Isso mata nginx confs comoproxy_set_header Host $http_host;
destruindo
22
@shredding você pode passar em nomes de variáveis a serem substituídas - outros não são tocados: command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"funciona para me b / c eu sei que eles são chamados ...
pkyeck
4
ok, esqueceu-se para escapar da $, deve sercommand: /bin/bash -c "envsubst '\$VAR1 \$VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
pkyeck
2
Com a consideração de escapar, isso funciona em seu próprio 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
Esta é uma otima soluçao. Obrigado. Além disso, o link mencionado acima github.com/docker-library/docs/issues/496 tem uma ótima solução para o problema.
Kabirbaidhya
34

Fazer isso com Lua é substancialmente mais fácil do que parece:

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

Eu achei isso aqui:

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

Editar:

Aparentemente, isso requer a instalação do módulo lua: https://github.com/openresty/lua-nginx-module

Edição 2:

Observe que, com essa abordagem, você deve definir a envvariável no Nginx:

env ENVIRONMENT_VARIABLE_NAME

Você precisa fazer isso no contexto de nível superior nginx.confou não funcionará! Não está no bloco do servidor ou na configuração de algum site /etc/nginx/sites-available, porque está incluído nginx.confno httpcontexto (que não é o contexto de nível superior).

Observe também que, com essa abordagem, se você tentar fazer um redirecionamento, por exemplo:

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

não vai funcionar tão bem:

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

E se você der um nome de variável separado para ela:

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

O nginx não o interpretará e o redirecionará para https://%24server_name_from_env/.

Colton Leekly-Winslow
fonte
3
você pode precisar de env NGINX_SERVERNAMEalgum lugar no seu nginx.conf.
Hirsoni
Isso não funcionou para mim, embora eu tenha o módulo lua na minha imagem do docker nginx. Isso pode estar relacionado ao fato de eu incluir um arquivo de configuração no meu nginx.conf? Eu estava tentando set_by_luaa variável no arquivo de configuração incluído, enquanto a env MY_VARdeclaração estava no nginx.conf principal, conforme sugerido. Que pena, essa teria sido a solução mais limpa!
Pederpansen
Alguém sabe os prós / contras de usar esse método envsubst? Eu acho que o profissional é que você não precisa executar o envsubstrcomando antes de iniciar o servidor e o golpe é que você precisa instalar o módulo lua? Gostaria de saber se existem implicações de segurança em qualquer uma das abordagens.
Mark Winterbottom
O @ MarkWinterbottom ainda não testou isso, mas parece que você não precisaria conceder acesso de gravação aos arquivos de configuração do nginx, que você precisa usar envsubste que não é
aceito
14

Eu escrevi algo que pode ou não ser útil: https://github.com/yawn/envplate

Ele inline edita arquivos de configuração com $ {key} referências a variáveis ​​de ambiente, opcionalmente criando backups / registrando o que faz. Está escrito em Go e o binário estático resultante pode ser simplesmente baixado na guia de lançamento do Linux e MacOS.

Também pode exec () processa, substitui padrões, registra e tem semântica de falha sensível.

bocejar
fonte
10

A imagem oficial do nginx recomenda o usoenvsubst , mas, como indicado por outras pessoas, ela substituirá também $hoste outras variáveis, o que não é desejável. Felizmente, porém, envsubstpodemos tomar como parâmetro os nomes das variáveis ​​a serem substituídas .

Para evitar um parâmetro de comando muito complexo no contêiner (como no exemplo vinculado), você pode escrever um script de ponto de entrada do Docker que preencherá as variáveis ​​de ambiente antes de executar o comando. O script do ponto de entrada também é um bom local para validar os parâmetros e definir valores padrão.

Aqui está um exemplo de um contêiner nginx que recebe API_HOSTe API_PORTparâmetros como variáveis ​​de ambiente.

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

O que eu fiz foi usar o erb !

cat nginx.conf  | grep -i error_log

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

- Depois de usar erb

export APP_ROOT=/tmp

erb nginx.conf  | grep -i error_log

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

Isso é usado no staticfile-buildpack Cloudfoundry

Exemplo de configuração do nginx: https://github.com/cloudfoundry/staticfile-buildpack/blob/master/conf/nginx.conf

No seu caso

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

tornar-se

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

Referindo-se à resposta sobre o uso de erb, isso pode ser feito conforme abaixo.

Escreva o arquivo de configuração NGINX como um arquivo erb que contém a variável de ambiente e avalie-o usando o comando erb em um arquivo de configuração normal.

erb nginx.conf.erb > nginx.conf

Dentro do bloco do servidor do arquivo nginx.conf.erb, pode haver

listen <%= ENV["PORT"] %>;
Ruifeng Ma
fonte
1
Preciso instalar o Ruby então, dentro do contêiner? (Se não, como é erb? Capaz de fazer a substituição de variáveis ... depois de o contentor foi iniciado, à direita) - erbé este direito material Ruby: stuartellis.name/articles/erb
KajMagnus
1
É uma ferramenta de linha de comando que vem com a instalação padrão do Ruby. Tente outras opções se você não o tiver no contêiner.
Ruifeng Ma
3

Sei que essa é uma pergunta antiga, mas, caso alguém se depare com essa questão (como agora tenho), há uma maneira muito melhor de fazer isso. Como o docker insere o alias do contêiner vinculado em / etc / hosts, você pode simplesmente

upstream upstream_name {
    server docker_link_alias;
}

assumindo que o seu comando docker é algo parecido docker run --link othercontainer:docker_link_alias nginx_container.

cderwin
fonte
1
Você pode obter o host usando esse método, mas não a porta. Você tem que quer código rígido o porto ou assumir que ele está usando a porta 80.
Ben Whaley
2

Outra opção ... Acabei de encontrar esta ferramenta hoje: https://github.com/kreuzwerker/envplate ... Escrita no Go, ela pode ser instalada com muita facilidade. Usá-lo é bastante simples. Embora você precise colocar variáveis ​​de modelo no seu nginx.conf.

Por exemplo ${SOME_ENV_VAR}, será substituído quando o epcomando envplate for chamado no arquivo. Portanto, se o seu Dockerfile falhar ao obter esse binário ou se não for executado por algum motivo, isso tornaria sua configuração inválida. Apenas uma pequena nota em comparação com outras soluções, como usar extensões perl ou lua.

Eu realmente gosto de como você pode definir valores padrão também para quando a variável de ambiente não está definida. Ex. ${SOME_ENV_VAR:default-value}(e você pode escapar de valores). Novamente, o envplate ainda deve ser executado com êxito.

Um benefício do uso de uma abordagem como essa é que você não acaba com uma imagem do Docker maior que o necessário, porque instalou todos os tipos de módulos extras que não seriam necessários. Também pode ser mais fácil do que usar o sed se as coisas começarem a ficar complexas e contiver essa funcionalidade de valor padrão.

Tom
fonte
Aconselho o github.com/gliderlabs/sigil enquanto encontrei muitos bugs e problemas com o envplate.
Nick
2

Eu faço isso usando um script de shell.

Aqui está o modelo 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;
    }

}

E o script para substituir as variáveis ​​de ambiente está aqui:

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

Outra possibilidade é usar o comando 'sed' com expressões regulares, para que você não precise mexer nos seus arquivos de configuração! Dessa forma, você pode usar seus arquivos de configuração normalmente, mas quando você executa o docker, ele troca os valores com as variáveis ​​env. Nada disso "adiciona uma sequência de texto aos arquivos de configuração que você pesquisa e substitui".

Você pode criar um arquivo run.sh com valores de substituição usando suas variáveis ​​de ambiente.

Para alterar os "7s" nesta linha:

client_body_timeout 7s;         #Default 60s

Comando Sed usando client_body_timeout como linha de pesquisa e $ client_body_timeout como variável de env de substituição:

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

Copie / cole esta linha para cada parâmetro que você deseja definir e altere o client_body_timeout com a opção de configuração e $ client_body_timeout com a variável env à qual está associado. Use com o arquivo de configuração existente e ele simplesmente funcionará.

Jeff
fonte
0

Aqui está um exemplo de uso da sedabordagem, não necessariamente melhor, mas pode ser útil para alguns. Primeiro, adicione uma palavra-chave personalizada para ser substituída no arquivo conf. Segundo, crie um arquivo docker que declare uma ENVvariável e depois um CMDque use sedpara editar a configuração antes de executar o nginx explicitamente.

Então, suponha que seu default.conf contenha isso com a palavra docker_host- chave :

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

E escreva seu Dockerfile semelhante a:

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

Em seguida, crie a imagem e execute o contêiner usando

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

Maneira correta de fazer isso com lua, pois a resposta acima é obsoleta:

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

Eric Meadows
fonte
-2

Você deve poder fazer o que quiser com os modelos do Dockerize .

Claude
fonte
7
Você pode adicionar mais alguns detalhes?
21416 Pierre