利用环境变量注入修改Docker中的Nginx配置
2019-03-22 22:27:19 +08 字数:1685 标签: Nginx DockerDocker官方的Nginx镜像,是不支持环境变量修改配置的。 当然,这是因为Nginx本身就不支持。 通过环境变量修改软件运行方式,本身就是一件很糟糕的事,远不如配置文件直观可靠——在Docker以前的时代。 在Docker时代,配置的修改,远不如环境变量来得方便;尤其是集群化之后,更是使得挂载的方式失效,必须再加一层镜像。 虽然Kubernetes的ConfigMap能勉强解决问题,但环境变量显得更加通用。
但Docker官方的文档,也给出了解决方案,虽然是个歪招——envsubst。
envsubst ¶
Shell中,是可以通过以下两种形式来拼接、生成字符串的。
$ echo "I am $USER."
I am yanqd0.
这是最常见的用变量拼接字符串,有时也用更严谨的${USER}
。
这种设计虽然古老,但异常好用。
在很多新生语言,如Groovy,乃至古老语言的新版本,如Python的3.6+等,都借用了它。
docker rmi `docker images -q`
这句是删除所有Docker镜像。
(危险操作,慎用!)
在``
中,
或$()
中的表达式,将执行后输出的stdout替换到外部表达式执行。
这就是一种简单而强大的元编程。
(虽然Pipeline也能实现上句中的相同功能。)
这么方便的字符串功能,能不能用在写配置文件上? envsubst就是为此而设计的。
官方样例 ¶
官方样例非常简单,能明了地展示用envsubst实现配置生成的思路。
web:
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 && exec nginx -g 'daemon off;'"
利用mysite.template
中的${NGINX_PORT}
和${NGINX_HOST}
,生成了default.conf
。
官方的Nginx配置的样例未给出(谜之消失),以下给出一个参考文件。
server {
listen ${NGINX_PORT};
listen [::]:${NGINX_PORT};
server_name ${NGINX_HOST};
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
这个例子虽然看似能解决问题,其实也只是样例。 首先,配置既然是挂载进去的,而环境变量也是写死的,那不如写死在一起,省事。 然后……应该已经不需要『然后』了。 当然,官方文档只是为了展示这种技术可能性。
真实案例 ¶
以下展示一个更复杂的例子。
Nginx配置 ¶
首先看看配置文件default.template
。
# vim: set filetype=nginx:
server {
listen 80 default_server;
listen [::]:80;
server_name _;
client_max_body_size 0;
underscores_in_headers on;
location ^~ /api/ {
proxy_pass ${BACKEND_URL};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 1s;
}
location / {
proxy_pass http://frontend:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
在这个配置文件中,$host
、$remote_addr
等,是与Nginx的约定,并非需要动态修改的变量。
然而,在官方的使用方法下,它们都惨遭修改、面目全非。
如果避免修改$host
,精准地只修改${BACKEND_URL}
呢?
docker-compose ¶
这是一个NodeJS项目调试用的docker-compose.yaml
文件。
前端就是当前项目,而后端则不确定在哪,以环境变量BACKEND_URL
的方式配置。
这个环境变量,也是npm run serve
运行时使用的。
version: "3"
services:
frontend:
build: .
image: your/frontend:latest
expose:
- 80
nginx:
image: nginx:1.14.2
entrypoint: /opt/entry.bash
ports:
- 80:80
volumes:
- ./nginx/entry.bash:/opt/entry.bash:ro
- ./nginx/conf.d/default.template:/etc/nginx/conf.d/default.template:ro
environment:
- BACKEND_URL
depends_on:
- frontend
可以看到,与官方样例有两点不同。
一是环境变量BACKEND_URL
并非写死的,而是将更外层运行时的环境变量转手注入容器中;
二是并非通过command
,而是通过entrypoint
实现envsubst。
entry.bash ¶
首先,我们假设环境变量是这样的东西:
export BACKEND_URL=http://domain-or-ip:5000/api/v1/
通过端口号,应该很容易猜到这是什么框架开发的后端。
但这不是重点。
重点是,前端在运行时,竟然注入的是个带/api/v1/
的东西。
以下是entry.bash
。
#!/bin/bash
BACKEND_URL=${BACKEND_URL/?api*/}
envsubst '$BACKEND_URL' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf
cat /etc/nginx/conf.d/default.conf
if [ $# = 0 ]
then
exec nginx -g 'daemon off;'
else
exec "$@"
fi
首先,利用Bash的字符串切分操作,把/api/v1/
切去。
这一步是使用entrypoint
的原因,它无法在bash -c
中完成。
其次,利用envsubst限定变量的功能,只对$BACKEND_URL
做替换,忽略其它。
最后,启动Nginx。
知道上面最重要的是哪一行吗?
血泪会告诉你,是cat
那一行。
总结 ¶
同样,这个复杂案例也不是没有其它办法实现。
/api/v1/
的问题,可以通过Nginx自身的配置调整来解决。
(但你会吗?)
这里的案例是为了开发阶段的类生产环境调试。
如果是生产环境,volumes
、entrypoint
配置可以省去,直接build新镜像来使用,会简洁、灵活许多。