从 docker-compose 替换 NGINX 配置中的环境变量

Posted

技术标签:

【中文标题】从 docker-compose 替换 NGINX 配置中的环境变量【英文标题】:Substitute environment variables in NGINX config from docker-compose 【发布时间】:2019-11-01 03:01:16 【问题描述】:

我正在尝试在通过 docker-compose 配置的 docker 容器中启动 nginx 服务器。但是,问题是我想在 http 部分中替换一个环境变量,特别是在“上游”块中。

让这个工作真棒,因为我还有几个其他容器都是通过环境变量配置的,而且我有大约 5 个环境需要在任何给定时间运行。我尝试过使用“envsubst”(按照官方 NGINX 文档的建议)、perl_set 和 set_by_lua,但是它们似乎都没有工作。

下面是 NGINX 配置,因为它是我最近一次试用后的样子

user  nginx;
worker_processes  1;
env NGINXPROXY;

load_module modules/ngx_http_perl_module.so;

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


events 
    worker_connections  1024;


http 
    perl_set $nginxproxy 'sub  return $ENV"NGINXPROXY"; ';

    upstream api-upstream 
        server $nginxproxy;
    

    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        off;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;


下面是 NGINX dockerfile

# build stage
FROM node:latest
WORKDIR /app
COPY ./ /app
RUN npm install
RUN npm run build

# production stage
FROM nginx:1.17.0-perl
COPY --from=0 /app/dist /usr/share/nginx/html
RUN apt-get update && apt-get install -y gettext-base
RUN rm /etc/nginx/conf.d/default.conf
RUN rm /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d
COPY nginx.conf /etc/nginx
RUN mkdir /certs
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

以下是 NGINX 服务器的 docker-compose.yml 部分(名称和 IP 已更改)。 envsubst 命令在我的故障排除中被有意注释掉了。

front-end:
        environment:
            - NGINXPROXY=172.31.67.100:9300
        build: http://gitaccount:password@gitserver.com/group/front-end.git#develop
        container_name: qa_front_end
        image: qa-front-end
        restart: always
        networks:
            qa_network:
                ipv4_address: 172.28.0.215
        ports:
            - "9080:80"
        # command: /bin/bash -c "envsubst '$$NGINXPROXY' < /etc/nginx/nginx.conf > /etc/nginx/nginx.conf && nginx -g 'daemon off;'"

似乎正在发生的事情是,当我在上游块中引用 $nginxproxy 变量时(就在“服务器”之后),我得到的输出使它看起来像是在引用字符串文字“$nginxproxy”而不是替换值变量。

qa3_front_end       | 2019/06/18 12:35:36 [emerg] 1#1: host not found in upstream "$nginx_upstream" in /etc/nginx/nginx.conf:19
qa3_front_end       | nginx: [emerg] host not found in upstream "$nginx_upstream" in /etc/nginx/nginx.conf:19
qa3_front_end exited with code 1

当我尝试使用 envsubst 时,我收到一个错误,听起来像是命令与 nginx.conf 文件的格式混淆了

qa3_front_end       | 2019/06/18 12:49:02 [emerg] 1#1: no "events" section in configuration
qa3_front_end       | nginx: [emerg] no "events" section in configuration
qa3_front_end exited with code 1

我很困惑,所以提前感谢您的帮助。

【问题讨论】:

【参考方案1】:

Since nginx 1.19 您现在可以通过 docker-compose 在配置中使用环境变量。我使用了以下设置:

# file: docker/nginx/templates/default.conf.conf
upstream api-upstream 
    server $API_HOST;



# file: docker-compose.yml
services:
    nginx:
        image: nginx:1.19-alpine
        volumes:
            - "./docker/nginx/templates:/etc/nginx/templates/"
        environment:
            NGINX_ENVSUBST_TEMPLATE_SUFFIX: ".conf"
            API_HOST: api.example.com
        

我将从文档中的示例中稍微偏离脚本。注意模板文件上额外的.conf 扩展名——这不是错字。在 nginx 映像的文档中,建议为文件命名,例如,default.conf.template。启动时,脚本将获取该文件,替换环境变量,然后使用原始文件名将文件输出到 /etc/nginx/conf.d/,删除 .template 后缀。

默认情况下,后缀是.template,但这会破坏语法高亮,除非你配置你的编辑器。相反,我将.conf 指定为模板后缀。如果您仅将文件命名为 default.conf,则结果将是一个名为 /etc/nginx/conf.d/default 的文件,并且您的站点将无法按预期提供。

【讨论】:

