小Cの已经记不起来的博客

蓝绿部署 的十种打开方式:工程实践笔记

之前我们发版都是老三样:半夜、停机、祈祷。有一次凌晨两点升级,服务起不来,回滚脚本又是上一个人改过没测过的,折腾到四点才收场。从那以后我就认了一个理:发版这件事,必须给自己留退路。蓝绿部署说白了就是同时养两套环境,一套在跑,一套随时能顶上,所谓发布和回滚,都只是“指一下”的动作。这几年大大小小的项目试下来,攒了十种用法,从土办法到正规军都有,记在这里。

一、最土的:两台机器 + DNS

最早在公司内网折腾的时候,就是两台虚拟机,一台蓝一台绿。新版本部署到绿机,自己先点一遍,没问题就去改 DNS 记录。这个方式唯一的坑是 TTL:平时要是设的 3600,切完之后最长一小时里还有用户在访问老机器,回滚也得等缓存过期。所以我把域名 TTL 常年设在 60,切的时候开着 dig +short web.example.com 盯解析变化。土,但是能用,几个内网小系统到现在还在这么玩。

二、Nginx upstream:改一行配置的事

大多数业务其实不用动 DNS,Nginx 就够了:

upstream app {
    server 10.0.0.11:8000; # blue
    # server 10.0.0.12:8000; # green,平时注释着
}

发新版时把 green 起起来,先 curl 一遍健康检查接口,确认没问题,把注释换一下,nginx -s reload 完事。回滚就是把注释换回去再 reload,整套动作我已经能背下来了,凌晨操作也不慌。有个细节:reload 是平滑的,但 keepalive 的长连接池记得看一眼,有次老连接一直挂着,我排查了半天才发现流量其实早就切干净了。

三、云负载均衡:换目标组

上了云之后更省事,比如 ALB,蓝绿各建一个 target group,切换就是把监听规则指过去:

aws elbv2 modify-listener \
  --listener-arn arn:aws:elasticloadbalancing:... \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/green/xxx

一条命令,秒切。回滚就是把 arn 换回 blue 再跑一遍。我靠这个处理过好几次“上线五分钟发现不对劲”的紧急回退,比重新部署快太多了。

四、Kubernetes:换个 label 就完事

K8s 里可以做得更干净。新旧两个 deployment 分别打上 version: blue / version: green,Service 的 selector 平时指向 blue:

spec:
  selector:
    app: web
    version: blue

切换就是:

kubectl patch service web -p '{"spec":{"selector":{"version":"green"}}}'

endpoints 立刻跟着变,Pod 都不用重启。第一次这么切的时候我盯着 kubectl get endpoints 看了半天,真的就是一瞬间全换过去了。

五、权重切换:不用一把梭

上面几种都是 0 和 100 的切换,心跳有点快。其实可以温柔一点:

split_clients "${remote_addr}" $backend {
    10% green;
    *   blue;
}

先放 10% 流量到 green,观察半小时错误率和延迟,再调成 50%、100%。这么搞已经有点像灰度发布了,但本质上还是那两套环境,只是切换粒度细了,我认为这是蓝绿最实用的进化形态。

六、服务网格版:改 yaml 的事交给 Istio

有 Istio 的话,VirtualService 写权重更直观:

http:
- route:
  - destination:
      host: web
      subset: blue
    weight: 90
  - destination:
      host: web
      subset: green
    weight: 10

改个数字 apply 一下就行。缺点是要养一套网格,小项目上纯属杀鸡用牛刀,我一般只在几十个服务的集群里才推荐。

七、数据库:最容易翻车的地方

前面切得多爽,数据库这关就得多谨慎。蓝是 v1,绿是 v2,但库只有一套。我的原则是 schema 变更永远向后兼容一个版本:加字段可以,删字段改名不行,先加新的、双写、切流量、观察、再下线旧的,也就是常说的 expand-contract。有一回同事直接把字段改了名,green 一上去读写全炸,蓝也回不去了,因为数据格式已经不兼容。那次之后发布流程里加了一条硬性检查:所有 migration 必须标注“是否影响上一版本”。

八、前端蓝绿:symlink 就够了

静态资源更简单,发布目录按版本放,current 是个软链:

releases/
  20240601/
  20240610/
current -> releases/20240610

nginx 的 root 指向 current,切换就是 ln -sfn releases/20240611 current,原子操作,不存在切到一半的状态。回滚就是改回上一个目录。我这个站点的发布脚本总共十来行,一直是这个套路。

九、没 K8s 的小项目:docker compose 双份跑

连专职运维都没有的项目,compose 也能蓝绿。同一份 compose 文件,用两个 project name 起两份:

docker compose -p app-blue up -d
# 发新版
docker compose -p app-green up -d
# 验证没问题,nginx 指向 green 端口后
docker compose -p app-blue down

注意 down 的时候别手快带 -v,有次把 green 的数据卷一起删了,还好只是测试环境,一身冷汗。

十、切完之后:老环境别急着销毁

最后说下善后。green 稳定之后,blue 我一般留 24 到 48 小时再销毁,一方面是回滚保险,另一方面老机器里的日志、临时文件有时候还得上去翻。销毁前把要紧的东西摘出来,尤其是日志和配置 diff。另外每次切换我都在群里甩一条消息,写清楚切的什么、从哪到哪、谁批的——不是为了仪式感,是三个月后查问题的时候有迹可循。

蓝绿部署没什么高深的,核心就一句话:让新旧两套环境同时可用,把“发布”从一次冒险变成一次可逆的选择。至于用哪种打开方式,两台机器也好,服务网格也好,看手头有什么就用什么,能让你睡个好觉的就是好方案。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放