零停机部署策略:在更新期间保持应用程序运行
计划内停机平均每分钟给企业造成 5,600 美元的成本。 然而,43% 的公司仍然在部署期间将其应用程序脱机。零停机部署并不是一种奢侈——而是一种期望。客户、搜索引擎和集成合作伙伴都会对离线(即使是短暂离线)的应用程序进行惩罚。
本指南涵盖了三种主要的零停机部署策略、保持正常运行时间的数据库迁移技术以及自动回滚机制。
要点
- 蓝绿部署是最安全的策略:通过将流量切换回之前的版本来即时回滚
- 数据库迁移必须向后兼容 --- 旧应用程序版本必须适用于新架构
- 健康检查和就绪探测可防止将流量路由到尚未准备好提供服务的 Pod
- 基于错误率监控的自动回滚将平均恢复时间缩短至 2 分钟以下
策略比较
| 战略 | 复杂性 | 回滚速度 | 基础设施成本 | 最适合 |
|---|---|---|---|---|
| 蓝绿色 | 低 | 即时(秒) | 部署期间 2x | Critical applications, infrequent deploys |
| 滚动更新 | 中等 | 分钟 | 部署期间 1.25 倍 | Kubernetes,频繁部署 |
| 金丝雀 | 高 | 快(秒) | 部署期间 1.05 倍 | 高流量、风险敏感 |
| 功能标志 | 中等 | 即时 | 1x | 逐步推出功能 |
蓝绿部署
架构
Load Balancer
|
|--- [ACTIVE] Blue environment (v2.0.0) <-- receives 100% traffic
|
|--- [IDLE] Green environment (v2.1.0) <-- deployed, tested, waiting
部署时: 1、部署v2.1.0到空闲(绿色)环境 2. 针对绿色进行冒烟测试 3. 将负载均衡器切换为绿色 4.蓝色变为空闲(可即时回滚)
使用 Nginx 实现
# /etc/nginx/conf.d/app.conf
upstream blue {
server 10.0.1.10:3000;
server 10.0.1.11:3000;
}
upstream green {
server 10.0.2.10:3000;
server 10.0.2.11:3000;
}
# Active environment - change this during deployment
map $host $active_upstream {
default blue; # Change to 'green' to switch
}
server {
listen 443 ssl;
server_name app.example.com;
location / {
proxy_pass http://$active_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
部署脚本
#!/bin/bash
set -e
CURRENT=$(cat /etc/nginx/active-env) # "blue" or "green"
TARGET=$( [ "$CURRENT" = "blue" ] && echo "green" || echo "blue" )
echo "Current: $CURRENT, deploying to: $TARGET"
# Deploy to inactive environment
ssh "deploy@$TARGET-1" "cd /opt/app && git pull && pnpm install --frozen-lockfile && pnpm build && pm2 restart all"
ssh "deploy@$TARGET-2" "cd /opt/app && git pull && pnpm install --frozen-lockfile && pnpm build && pm2 restart all"
# Wait for health checks
for i in 1 2; do
echo "Checking $TARGET-$i health..."
for attempt in $(seq 1 30); do
if curl -sf "http://$TARGET-$i:3000/health" > /dev/null; then
echo "$TARGET-$i is healthy"
break
fi
sleep 2
done
done
# Run smoke tests
pnpm test:smoke --base-url "http://$TARGET-1:3000"
# Switch traffic
sed -i "s/default $CURRENT/default $TARGET/" /etc/nginx/conf.d/app.conf
nginx -s reload
echo "$TARGET" > /etc/nginx/active-env
echo "Traffic switched to $TARGET. Rollback: change active-env back to $CURRENT"
滚动更新
滚动更新逐步替换实例,确保部分容量始终可用。
Kubernetes 滚动更新
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Create 1 extra pod during update
maxUnavailable: 0 # Never reduce below desired replicas
template:
spec:
containers:
- name: api
image: registry.example.com/api:v2.1.0
readinessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 15
periodSeconds: 10
使用 maxSurge: 1 和 maxUnavailable: 0 的滚动更新过程:
- 使用 v2.1.0 创建 1 个新 pod(总共 6 个 pod:5 个旧 + 1 个新)
- Wait for new pod readiness probe to pass
- 终止 1 个旧 Pod(5 个 Pod:4 个旧 Pod + 1 个新 Pod)
- 创建另一个新 Pod(6 个 Pod:4 个旧 + 2 个新)
- 重复直到所有 pod 都是 v2.1.0
金丝雀部署
流量分割
# Istio VirtualService for canary routing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: api-canary
spec:
hosts:
- api.example.com
http:
- route:
- destination:
host: api-stable
port:
number: 3001
weight: 95
- destination:
host: api-canary
port:
number: 3001
weight: 5
渐进式金丝雀推出
| 相 | 金丝雀交通 | 持续时间 | 成功标准 |
|---|---|---|---|
| 1 | 1% | 10 分钟 | 错误率<0.1%,延迟<500ms |
| 2 | 5% | 30 分钟 | 错误率<0.1%,延迟<500ms |
| 3 | 25% | 1小时 | 错误率<0.5%,延迟<1s |
| 4 | 50% | 2小时 | 错误率<0.5%,延迟<1s |
| 5 | 100% | 全面推出 | 24小时稳定 |
数据库迁移无需停机
零停机部署的最大挑战是数据库架构更改。旧应用程序版本必须适用于新架构,反之亦然。
扩展-收缩模式
阶段 1:扩展(部署架构更改)
-- Add new column (nullable, no default)
ALTER TABLE orders ADD COLUMN shipping_method VARCHAR(50);
旧的应用程序代码会忽略新列。新的应用程序代码写入旧列和新列。
阶段 2:迁移数据
-- Backfill existing data
UPDATE orders SET shipping_method = 'standard' WHERE shipping_method IS NULL;
阶段 3:合同(部署专门使用新列的代码)
所有应用程序实例都使用新列后:
-- Make column required
ALTER TABLE orders ALTER COLUMN shipping_method SET NOT NULL;
ALTER TABLE orders ALTER COLUMN shipping_method SET DEFAULT 'standard';
危险的迁移模式
| 图案 | 风险 | 安全替代方案 |
|---|---|---|
| 重命名列 | 打破旧代码 | 添加新列、迁移、删除旧列 |
| 删除栏目 | 打破旧代码 | 停止使用,然后投入下一个版本 |
| 添加 NOT NULL 列 | 锁具表 | 添加可空、回填、更改为 NOT NULL |
| 更改列类型 | 锁定表,中断查询 | 添加具有新类型的新列,迁移 |
| 添加唯一索引 | 在大桌子上锁定桌子 | 代码0 |
自动回滚
基于错误率的回滚
#!/bin/bash
# post-deploy-monitor.sh
DEPLOY_TIME=$(date +%s)
MONITOR_DURATION=300 # 5 minutes
ERROR_THRESHOLD=0.02 # 2%
while [ $(($(date +%s) - DEPLOY_TIME)) -lt $MONITOR_DURATION ]; do
ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..'}[2m])/rate(http_requests_total[2m])" | jq -r '.data.result[0].value[1]')
if (( $(echo "$ERROR_RATE > $ERROR_THRESHOLD" | bc -l) )); then
echo "ERROR: Rate $ERROR_RATE exceeds threshold $ERROR_THRESHOLD"
echo "Initiating rollback..."
kubectl rollout undo deployment/api
exit 1
fi
sleep 15
done
echo "Deployment healthy for $MONITOR_DURATION seconds"
常见问题
我们应该从哪种策略开始?
从蓝绿部署开始。它实现起来最简单,提供即时回滚,并且适用于任何应用程序架构。滚动更新更适合具有许多副本的 Kubernetes 环境。金丝雀部署适用于高流量应用程序,您希望在全面部署之前使用实际流量验证更改。
我们如何在部署过程中处理长时间运行的后台作业?
使用优雅关闭。当 Pod 收到终止信号时,停止接受新作业,完成正在进行的作业(有超时),然后关闭。在 Kubernetes 中,配置 terminationGracePeriodSeconds 以留出足够的时间来完成作业。对于需要比宽限期更长的作业,请使用作业队列(Redis、RabbitMQ)在幸存的工作线程上重试失败的作业。
部署期间的 WebSocket 连接怎么样?
WebSocket 连接是长期存在的,必须小心处理。在滚动更新期间,旧 Pod 上的现有连接保持活动状态,直到 Pod 终止。客户端应该实现自动重新连接逻辑。对于蓝绿部署,将新连接切换到新环境,同时允许现有连接在超时的情况下耗尽旧环境。
我们如何测试零停机部署?
在部署期间运行负载测试。使用k6或类似工具生成持续流量,然后触发部署。检查翻转期间是否有任何错误、延迟增加或连接丢失。请参阅我们的负载测试指南 了解实施细节。
接下来会发生什么
零停机部署是频繁、自信发布的先决条件。将其与 CI/CD 自动化 相结合以实现完整的部署管道,并与 监控 相结合以进行部署后验证。
联系 ECOSIRE 获取部署策略咨询,或浏览我们的 DevOps 指南 获取完整的基础设施路线图。
由 ECOSIRE 发布——帮助企业无中断部署。
作者
ECOSIRE Research and Development Team
在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。