使用 Kubernetes(k8s) 搭建 Redis 3 主 3 从集群教程

一、技术选型

一、为什么用 StatefulSet 部署 Redis 而非 Deployment?

Redis 集群是典型的有状态应用,对 "节点身份" 和 "存储稳定性" 有强依赖,而 StatefulSet 相比 Deployment 的核心优势恰好匹配这些需求:

  1. 稳定的网络标识 StatefulSet 为每个 Pod 分配固定名称(如redis-cluster-0redis-cluster-1)和 DNS 记录(如redis-cluster-0.redis-cluster-service.redis-cluster.svc.cluster.local),确保 Redis 节点之间的通信地址固定(集群初始化时配置的节点地址不会因 Pod 重建失效)。

  2. 有序部署与扩展StatefulSet 会按顺序(0→1→2→...)创建 Pod,且只有前一个 Pod 就绪后才会创建下一个,避免了集群初始化时节点未就绪导致的配置失败。

  3. 绑定专属持久存储 通过volumeClaimTemplates为每个 Pod 自动创建独立的 PersistentVolumeClaim(PVC),确保每个 Redis 节点的数据存储在专属的持久卷(PV)中,即使 Pod 被删除重建,数据也不会丢失(Deployment 的 PVC 会被多个 Pod 共享,不适合有状态场景)。

二、为什么使用 Headless Service(无 ClusterIP)?

在方案中,Redis 服务定义为clusterIP: None的 Headless Service,而非普通 Service,原因是:

  1. 支持 Redis 节点间的点对点通信 Redis 集群需要节点间通过 Gossip 协议交换状态(端口 16379),并在客户端请求时重定向到目标节点(槽位所在主节点)。Headless Service 会为每个 Pod 生成唯一的 DNS A 记录(如redis-cluster-0.redis-cluster-service),节点可通过域名直接访问其他节点,无需经过 Service 的负载均衡(普通 Service 的 ClusterIP 会导致请求被随机转发,破坏 Redis 集群的路由逻辑)。

  2. 简化集群初始化配置 集群初始化时,可直接通过固定的 DNS 域名(如redis-cluster-0.redis-cluster-service.redis-cluster.svc.cluster.local:6379)指定节点地址,无需担心 IP 变化(K8s 中 Pod 的 IP 是动态的,而 DNS 域名是固定的)。

三、为什么用 ConfigMap 管理 Redis 配置?

  1. 配置与镜像解耦 Redis 的核心配置(如cluster-enabled yescluster-node-timeout 5000)通过 ConfigMap 挂载到容器,而非硬编码到镜像中。当需要调整配置时,只需更新 ConfigMap 并重启 Pod,无需重新构建镜像,符合 "配置即代码" 的最佳实践。

  2. 集中管理与复用所有 Redis 节点共享同一套基础配置,避免了 "每个节点单独配置" 的冗余,且便于后期统一修改(如调整超时时间、开启 AOF 持久化等)。

二、部署实战

本次部署使用存储方面使用的是StorageClass动态供给,本次不演示创建过程

前提条件:

  1. 已搭建好的 Kubernetes 集群
  2. kubectl命令行工具已配置并能连接到 K8s 集群
  3. 集群中至少有 3 个节点(推荐)

1、部署configmap

Redis 的核心配置(如cluster-enabled yescluster-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: 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: ServiceAccount
      name: redis-cluster
      namespace: middleware
      roleRef:
      kind: ClusterRole
      name: redis-cluster-pod-reader
      apiGroup: rbac.authorization.k8s.io

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

结果:出现下面的情况就是完成搭建了

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

相关推荐
祁同伟.3 分钟前
【OJ】二叉树的经典OJ题
数据结构·c++·算法·容器·stl
CS_GUIDER3 分钟前
踩坑记录:Redis 连接报错 “Failed to get reply: connection reset“ 之端口冲突问题
redis
thinktik9 分钟前
AWS EKS 计算资源自动扩缩之Karpenter[AWS 海外区]
后端·kubernetes·aws
aristo_boyunv15 分钟前
Redis发布订阅【充当消息中间件】
数据库·redis·缓存
艾德金的溪9 小时前
redis-7.4.6部署安装
前端·数据库·redis·缓存
爱宇阳9 小时前
离线环境下运行 Docker 容器编排指南
docker·容器·eureka
东城绝神10 小时前
《Linux运维总结:基于ARM64+X86_64架构CPU使用docker-compose一键离线部署redis 7.4.5容器版分片集群》
linux·运维·redis·架构·分片集群
我的offer在哪里10 小时前
Redis
数据库·redis·缓存
回忆是昨天里的海13 小时前
k8s集群-节点间通信之安装kube-flannel插件
java·docker·kubernetes
阿维的博客日记13 小时前
从夯到拉的Redis和MySQL双写一致性解决方案排名
redis·分布式·mysql