本文目录导读:

- 核心原理
- 方案一:基于Nginx upstream的动态切换(最常用)
- 方案二:基于HAProxy的切换(适合高并发)
- 方案三:基于Kubernetes Service的切换(容器化PHP)
- 方案四:基于DNS的切换(相对粗粒度)
- 切换时的关键注意事项
- 推荐实践:最小中断切换流水线
在PHP项目的蓝绿部署中,负载切换服务节点主要涉及流量调度层(如Nginx、HAProxy、Kubernetes Service)的配置更新,以下是几种常见且实用的实现方案:
核心原理
保持两套完全相同的生产环境(蓝组、绿组),但只有一组接收实时流量,另一组待命或用于预发布验证,切换时,只需要修改负载均衡器的目标服务器组即可。
基于Nginx upstream的动态切换(最常用)
配置示例
# /etc/nginx/conf.d/php-app.conf
upstream php-backend {
# 默认指向绿色组
server 10.0.1.10:9000 weight=10; # 绿组节点1
server 10.0.1.11:9000 weight=10; # 绿组节点2
# server 10.0.2.10:9000 weight=10; # 蓝组节点1(备用,切换时启用)
# server 10.0.2.11:9000 weight=10; # 蓝组节点2(备用)
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://php-backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# PHP-FPM 相关配置(如果直接连接FPM)
include fastcgi_params;
fastcgi_pass php-backend;
}
}
切换脚本(bash)
#!/bin/bash
# blue-green-switch.sh
CURRENT="$(grep '#' /etc/nginx/conf.d/php-app.conf | grep -v '^$' | wc -l)"
if [ "$CURRENT" -eq 0 ]; then
# 当前是绿组(无注释),切换到蓝组
sed -i 's/server 10.0.1/sd server 10.0.1/' /etc/nginx/conf.d/php-app.conf
sed -i 's/sd server 10.0.2/server 10.0.2/' /etc/nginx/conf.d/php-app.conf
sed -i 's/# server 10.0.1/server 10.0.1/' /etc/nginx/conf.d/php-app.conf
else
# 当前是蓝组,切换到绿组
sed -i 's/server 10.0.2/sd server 10.0.2/' /etc/nginx/conf.d/php-app.conf
sed -i 's/sd server 10.0.1/server 10.0.1/' /etc/nginx/conf.d/php-app.conf
sed -i 's/# server 10.0.2/server 10.0.2/' /etc/nginx/conf.d/php-app.conf
fi
# 检查配置并重载
nginx -t && nginx -s reload
注意:实际生产中可以维护两组完整的IP列表,通过注释/取消注释来切换。
更优雅的方式:双upstream + map
upstream green {
server 10.0.1.10:9000;
server 10.0.1.11:9000;
}
upstream blue {
server 10.0.2.10:9000;
server 10.0.2.11:9000;
}
# 通过map变量控制
map $cookie_blu_gren $backend {
default green; # 默认绿色
blue blue; # 通过Cookie切换到蓝色
}
server {
# ...
location ~ \.php$ {
fastcgi_pass $backend;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
切换时只需设置Cookie Set-Cookie: blu_gren=blue,或者使用Lua/OpenResty动态从Redis/Etcd读取状态。
基于HAProxy的切换(适合高并发)
haproxy.cfg 配置
frontend php_front
bind *:80
default_backend php_green
backend php_green
balance roundrobin
server node1 10.0.1.10:9000 check weight 10
server node2 10.0.1.11:9000 check weight 10
backend php_blue
balance roundrobin
server node1 10.0.2.10:9000 check weight 10
server node2 10.0.2.11:9000 check weight 10
切换命令(无需重启)
# 切换到蓝组 echo "set weight php_blue/node1 10" | socat /var/run/haproxy.sock stdio echo "set weight php_blue/node2 10" | socat /var/run/haproxy.sock stdio echo "set weight php_green/node1 0" | socat /var/run/haproxy.sock stdio echo "set weight php_green/node2 0" | socat /var/run/haproxy.sock stdio # 或者更彻底:修改default_backend(需reload但几乎无中断) echo "set weight php_blue/node1 10" | socat /var/run/haproxy.sock stdio echo "set weight php_blue/node2 10" | socat /var/run/haproxy.sock stdio echo "disable backend php_green" | socat /var/run/haproxy.sock stdio
更推荐:使用Runtime API实现平滑切换
# 用Python脚本通过API控制
import subprocess
def switch_to_blue():
commands = [
"disable backend php_green",
"enable backend php_blue"
]
for cmd in commands:
subprocess.run(['socat', '/var/run/haproxy.sock', '-'], input=cmd + '\n', text=True)
基于Kubernetes Service的切换(容器化PHP)
创建两个Deployment + 两个Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-green
labels:
app: php-app
color: green
spec:
replicas: 2
selector:
matchLabels:
app: php-app
color: green
template:
metadata:
labels:
app: php-app
color: green
spec:
containers:
- name: php-fpm
image: myapp:green-v1
---
apiVersion: v1
kind: Service
metadata:
name: php-app-green-svc
spec:
selector:
app: php-app
color: green
ports:
- port: 9000
类似地创建 php-app-blue 和 php-app-blue-svc。
入口Ingress切换
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-app-ingress
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: php-app-green-svc # 切换时改为 blue-svc
port:
number: 9000
一键切换命令
# 切换到蓝色
kubectl patch ingress php-app-ingress -p '{"spec":{"rules":[{"host":"app.example.com","http":{"paths":[{"path":"/","pathType":"Prefix","backend":{"service":{"name":"php-app-blue-svc","port":{"number":9000}}}}]}}]}}'
# 或者使用kubectl edit手动修改
kubectl edit ingress php-app-ingress
基于DNS的切换(相对粗粒度)
- 为两组节点配置不同A记录:
green.app.example.com→ 绿组节点IP列表blue.app.example.com→ 蓝组节点IP列表
- 生产域名
app.example.com的CNAME指向green或blue - 降低DNS TTL到60-120秒
缺点:DNS缓存导致切换延迟不可控,不建议用于关键业务。
切换时的关键注意事项
| 要点 | 说明 |
|---|---|
| Session共享 | PHP Session必须存储在外部(Redis/Memcached),不能依赖本地文件 |
| 数据库兼容 | 新版本代码必须兼容当前数据库结构(或使用读写分离+渐进式迁移) |
| 缓存清理 | 切换后若代码变更,需要清除OPcache(opcache_reset() 或重启PHP-FPM) |
| 健康检查 | 负载均衡器必须配置对新组的健康检查(HTTP 200或PHP探针) |
| 灰度验证 | 推荐先切换部分流量(5%-10%)观察5-10分钟,再全量切换 |
| 回滚预案 | 保留回滚脚本或一键恢复命令,确保切换失败时60秒内恢复 |
推荐实践:最小中断切换流水线
graph LR
A[部署蓝组] --> B[运行自动化测试]
B --> C[健康检查通过?]
C -->|Yes| D[负载均衡切换]
C -->|No| E[回滚]
D --> F[观察5分钟]
F --> G[正常?]
G -->|Yes| H[下线绿组]
G -->|No| I[切回绿组]
E --> J[修复]
I --> J
J --> A
- 中小项目:Nginx upstream配置热加载(方案一)最简单直接
- 高并发/复杂路由:HAProxy + Runtime API(方案二)更灵活
- K8s环境:Ingress + Service切换(方案三)最优雅
- 最重要:无论哪种方案,Session和缓存的外部化 是蓝绿发布成功的前提