Shell脚本实现容器流量A/B测试路由:从原理到生产实践
目录导读
- A/B测试与容器流量路由的核心逻辑
- Shell脚本实现流量切分的底层技术
- 基于Kubernetes/Ingress的脚本路由方案
- 脚本可靠性保障与错误处理
- 实战案例:灰度发布中的权重分配
- 常见问题QA
A/B测试与容器流量路由的核心逻辑
A/B测试在容器化架构中的本质是动态控制请求在不同服务版本间的分配比例,通常我们需要实现:

- 按比例分流(如10%流量到v2版本)
- 按请求特征分流(如特定Header/Query参数触发)
- 动态切换(无需重启容器)
Shell脚本之所以适合这一场景,是因为它能直接操作系统级工具(如iptables、ipvs)或调用Kubernetes API,无需引入复杂中间件,其核心是通过脚本修改路由规则文件,并触发容器运行时刷新配置。
关键问题:当你的容器运行在Docker Compose或Kubernetes之上时,如何用Shell脚本实现“无侵入”的流量控制?答案是操作它们暴露的底层接口。
Shell脚本实现流量切分的底层技术
1 基于iptables的分流脚本
假设你有两个容器:app-v1:8080和app-v2:8081,下面的脚本通过修改iptables规则实现按比例转发:
#!/bin/bash # 分流比例设置(10%流量到v2) V1_WEIGHT=90 V2_WEIGHT=10 # 清空旧规则(谨慎操作!) iptables -t nat -F PREROUTING # 随机匹配:利用iptables的statistic模块 iptables -t nat -A PREROUTING -p tcp --dport 80 -m statistic --mode random --probability 0.9 -j DNAT --to-destination 10.0.0.2:8080 iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.3:8081
原理:第一条规则以90%概率命中v1,第二条规则捕获剩余所有请求到v2,通过调整--probability值实现1%~100%粒度控制。
2 基于Nginx upstream的脚本修改
如果你的容器通过Nginx代理,脚本可以动态生成upstream配置:
#!/bin/bash
# 配置生成脚本
cat > /etc/nginx/conf.d/ab_test.conf <<EOF
upstream app_backend {
server 10.0.0.2:8080 weight=90;
server 10.0.0.3:8081 weight=10;
}
server {
listen 80;
location / { proxy_pass http://app_backend; }
}
EOF
nginx -s reload
关键点:这种方法需要Nginx配置支持动态reload,且需要处理并发写入时的锁问题。
基于Kubernetes/Ingress的脚本路由方案
在K8s中,更推荐用Shell脚本操作Kubernetes API实现无中断路由,以下脚本使用kubectl patch Ingress资源:
#!/bin/bash
# 将80%流量路由到v1,20%到v2
kubectl patch ingress my-app -n default --type=json -p='[
{"op": "replace", "path": "/spec/rules/0/http/paths/0/backend/service/name", "value": "app-v1"},
{"op": "replace", "path": "/spec/rules/0/http/paths/0/backend/service/port/number", "value": 80},
{"op": "add", "path": "/spec/rules/0/http/paths/-", "value": {
"path": "/v2",
"pathType": "Prefix",
"backend": {"service": {"name": "app-v2", "port": {"number": 80}}}
}}
]'
注意:这里使用了Kubernetes Ingress的权重注解(如nginx.ingress.kubernetes.io/canary-weight),需要Ingress Controller支持。
脚本可靠性保障与错误处理
1 幂等性与回滚
每次运行脚本前,备份当前路由配置:
BACKUP_FILE="/tmp/route_backup_$(date +%s).conf" iptables-save > $BACKUP_FILE # 如果脚本中途失败,自动恢复 trap 'echo "ERROR: Rolling back"; iptables-restore < $BACKUP_FILE; exit 1' ERR
2 避免中断已有连接
- 使用
conntrack工具维护连接状态 - 对于TCP连接,采用
ipvsadm的持久连接模式 - 在Shell脚本中引入优雅退出逻辑,先停新连接再等旧连接超时
3 健康检查联动
在修改路由前,先检测目标容器是否健康:
if ! curl -f http://10.0.0.3:8081/health; then
echo "v2容器不健康,放弃分流"
exit 1
fi
实战案例:灰度发布中的权重分配
场景:电商平台需要对“商品详情页”进行新旧版本测试,要求逐步放量:第1天5%用户看到新版,第2天20%,第3天50%。
Shell脚本实现(基于K8s和Nginx Ingress):
#!/bin/bash
# 权重递增函数
set_canary_weight() {
local weight=$1
kubectl patch ingress my-shop -n production \
--type=json \
-p="[{'op':'add','path':'/metadata/annotations','value':{'nginx.ingress.kubernetes.io/canary-weight':\"$weight\"}}]"
echo "已设置canary权重为$weight%"
}
# 执行灰度计划
set_canary_weight 5
sleep 86400 # 24小时
set_canary_weight 20
sleep 86400
set_canary_weight 50
优化点:实际生产中应集成到CI/CD流水线,通过脚本参数化SLEEP_INTERVAL,并添加监控告警。
常见问题QA
Q1:Shell脚本修改路由后,为什么有些请求仍保持旧路由?
A:这通常是连接持久化导致,可在脚本中添加conntrack -D -p tcp --dport 80清除现有连接条目(注意这会中断长连接)。
Q2:K8s中脚本patch Ingress后需要重启Nginx吗?
A:无需手动重启,多数Ingress Controller(如nginx-ingress)会监听API Server变化自动reload,延迟通常在1~2秒内,极端情况下可调用Ingress Controller的reload端点。
Q3:如何确保脚本不因并发冲突而破坏路由规则?
A:使用文件锁(flock)或Kubernetes的ResourceVersion乐观锁。
exec 9>/tmp/route.lock flock -n 9 || exit 1 # 修改规则 flock -u 9
Q4:canary权重为0时怎么办?
A:应直接删除canary注解,让Ingress仅路由到稳定版本,脚本可判断权重为0时执行kubectl annotate ingress ... canary-weight-。
Shell脚本实现容器流量A/B测试的核心在于统一切入点,无论是iptables、Nginx upstream还是Kubernetes API,脚本都需要考虑幂等性、回滚机制和健康检查,在微服务规模较大时,建议将脚本包装为CI/CD流水线步骤或集成到Operator中,以避免人工误操作,对于超过100个容器的集群,可考虑使用专业服务网格(如Istio)的流量管理能力,但Shell脚本方案在小型团队和快速原型阶段仍有无可替代的轻量优势。