企业级灰度发布架构:基于Nginx的精细化流量治理与平滑演进实践
文章目录
- 基于Nginx权重实现灰度发布原理
- 1. 灰度发布讲解
- 2. Nginx权重分流原理
- 3. 精细化流量控制策略
- 4. 灰度发布流程图
- 基于Nginx权重实现灰度发布实验
- 1. 创建Version1服务
- 2. 创建Version2服务
- 3. 编写Nginx配置文件
- 4. 访问测试
- 5. 基于特定Cookies进行访问
- 问题思考
- 1. 公共服务连接影响
- 2. 服务连续性问题
- 基于Cookie的会话保持
- 1. 修改Nginx配置文件
- 2. 服务验证
- 3. 配置文件解读

基于Nginx权重实现灰度发布原理
1. 灰度发布讲解
灰度发布(Gray Release)是一种渐进式的软件发布策略,它允许将新版本服务逐步推向生产环境,而不是一次性替换整个系统。这种发布方式的核心目标是降低发布风险、保证系统稳定性,同时能够实时监控新版本的运行状态。
优点:
- 风险控制:将新版本可能带来的故障影响范围控制在最小
- 用户体验:避免全量发布导致的整体服务中断或性能下降
- 实时反馈:通过小流量测试收集真实用户反馈和性能数据
- 快速回滚:发现问题时可立即切回稳定版本,减少损失
2. Nginx权重分流原理
Nginx通过upstream
模块实现负载均衡,这是灰度发布的基石。其核心配置如下:
vim /etc/nginx/conf.d/gray-release.conf
# 定义后端服务集群 - 增加健康检查参数
upstream backend_servers {server 127.0.0.1:8080 weight=50 max_fails=2 fail_timeout=30s; # Version1 - 增加容错server 127.0.0.1:8081 weight=50 max_fails=2 fail_timeout=30s; # Version2 - 增加容错
}
工作原理:
-
Nginx根据权重比例计算每个服务器的概率分布
-
对于每个新请求,Nginx按照权重概率选择后端服务器
-
权重算法基于平滑加权轮询,确保流量分布均匀且符合比例
-
max_fails=2
:最多失败2次后标记服务器不可用 -
fail_timeout=30s
:失败后30秒内不再向该服务器转发请求 -
30秒后会自动重试,如成功则重新加入负载均衡池
3. 精细化流量控制策略
基于Cookie的精准路由:
# 优化基于Cookie的灰度发布配置 - 使用更精确的正则匹配
map $cookie_gray_release $backend_group {default "backend_servers"; # 默认使用权重分配"~*v1" "version1_server"; # 使用正则确保匹配包含v1的cookie值"~*v2" "version2_server"; # 使用正则确保匹配包含v2的cookie值
}
- 内部测试:测试人员通过设置Cookie值强制访问特定版本
- 用户体验:特定用户群体优先体验新功能
- 问题排查:重现用户问题时保持环境一致性
基于IP段的灰度发布:
geo $gray_ip_group {default "backend_servers";192.168.1.0/24 "version2_server"; # 内部测试网络访问新版本10.0.0.100 "version2_server"; # 特定测试机访问新版本
}
- 内部办公网络优先体验新版本
- 特定机房或地域逐步放开
监控模块
通过Nginx status模块实时监控:
location /nginx_status {stub_status on; # 开启状态模块access_log off; # 可选:关闭此位置的访问日志allow 127.0.0.1; # 允许访问的IP地址,例如本地allow 192.168.16.0/24; # 允许的内网网段,按需设置deny all; # 拒绝其他所有IP访问,重要!}# 状态信息:
Active connections: 3
server accepts handled requests307 307 487
Reading: 0 Writing: 1 Waiting: 2
- Reading:正在读取请求头的连接数(待处理请求)
- Writing:正在发送响应的连接数(处理中请求)
- Waiting:保持连接但空闲的连接数
- accepts/handled/requests:总接受/处理/请求数,用于计算成功率
4. 灰度发布流程图
流量入口 (客户端 → Nginx)
- 动作:所有外部客户端的请求首先统一到达部署好的 Nginx 网关服务器(监听8082端口)。
- 角色:Nginx 在这里扮演了 “流量调度员” 和 “策略执行者” 的核心角色。它是整个灰度系统的大脑。
流量调度 (Nginx 权重分发)
- 动作:Nginx 根据预先配置的权重规则(
upstream
模块),将接收到的请求按比例分发到后端的两个服务实例。 90% 的请求被转发到运行 旧版本 V1.0 的服务端(A服务器,监听8080端口)。 10% 的请求被转发到运行 新版本 V1.1 的服务端(B服务器,监听8081端口)。 - 特点:这个过程对用户是完全透明的,用户无需感知也控制不了自己访问的是哪个版本,分流是随机的。
业务处理 (服务端 → 数据层)
- 动作:两个版本的服务实例接收到请求后,各自独立处理业务逻辑。它们都需要读写同一个数据库/Redis。
- 核心要求:这里存在一个灰度发布的关键前提,即版本V1.1对数据的任何修改(数据库表结构、缓存格式等)都必须是向后兼容的,不能影响V1.0的正常读写,否则会导致数据错乱。
基于Nginx权重实现灰度发布实验
注意:安装Nginx版本要≥1.18.0,若公网访问,请放行所需端口。
1. 创建Version1服务
sudo mkdir -p /var/www/html/{v1,v2,static}
vim /var/www/html/v1/index.html
Version1服务:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>服务版本 1.0</title><style>body { font-family: Arial, sans-serif; background-color: #f0f8ff; text-align: center; padding: 50px;}.version { color: #ff6b6b; font-size: 2.5em; margin-bottom: 20px;}.features { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);max-width: 600px;margin: 0 auto;}</style>
</head>
<body><div class="version">Version 1.0 - 稳定版</div><div class="features"><h2>当前功能特性</h2><ul style="text-align: left; display: inline-block;"><li>基础用户管理功能</li><li>标准报告生成</li><li>基础数据分析</li><li>传统界面设计</li></ul><p><strong>服务器端口: 8080</strong></p><p>更新时间: 2024-01-01</p></div>
</body>
</html>
2. 创建Version2服务
vim /var/www/html/v2/index.html
Version2服务:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>服务版本 2.0 - 新特性</title><style>body { font-family: Arial, sans-serif; background-color: #fff0f5; text-align: center; padding: 50px;}.version { color: #4ecdc4; font-size: 2.5em; margin-bottom: 20px;}.features { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);max-width: 600px;margin: 0 auto;}.new-badge { background: #4ecdc4; color: white; padding: 2px 8px; border-radius: 10px; font-size: 0.8em; margin-left: 5px;}</style>
</head>
<body><div class="version">Version 2.0 - 新特性版 <span class="new-badge">NEW</span></div><div class="features"><h2>全新功能特性</h2><ul style="text-align: left; display: inline-block;"><li>智能用户推荐系统 <span class="new-badge">新</span></li><li>AI报告自动生成 <span class="new-badge">新</span></li><li>实时数据分析看板</li><li>现代化界面设计 <span class="new-badge">新</span></li><li>移动端优化支持</li></ul><p><strong>服务器端口: 8081</strong></p><p>更新时间: 2025-10-09</p></div>
</body>
</html>
3. 编写Nginx配置文件
vim /etc/nginx/conf.d/gray-release.conf
# 定义后端服务集群 - 增加健康检查参数
upstream backend_servers {server 127.0.0.1:8080 weight=50 max_fails=2 fail_timeout=30s; # Version1 - 增加容错server 127.0.0.1:8081 weight=50 max_fails=2 fail_timeout=30s; # Version2 - 增加容错
}# 基于Cookie的灰度发布配置 - 使用更精确的正则匹配
map $cookie_gray_release $backend_group {default "backend_servers"; # 默认使用权重分配"~*v1" "version1_server"; # 使用正则确保匹配包含v1的cookie值"~*v2" "version2_server"; # 使用正则确保匹配包含v2的cookie值
}# 基于IP的灰度配置示例(可选策略)
geo $gray_ip_group {default "backend_servers";192.168.1.0/24 "version2_server"; # 特定IP段访问新版本10.0.0.100 "version2_server"; # 特定IP访问新版本
}upstream version1_server {server 127.0.0.1:8080 max_fails=2 fail_timeout=30s;
}upstream version2_server {server 127.0.0.1:8081 max_fails=2 fail_timeout=30s;
}# 主服务器配置
server {listen 8083;server_name localhost;# 设置全局访问日志格式,便于区分流量access_log /var/log/nginx/gray_access.log ;error_log /var/log/nginx/gray_error.log;# 静态资源服务location /static/ {root /var/www/html;expires 30d;add_header Cache-Control "public, immutable";}location /nginx_status {stub_status on; # 开启状态模块access_log off; # 可选:关闭此位置的访问日志allow 127.0.0.1; # 允许访问的IP地址,例如本地allow 192.168.16.0/24; # 允许的内网网段,按需设置deny all; # 拒绝其他所有IP访问,重要!}# 主要请求处理 - 增强的灰度路由location / {# 可根据需要选择使用Cookie或IP策略:# 当前使用Cookie策略,如需切换可注释下一行并使用IP策略proxy_pass http://$backend_group;# 如需使用IP策略,取消下面这行的注释:# proxy_pass http://$gray_ip_group;# 设置代理头信息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 $scheme;proxy_set_header X-Gray-Version $backend_group; # 新增:向后端传递灰度版本信息# 超时设置proxy_connect_timeout 30s;proxy_send_timeout 30s;proxy_read_timeout 30s;# 重试设置(谨慎使用)proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;proxy_next_upstream_tries 2;}
}# Version1 服务配置 (8080端口) - 保持稳定
server {listen 8080;server_name localhost;location / {root /var/www/html/v1;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version1.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version1 Healthy\n";add_header Content-Type text/plain;}
}# Version2 服务配置 (8081端口) - 保持稳定
server {listen 8081;server_name localhost;location / {root /var/www/html/v2;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version2.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version2 Healthy\n";add_header Content-Type text/plain;}
}
启动Nginx:
# 检查Nginx的语法
nginx -t # 启动nginx
nginx -s reload
# 可不重启Nginx达到动态更新的效果
4. 访问测试
访问8083端口,查看是否与我们定义的配置文件内定义的比例是否相同:(目前是50:50,每两次必定刷新新旧两个版本。)
后续测试和动态平滑更新均可以使用该字段进行更新。
如果我们想下线Version1,完成大的版本迭代,我们可以修改配置文件:
# 定义后端服务集群 - 增加健康检查参数
upstream backend_servers {server 127.0.0.1:8080 weight=10 max_fails=2 fail_timeout=30s down; # 使用down标记服务下线完成更新server 127.0.0.1:8081 weight=90 max_fails=2 fail_timeout=30s;
}
加上 down
标记后,Nginx 在负载均衡时将完全忽略该服务器,所有新请求都会发给健康的服务器。
不过,在彻底下线旧版本的服务之前,我们要进行例行的检查,这里提供一个shell脚本进行检查:
#!/bin/bash# 服务下线预检查脚本
# 功能:检查服务状态,判断是否可以安全下线,不执行任何下线操作。set -e # 遇到错误立即退出,确保检查的严谨性# 颜色定义,用于清晰显示结果
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # 恢复默认颜色# 检查结果汇总
CAN_DOWN=true
WARNING_MESSAGES=()
ERROR_MESSAGES=()SERVICE_ADDR="127.0.0.1:8080" # 待下线的服务地址(Version1)
SERVICE_NAME="Version1@${SERVICE_ADDR}"echo -e "🔍 开始对 ${YELLOW}${SERVICE_NAME}${NC} 进行下线前健康检查...\n"
echo "=========================================="# 1. 检查服务进程是否存在
echo -e "1. 检查服务进程..."
PID=$(lsof -ti :8080)
if [ -n "$PID" ]; thenecho -e " ${GREEN}✓ 服务进程存在 (PID: $PID)${NC}"
elseecho -e " ${RED}✗ 服务进程不存在${NC}"CAN_DOWN=falseERROR_MESSAGES+=("服务进程已不存在,无需执行下线操作,但请排查为何进程消失。")
fi# 2. 检查服务健康接口
echo -e "2. 检查服务健康接口 (HTTP)..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://${SERVICE_ADDR}/health" --connect-timeout 3 --max-time 5 || echo "000")
if [ "$HTTP_CODE" -eq 200 ]; thenecho -e " ${GREEN}✓ 健康接口返回 200${NC}"
elif [ "$HTTP_CODE" -eq 000 ]; thenecho -e " ${RED}✗ 无法连接到健康接口 (服务可能已完全不可用)${NC}"WARNING_MESSAGES+=("服务健康接口无法连接,下线操作风险较低,但需确认是否为预期情况。")
elseecho -e " ${YELLOW}⚠ 健康接口返回非200状态码: ${HTTP_CODE}${NC}"WARNING_MESSAGES+=("服务健康接口异常 (HTTP ${HTTP_CODE}),建议先排查此问题再下线。")CAN_DOWN=false # 若非200状态码认为不健康,可根据您的策略调整
fi# 3. 检查Nginx upstream中该服务的当前状态
echo -e "3. 检查Nginx配置中该服务的状态..."
NGINX_CONF="/etc/nginx/conf.d/gray-release.conf" # 请修改为您的实际配置文件路径
if grep -q "server ${SERVICE_ADDR}" "$NGINX_CONF"; thenif grep -q "server ${SERVICE_ADDR}.*down" "$NGINX_CONF"; thenecho -e " ${YELLOW}⚠ 服务在Nginx配置中已被标记为 'down'${NC}"WARNING_MESSAGES+=("该服务在Nginx配置中已处于 'down' 状态,请确认是否已执行过下线操作。")elseecho -e " ${GREEN}✓ 服务在Nginx配置中处于活跃状态${NC}"fi
elseecho -e " ${RED}✗ 在Nginx配置中未找到该服务${NC}"ERROR_MESSAGES+=("在Nginx配置文件中未找到目标服务,请检查配置文件路径和服务地址是否正确。")CAN_DOWN=false
fi# 4. 检查服务在负载均衡中的活跃请求(核心修改部分)
echo -e "4. 检查服务在负载均衡中的活跃请求..."
STATUS_URL="http://127.0.0.1:8083/nginx_status"# 使用curl获取状态页内容,并提取Reading和Writing的数值
STATUS_OUTPUT=$(curl -s $STATUS_URL)
READING=$(echo "$STATUS_OUTPUT" | awk '/Reading/ {print $2}')
WRITING=$(echo "$STATUS_OUTPUT" | awk '/Writing/ {print $4}')# 显式打印出关键指标
echo " 当前连接状态: Reading=$READING, Writing=$WRITING"# 核心判断逻辑:只有当读写操作均为0时,才认为可以安全下线
if [ "$READING" -eq 0 ] && [ "$WRITING" -eq 0 ]; thenecho -e " ${GREEN}✓ 当前无活跃请求(Reading + Writing = 0)${NC}"CAN_DOWN=true
elseecho -e " ${RED}✗ 当前仍有活跃请求(Reading: $READING, Writing: $WRITING)${NC}"ERROR_MESSAGES+=("服务仍有 [$READING] 个读取请求和 [$WRITING] 个写入请求,请等待请求处理完毕后再下线。")CAN_DOWN=false
fi# 5. 检查系统资源(可选,判断是否存在其他潜在问题)
echo -e "5. 快速系统资源检查..."
LOAD_AVG=$(uptime | awk -F 'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
MEM_USAGE=$(free | grep Mem | awk '{printf "%.1f", $3/$2 * 100.0}')
echo -e " 系统平均负载: $LOAD_AVG"
echo -e " 内存使用率: $MEM_USAGE%"
# 简单阈值判断,您可以根据服务器情况调整
if (( $(echo "$LOAD_AVG > 5" | bc -l 2>/dev/null) )); thenecho -e " ${YELLOW}⚠ 系统负载较高${NC}"WARNING_MESSAGES+=("当前系统负载较高 [${LOAD_AVG}],请评估下线服务对整体性能的影响。")
fiecho ""
echo "=========================================="
echo -e "📋 ${YELLOW}检查结果汇总:${NC}"# 输出警告和信息消息
if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; thenecho -e "\n${YELLOW}⚠ 警告信息:${NC}"for msg in "${WARNING_MESSAGES[@]}"; doecho " - $msg"done
fiif [ ${#ERROR_MESSAGES[@]} -gt 0 ]; thenecho -e "\n${RED}❌ 错误信息:${NC}"for msg in "${ERROR_MESSAGES[@]}"; doecho " - $msg"done
fi# 最终决策
echo ""
if [ "$CAN_DOWN" = true ]; thenecho -e "${GREEN}✅【结论:可以下线】${NC}"echo -e "所有关键检查已通过。当前服务状态允许执行下线操作。请您手动执行下线命令。"
elseecho -e "${RED}❌【结论:不建议下线】${NC}"echo -e "存在关键问题,当前下线服务可能存在风险。请先解决上述错误信息中提到的问题。"
fiecho ""
echo "=========================================="
echo "检查完成于: $(date)"
检查项 | 检查内容 | 为何重要 |
---|---|---|
服务进程 | 服务进程是否仍在运行。 | 确认操作对象存在,避免对已停止的服务进行无效操作。 |
健康接口 | 服务的 /health 端点是否返回200。 | 这是判断服务内部状态是否健康的直接证据,比进程是否存在更重要。 |
Nginx配置状态 | 服务在Nginx配置中是否已被标记为 down 。 | 防止重复操作,提醒您当前配置状态。 |
活跃连接(建议) | 服务是否还有未处理完的请求。 | 最关键的一环。如果仍有活跃连接,强制下线会中断用户请求。 |
系统资源 | 当前系统负载和内存使用情况。 | 评估下线单个服务对整体系统稳定性的潜在影响。 |
执行该脚本后,会简单的为我们分析当前环境是否可以完全去除旧版本的服务:(注意配置文件路径和服务暴露端口等)
🔍 开始对 Version1@127.0.0.1:8080 进行下线前健康检查...==========================================
1. 检查服务进程...✓ 服务进程存在 (PID: 1740674
1740675
1740676
1740677
1740678)
2. 检查服务健康接口 (HTTP)...✓ 健康接口返回 200
3. 检查Nginx配置中该服务的状态...✓ 服务在Nginx配置中处于活跃状态
4. 检查服务在负载均衡中的活跃请求...当前连接状态: Reading=0, Writing=1✗ 当前仍有活跃请求(Reading: 0, Writing: 1)
5. 快速系统资源检查...系统平均负载: 0.02内存使用率: 9.1%==========================================
📋 检查结果汇总:❌ 错误信息:- 服务仍有 [0] 个读取请求和 [1] 个写入请求,请等待请求处理完毕后再下线。❌【结论:不建议下线】
存在关键问题,当前下线服务可能存在风险。请先解决上述错误信息中提到的问题。==========================================
检查完成于: Thu Oct 9 14:21:18 CST 2025
当Read和Write均为0后即可下线服务,完成灰度发布:
curl http://127.0.0.1:8083/nginx_status
Active connections: 1
server accepts handled requests5 5 5
Reading: 0 Writing: 1 Waiting: 0
5. 基于特定Cookies进行访问
Cookie 名称: gray_release
(由 $cookie_gray_release
变量定义)
打开测试页面:在浏览器(Chrome/Firefox/Edge)中访问您的网站 http://您的域名或IP:8083
。
打开开发者工具:按 F12
键打开开发者工具。
找到 Application/C存储 面板:Chrome/Edge:切换到 Application
标签页 -> 左侧找到 Cookies
并展开 -> 选择您的网站。Firefox:切换到 存储
标签页 -> 左侧找到 Cookie
并展开 -> 选择网站。
添加 Cookie:点击 +
或右键点击空白处选择 Add
。
填写以下信息:
Name: gray_release
Value: v2
(如果想测试新版) 或 v1
(如果想测试旧版)
Domain: 域名或IP(如 localhost
、IP,通常会自动填充)
Path: /
(确保对所有路径生效)
刷新页面:添加完成后,刷新页面,您的所有请求就会被定向到指定的版本,不会受到灰度发布策略所影响。
问题思考
1. 公共服务连接影响
问题:如果新版本(V2)服务所需的数据库表结构与旧版本(V1)不一致,如何操作?是让V1和V2连接同一个数据库,还是各自连接不同的数据库?
回答:绝对必须使用同一个数据库,并且必须保证数据结构的向后兼容。 让V1和V2服务连接不同的数据库是一个非常危险且错误的选择,会导致严重的数据不一致和业务逻辑混乱。
2. 服务连续性问题
问题:一个用户第一次访问被灰度策略分配到了V2服务,他的后续请求是会持续访问V2,还是可能被随机跳转到V1服务?这个服务链路是否是连续、一致的?
回答:默认的基于权重的灰度策略下,用户的后续请求是随机的,可能会在V1和V2之间跳跃。 但可以通过会话保持技术来解决,强制同一用户的请求始终落在同一个版本上。Nginx默认的权重分流策略是无状态的。对每一个新到来的请求都会根据权重比例重新“掷一次骰子”,独立决定将其转发到V1还是V2。它不关心这个请求来自谁,也不关心这个用户之前的请求去了哪里。
解决方案:
基于Cookie的会话保持
- Nginx可以提供
sticky
模块或使用hash
算法,主动给用户浏览器设置一个Cookie,里面包含版本信息。 - 流程:用户第一次访问,Nginx按权重分配并为其设置一个Cookie(如
route_version=v2
)。该用户后续所有请求都带着这个Cookie,Nginx根据Cookie值直接将其路由到V2,不再重新计算权重。
正是这样的思考,就有了文章接下来的部分:
基于Cookie的会话保持
1. 修改Nginx配置文件
修改Nginx配置文件,本次的文件为生产级Nginx实现灰度发布文件:
# 1) Seed selection: prefer logged-in user id cookie (COOKIE_USER_ID), otherwise use client IP.
# Replace COOKIE_USER_ID below with your actual login cookie name or leave as empty string "" to always use remote_addr.
# If your login cookie is "user_id", change the first map line to map $cookie_user_id ...
map $cookie_user_id $seed {"" $remote_addr;default $cookie_user_id;
}# 2) Percent-based stable assignment based on seed (decided in request phase)
# Adjust 50%/50% as needed. Uses the seed so same seed maps consistently.
split_clients "$seed" $percent_choice {10% version1_server; # change 10% to your desired percentage for V1* version2_server;
}# 3) Cookie mapping (if client already has gray cookie)
map $cookie_gray_version $cookie_group {default "";"v1" "version1_server";"v2" "version2_server";
}# 4) Final selection: cookie takes precedence, otherwise percent_choice
map "$cookie_group:$percent_choice" $final_group {"~^version1_server:" "version1_server";"~^version2_server:" "version2_server";default $percent_choice;
}# 5) Compose Set-Cookie string depending on whether cookie already exists AND scheme
# If cookie is empty and final_group==versionX, generate cookie.
# Use Secure+SameSite=None when over HTTPS; otherwise omit Secure and use SameSite=Lax.
map "$cookie_gray_version:$final_group:$scheme" $set_gray_cookie {"~^:version1_server:https$" "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version2_server:https$" "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version1_server:http$" "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";"~^:version2_server:http$" "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";default "";
}# Optionally, if you want to set Domain=.yourdomain.com (for sharing across subdomains),
# you can create a map that appends "; Domain=.yourdomain.com" to the cookie strings for https/http cases.
# Example (uncomment and adjust your domain if needed):
# map $set_gray_cookie $set_gray_cookie_domain {
# default "$set_gray_cookie; Domain=.yourdomain.com";
# }
# Then use add_header Set-Cookie $set_gray_cookie_domain always; (instead of $set_gray_cookie)# 6) Upstream definitions
upstream version1_server {server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;keepalive 32;
}upstream version2_server {server 127.0.0.1:8081 max_fails=3 fail_timeout=10s;keepalive 32;
}# 7) Logs (include decision & cookie for observability)
log_format grayformat '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" "$http_user_agent" ''upstream=$upstream_addr final_group=$final_group percent="$percent_choice" ''cookie_gray="$cookie_gray_version" setcookie="$set_gray_cookie"';# 8) Main server block
server {listen 8083;server_name YOUR_DOMAIN; # replace with IP or domain as appropriateaccess_log /var/log/nginx/gray_access.log grayformat;error_log /var/log/nginx/gray_error.log warn;# Health/status and static can be added as neededlocation /nginx_status {stub_status on;access_log off;allow 127.0.0.1;deny all;}location / {# Expose decision headers (debugging/observability)add_header X-Gray-Decision $final_group always;add_header X-Gray-Percent $percent_choice always;# Add Set-Cookie only when map produced a non-empty cookie string# If you need to avoid emitting empty Set-Cookie header when $set_gray_cookie is empty,# consider installing the headers_more module and conditionally set headers there.add_header Set-Cookie $set_gray_cookie always;# Proxy to selected upstreamproxy_pass http://$final_group;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 $scheme;proxy_set_header X-Gray-Version $final_group;# Timeouts & buffering tuning (adjust to your app)proxy_connect_timeout 5s;proxy_send_timeout 60s;proxy_read_timeout 60s;proxy_buffering on;# Failover behavior: be careful — automatic retry may mask new-version issues.# Consider removing proxy_next_upstream or limit its use in strict gray tests.proxy_next_upstream error timeout http_500 http_502 http_503 http_504;proxy_next_upstream_tries 1;}
}# 9) Backend test servers (example) - replace with real upstream or remove# Version1 服务配置 (8080端口) - 保持稳定
server {listen 8080;server_name localhost;location / {root /var/www/html/v1;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version1.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version1 Healthy\n";add_header Content-Type text/plain;}
}# Version2 服务配置 (8081端口) - 保持稳定
server {listen 8081;server_name localhost;location / {root /var/www/html/v2;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version2.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version2 Healthy\n";add_header Content-Type text/plain;}
}
2. 服务验证
执行该配置文件后进行查看:
3. 配置文件解读
智能种子选择机制
map $cookie_user_id $seed {"" $remote_addr;default $cookie_user_id;
}
- 创新点:优先使用已登录用户的ID作为种子,如果没有则回退到客户端IP
- 优势:为登录用户提供更稳定的版本分配(同一用户始终访问同一版本),未登录用户按IP分配
稳定的百分比分配
split_clients "$seed" $percent_choice {10% version1_server;* version2_server;
}
- 优势:基于种子值的哈希分配,确保同一用户/IP的分配结果一致
- 可调参数:您可以轻松调整百分比(如改为5%/95%)
Cookie处理
map "$cookie_gray_version:$final_group:$scheme" $set_gray_cookie {"~^:version1_server:https$" "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version2_server:https$" "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version1_server:http$" "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";"~^:version2_server:http$" "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";default "";
}
- 安全特性:HTTPS环境下:使用
Secure
和SameSite=None
,适合跨站场景HTTP环境下:使用SameSite=Lax
,提供基本安全保护所有Cookie都设置HttpOnly
。
优先级决策逻辑
map "$cookie_group:$percent_choice" $final_group {"~^version1_server:" "version1_server";"~^version2_server:" "version2_server";default $percent_choice;
}
- 明确优先级:现有Cookie > 百分比分配
- 正则匹配:使用正则表达式确保精确匹配
用户体验优化
- 会话一致性:登录用户在整个用户会话中保持同一版本
- 跨设备支持:基于用户ID而非IP,用户在不同设备上也能获得一致体验
安全性与合规性
- 安全Cookie设置:区分HTTP/HTTPS环境,符合现代浏览器安全要求
- SameSite策略:防止CSRF攻击,同时支持跨站使用
- HttpOnly标志:保护Cookie不被JavaScript访问
可观测性与调试
log_format grayformat '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" "$http_user_agent" ''upstream=$upstream_addr final_group=$final_group percent="$percent_choice" ''cookie_gray="$cookie_gray_version" setcookie="$set_gray_cookie"';
- 详细日志:记录所有关键决策变量,便于故障排查和数据分析
- 响应头调试:添加
X-Gray-Decision
和X-Gray-Percent
头,便于实时调试
灵活性和可扩展性
- 易于调整:只需修改split_clients百分比即可调整流量比例
- 多环境支持:自动适应HTTP和HTTPS环境
- 域名配置:提供Domain配置示例,支持跨子域名共享Cookie
至此,本篇文章就结束啦,拜拜。