通过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 nginxCOPY ./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.目录结构
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-slimRUN 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=4096 m \ 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: - PROXY_PASS=http://backend:3000/