我有一个运行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


评论

这不是环境变量的通用解决方案,但是如果您想使用环境变量作为上游服务器的主机名/ IP地址,请注意,Docker(至少在最新版本中)会为您修改/ etc / hosts。请参阅docs.docker.com/userguide/dockerlinks。这意味着,如果链接的容器名为“ app_web_1”,则docker将在Nginx容器的/ etc / hosts中创建一行。因此,您只需替换服务器$ ENV {“ APP_WEB_1_PORT_5000_TCP_ADDR”}:5000;使用服务器app_web_1:5000;

感谢@ mozz100-这非常有用-在这种情况下/ etc / hosts条目比env vars有效得多。唯一缺少的一点是,如果上游容器重新启动并获取新的IP,将会发生什么。我以为子容器仍将指向原始IP,而不是新IP?

是的,如果您重新启动app_web_1,它将获得一个新的IP地址,因此您也需要重新启动nginx容器。 Docker将使用更新的/ etc / hosts重新启动它,因此您无需更改nginx配置文件。

仔细阅读这里的答案,我感到很惊讶,可能是全球最流行的Web服务器并没有优雅地实现如此基本的功能。

#1 楼

从官方Nginx泊坞文件:


在nginx配置中使用环境变量:

现成的Nginx不支持使用环境变量
在大多数配置块中。

但是,如果需要在nginx启动之前动态生成nginx配置,则可以将envsubst用作解决方法。
这是使用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自身变量的nginx.conf.template文件的内容是这样的:

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


我定义的变量是这样的:

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


这里也是我真正的用例。

评论


那会杀死像proxy_set_header Host $ http_host这样的nginx conf;

–切碎
16年2月17日在17:00

@shredding,您可以传递要替换的变量名称-其他名称不变:命令:/ bin / bash -c“ envsubst'$ VAR1 $ VAR2' / etc / nginx / conf.d / default.conf && nginx -g'daemon off;'“对我有用b / c我知道他们叫什么...

–pkyeck
16-2-19在10:13

好的,忘记转义$,应该使用以下命令:/ bin / bash -c“ envsubst'\ $ VAR1 \ $ VAR2' /etc/nginx/conf.d /default.conf && nginx -g'守护进程关闭;'“

–pkyeck
16-2-19在10:33

考虑到转义,这可以在您自己的Dockerfile中使用:CMD [“ / bin / sh”,“-c”,“ if [-n \” $ {SOME_ENV} \“]];然后使用以下命令回显envsubst'$ {SOME_ENV}' $ {SOME_ENV} && envsubst'$ {SOME_ENV}' /etc/nginx/conf.d/default.conf; fi; nginx -g'守护进程关闭;'“ ]

– KCD
16-11-28在0:12



在docker-compose.yml中转义$应该用另一个$来完成。因此,命令应如下所示:命令:/ bin / bash -c“ envsubst'$$ {VAR1} $$ {VAR2}' / etc / nginx / conf.d / default.conf && nginx -g'daemon off;'“

–谢尔盖
19年7月5日在10:49

#2 楼

官方的nginx图像建议使用envsubst,但是正如其他人指出的那样,它将替换$host和其他变量,这是不希望的。但是幸运的是envsubst可以将要替换的变量名称作为参数。

要避免对容器使用非常复杂的命令参数(如链接示例中所示),您可以编写一个Docker入口点脚本,该脚本将在执行命令之前填写环境变量。入口点脚本也是验证参数和设置默认值的好地方。

这里是一个nginx容器的示例,该容器将API_HOSTAPI_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;"]


评论


我稍微更改了脚本以从conf.d中清除模板。最终版本:```#!/ usr / bin / env sh set -eu envsubst'$ {API_HOST} $ {API_PORT}' / etc / nginx / conf.d / default.conf rm /etc/nginx/conf.d/default.conf/template exec“ $ @”```很棒的脚本,谢谢!

– phillipuniverse
19年11月20日在18:29



#3 楼

使用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:

请注意,使用这种方法必须在Nginx中定义env变量:

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/

评论


您可能需要在nginx.conf中的某个位置使用env NGINX_SERVERNAME。

– 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

@MarkWinterbottom尚未对此进行测试,但是看起来您不必授予对nginx配置文件的写访问权限,您必须使用envsubst,在我看来这是不行

– Griddo
18年6月5日在14:04

当您说必须在“ nginx.conf的顶级上下文中”完成env指令时,“ nginx.conf”到底是什么?该文件应该在哪里?

–杰克M
19-10-12在20:23

#4 楼


现在,envsubstr映像可以自动处理nginx。但是此图像具有一个函数,该函数将在nginx启动之前提取环境变量。