只是一个快速的澄清点:这个文件需要在 nginx 容器的模板目录中。如果您使用 ./docker/nginx/conf.d/:/etc/nginx/conf.d/ docker compose 卷来管理此文件,则需要将其更改为 ./docker/nginx/templates/:/etc/nginx/templates/ @JosephMarikle 好主意,我已经更新了示例以反映您的评论 是否需要手动将模板文件复制到 /etc/nginx/templates 中?我正在遵循这个设置,但它没有复制到模板中。我的模板文件夹是空的。 在本例中,您在一个文件夹中有一个 docker-compose.yml,其中 ./docker/nginx/templates/ 包含与您主机上的模板相关的模板。 如果您将docker-compose.yml 提交给版本控制并希望使用单独的未跟踪.env 文件来维护特定于环境的变量,docker-compose 会将您的.env 变量替换为@ 987654334@ 在它启动你的容器之前。所以docker-compose.yml可以指定环境变量为API_HOST: $API_HOST,而.env可以设置实际值:API_HOST=api.example.com。对 docker-compose 专家来说可能是显而易见的,但我花了几个小时才弄清楚。【参考方案2】:

您可以通过定义自己的入口点来避免使用 Compose 解释环境变量的一些麻烦。看这个简单的例子:

entrypoint.sh(确保该文件可执行)
#!/bin/sh

export NGINXPROXY

envsubst '$NGINXPROXY' < /config.template > /etc/nginx/nginx.conf

exec "$@"
docker-compose.yml
version: "3.7"

services:
    front-end:
        image: nginx
        environment:
            - NGINXPROXY=172.31.67.100:9300
        ports:
            - 80:80
        volumes:
            - ./config:/config.template
            - ./entrypoint.sh:/entrypoint.sh
        entrypoint: ["/entrypoint.sh"]
        command: ["nginx", "-g", "daemon off;"]

我的config 文件与您的nginx.conf 具有相同的内容,除了我必须使用 Perl 模块注释这些行。

请注意,我必须先将我的配置文件挂载到另一个位置,然后才能envsubst。我遇到了一些奇怪的行为,即替换后文件最终为空,这种方法可以避免这种情况。在您的特定情况下,这应该不是问题,因为您已经在构建时将其嵌入到您的图像中。


编辑

为了完整起见,要尽可能少地更改您的设置,您只需确保您的环境变量为export。像这样调整你的命令:

command: ["/bin/bash", "-c", "export NGINXPROXY && envsubst '$$NGINXPROXY' < /etc/nginx/nginx.conf > /etc/nginx/nginx.conf && nginx -g 'daemon off;'"]

...你应该很高兴。不过,我总是会推荐使用“更简洁”的方式来定义自己的入口点。

【讨论】:

我可能会在 Dockerfile 中 COPY 这个脚本并将其指定为映像的 ENTRYPOINT,而不是仅在运行时覆盖它。【参考方案3】:

因此,在解决了这个问题之后,我设法让它的工作方式与 bellackn 提供的答案类似。我将在这里发布我的确切解决方案,以防其他人需要参考完整的解决方案。

步骤 1:按照通常的编写方式编写 nginx.conf 或 default.conf。将文件另存为“nginx.conf.template”或“default.conf.template”,具体取决于您尝试将变量替换为哪个。

user  nginx;
worker_processes  1;

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


events 
    worker_connections  1024;


http 
    upstream api-upstream 
        server 192.168.25.254;
    

    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        off;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;


第 2 步:将格式为 $VARNAME 的变量替换为您要替换为环境变量的任何值:

user  nginx;
worker_processes  1;

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


events 
    worker_connections  1024;


http 
    upstream api-upstream 
        server $SERVER_NAME;
    

    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        off;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;

第 3 步:在您的 docker-file 中,将您的 nginx 配置文件(您的 nginx.conf.template 或 default.conf.template)复制到容器中的适当位置:

# build stage
FROM node:latest
WORKDIR /app
COPY ./ /app
RUN npm install
RUN npm run build

# production stage
FROM nginx:1.17.0-perl
COPY --from=0 /app/dist /usr/share/nginx/html
RUN apt-get update && apt-get install -y gettext-base
RUN rm /etc/nginx/conf.d/default.conf
RUN rm /etc/nginx/nginx.conf
#-----------------------------------#
|COPY default.conf /etc/nginx/conf.d|
|COPY nginx.conf.template /etc/nginx|
#-----------------------------------#
RUN mkdir /certs
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

第 4 步:使用“环境”部分标签在 docker-compos.yml 文件中设置环境变量。确保您的环境变量名称与您在 nginx 配置文件中选择的任何变量名称匹配。使用 docker 容器中的“envsubt”命令将变量值替换为 nginx.conf.template 中的变量,并将输出写入正确位置的名为 nginx.conf 的文件。这可以通过使用“命令”部分标签在 docker-compose.yml 文件中完成:

