Nginx 配置参数化实践:通过 Docker Run 传递参数实现动态代理配置
·
概述
在微服务架构中,前端应用通常需要代理多个后端服务的 API 请求。传统的做法是为每个环境(开发、测试、生产)构建不同的镜像,这会导致镜像管理复杂、部署效率低下。本文将介绍如何通过 Nginx 配置参数化,结合 Docker 环境变量传递,实现一个镜像适配多个环境的动态代理配置方案。
为什么需要 Nginx 配置参数化?
传统方式的痛点
- 多环境配置差异:不同环境的后端服务地址不同,需要为每个环境维护不同的配置文件
- 镜像管理复杂:每个环境都需要构建独立的镜像,镜像版本管理困难
- 部署效率低:环境变更需要重新构建镜像,无法快速响应
- 配置硬编码:后端服务地址写死在配置文件中,缺乏灵活性
参数化的优势
- ✅ 一次构建,多处部署:同一个镜像可以在不同环境使用
- ✅ 配置动态化:通过环境变量动态配置后端服务地址
- ✅ 运维友好:无需重新构建镜像即可调整代理配置
- ✅ 符合 DevOps 最佳实践:配置与代码分离
实现方案
方案架构
Docker Run (传递环境变量)
↓
Entrypoint Script (替换占位符)
↓
Nginx 配置文件 (使用环境变量值)
↓
Nginx 服务启动
核心思路
- 配置模板化:在 Nginx 配置文件中使用占位符(如
${DATA_URL}) - 启动时替换:通过 entrypoint 脚本在容器启动时替换占位符
- 环境变量传递:通过 Docker 环境变量传递实际的后端服务地址
详细实现步骤
1. Nginx 配置文件模板
在 conf.d/default.conf 中使用环境变量占位符:
server {
listen 80;
server_name localhost;
# 日志配置
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# 缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 前端静态资源
location = /standard_front {
return 301 /standard_front/;
}
location ^~ /standard_front/ {
alias /usr/share/nginx/html/;
index index.html;
include /etc/nginx/mime.types;
default_type application/octet-stream;
}
# API 代理配置 - 使用环境变量占位符
location /unify/data {
proxy_pass http://${DATA_URL}:8096;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /database {
proxy_pass http://${DATABASE_URL}:8097;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /approve {
proxy_pass http://${APPROVE_URL}:8098;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /framework {
proxy_pass http://${FRAMEWORK_URL}:8079;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /info {
proxy_pass http://${INFO_URL}:8100;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /datafilling {
proxy_pass http://${DATAFILLING_URL}:8092;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /api/cockpit {
proxy_pass http://${COCKPIT_URL}:8096;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /warning {
proxy_pass http://${WARNING_URL}:8093;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
location /fastdfs {
proxy_pass http://${OBS_URL}/;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
关键点:
- 使用
${变量名}作为占位符 - 占位符会被 entrypoint 脚本替换为实际的环境变量值
- 支持多个后端服务的代理配置
2. Entrypoint 启动脚本
创建 entrypoint.sh 脚本,在容器启动时替换配置中的占位符:
#!/bin/sh
# 修复:仅保留ash支持的set选项(-e 报错立即退出,-u 未定义变量报错)
# 设置错误处理选项
set -e
set -u
# 定义默认值(统一管理,便于维护)
DEFAULT_URL="localhost"
# 环境变量赋值,使用默认值如果未定义
DATA_URL=${DATA_URL:-$DEFAULT_URL}
DATABASE_URL=${DATABASE_URL:-$DEFAULT_URL}
COCKPIT_URL=${COCKPIT_URL:-$DEFAULT_URL}
WARNING_URL=${WARNING_URL:-$DEFAULT_URL}
FRAMEWORK_URL=${FRAMEWORK_URL:-$DEFAULT_URL}
APPROVE_URL=${APPROVE_URL:-$DEFAULT_URL}
DATAFILLING_URL=${DATAFILLING_URL:-$DEFAULT_URL}
INFO_URL=${INFO_URL:-$DEFAULT_URL}
OBS_URL=${OBS_URL:-"mqc-obs-prd.obsv3.xm-gmgf-1.itg.com.cn"}
# 优化:合并多次sed调用为一次(减少文件IO,提升效率)
# 用|作为分隔符,避免URL中的/冲突;转义$和{确保精准匹配
sed -i \
-e "s|\\\${DATA_URL}|${DATA_URL}|g" \
-e "s|\\\${DATABASE_URL}|${DATABASE_URL}|g" \
-e "s|\\\${COCKPIT_URL}|${COCKPIT_URL}|g" \
-e "s|\\\${WARNING_URL}|${WARNING_URL}|g" \
-e "s|\\\${FRAMEWORK_URL}|${FRAMEWORK_URL}|g" \
-e "s|\\\${APPROVE_URL}|${APPROVE_URL}|g" \
-e "s|\\\${DATAFILLING_URL}|${DATAFILLING_URL}|g" \
-e "s|\\\${OBS_URL}|${OBS_URL}|g" \
-e "s|\\\${INFO_URL}|${INFO_URL}|g" \
/etc/nginx/conf.d/default.conf
# 可选:校验Nginx配置语法(提前发现错误)
if ! nginx -t; then
echo "Error: Nginx config syntax error!"
exit 1
fi
# 前台启动Nginx(确保容器不退出)
exec nginx -g "daemon off;"
脚本说明:
- 错误处理:
set -e和set -u确保脚本在出错时立即退出 - 默认值设置:使用
${VAR:-default}语法为环境变量设置默认值 - 批量替换:使用
sed -i一次性替换所有占位符- 使用
|作为分隔符,避免 URL 中的/冲突 - 转义
${确保精确匹配占位符
- 使用
- 配置校验:使用
nginx -t验证配置语法 - 前台启动:使用
exec nginx -g "daemon off;"确保容器不退出
3. Dockerfile 配置
# ============================================
# Nginx 前端应用 Dockerfile
# 基于 nginx:latest,支持代理配置
# ============================================
FROM cr.kylinos.cn/basekylin/nginx-aarch64:1.16.1-v10sp1
LABEL maintainer="your-email@example.com"
LABEL description="Nginx Frontend Application with Configurable Proxy"
# 创建应用目录
RUN mkdir -p /usr/share/nginx/html
RUN mkdir -p /apply
# 复制前端应用文件(默认index.html)
COPY html/ /usr/share/nginx/html
# 复制 nginx 配置文件
COPY nginx.conf /etc/nginx/nginx.conf
COPY mime.types /etc/nginx/mime.types
COPY conf.d/ /etc/nginx/conf.d/
# 复制并设置 entrypoint 脚本权限
COPY entrypoint.sh /apply/entrypoint.sh
RUN chmod +x /apply/entrypoint.sh
# 暴露端口(HTTP和HTTPS)
EXPOSE 80 443
ENTRYPOINT ["sh","/apply/entrypoint.sh"]
关键点:
- 复制包含占位符的配置文件模板
- 复制 entrypoint 脚本并设置执行权限
- 使用 entrypoint 脚本作为容器入口点
Docker 运行方式
方式一:直接使用 -e 参数传递环境变量
docker run -d \
--name frontend \
-p 8011:80 \
-e DATA_URL=10.2.3.1 \
-e DATABASE_URL=192.168.173.8 \
-e APPROVE_URL=192.168.173.9 \
-e FRAMEWORK_URL=192.168.173.10 \
-e INFO_URL=192.168.173.11 \
-e DATAFILLING_URL=192.168.173.12 \
-e COCKPIT_URL=10.2.3.1 \
-e WARNING_URL=192.168.173.13 \
-e OBS_URL=mqc-obs-prd.obsv3.xm-gmgf-1.itg.com.cn \
your-image:tag
方式二:使用环境变量文件 --env-file
创建环境变量文件 prod.env:
# prod.env
DATA_URL=10.2.3.1
DATABASE_URL=192.168.173.8
APPROVE_URL=192.168.173.9
FRAMEWORK_URL=192.168.173.10
INFO_URL=192.168.173.11
DATAFILLING_URL=192.168.173.12
COCKPIT_URL=10.2.3.1
WARNING_URL=192.168.173.13
OBS_URL=mqc-obs-prd.obsv3.xm-gmgf-1.itg.com.cn
使用环境变量文件启动:
docker run -d \
--name frontend \
-p 8011:80 \
--env-file prod.env \
your-image:tag
方式三:使用启动脚本
创建 start-docker.sh 脚本:
#!/bin/bash
# ============================================
# Nginx 前端 Docker 容器启动脚本
# ============================================
echo "======================================"
echo " Docker 容器启动脚本"
echo "======================================"
echo ""
# 固定容器名称
CONTAINER_NAME="frontend"
# 应用端口
APP_PORT=${APP_PORT:-"8011"}
# 后端服务地址配置(可修改默认值)
DATA_URL=${DATA_URL:-"localhost"}
DATABASE_URL=${DATABASE_URL:-"localhost"}
APPROVE_URL=${APPROVE_URL:-"localhost"}
FRAMEWORK_URL=${FRAMEWORK_URL:-"localhost"}
INFO_URL=${INFO_URL:-"localhost"}
DATAFILLING_URL=${DATAFILLING_URL:-"localhost"}
COCKPIT_URL=${COCKPIT_URL:-"localhost"}
WARNING_URL=${WARNING_URL:-"localhost"}
OBS_URL=${OBS_URL:-"mqc-obs-prd.obsv3.xm-gmgf-1.itg.com.cn"}
echo ""
echo "======================================"
echo " 配置信息"
echo "======================================"
echo " 容器名: $CONTAINER_NAME"
echo " 应用端口: $APP_PORT"
echo " 数据服务: $DATA_URL:8096"
echo " 数据库服务: $DATABASE_URL:8097"
echo " 审批服务: $APPROVE_URL:8098"
echo " 框架服务: $FRAMEWORK_URL:8079"
echo " 信息服务: $INFO_URL:8100"
echo " 数据填充服务: $DATAFILLING_URL:8092"
echo " 驾驶舱服务: $COCKPIT_URL:8096"
echo " 预警服务: $WARNING_URL:8093"
echo " 对象存储: $OBS_URL"
echo ""
# 确认是否继续
read -p "是否启动容器? (y/n): " CONFIRM
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
echo "已取消操作"
exit 0
fi
echo ""
echo "清理旧容器..."
docker stop $CONTAINER_NAME 2>/dev/null
docker rm $CONTAINER_NAME 2>/dev/null
echo "启动容器中..."
docker run -d \
--name $CONTAINER_NAME \
-p ${APP_PORT}:80 \
-e DATA_URL=$DATA_URL \
-e DATABASE_URL=$DATABASE_URL \
-e APPROVE_URL=$APPROVE_URL \
-e FRAMEWORK_URL=$FRAMEWORK_URL \
-e INFO_URL=$INFO_URL \
-e DATAFILLING_URL=$DATAFILLING_URL \
-e COCKPIT_URL=$COCKPIT_URL \
-e WARNING_URL=$WARNING_URL \
-e OBS_URL=$OBS_URL \
crpi-77ngqnae8dl9woor.cn-hangzhou.personal.cr.aliyuncs.com/xmgm_123/frontend:v1
if [ $? -eq 0 ]; then
echo ""
echo "======================================"
echo " 容器启动成功!"
echo "======================================"
echo ""
echo "访问地址: http://localhost:$APP_PORT"
echo ""
echo "常用命令:"
echo " 查看日志: docker logs -f $CONTAINER_NAME"
echo " 进入容器: docker exec -it $CONTAINER_NAME sh"
echo " 停止容器: docker stop $CONTAINER_NAME"
echo " 删除容器: docker rm $CONTAINER_NAME"
echo " 查看配置: docker exec $CONTAINER_NAME cat /etc/nginx/conf.d/default.conf"
else
echo ""
echo "错误: 容器启动失败!"
echo "请检查镜像名称是否正确"
fi
echo ""
使用脚本启动:
# 方式1:使用默认值
./start-docker.sh
# 方式2:通过环境变量覆盖默认值
DATA_URL=10.2.3.1 DATABASE_URL=192.168.173.8 ./start-docker.sh
配置参数说明
| 环境变量 | 说明 | 默认值 | 对应后端服务端口 |
|---|---|---|---|
DATA_URL |
数据服务地址 | localhost |
8096 |
DATABASE_URL |
数据库服务地址 | localhost |
8097 |
APPROVE_URL |
审批服务地址 | localhost |
8098 |
FRAMEWORK_URL |
框架服务地址 | localhost |
8079 |
INFO_URL |
信息服务地址 | localhost |
8100 |
DATAFILLING_URL |
数据填充服务地址 | localhost |
8092 |
COCKPIT_URL |
驾驶舱服务地址 | localhost |
8096 |
WARNING_URL |
预警服务地址 | localhost |
8093 |
OBS_URL |
对象存储服务地址 | mqc-obs-prd.obsv3.xm-gmgf-1.itg.com.cn |
- |
最佳实践
1. 占位符命名规范
- ✅ 使用大写字母和下划线:
DATA_URL、DATABASE_URL - ✅ 使用有意义的名称:避免使用
URL1、URL2等无意义名称 - ✅ 保持一致性:同一类型的配置使用统一的前缀或后缀
2. Entrypoint 脚本优化
使用数组批量处理(可选优化)
#!/bin/sh
set -e
set -u
# 定义默认值
DEFAULT_URL="localhost"
# 定义需要替换的变量数组
VARS="DATA_URL DATABASE_URL COCKPIT_URL WARNING_URL FRAMEWORK_URL APPROVE_URL DATAFILLING_URL INFO_URL OBS_URL"
# 设置默认值并替换
for VAR in $VARS; do
case $VAR in
OBS_URL)
eval "${VAR}=\${${VAR}:-mqc-obs-prd.obsv3.xm-gmgf-1.itg.com.cn}"
;;
*)
eval "${VAR}=\${${VAR}:-$DEFAULT_URL}"
;;
esac
# 替换占位符
sed -i "s|\\\${${VAR}}|$(eval echo \$$VAR)|g" /etc/nginx/conf.d/default.conf
done
# 校验配置
nginx -t || exit 1
# 启动 Nginx
exec nginx -g "daemon off;"
3. 安全性建议
- ✅ 敏感信息管理:不要将包含敏感信息的
.env文件提交到代码仓库 - ✅ 文件权限:
.env文件应设置适当的文件权限(如chmod 600) - ✅ 默认值安全:生产环境的默认值应该是安全的,避免使用测试环境的地址
4. 配置验证
在 entrypoint 脚本中添加配置验证:
# 验证必需的环境变量
if [ -z "$DATA_URL" ] || [ "$DATA_URL" = "localhost" ]; then
echo "Warning: DATA_URL is not set or using default value"
fi
# 验证 Nginx 配置语法
if ! nginx -t; then
echo "Error: Nginx config syntax error!"
exit 1
fi
5. 日志和调试
在 entrypoint 脚本中添加调试信息:
# 显示替换后的配置(仅用于调试,生产环境可注释)
if [ "${DEBUG:-false}" = "true" ]; then
echo "=== Nginx Configuration ==="
cat /etc/nginx/conf.d/default.conf
echo "==========================="
fi
验证配置
1. 查看容器环境变量
# 查看容器的所有环境变量
docker exec frontend env
# 查看特定环境变量
docker exec frontend env | grep DATA_URL
2. 查看替换后的配置文件
# 查看替换后的 Nginx 配置
docker exec frontend cat /etc/nginx/conf.d/default.conf
# 检查特定代理配置
docker exec frontend grep -A 5 "location /database" /etc/nginx/conf.d/default.conf
3. 测试代理配置
# 测试代理是否正常工作
curl http://localhost:8011/database/health
# 查看 Nginx 访问日志
docker exec frontend tail -f /var/log/nginx/access.log
# 查看 Nginx 错误日志
docker exec frontend tail -f /var/log/nginx/error.log
4. 验证配置语法
# 在容器内验证配置语法
docker exec frontend nginx -t
常见问题
Q1: 环境变量没有生效?
A: 检查以下几点:
- 环境变量名称是否正确(区分大小写)
docker run命令中是否正确使用-e参数- entrypoint 脚本中的 sed 替换命令是否正确
- 查看容器日志:
docker logs frontend
Q2: Nginx 配置语法错误?
A:
- 检查 entrypoint 脚本中的 sed 替换是否正确
- 确保占位符格式正确:
${VAR_NAME} - 使用
nginx -t验证配置语法 - 查看错误日志:
docker logs frontend
Q3: 代理请求失败?
A:
- 检查后端服务地址是否正确
- 检查后端服务是否可访问(网络连通性)
- 查看 Nginx 错误日志:
docker exec frontend tail -f /var/log/nginx/error.log - 检查代理配置中的端口是否正确
Q4: 如何为不同环境设置不同的配置?
A: 可以创建多个环境变量文件:
dev.env- 开发环境test.env- 测试环境prod.env- 生产环境
启动时指定对应的文件:
docker run -d --env-file prod.env your-image:tag
Q5: 如何在 Docker Compose 中使用?
A: 在 docker-compose.yml 中使用 environment 或 env_file:
version: '3.8'
services:
frontend:
image: your-image:tag
ports:
- "8011:80"
environment:
- DATA_URL=10.2.3.1
- DATABASE_URL=192.168.173.8
- APPROVE_URL=192.168.173.9
# 或者使用 env_file
env_file:
- prod.env
Q6: 如何动态更新配置而不重启容器?
A: Nginx 配置参数化是在容器启动时进行的,如果需要更新配置:
- 停止并删除旧容器
- 使用新的环境变量启动新容器
- 或者使用
docker exec进入容器手动修改配置并重载:nginx -s reload
方案对比
方案一:配置模板 + Entrypoint 替换(本文方案)
优点:
- ✅ 实现简单,易于理解
- ✅ 支持所有 Nginx 配置项
- ✅ 配置校验在启动时完成
缺点:
- ⚠️ 需要维护 entrypoint 脚本
- ⚠️ 配置变更需要重启容器
方案二:使用 envsubst 工具
优点:
- ✅ 更标准的做法
- ✅ 支持更复杂的模板语法
缺点:
- ⚠️ 需要安装 envsubst(通常包含在 gettext 包中)
- ⚠️ 需要确保镜像中包含该工具
示例:
#!/bin/sh
envsubst < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
nginx -g "daemon off;"
方案三:使用 Nginx Lua 模块
优点:
- ✅ 真正的动态配置
- ✅ 无需重启容器
缺点:
- ⚠️ 需要安装 Nginx Lua 模块
- ⚠️ 配置复杂度较高
- ⚠️ 性能开销略大
总结
通过 Nginx 配置参数化结合 Docker 环境变量传递,我们可以实现:
- 配置与代码分离:配置不进入镜像,提高灵活性
- 一次构建,多处部署:同一个镜像可以在不同环境使用
- 运维友好:无需重新构建即可调整代理配置
- 符合最佳实践:遵循 12-Factor App 原则
这种方式特别适合微服务架构中的前端应用,能够显著提高部署效率和可维护性。
参考资料
更多推荐



所有评论(0)