一、技术选型
一、为什么用 StatefulSet 部署 Redis 而非 Deployment?
Redis 集群是典型的有状态应用,对 "节点身份" 和 "存储稳定性" 有强依赖,而 StatefulSet 相比 Deployment 的核心优势恰好匹配这些需求:
-
稳定的网络标识 StatefulSet 为每个 Pod 分配固定名称(如
redis-cluster-0、redis-cluster-1)和 DNS 记录(如redis-cluster-0.redis-cluster-service.redis-cluster.svc.cluster.local),确保 Redis 节点之间的通信地址固定(集群初始化时配置的节点地址不会因 Pod 重建失效)。 -
有序部署与扩展StatefulSet 会按顺序(0→1→2→...)创建 Pod,且只有前一个 Pod 就绪后才会创建下一个,避免了集群初始化时节点未就绪导致的配置失败。
-
绑定专属持久存储 通过
volumeClaimTemplates为每个 Pod 自动创建独立的 PersistentVolumeClaim(PVC),确保每个 Redis 节点的数据存储在专属的持久卷(PV)中,即使 Pod 被删除重建,数据也不会丢失(Deployment 的 PVC 会被多个 Pod 共享,不适合有状态场景)。
二、为什么使用 Headless Service(无 ClusterIP)?
在方案中,Redis 服务定义为clusterIP: None的 Headless Service,而非普通 Service,原因是:
-
支持 Redis 节点间的点对点通信 Redis 集群需要节点间通过 Gossip 协议交换状态(端口 16379),并在客户端请求时重定向到目标节点(槽位所在主节点)。Headless Service 会为每个 Pod 生成唯一的 DNS A 记录(如
redis-cluster-0.redis-cluster-service),节点可通过域名直接访问其他节点,无需经过 Service 的负载均衡(普通 Service 的 ClusterIP 会导致请求被随机转发,破坏 Redis 集群的路由逻辑)。 -
简化集群初始化配置 集群初始化时,可直接通过固定的 DNS 域名(如
redis-cluster-0.redis-cluster-service.redis-cluster.svc.cluster.local:6379)指定节点地址,无需担心 IP 变化(K8s 中 Pod 的 IP 是动态的,而 DNS 域名是固定的)。
三、为什么用 ConfigMap 管理 Redis 配置?
-
配置与镜像解耦 Redis 的核心配置(如
cluster-enabled yes、cluster-node-timeout 5000)通过 ConfigMap 挂载到容器,而非硬编码到镜像中。当需要调整配置时,只需更新 ConfigMap 并重启 Pod,无需重新构建镜像,符合 "配置即代码" 的最佳实践。 -
集中管理与复用所有 Redis 节点共享同一套基础配置,避免了 "每个节点单独配置" 的冗余,且便于后期统一修改(如调整超时时间、开启 AOF 持久化等)。
二、部署实战
本次部署使用存储方面使用的是StorageClass动态供给,本次不演示创建过程
前提条件:
- 已搭建好的 Kubernetes 集群
kubectl命令行工具已配置并能连接到 K8s 集群- 集群中至少有 3 个节点(推荐)
1、部署configmap
Redis 的核心配置(如cluster-enabled yes、cluster-node-timeout 5000)通过 ConfigMap 挂载到容器,而非硬编码到镜像中。
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-cluster-config
namespace: test #名称空间
data:
redis.conf: |
port 6380
cluster-enabled yes
cluster-config-file /data/nodes.conf
cluster-node-timeout 15000
cluster-require-full-coverage no
appendonly yes
appendfsync everysec
protected-mode no
dir /data
2、创建Headless Service
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-cluster
namespace: test
spec:
clusterIP: None
ports:
- port: 6380 #端口,可以修改
targetPort: 6380 #端口,可以修改
name: client
- port: 16380 #端口,可以修改
targetPort: 16380 #端口,可以修改
name: cluster
selector:
app: redis-cluster
3、RBAC权限创建
创建权限原因:为Redis集群创建一个专用的ServiceAccount并授予必要的权限用于获取pod的ip
为什么需要获取ip??
原因如下:
-
Redis集群初始化时可以使用域名,但集群运行后依赖IP地址。
-
在Kubernetes中,由于Pod IP可能会变化,所以使用域名初始化集群在Pod重启后可能会失败。
-
使用IP初始化集群,并结合
cluster-announce-ip配置,可以避免Pod重启后集群通信失败的问题。apiVersion: v1
kind: ServiceAccount
metadata:
name: redis-cluster
namespace: testapiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: redis-cluster-pod-reader
rules:- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: redis-cluster-pod-reader
subjects:- kind: ServiceAccount
name: redis-cluster
namespace: middleware
roleRef:
kind: ClusterRole
name: redis-cluster-pod-reader
apiGroup: rbac.authorization.k8s.io
- apiGroups: [""]
4、StatefulSet构建pod
通过StatefulSet创建6个redis的pod ,实现3主3从的redis集群
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
namespace: test
spec:
serviceName: redis-cluster
replicas: 6
selector:
matchLabels:
app: redis-cluster
template:
metadata:
labels:
app: redis-cluster
spec:
serviceAccountName: redis-cluster # 使用具有get pod权限的ServiceAccount
containers:
- name: redis
image: 192.168.1.11:8000/library/redis:6.2.14
ports:
- containerPort: 6380
name: client
- containerPort: 16380
name: cluster
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
command:
- sh
- -c
- |
# 从ConfigMap读取基础配置,并添加IP相关配置
cat /etc/redis/redis.conf > /tmp/redis.conf
echo "cluster-announce-ip ${POD_IP}" >> /tmp/redis.conf
echo "cluster-announce-port 6380" >> /tmp/redis.conf
echo "cluster-announce-bus-port 16380" >> /tmp/redis.conf
echo "replica-announce-ip ${POD_IP}" >> /tmp/redis.conf
echo "replica-announce-port 6380" >> /tmp/redis.conf
echo "使用配置文件:"
cat /tmp/redis.conf
redis-server /tmp/redis.conf
volumeMounts:
- name: redis-config
mountPath: /etc/redis
- name: redis-data
mountPath: /data
livenessProbe:
tcpSocket:
port: 6380
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 6380
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
volumes:
- name: redis-config
configMap:
name: redis-cluster-config
items:
- key: redis.conf
path: redis.conf
volumeClaimTemplates:
- metadata:
name: redis-data
namespace: middleware
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: zk-sc
resources:
requests:
storage: 10Gi
5、创建job任务,实现集群初始化
镜像问题:redis-tools:6.2.14这个镜像是我使用dockerfile分层构建的,基础镜像也是redis,这个可以自行选择。
dockerfile:
FROM redis:6.2.14
# 安装curl工具并清理缓存
RUN apt-get add --no-cache curl bash
# 保留原始Redis启动命令
CMD ["redis-server"]
构建的目的:因为我需要通过curl等命令自动获取ip,使用脚本自动加入集群,不需要像其他人一样手动获取pod的ip,然后使用命令初始化集群
apiVersion: batch/v1
kind: Job
metadata:
name: redis-cluster-init
namespace: test
spec:
template:
spec:
serviceAccountName: redis-cluster
containers:
- name: init
image: 192.168.1.11:8000/library/redis-tools:6.2.14 # 使用带有工具的Redis镜像
command:
- /bin/sh
- -c
- |
echo "等待Redis Pod完全启动..."
sleep 20
# 获取Service Account token和CA证书
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API_SERVER="https://kubernetes.default.svc"
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
# 使用Kubernetes API获取所有Pod IP
POD_IPS=""
for i in 0 1 2 3 4 5; do
echo "获取 redis-cluster-$i 的IP..."
IP=$(curl -s --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" \
"$API_SERVER/api/v1/namespaces/$NAMESPACE/pods/redis-cluster-$i" | \
jq -r '.status.podIP // empty')
if [ -n "$IP" ]; then
POD_IPS="$POD_IPS $IP:6380"
echo "获取到 redis-cluster-$i IP: $IP"
# 测试Redis连接
if redis-cli -h $IP -p 6380 ping; then
echo "Redis服务正常"
else
echo "Redis服务异常"
fi
else
echo "无法获取 redis-cluster-$i 的IP"
fi
done
echo "所有Pod IP: $POD_IPS"
# 检查是否获取到所有6个IP
IP_COUNT=$(echo $POD_IPS | wc -w)
if [ "$IP_COUNT" -ne 6 ]; then
echo "错误:只获取到 $IP_COUNT 个IP,需要6个IP才能初始化集群"
exit 1
fi
# 初始化集群
echo "开始初始化Redis集群..."
redis-cli -h $(echo $POD_IPS | cut -d' ' -f1 | cut -d: -f1) -p 6380 \
--cluster create $POD_IPS --cluster-replicas 1 --cluster-yes
# 验证集群状态
echo "集群初始化完成,验证状态..."
redis-cli -h $(echo $POD_IPS | cut -d' ' -f1 | cut -d: -f1) -p 6380 cluster info
echo "节点列表:"
redis-cli -h $(echo $POD_IPS | cut -d' ' -f1 | cut -d: -f1) -p 6380 cluster nodes
echo "Redis集群初始化成功!"
restartPolicy: OnFailure
backoffLimit: 3
6、shell脚本验证集群状态
#!/bin/bash
echo "=== Redis集群状态验证 ==="
# 1. 获取集群节点信息
echo "1. 集群节点列表:"
kubectl exec -it -n test redis-cluster-0 -c redis -- redis-cli -p 6380 cluster nodes
echo -e "\n2. 主从节点统计:"
kubectl exec -it -n test redis-cluster-0 -c redis -- redis-cli -p 6380 cluster nodes | \
awk '{print $3}' | sort | uniq -c
echo -e "\n3. 哈希槽分配情况:"
kubectl exec -it -n test redis-cluster-0 -c redis -- redis-cli -p 6380 cluster nodes | \
grep master | awk '{print $8 " -> " $9}'
echo -e "\n4. 集群基本信息:"
kubectl exec -it -n test redis-cluster-0 -c redis -- redis-cli -p 6380 cluster info
echo -e "\n5. 数据读写测试:"
#kubectl exec -it -n test redis-cluster-0 -c redis -- redis-cli -p 6380 -c set test-cluster "hello-redis"
#kubectl exec -it -n test redis-cluster-0 -c redis -- redis-cli -p 6380 -c get test-cluster
结果:出现下面的情况就是完成搭建了

到这里就搭建完成了,有什么问题欢迎评论区讨论