使用 Kubernetes(k8s) 搭建 Redis 3 主 3 从集群教程
一、技术选型
一、为什么用 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-confignamespace: test #名称空间
data:redis.conf: |port 6380cluster-enabled yescluster-config-file /data/nodes.confcluster-node-timeout 15000cluster-require-full-coverage noappendonly yesappendfsync everysecprotected-mode nodir /data
2、创建Headless Service
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:name: redis-clusternamespace: test
spec:clusterIP: Noneports:- port: 6380 #端口,可以修改targetPort: 6380 #端口,可以修改name: client - port: 16380 #端口,可以修改targetPort: 16380 #端口,可以修改name: clusterselector: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-clusternamespace: test
---
apiVersion: 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: ServiceAccountname: redis-clusternamespace: middleware
roleRef:kind: ClusterRolename: redis-cluster-pod-readerapiGroup: rbac.authorization.k8s.io
4、StatefulSet构建pod
通过StatefulSet创建6个redis的pod ,实现3主3从的redis集群
apiVersion: apps/v1
kind: StatefulSet
metadata:name: redis-clusternamespace: test
spec:serviceName: redis-clusterreplicas: 6selector:matchLabels:app: redis-clustertemplate:metadata:labels:app: redis-clusterspec:serviceAccountName: redis-cluster # 使用具有get pod权限的ServiceAccountcontainers:- name: redisimage: 192.168.1.11:8000/library/redis:6.2.14ports:- containerPort: 6380name: client- containerPort: 16380name: clusterenv:- name: POD_IPvalueFrom:fieldRef:fieldPath: status.podIPcommand: - sh- -c- |# 从ConfigMap读取基础配置,并添加IP相关配置cat /etc/redis/redis.conf > /tmp/redis.confecho "cluster-announce-ip ${POD_IP}" >> /tmp/redis.confecho "cluster-announce-port 6380" >> /tmp/redis.confecho "cluster-announce-bus-port 16380" >> /tmp/redis.confecho "replica-announce-ip ${POD_IP}" >> /tmp/redis.confecho "replica-announce-port 6380" >> /tmp/redis.confecho "使用配置文件:"cat /tmp/redis.confredis-server /tmp/redis.confvolumeMounts:- name: redis-configmountPath: /etc/redis- name: redis-datamountPath: /datalivenessProbe:tcpSocket:port: 6380initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 5failureThreshold: 3readinessProbe:tcpSocket:port: 6380initialDelaySeconds: 10periodSeconds: 5timeoutSeconds: 3failureThreshold: 3volumes:- name: redis-configconfigMap:name: redis-cluster-configitems:- key: redis.confpath: redis.confvolumeClaimTemplates:- metadata:name: redis-datanamespace: middlewarespec:accessModes: [ "ReadWriteOnce" ]storageClassName: zk-scresources: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-initnamespace: test
spec:template:spec:serviceAccountName: redis-clustercontainers:- name: initimage: 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.crtAPI_SERVER="https://kubernetes.default.svc"NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)# 使用Kubernetes API获取所有Pod IPPOD_IPS=""for i in 0 1 2 3 4 5; doecho "获取 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" ]; thenPOD_IPS="$POD_IPS $IP:6380"echo "获取到 redis-cluster-$i IP: $IP"# 测试Redis连接if redis-cli -h $IP -p 6380 ping; thenecho "Redis服务正常"elseecho "Redis服务异常"fielseecho "无法获取 redis-cluster-$i 的IP"fidoneecho "所有Pod IP: $POD_IPS"# 检查是否获取到所有6个IPIP_COUNT=$(echo $POD_IPS | wc -w)if [ "$IP_COUNT" -ne 6 ]; thenecho "错误:只获取到 $IP_COUNT 个IP,需要6个IP才能初始化集群"exit 1fi# 初始化集群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 infoecho "节点列表:"redis-cli -h $(echo $POD_IPS | cut -d' ' -f1 | cut -d: -f1) -p 6380 cluster nodesecho "Redis集群初始化成功!"restartPolicy: OnFailurebackoffLimit: 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 nodesecho -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 infoecho -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
到这里就搭建完成了,有什么问题欢迎评论区讨论