抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

通过docker构建nginx有一个较大的问题,就是无法方便修改nginx的conf配置,特别是其中proxy_pass地址,极有可能会经常变化,而每次变化都需要重新构建镜像显得极不优雅,因此本文将介绍如何通过环境变量来实现动态nginx的conf配置。

一、准备文件

1、ui.conf

nginx其实已支持读取环境变量的值,但需要通过template文件转换,此时为了vscode能够有语法提示,配置文件的名称仍然以.conf结尾。
其中通过${xxx}的写法,即可读取到环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
listen ${LISTEN_PORT};
server_name ${SERVER_NAME};

gzip on;
gzip_static on;
gzip_min_length 1k;
gzip_comp_level 4;
gzip_proxied any;
gzip_types text/plain text/xml text/css;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";

location /webapi/ {
proxy_pass ${PROXY_PASS};
proxy_connect_timeout 15s;
proxy_send_timeout 15s;
proxy_read_timeout 15s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location / {
root /html;
try_files $uri $uri/ /index.html;
}
}

2、Dockerfile

其中主要为通过ENV配置默认环境变量,其次将配置文件添加至镜像中nginx目录下的templates,并将文件名改为default.conf.template
当容器启动后,nginx会将模版文件转化为conf文件并放置于conf.d目录中

1
2
3
4
5
6
7
8
9
FROM nginx

COPY ./dist/ /html

ENV LISTEN_PORT=80\
SERVER_NAME=localhost\
PROXY_PASS=http://127.0.0.1

ADD ./ui.conf /etc/nginx/templates/default.conf.template

二、构建镜像

1.目录结构

  • root
    • ui.conf
    • Dockerfile
    • dist
      • index.html

2.构建镜像

在root目录中执行命令

1
docker build -t my-nginx:1.0 .

3、检验

此时通过docker run等运行镜像即可,例如通过docker swarm运行

1
2
3
4
5
6
7
8
9
version: '3.7'
services:
my-nginx:
image: my-nginx:1.0
environment:
# 指定环境变量
PROXY_PASS: http://192.168.3.75:9898/
ports:
- 80:80

三、动态DNS问题

PROXY_PASS 使用了域名或者hostname,因nginx的dns只在启动那一刻解析并缓存,因此若在 docker 集群中通过 hostname 指定后端地址例如

1
PROXY_PASS: http://backend_host:9898/

当后端程序重启后,或者 docker 网络环境变化,容器ip可能发生改变,导致 nginx 反向代理到后端的ip是错误的而无法访问,因此我们可以编写一个脚本检测,当ip变化,重新加载nginx,让其重新进行 dns 解析

例如以vue项目为例,目录结构可如下所示

1
2
3
4
5
6
7
- vue-project
- dist
- index.html
- docker
- Dockerfile
- ui.conf
- checkip.sh

1. 准备 checkip.sh

脚本内容通过解析 PROXY_PASS 环境变量,若非ip则通过nslookup解析实际ip并缓存,之后若ip变化,则执行 nginx -s reload 重新加载 nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh
if [ -z "$PROXY_PASS" ]; then
exit 0
fi
IP_FILE="/var/tmp/check_proxy_ip.txt"
HOSTNAME=$(echo "$PROXY_PASS" | sed -E 's|^.*://||; s|[:/].*$||')
if echo "$HOSTNAME" | grep -E "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$" > /dev/null; then
exit 0
fi
CURRENT_IP=$(nslookup "$HOSTNAME." | awk "/^Address: / { print \$2 }")
if [ -z "$CURRENT_IP" ]; then
exit 1
fi
if [ -f "$IP_FILE" ]; then
SAVED_IP=$(cat "$IP_FILE")
else
echo "$CURRENT_IP" > "$IP_FILE"
echo "[checkip] host $HOSTNAME record ip $CURRENT_IP"
exit 0
fi
if [ "$CURRENT_IP" != "$SAVED_IP" ]; then
echo "$CURRENT_IP" > "$IP_FILE"
echo "[checkip] host $HOSTNAME ip replace $SAVED_IP with $CURRENT_IP"
nginx -s reload
fi

2. 修改 ui.conf

一个较完整的 ui.conf 可如下所示,添加了 gzip 和 client_max_body_size 配置,以及添加了 proxy_buffering off; 确保支持后端的流式传输(如AI需要)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
server {
listen ${LISTEN_PORT};
server_name ${SERVER_NAME};

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE [1-6]\.";

client_max_body_size ${CLIENT_MAX_BODY_SIZE};
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;

location /api/ {
proxy_pass ${PROXY_PASS};
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_set_header X-Forwarded-Proto http;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
add_header X-Cache $upstream_cache_status;
proxy_buffering off;
}

location / {
root /html;
index index.html;
try_files $uri $uri/ /index.html;
}
}

3. 修改 Dockerfile

完整 dockerfile 模版可以参考如下,基于nginx官方alpine-slim镜像,添加了时区和ntp时间同步,以及checkip脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM nginx:1.27.1-alpine-slim

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
&& apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apk del tzdata \
&& echo "*/30 * * * * ntpd -d -q -n -p ntp.aliyun.com >> /var/log/ntpd.log" >> /etc/crontabs/root

COPY ./docker/checkip.sh /root/checkip.sh
RUN chmod +x /root/checkip.sh \
&& echo "*/1 * * * * sh /root/checkip.sh >> /var/log/checkip.log" >> /etc/crontabs/root

RUN echo "crond" > /docker-entrypoint.d/crond.sh \
&& chmod +x /docker-entrypoint.d/crond.sh

ENV LISTEN_PORT=80 \
SERVER_NAME=localhost \
CLIENT_MAX_BODY_SIZE=4096m \
PROXY_PASS=http://172.17.0.1:8080/
EXPOSE ${LISTEN_PORT}

ADD ./docker/ui.conf /etc/nginx/templates/default.conf.template
COPY ./dist /html
RUN chmod -R 755 /html

4. 构建镜像

通过命令构建

1
docker build -t vue-project -f docker/Dockerfile .

5. 运行

1
2
3
4
5
6
7
8
9
10
11
services:
backend:
image: springboot-app:latest

nginx:
image: vue-project:latest
ports:
- 80:80
environment:
# 可直接通过 hostname 指向后端
- PROXY_PASS=http://backend:3000/

评论