使用nginx进行灰度发布的几种方式介绍
标签搜索

使用nginx进行灰度发布的几种方式介绍

mrui
2025-12-08 / 0 评论 / 2 阅读 / 正在检测是否收录...

通常一个新功能开发完,测试环境可能跑得好好的,但生产环境因为数据量、并发量、网络环境完全不一样,可能会产生各种意料之外的错误。等发现问题的时候,可能已经造成了不小的影响。

因此,新功能的发布推荐使用灰度发布的方式,先给少量用户试用新版本,确认无问题后再逐渐放量到全部用户。

  • 先给少量(如5%)的用户试用新版本,观察一段时间
  • 如果没问题,再逐步放量到20%、50%、100%
  • 一旦发现问题,立刻切回旧版本
  • 整个过程用户基本无感知

值得注意的是,进行灰度发布时,监控和告警特别重要

推荐做法:在Nginx日志里加上版本标记,然后用ELK收集日志,实时对比新旧版本的各项指标。这样在Kibana里就能看到每个版本的请求量、错误率、响应时间等指标,一目了然。

此外还应配置告警规则,如果新版本的错误率比旧版本高出10%,或者响应时间慢了50%,就会自动发送告警,甚至可以自动回滚。

log_format canary '$remote_addr - $remote_user [$time_local]'
                  '"$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent" '
                  'backend=$backend rt=$request_time';

access_log /var/log/nginx/access.log canary;

优化建议

灰度策略要灵活

不要一上来就5%、10%、50%、100%这样机械地放量。要根据实际情况灵活调整。

比如一个小功能,可能5%观察半小时没问题,就直接100%了。但如果是核心功能的大改动,可能要5%观察一天,10%观察一天,慢慢来。

最好是先在内部员工里测试,没问题后再给1%的真实用户,然后5%、10%、30%、50%、100%,每个阶段都要观察一段时间。

做好回滚预案

灰度发布虽然降低了风险,但不代表没有风险。一定要做好回滚预案。

推荐做法是,每次发布前都要演练一遍回滚流程,确保出问题的时候能在5分钟内回滚。而且回滚不能只是把流量切回去,还要考虑数据一致性、缓存清理等问题。

用户体验要考虑

虽然灰度发布对用户来说应该是无感知的,但有些细节还是要注意。

比如不要在用户操作的过程中切换版本,这样可能导致数据丢失或者页面错乱。我们的做法是,用Cookie做用户粘性,保证同一个用户在一段时间内(比如24小时)始终访问同一个版本。

还有就是,如果新旧版本的UI差异比较大,最好在客户端做个平滑过渡,不要让用户觉得突兀。

使用nginx可以通过多种 不同的方式来进行灰度发布,分别介绍如下:

基于权重的流量分配

这是最简单的一种方式,原理就是通过upstream的权重参数,把流量按比例分配到不同版本的服务器上。

下面这个配置的意思是,100个请求里面,95个会打到旧版本服务器,5个会打到新版本。

不过这种方式有个问题,就是同一个用户的请求可能一会儿打到旧版本,一会儿打到新版本,体验不太好。因此最好设置一个ip_hash。

upstream backend {
    ip_hash;
    server 192.168.1.10:8080 weight=95;  # 旧版本
    server 192.168.1.11:8080 weight=5;   # 新版本
}

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

基于Cookie的灰度发布

一个用户第一次访问的时候,我们给他打个标记,后续的请求都根据这个标记来决定走哪个版本。

实现方式:用户第一次访问的时候,随机决定他是不是可以访问新版本,然后种个Cookie。后续请求都会带着这个Cookie,保证同一个用户始终访问同一个版本。

但是这个方案还是有点粗糙,因为Nginx原生不支持生成随机数,我们需要借助一些技巧或者第三方模块。

upstream backend_v1 {
    server 192.168.1.10:8080;
}

upstream backend_v2 {
    server 192.168.1.11:8080;
}

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        set $backend "backend_v1";
        
        # 如果Cookie中有canary标记,走新版本
        if ($http_cookie ~* "canary=true") {
            set $backend "backend_v2";
        }
        
        # 随机给5%的新用户打上canary标记
        set $random_canary "";
        if ($http_cookie !~* "canary") {
            set $random_canary "${random_canary}A";
        }
        
        # 生成1-100的随机数
        set $rand_num $request_id;
        if ($random_canary = "A") {
            # 这里简化处理,实际可以用Lua脚本
            add_header Set-Cookie "canary=false; Path=/; Max-Age=86400";
        }
        
        proxy_pass http://$backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

基于Header的灰度发布

可以更精确地控制哪些用户走新版本,比如内部员工、测试账号、特定地区的用户等等。这时候可以用Header来做判断。

这种方式特别适合做内部测试。

使用方式:在客户端(比如App或者前端页面)加个开关,员工登录后自动在请求头里加上特定标记,这样就能体验新版本了。

upstream backend_v1 {
    server 192.168.1.10:8080;
}

upstream backend_v2 {
    server 192.168.1.11:8080;
}

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        set $backend "backend_v1";
        
        # 如果请求头中有特定标记,走新版本
        if ($http_x_canary_version = "v2") {
            set $backend "backend_v2";
        }
        
        # 内部员工走新版本
        if ($http_x_employee_id != "") {
            set $backend "backend_v2";
        }
        
        proxy_pass http://$backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

基于IP地址的灰度发布

还有一种场景,比如计划先在某个地区试点新功能,或者只给公司内网用户开放新版本。这时候可以用IP地址来做判断。

geo模块是一个Nginx内置核心模块,用于根据客户端 IP 地址创建变量,实现基于地理位置或 IP 段的条件处理。它允许服务器对不同来源的请求执行不同的操作,无需额外安装,随 Nginx 一起编译。

geo $canary_user {
    default 0;
    10.0.0.0/8 1;        # 公司内网
    192.168.1.0/24 1;    # 特定网段
    123.45.67.89 1;      # 特定IP
}

upstream backend_v1 {
    server 192.168.1.10:8080;
}

upstream backend_v2 {
    server 192.168.1.11:8080;
}

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        set $backend "backend_v1";
        
        if ($canary_user = 1) {
            set $backend "backend_v2";
        }
        
        proxy_pass http://$backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

灰度发布中的一些常见问题

Session一致性问题

如果Session存在服务器本地,用户的请求可能一会儿打到v1,一会儿打到v2,那么就会造成Session的丢失。于是就会造成用户登录状态老是丢失的问题。

解决办法有两个:

  • 把Session存到Redis这种共享存储里
  • 用Cookie或者Header做用户粘性,保证同一个用户的请求打到同一个版本

数据库兼容性问题

比如新版本改了数据库表结构,结果灰度发布的时候,新旧版本同时在跑,旧版本写入的数据新版本读不了,新版本写入的数据旧版本也读不了,整个系统乱套了。

解决方案

  1. 先发布一个兼容版本,既能处理旧数据格式,也能处理新数据格式
  2. 等兼容版本全量发布后,再发布纯新版本

这样虽然麻烦点,但安全多了。

0

评论 (0)

取消