[从https://stackoverflow.com/questions/21933955交叉发布和编辑,因为对于StackOverflow来说,它过于像sysadmin。]
我有一个运行Nginx的Docker容器,该容器链接到另一个Docker容器。第二个容器的主机名和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
根目录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
从 Nginx官方docker文件中:
在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;
– 在 2016年 2月17日17:00粉碎 -
27
-
6好的,忘了逃脱
$
,应该是command: /bin/bash -c "envsubst '$VAR1 $VAR2' < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
– pkyeck '16 Feb 19'在10:33 -
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 2016年 11月28日,0:12 -
2这是一个很好的解决方案。谢谢。同样,上面提到的链接github.com/docker-library/docs/issues/496也提供了一个很好的解决方案。 – kabirbaidhya 17年 2月9日在17:48
使用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 16年 2月15日,14:28 -
尽管我在nginx docker映像中有lua模块,但这对我不起作用。这可能与我在nginx.conf中包含配置文件的事实有关吗?如所建议的那样
set_by_lua
,我在env MY_VAR
声明位于主nginx.conf中的同时尝试包含的配置文件中的变量。真可惜,这本来是最干净的解决方案! – pederpansen 17年 5月16日在10:16 -
有谁知道使用这种方法的利弊
envsubst
?我想专业人士是您不需要envsubstr
在启动服务器之前运行命令,缺点是您需要安装lua模块?我想知道这两种方法是否对安全性有影响? – Mark Winterbottom 18年3月4日在16:19 -
-
当您说
env
必须在“ nginx.conf的顶级上下文中”完成指令时,“ nginx.conf”到底是什么?该文件应该在哪里? – 杰克M 19年10月12日在20:23
官方的nginx图像建议使用envsubst
,但是正如其他人指出的那样,它也会替代$host
和其他变量,这是不希望的。但是幸运的是envsubst
可以将要替换的变量名称作为参数。
为了避免容器使用非常复杂的命令参数(如链接示例中所示),您可以编写Docker入口点脚本,该脚本将在执行命令之前填充环境变量。入口点脚本也是验证参数和设置默认值的好地方。
这是一个nginx容器的示例,该容器将in 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 "$@"
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;"]
-
我稍微更改了脚本,以从conf.d中清除模板。最终版本:```#!/ usr / bin / env sh set -eu envsubst'$ {API_HOST} $ {API_PORT}'</etc/nginx/conf.d/default.conf.template> / etc / nginx / conf.d / default.conf rm /etc/nginx/conf.d/default.conf/template exec“ $ @”```很棒的脚本,谢谢! – phillipuniverse 19年 11月20日在18:29
我写了一些可能有用或可能没有帮助的东西:https : //github.com/yawn/envplate
它使用对环境变量的$ {key}引用内联编辑配置文件,可以选择创建备份/记录其操作。它是用Go语言编写的,生成的静态二进制文件可以简单地从Linux和MacOS的发行版标签中下载。
它也可以执行()处理,替换默认值,记录日志并具有明智的故障语义。
关于使用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 17年 4月8日,7:57 -
1个这是标准Ruby安装随附的命令行工具。如果您没有在容器中尝试其他选项。 – 马瑞峰 17年4月12日在11:45
我所做的就是使用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
}
我知道这是一个古老的问题,但是以防万一有人偶然发现了这个问题(如我现在所知道的),有更好的方法来做到这一点。因为docker在/ etc / hosts中插入了链接容器的别名,所以您可以
upstream upstream_name {
server docker_link_alias;
}
假设您的docker命令类似于docker run --link othercontainer:docker_link_alias nginx_container
。
我使用shell脚本完成此操作。
这是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;'
另一个选择...我今天才发现此工具:https : //github.com/kreuzwerker/envplate ...用Go语言编写,可以很容易地安装。使用它非常简单。尽管您将需要在nginx.conf中放置模板变量。
例如,在文件上调用${SOME_ENV_VAR}
envplate的ep
命令时将被替换。因此,如果您的Dockerfile无法获取该二进制文件或由于某种原因而无法运行,它将使您的配置无效。与使用perl或lua扩展之类的其他解决方案相比,仅需一点注意。
我真的很喜欢如何在未设置环境变量的情况下设置默认值。例如 ${SOME_ENV_VAR:default-value}
(并且您可以转义值)。同样,envplate必须仍然成功运行。
使用这种方法的一个好处是,您最终不会获得比所需镜像更大的Docker镜像,因为您无需安装其他不需要的各种额外模块。如果事情开始变得复杂并且包含默认值功能,它可能比使用sed更容易。
有很多方法。在某些答案中已概述了一些内容。
如果您使用ngx_http_js_module
,则还有一种使用JS的方法:
## /etc/nginx/fetch_env.js
function fetch_upstream_host(r) {
return process.env.UPSTREAM_HOST;
}
function fetch_upstream_port(r) {
return process.env.UPSTREAM_PORT;
}
和
## /etc/nginx/nginx.conf
load_module modules/ngx_http_js_module.so;
env UPSTREAM_HOST;
env UPSTREAM_PORT;
http {
js_include fetch_env.js;
js_set $upstream_host fetch_upstream_host;
js_set $upstream_port fetch_upstream_port;
server {
...
location / {
...
proxy_pass http://$upstream_host:$upstream_port;
}
}
请确保使用njs模块0.33或更高版本
另一种可能性是将'sed'命令与正则表达式一起使用,那么您根本不必弄乱配置文件!这样,您可以正常使用配置文件,但是在运行docker时,它将与env变量交换出值。这些都不是“向搜索并替换为的配置文件中添加文本字符串”。
您可以使用环境变量创建带有替换值的run.sh文件。
要更改此行上的“ 7s”:
client_body_timeout 7s; #Default 60s
使用client_body_timeout作为搜索行和$ client_body_timeout作为替换环境变量的Sed命令:
sed -i "s/(client_body_timeout).*?;/1 $client_body_timeout;/" /usr/local/nginx/conf/nginx.conf
复制/粘贴您要设置的每个参数的这一行,并使用config选项更改client_body_timeout,并使用与其关联的env变量更改$ client_body_timeout。与现有的配置文件一起使用,它将可以正常工作。
这是使用此sed
方法的示例,不一定更好,但对某些人可能有用。首先,在conf文件中添加要替换的自定义关键字。二,创建声明的一个dockerfile ENV
变量,然后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
用lua做到这一点的正确方法,因为上面的答案是陈旧的:
server {
set_by_lua $curr_server_name 'return os.getenv("NGINX_SERVERNAME")';
server_name = $curr_server_name;
}
server $ENV{"APP_WEB_1_PORT_5000_TCP_ADDR"}:5000;
为server app_web_1:5000;
– mozz100 2014年 7月10日在17:18app_web_1
,它将获得一个新的IP地址,因此您也需要重新启动nginx容器。Docker将通过更新将其重新启动,/etc/hosts
因此您无需更改nginx配置文件。 – mozz100 2014年 7月14日14:36