企业级灰度发布架构:基于Nginx的精细化流量治理与平滑演进实践
文章目录
基于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 requests
307 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" ]; then
echo -e " ${GREEN}✓ 服务进程存在 (PID: $PID)${NC}"
else
echo -e " ${RED}✗ 服务进程不存在${NC}"
CAN_DOWN=false
ERROR_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 ]; then
echo -e " ${GREEN}✓ 健康接口返回 200${NC}"
elif [ "$HTTP_CODE" -eq 000 ]; then
echo -e " ${RED}✗ 无法连接到健康接口 (服务可能已完全不可用)${NC}"
WARNING_MESSAGES+=("服务健康接口无法连接,下线操作风险较低,但需确认是否为预期情况。")
else
echo -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"; then
if grep -q "server ${SERVICE_ADDR}.*down" "$NGINX_CONF"; then
echo -e " ${YELLOW}⚠ 服务在Nginx配置中已被标记为 'down'${NC}"
WARNING_MESSAGES+=("该服务在Nginx配置中已处于 'down' 状态,请确认是否已执行过下线操作。")
else
echo -e " ${GREEN}✓ 服务在Nginx配置中处于活跃状态${NC}"
fi
else
echo -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 ]; then
echo -e " ${GREEN}✓ 当前无活跃请求(Reading + Writing = 0)${NC}"
CAN_DOWN=true
else
echo -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) )); then
echo -e " ${YELLOW}⚠ 系统负载较高${NC}"
WARNING_MESSAGES+=("当前系统负载较高 [${LOAD_AVG}],请评估下线服务对整体性能的影响。")
fi
echo ""
echo "=========================================="
echo -e "📋 ${YELLOW}检查结果汇总:${NC}"
# 输出警告和信息消息
if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; then
echo -e "\n${YELLOW}⚠ 警告信息:${NC}"
for msg in "${WARNING_MESSAGES[@]}"; do
echo " - $msg"
done
fi
if [ ${#ERROR_MESSAGES[@]} -gt 0 ]; then
echo -e "\n${RED}❌ 错误信息:${NC}"
for msg in "${ERROR_MESSAGES[@]}"; do
echo " - $msg"
done
fi
# 最终决策
echo ""
if [ "$CAN_DOWN" = true ]; then
echo -e "${GREEN}✅【结论:可以下线】${NC}"
echo -e "所有关键检查已通过。当前服务状态允许执行下线操作。请您手动执行下线命令。"
else
echo -e "${RED}❌【结论:不建议下线】${NC}"
echo -e "存在关键问题,当前下线服务可能存在风险。请先解决上述错误信息中提到的问题。"
fi
echo ""
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 requests
5 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_releaseValue: 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 appropriate
access_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 needed
location /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 upstream
proxy_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
至此,本篇文章就结束啦,拜拜。
更多推荐



所有评论(0)