K8s 部署 Redis 哨兵集群:主从自动切换 + 高可用实战全攻略
一、Redis 哨兵集群是什么?
Redis 主从复制保证了数据的冗余备份,但如果主节点挂了,需要人工介入手动切换——在生产环境这是不可接受的。Redis Sentinel(哨兵)就是来解决这个问题的:
- 监控(Monitoring):持续监控主从节点是否存活
- 自动故障转移(Automatic Failover):主节点宕机后,自动选举新主并重新配置从节点
- 通知(Notification):故障转移后通知客户端新的主节点地址
Redis Sentinel vs Redis Cluster
| 对比项 | Sentinel(哨兵) | Cluster(集群) |
|---|---|---|
| 数据分片 | 无,所有数据在同一实例 | 有,16384 个槽自动分片 |
| 高可用 | ✅ 自动故障转移 | ✅ 故障自动分片迁移 |
| 写能力 | 只有主节点可写 | 每个分片主节点可写 |
| 适用场景 | 单节点写压力、数据量适中 | 数据量大、需要水平扩展 |
| 最小节点 | 1 主 + 2 从 + 3 哨兵 = 6 个 | 至少 6 个节点(3 主 3 从) |
本文聚焦哨兵模式,适合不需要数据分片、但必须保证高可用的业务场景。
二、整体架构设计
生产环境推荐的最小高可用架构:
🟢 Redis Master(主节点)—— 接收读写请求
🔵 Redis Slave x2(从节点)—— 异步复制 + 读请求分发
🟡 Sentinel x3(哨兵节点)—— 监控 + 投票 + 故障转移
(哨兵节点应分布在不同物理机/可用区,避免单点)
K8s 部署拓扑
- 1 个 Master StatefulSet(1 副本,固定域名 redis-master)
- 2 个 Slave StatefulSet(2 副本,通过 Service 分发读请求)
- 3 个 Sentinel Deployment(3 副本,共享同一个 Service)
- 3 个 Headless Service(稳定 Pod DNS)
- 1 个 Master Service(固定指向当前主节点)
- 1 个 Slave Service(负载均衡所有从节点)
- 1 个 Sentinel Service(客户端连接哨兵用)
三、配置文件准备
3.1 Redis Master 配置
📄 redis-master.conf
bind 0.0.0.0
port 6379
protected-mode no
daemonize no
pidfile /var/run/redis.pid
loglevel notice
logfile ""
# 持久化(AOF + RDB 混合)
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
# RDB 快照
save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
# 连接数
maxclients 10000
timeout 300
# 内存(根据节点规格调整)
maxmemory 2gb
maxmemory-policy allkeys-lru
3.2 Redis Slave 配置
📄 redis-slave.conf
bind 0.0.0.0
port 6379
protected-mode no
daemonize no
pidfile /var/run/redis.pid
loglevel notice
logfile ""
# 从节点配置
replica-read-only yes
repl-diskless-sync yes # 无盘复制,减少 IO
repl-diskless-sync-delay 5
# 指向主节点(会被动态覆盖,但需要默认值)
replicaof redis-master 6379
# 持久化
appendonly yes
appendfilename "appendonly.aof"
maxmemory 2gb
maxmemory-policy allkeys-lru
3.3 Sentinel 配置(核心)
📄 sentinel.conf
port 26379
protected-mode no
daemonize no
pidfile /var/run/redis-sentinel.pid
loglevel notice
logfile ""
# 监控的主节点配置
# sentinel monitor
# quorum = 哨兵投票数,超过半数即认定主节点宕机
sentinel monitor mymaster redis-master 6379 2
# 主节点无响应多久后认定宕机(毫秒)
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时时间
sentinel failover-timeout mymaster 180000
# 故障转移后并行同步的从节点数量(越多越快,但压力大)
sentinel parallel-syncs mymaster 1
# 通知脚本(可选)
sentinel notification-script mymaster /opt/notify.sh
⚠️ quorum 解读:假设有 3 个哨兵节点,quorum=2 表示至少 2 个哨兵认为主节点挂了,才触发故障转移。推荐哨兵数量 N,quorum 设置为 (N/2+1)。
四、K8s 资源编排
4.1 ConfigMap(统一管理配置)
📄 redis-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
namespace: redis
data:
redis-master.conf: |
bind 0.0.0.0
port 6379
protected-mode no
daemonize no
appendonly yes
maxmemory 2gb
maxmemory-policy allkeys-lru
redis-slave.conf: |
bind 0.0.0.0
port 6379
protected-mode no
daemonize no
replica-read-only yes
repl-diskless-sync yes
replicaof redis-master 6379
appendonly yes
maxmemory 2gb
maxmemory-policy allkeys-lru
sentinel.conf: |
port 26379
protected-mode no
daemonize no
sentinel monitor mymaster redis-master 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
4.2 Master StatefulSet
📄 redis-master-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-master
namespace: redis
spec:
serviceName: redis-master
replicas: 1
selector:
matchLabels:
app: redis
role: master
template:
metadata:
labels:
app: redis
role: master
spec:
initContainers:
- name: init
image: redis:7-alpine
command: ["sh", "-c", "cp /config/redis-master.conf /redis.conf"]
volumeMounts:
- name: conf
mountPath: /config
- name: redis-config
mountPath: /redis.conf
subPath: redis-master.conf
containers:
- name: redis
image: redis:7-alpine
command: ["redis-server", "/redis.conf"]
ports:
- containerPort: 6379
volumeMounts:
- name: data
mountPath: /data
- name: conf
mountPath: /config
volumes:
- name: conf
emptyDir: {}
- name: redis-config
configMap:
name: redis-config
items:
- key: redis-master.conf
path: redis-master.conf
4.3 Slave StatefulSet(带 initContainers 自动配置 replicaof)
📄 redis-slave-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-slave
namespace: redis
spec:
serviceName: redis-slave
replicas: 2
selector:
matchLabels:
app: redis
role: slave
template:
metadata:
labels:
app: redis
role: slave
spec:
initContainers:
- name: init
image: redis:7-alpine
command: ["sh", "-c", "cp /config/redis-slave.conf /redis.conf && echo 'replicaof redis-master 6379' >> /redis.conf"]
volumeMounts:
- name: conf
- name: redis-config
mountPath: /config
mountPath: /redis.conf
subPath: redis-slave.conf
containers:
- name: redis
image: redis:7-alpine
command: ["redis-server", "/redis.conf"]
ports:
- containerPort: 6379
volumeMounts:
- name: data
mountPath: /data
- name: conf
mountPath: /config
volumes:
- name: conf
emptyDir: {}
- name: redis-config
configMap:
name: redis-config
4.4 Sentinel Deployment
📄 redis-sentinel-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-sentinel
namespace: redis
spec:
replicas: 3
selector:
matchLabels:
app: redis-sentinel
template:
metadata:
labels:
app: redis-sentinel
spec:
initContainers:
- name: init
image: redis:7-alpine
command: ["sh", "-c", "cp /config/sentinel.conf /sentinel.conf"]
volumeMounts:
- name: conf
mountPath: /config
- name: sentinel-conf
mountPath: /sentinel.conf
subPath: sentinel.conf
containers:
- name: sentinel
image: redis:7-alpine
command: ["redis-sentinel", "/sentinel.conf"]
ports:
- containerPort: 26379
volumeMounts:
- name: conf
mountPath: /config
volumes:
- name: conf
emptyDir: {}
- name: sentinel-conf
configMap:
name: redis-config
4.5 Service 配置
📄 redis-services.yaml
---
# Master Headless Service(哨兵需要通过 DNS 发现 master)
apiVersion: v1
kind: Service
metadata:
name: redis-master
namespace: redis
spec:
type: ClusterIP
clusterIP: None # Headless
selector:
app: redis
role: master
ports:
- port: 6379
targetPort: 6379
---
# Slave Service(读请求分发)
apiVersion: v1
kind: Service
metadata:
name: redis-slave
namespace: redis
spec:
type: ClusterIP
selector:
app: redis
role: slave
ports:
- port: 6379
targetPort: 6379
---
# Sentinel Service(客户端连接哨兵用)
apiVersion: v1
kind: Service
metadata:
name: redis-sentinel
namespace: redis
spec:
type: ClusterIP
selector:
app: redis-sentinel
ports:
- port: 26379
targetPort: 26379
五、一键部署
📋 部署命令
# 1. 创建命名空间
kubectl create ns redis
# 2. 部署 ConfigMap
kubectl apply -f redis-configmap.yaml -n redis
# 3. 部署 Master
kubectl apply -f redis-master-sts.yaml -n redis
# 4. 部署 Slave
kubectl apply -f redis-slave-sts.yaml -n redis
# 5. 部署 Sentinel
kubectl apply -f redis-sentinel-deploy.yaml -n redis
# 6. 部署 Service
kubectl apply -f redis-services.yaml -n redis
# 7. 验证部署
kubectl get pods -n redis -o wide
kubectl get svc -n redis
六、验证集群状态
6.1 验证主从复制
# 连接到 Master,写入数据
kubectl exec -it redis-master-0 -n redis -- redis-cli -p 6379 SET test-key "Hello Sentinel" GET test-key
# 连接到 Slave,验证复制成功
kubectl exec -it redis-slave-0 -n redis -- redis-cli -p 6379 GET test-key
# 预期输出:Hello Sentinel
6.2 验证哨兵集群状态
# 查看哨兵感知到的主节点信息
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
# 查看所有哨兵节点
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 SENTINEL masters
# 查看从节点列表
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 SENTINEL slaves mymaster
6.3 验证集群整体健康
# 查看所有 Pod 状态
kubectl get pods -n redis -l 'app in (redis,redis-sentinel)'
# 查看 Redis info
kubectl exec -it redis-master-0 -n redis -- redis-cli -p 6379 INFO replication
# 查看 Sentinel 感知的主节点 IP
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
七、故障转移实战演示
模拟 Master 宕机
📋 Step 1: 记录当前主节点
# 记录当前主节点 IP
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
📋 Step 2: 注入故障(杀死 Master Pod)
kubectl delete pod redis-master-0 -n redis
kubectl get pods -n redis -w
📋 Step 3: 观察故障转移
# 等待 5-10 秒后,查看主节点是否已切换
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
# 查看哪个 Pod 被选为新主(role=master)
for p in $(kubectl get pods -n redis -l app=redis -o jsonpath='{.items[*].metadata.name}'); do
echo "=== $p ===" && kubectl exec -it $p -n redis -- redis-cli -p 6379 ROLE 2>/dev/null
done
📋 Step 4: 验证数据完整性
# 旧主节点重新上线后变为从节点
kubectl exec -it redis-master-0 -n redis -- redis-cli -p 6379 ROLE
# 预期输出:master 或 slave
# 验证数据是否丢失(好的故障转移应该零数据丢失)
kubectl exec -it redis-master-0 -n redis -- redis-cli -p 6379 GET test-key
八、客户端正确使用姿势
8.1 Python 客户端(正确连接哨兵)
📄 redis_client.py
from redis.sentinel import Sentinel
# 连接哨兵集群(填写 K8s Service 地址)
sentinel = Sentinel(
[('redis-sentinel.redis.svc.cluster.local', 26379)],
socket_timeout=0.1
)
# 获取当前主节点连接(用于写操作)
master = sentinel.master_for('mymaster', socket_timeout=1)
# 获取从节点连接(用于读操作)
slave = sentinel.slave_for('mymaster', socket_timeout=1)
# 写操作走主节点
master.set('key1', 'value1')
# 读操作走从节点(负载均衡)
result = slave.get('key1')
# 自动获取最新主节点(故障转移后无需改配置)
# Sentinel 会感知主节点变化,master_for 自动返回新主
8.2 Java 客户端(Jedis)
📄 RedisSentinelClient.java
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(20);
Set sentinels = new HashSet<>();
sentinels.add("redis-sentinel.redis.svc.cluster.local:26379");
JedisSentinelPool pool = new JedisSentinelPool(
"mymaster", # 与哨兵配置中的 master-name 一致
sentinels,
poolConfig,
3000 # 连接超时
);
// 获取连接(自动感知主节点变化)
Jedis jedis = pool.getResource();
jedis.set("hello", "world");
String value = jedis.get("hello");
jedis.close(); // 归还连接池,不是关闭!
✅ 正确做法:客户端始终连接哨兵集群,由哨兵返回当前主节点地址。故障转移后哨兵会自动更新,客户端无需重启或改配置。
九、生产环境高级配置
9.1 数据持久化(推荐 AOF + RDB 混合)
# 推荐持久化配置(AOF everysec + RDB 快照)
appendonly yes
appendfsync everysec # 性能与安全的平衡
save 900 1 # 15分钟至少1个key变化
save 300 10 # 5分钟至少10个key变化
save 60 10000 # 1分钟至少10000个key变化
9.2 资源限制(生产必须设置)
📄 redis-master-sts-resources.yaml(添加资源限制)
# 在 containers 下添加 resources 字段
resources:
requests:
cpu: "500m"
memory: "2Gi"
limits:
cpu: "2000m"
memory: "4Gi"
9.3 探针配置(保障服务可用性)
# 在 containers 下添加 livenessProbe 和 readinessProbe
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
exec:
command: ["redis-cli", "ping"]
initialDelaySeconds: 5
periodSeconds: 5
9.4 故障转移通知脚本
📄 notify.sh(挂载到哨兵容器)
#!/bin/sh
while true; do
sleep 60
# 发送告警到钉钉/企微/飞书
curl -H "Content-Type: application/json" \
-d '{"msgtype":"text","text":{"content":"Redis Sentinel 故障转移通知"}}' \
https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN
done
十、排障命令速查
# 查看所有 Redis 进程
kubectl get pods -n redis -l app=redis -o wide
# 查看主从角色
kubectl exec -it redis-master-0 -n redis -- redis-cli -p 6379 ROLE
# 查看复制延迟
kubectl exec -it redis-master-0 -n redis -- redis-cli -p 6379 INFO replication | grep -E "role|master_link_status|master_repl_offset"
# 查看哨兵集群状态
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 INFO sentinel
# 查看哨兵选举详情
kubectl exec -it $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis -- redis-cli -p 26379 SENTINEL ckquorum mymaster
# 查看 Redis 日志
kubectl logs redis-master-0 -n redis --tail=100
kubectl logs $(kubectl get pod -l app=redis-sentinel -n redis -o jsonpath='{.items[0].metadata.name}') -n redis --tail=50
更多推荐
所有评论(0)