默认情况下,该函数读取/etc/nginx/templates/*.template中的模板文件并将执行envsubst的结果输出到/etc/nginx/conf.d。 />

评论


这很棒!但是,我没有看到这种情况的发生。我将.template文件放在/ etc / nginx / templates /中,但未将任何内容写入/etc/nginx/conf.d。我正在使用docker镜像nginx:1.19.2。我有一个自定义的入口点脚本,但最终会调用nginx -g'daemon off;'。我想念什么吗?

– John L
8月31日20:31



@JohnL我看上去并不笨拙,但似乎此操作似乎是通过Dockerfile中包含的脚本完成的。您需要运行该脚本才能使用该功能

–马丁
9月1日4:36



这应该是新接受的答案。奇迹般有效。

–科林尼亚
9月18日19:28



#5 楼

我写了一些可能有用或可能无用的东西:https://github.com/yawn/envplate

它使用$ {key}对环境变量的引用内联编辑配置文件,可以选择创建备份/日志记录它能做什么。它是用Go语言编写的,生成的静态二进制文件可以从Linux和MacOS的发行版标签中简单下载。

它还可以exec()处理,替换默认值,记录日志并具有明智的故障语义。

#6 楼

关于使用erb的答案,可以按照以下步骤进行。

将NGINX配置文件写为包含环境变量的erb文件,并使用erb命令将其评估为普通配置文件。

erb nginx.conf.erb > nginx.conf


在nginx.conf.erb文件的服务器块中可能有

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


评论


然后,我需要在容器内安装Ruby吗? (如果不是,那么erb如何在​​容器启动后进行变量替换,对吗?)— erb是Ruby的正确用法:stuartellis.name/articles/erb

– KajMagnus
17-4-8在7:57



这是标准Ruby安装随附的命令行工具。如果容器中没有,请尝试其他选择。

–马瑞峰
17年4月12日在11:45

#7 楼

我所做的就是使用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
}


#8 楼

我使用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;'


评论


与docker-compose完美搭配。

–石tone
7月2日8:21

#9 楼

我知道这是一个古老的问题,但是以防万一有人偶然发现了这个问题(如我现在所知道的),有更好的方法来做到这一点。因为docker在/ etc / hosts中插入了链接容器的别名,所以您可以

upstream upstream_name {
    server docker_link_alias;
}


,假设您的docker命令类似于docker run --link othercontainer:docker_link_alias nginx_container

评论


您可以使用此方法获取主机,但不能获取端口。您必须对端口进行硬编码,或者假设它正在使用端口80。

– Ben Whaley
16 Mar 18 '16 at 13:58

#10 楼

有很多方法。在答案中已经概述了一些内容。

如果使用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或更高版本

#11 楼

另一个选择...我今天才发现此工具:https://github.com/kreuzwerker/envplate ...用Go语言编写,可以很容易地安装。使用它非常简单。尽管您需要将模板变量放在nginx.conf中。例如,当在文件上调用envplate的${SOME_ENV_VAR}命令时,ep将被替换。因此,如果您的Dockerfile无法获取该二进制文件或由于某种原因而无法运行,它将使您的配置无效。与使用perl或lua扩展之类的其他解决方案相比,只需要一点注意。

我真的很喜欢如何在未设置环境变量的情况下设置默认值。例如${SOME_ENV_VAR:default-value}(并且您可以转义值)。再次,envplate必须仍然能够成功运行。

使用这种方法的一个好处是,您不会得到比所需的更大的Docker映像,因为您无需安装各种额外的模块您根本不需要。如果事情开始变得复杂并且包含默认值功能,它可能比使用sed更容易。

评论


当我遇到envplate的许多错误和问题时,我建议github.com/gliderlabs/sigil。

–尼克
18年5月23日在17:49

#12 楼

这是使用sed方法的示例,不一定更好,但对某些人可能有用。首先,在conf文件中添加要替换的自定义关键字。其次,创建一个先声明一个ENV变量的dockerfile,然后创建一个先使用CMD编辑配置的sed,然后再显式运行nginx。
>
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


#13 楼

另一种可能性是将'sed'命令与正则表达式一起使用,那么您根本不必弄乱配置文件!这样,您可以正常使用配置文件,但是当您运行docker时,它将与env变量交换出值。这些都不是“向搜索和替换所用的配置文件中添加文本字符串。”

您可以使用环境变量创建具有替换值的run.sh文件。

要更改此行上的“ 7s”:

client_body_timeout 7s;         #Default 60s
sed -i "s/\(client_body_timeout\).*\?\;/ $client_body_timeout;/" /usr/local/nginx/conf/nginx.conf


为要设置的每个参数复制/粘贴此行,并使用config选项更改client_body_timeout,并使用与其关联的env变量更改$ client_body_timeout。与现有的配置文件一起使用,它将正常工作。

#14 楼

使用lua的正确方法,因为上面的答案是陈旧的:

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



#15 楼

如果需要保持简单:bash eval sed
并遵循两个简单规则:${ENV_IN_PARENTHESIS}并始终在模板中使用'引号
您可以在一个通用调用中交换所有环境变量。

 eval "echo \"$(sed 's/$/##/g; s/\##{/${/g;' some.template)\"" | sed 's/\##/$/g' > some.conf


发生了什么事:

A. $(sed 's/$/##/g; s/\##{/${/g;' some.template)
通过sed和:
1运行模板。将所有$字符替换为##
2。将所有##{替换回${

B。 eval "echo \"$(sed 's/$/##/g; s/\##{/${/g;' some.template)\""

通过eval运行修改后的模板,从而替换可见的${ENV_IN_PARENTHESIS}

C。 ... | sed 's/\##/$/g' > some.conf

eval的结果插入另一个sed调用,该调用从$返回所有原始##,并将最终结果输出到新的conf文件中。

#16 楼

您应该能够使用Dockerize的模板做您想做的事情。

评论


您可以添加更多详细信息吗?

– Pierre.Vriens
16年5月20日在11:35