7

利用环境变量注入修改Docker中的Nginx配置

 3 years ago
source link: https://note.qidong.name/2019/03/inject-env-to-nginx-docker/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

利用环境变量注入修改Docker中的Nginx配置

2019-03-22 22:27:19 +08  字数:1685  标签: Nginx Docker

Docker官方的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自身的配置调整来解决。 (但你会吗?)

这里的案例是为了开发阶段的类生产环境调试。 如果是生产环境,volumesentrypoint配置可以省去,直接build新镜像来使用,会简洁、灵活许多。

参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK