使用envsubst和Docker,在配置文件中嵌入环境变量的通用模式

首先

使用Docker时,我们尽量将镜像通用化,通过挂载配置文件并通过环境变量嵌入来处理不同环境的配置差异。然而,能否直接将环境变量嵌入配置文件取决于目标软件的实现。例如,fluentd可以直接在配置文件中嵌入环境变量,但nginx却无法直接这么做。

所以,常常做的是创建一个类似于entrypoint.sh的文件,在启动时使用sed进行原始的方法,但作为更聪明的方法,我想介绍一下envsubst,它是一种轻量级的模板引擎,可以方便地将环境变量嵌入到配置文件中,非常方便。

安装

nginx:由于alpine镜像中已经内置了envsubst命令,因此可以直接使用。

如果未在其他影像镜像中预加载的话,可以通过以下方式安装。

阿尔卑斯山:3.5

以下的安装方法很简单。

$ apk --no-cache add gettext

如果你想要再削减一点尺寸的话,可以这样做。

FROM alpine:3.5

RUN apk --no-cache add libintl && \
      apk --no-cache add --virtual .gettext gettext && \
      cp /usr/bin/envsubst /usr/local/bin/envsubst && \
      apk del .gettext

作为尺寸大约是这样,envsubst的文件大小大约是111KB左右。非常轻巧。

$ docker build -t envsubst .
$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
envsubst                        latest              1bab5f1656d7        6 seconds ago       4.095 MB
alpine                          3.5                 88e169ea8f46        5 weeks ago         3.984 MB

Debian:Jessie

$ apt-get install gettext-base

用法

假设有一个简单的设置文件如下:

$ cat test.conf.template
hoge=$HOGE

这样的情况下,可以挂载配置文件,插入环境变量,然后将其传递给envsubst来进行嵌入。

$ docker run --rm \
    -v $(pwd)/test.conf.template:/tmp/test.conf.template \
    -e HOGE=aaa \
    nginx:alpine \
    /bin/sh -c "envsubst < /tmp/test.conf.template > /tmp/test.conf && cat /tmp/test.conf"
hoge=aaa

听起来很实用,好像什么都可以用得到。

如果像nginx.conf一样,在配置文件中可以使用$符号,则需要明确指定要嵌入的环境变量名称。

假设我们有一个类似于以下样式的nginx.conf文件,我们想要替换${NGINX_SERVER_NAME},${NGINX_UPSTREAM_HOST}和${NGINX_UPSTREAM_PORT},但不希望替换$remote_addr、$host等。

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        '$status $body_bytes_sent "$http_referer" '
        '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    resolver           127.0.0.11 valid=5s;

    server {
        listen 80;
        set_real_ip_from   '10.0.0.0/8';
        real_ip_header     X-Forwarded-For;
        proxy_set_header   Host $host;
        proxy_set_header   X-Forwarded-Proto https;

        server_name ${NGINX_SERVER_NAME};

        set $upstream ${NGINX_UPSTREAM_HOST};

        location / {
            proxy_pass http://$upstream:${NGINX_UPSTREAM_PORT};
        }
    }
    include /etc/nginx/conf.d/*.conf;
}

在这种情况下,您需要在envsubst中指定要替换的环境变量名称。
由于参数太长,因此将其放置在docker-compose.yml中。(2017/02/05: 有人指出不需要使用换行符来修饰,所以进行了修正)

version: '2'
services:
  nginx:
    image: nginx:alpine
    command: >
      /bin/sh -c
      "envsubst '
      $$NGINX_SERVER_NAME
      $$NGINX_UPSTREAM_HOST
      $$NGINX_UPSTREAM_PORT
      '< /etc/nginx/nginx.conf.template
      > /etc/nginx/nginx.conf
      && nginx -g 'daemon off;'"
    volumes:
      - ./nginx.conf.template:/etc/nginx/nginx.conf.template
    ports:
      - 8080:80
    environment:
      NGINX_SERVER_NAME: "test.example.com"
      NGINX_UPSTREAM_HOST: "hoge"
      NGINX_UPSTREAM_PORT: "80"

我会启动并查看配置文件的内容。

$ docker-compose up -d
Starting 20170204_nginx_1

$ docker exec -it $(docker ps -l -q) cat /etc/nginx/nginx.conf

环境变量已经嵌入正确指定的位置。

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
        worker_connections  1024;
}

http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';

        access_log  /var/log/nginx/access.log  main;

        sendfile        on;
        #tcp_nopush     on;

        keepalive_timeout  65;

        #gzip  on;

        resolver           127.0.0.11 valid=5s;

        server {
                listen 80;
                set_real_ip_from   '10.0.0.0/8';
                real_ip_header     X-Forwarded-For;
                proxy_set_header   Host $host;
                proxy_set_header   X-Forwarded-Proto https;

                server_name test.example.com;

                set $upstream hoge;

                location / {
                        proxy_pass http://$upstream:80;
                }
        }
        include /etc/nginx/conf.d/*.conf;
}

总结

envsubst在Docker中使用可充当通用模式,方便地将环境变量嵌入到配置文件中。