version: '2.0'
services:
    front-end:
        environment:
            - SERVER_NAME=172.31.67.100:9100
        build: http://git-account:git-password@git-server.com/project-group/repository-name.git#branch-ame
        container_name: qa_front_end
        image: qa-front-end-vue
        restart: always
        networks:
            qa_network:
                ipv4_address: 172.28.0.215
        ports:
            - "9080:80"
        command: >
            /bin/sh -c
            "envsubst '
            $$SERVER_NAME
            '< /etc/nginx/nginx.conf.template
            > /etc/nginx/nginx.conf
            && nginx -g 'daemon off;'"

第 5 步:使用 docker-compose up 运行您的堆栈(使用您需要的任何其他开关),您的 nginx 服务器现在应该以您在 docker-compose.yml 的“环境”部分中提供的任何值开始

正如上面的解决方案中提到的,您也可以定义自己的入口点,但是这个解决方案也被证明工作得很好,并且将所有内容都包含在一个配置文件中,让我能够运行一堆服务直接来自 git,只有一个 docker-compose.yml 文件。

非常感谢所有花时间准备的人,感谢您花时间帮助我解决问题。

【讨论】:

我得到这个错误:`错误:服务'服务器'构建失败:复制失败:文件在构建上下文中找不到或被.dockerignore排除:stat nginx.conf.template:文件不存在`。我已经尝试了很多方法来保存 nginx.conf.template 并将这个文件检入到容器中,它存在。我需要使用哪些 docker-compose 配置? 我会看看 bellackn 的回答。这是一个比我尝试做的简单得多的设置。【参考方案4】:

就像explained in Jody's answer 一样,现在官方的 Nginx Docker 镜像支持解析模板。这使用envsubst 和its handling 确保不会与Nginx variables 混淆,例如$host 等等。好的。但是,envsubst 不支持像常规 shell 和 Docker Compose 在使用 $MY_VAR:-My Default 时所做的默认值。因此,即使使用默认值,这个内置模板总是需要对所有变量进行完整设置。

要在图像本身中定义默认值,可以使用自定义入口点首先设置默认值,然后简单地委托给the original entrypoint。喜欢docker-defaults.sh

#!/usr/bin/env sh
set -eu

# As of version 1.19, the official Nginx Docker image supports templates with
# variable substitution. But that uses `envsubst`, which does not allow for
# defaults for missing variables. Here, first use the regular command shell
# to set the defaults:
export PROXY_API_DEST=$PROXY_API_DEST:-http://host.docker.internal:8000/api/

# Due to `set -u` this would fail if not defined and no default was set above
echo "Will proxy requests for /api/* to $PROXY_API_DEST*"

# Finally, let the original Nginx entry point do its work, passing whatever is
# set for CMD. Use `exec` to replace the current process, to trap any signals
# (like Ctrl+C) that Docker may send it:
exec /docker-entrypoint.sh "$@"

还有一些docker-nginx-default.conf:

# After variable substitution, this will replace /etc/nginx/conf.d/default.conf
server 
    listen 80;
    listen [::]:80;
    server_name localhost;

    location / 
        root /usr/share/nginx/html;
        index index.html index.htm;
    

    location /api/ 
       # Proxy API calls to another destination; the default for the variable is
       # set in docker-defaults.sh
       proxy_pass $PROXY_API_DEST;
    

在 Dockerfile 中将模板复制到/etc/nginx/templates/default.conf.template 并设置自定义入口点:

FROM nginx:stable-alpine

...

# Each time Nginx is started it will perform variable substition in all template
# files found in `/etc/nginx/templates/*.template`, and copy the results (without
# the `.template` suffix) into `/etc/nginx/conf.d/`. Below, this will replace the
# original `/etc/nginx/conf.d/default.conf`; see https://hub.docker.com/_/nginx
COPY docker-nginx-default.conf /etc/nginx/templates/default.conf.template
COPY docker-defaults.sh /
# Just in case the file mode was not properly set in Git
RUN chmod +x /docker-defaults.sh

# This will delegate to the original Nginx `docker-entrypoint.sh`
ENTRYPOINT ["/docker-defaults.sh"]

# The default parameters to ENTRYPOINT (unless overruled on the command line)
CMD ["nginx", "-g", "daemon off;"]

现在使用,例如,docker run --env PROXY_API_DEST=https://example.com/api/ ... 将设置一个值,在此示例中,如果未设置,则默认为 http://host.docker.internal:8000/api/(在本地计算机上实际上是 http://localhost:8000/api/)。

【讨论】:

以上是关于从 docker-compose 替换 NGINX 配置中的环境变量的主要内容,如果未能解决你的问题,请参考以下文章

Nginx 从 docker-compose 运行返回“在上游找不到主机”

使用 nginx 将环境变量从 docker-compose 传递到 vue 应用程序

Docker 网络 - nginx:在上游找不到 [emerg] 主机

docker-compose 服务内没有互联网

docker-compose 简单搭建nginx的ssl环境

docker-compose 简单搭建nginx的ssl环境