K8s v1.28部署Redis三主三从集群实战

1. 准备工作

1-1. 安装NFC作为持久化存储(所有节点都需要安装 nfc-utils)

yum -y install  nfs-utils rpcbind 
# 创建NFS共享文件夹
mkdir -p /data/nfs/redis/pv{1..6}
#配置共享文件夹
vim  /etc/exports
/data/nfs/redis/pv1  *(rw,sync,no_root_squash)
/data/nfs/redis/pv2  *(rw,sync,no_root_squash)
/data/nfs/redis/pv3  *(rw,sync,no_root_squash)
/data/nfs/redis/pv4  *(rw,sync,no_root_squash)
/data/nfs/redis/pv5  *(rw,sync,no_root_squash)
/data/nfs/redis/pv5  *(rw,sync,no_root_squash)
# 生效
exportfs -r
# 启动NFS
systemctl start nfs-server
systemctl enabled nfs-server
systemctl start rpcbind
systemctl enabled rpcbind

1-2. 其他节点安装nfc-utils

yum -y install  nfs-utils

1-3. 创建ns(namespace:命名空间) nfc 客户端操作 以下没有特殊说明 都是在客户端节点操作

kubectl create ns redis-cluster

1-4. 设置对应的账号信息和角色

  1. ServiceAccount 服务账户

    • 创建名为 redis-nfs-client 的账户,作为NFS存储操作的身份凭证
    • 限定在 redis-cluster 命名空间内使用
  2. ClusterRole 集群角色

    • 定义nfs-client-runner角色,授予以下权限:
      • 对PV/PVC的‌全生命周期管理‌(创建/删除/查看)
      • 查看StorageClass信息(用于动态供给)
      • 操作Events和Endpoints资源(用于状态监控和服务发现)
  3. ‌**ClusterRoleBinding 集群角色绑定**‌

    • redis-nfs-client 服务账户与 nfs-client-runner 角色关联
    • 权限作用范围:‌整个集群‌(ClusterRole特性)
  4. 设计意图

    1. ‌安全隔离
      • 为Redis集群单独配置服务账户,避免使用default账户的高风险操作
    2. ‌最小权限原则
      • 精确控制NFS客户端所需的权限(如无需Secret访问权限)
    3. 支持动态存储供给
      • 通过PVC自动创建PV时需具备相关API操作权限
cat > redis-nfs-client-sa.yaml<<EOF
 
# ========== ServiceAccount 配置 ==========
apiVersion: v1
kind: ServiceAccount
metadata:
  name: redis-nfs-client  # 服务账户名称,将被Provisioner Pod使用
  namespace: redis-cluster  # 必须与后续资源同一命名空间

--- 
# ========== ClusterRole 集群角色配置 ==========
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-runner  # 集群角色名称(全局有效)
  namespace: redis-cluster
rules:
  - apiGroups: [""]  # 核心API组
    resources: ["persistentvolumes"]
    verbs: ["get","list","watch","create","delete"]  # PV全生命周期管理
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get","list","watch","create","delete"]  # PVC联动操作权限
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get","list","watch"]  # 需读取SC配置
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["get","list","watch","create","update","patch"]  # 事件记录
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create","delete","get","list","watch","patch","update"]  # NFS服务依赖

--- 
# ========== ClusterRoleBinding 配置 ==========
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-provisioner  # 绑定名称
  namespace: redis-cluster
subjects:
  - kind: ServiceAccount
    name: redis-nfs-client
    namespace: redis-cluster  # 指定服务账户来源命名空间
roleRef:
  kind: ClusterRole
  name: nfs-client-runner  # 必须与ClusterRole名称一致
  apiGroup: rbac.authorization.k8s.io

EOF

1-5. 创建NFC客户端

cat > redis-nfs-client.yaml<<EOF
# ========== 基础部署配置 ==========
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-nfs-client  # 部署名称
  labels:
    app: redis-nfs-client  # 用于Service/Selector匹配
  namespace: redis-cluster  # 必须与ServiceAccount同命名空间
# ========== 部署策略配置 ==========
spec:
  replicas: 1  # 单副本部署(NFS Provisioner建议单实例)
  strategy:
    type: Recreate  # 更新策略:先删除旧Pod再创建新Pod(避免PV冲突)
  selector:
    matchLabels:
      app: redis-nfs-client  # 必须与template.metadata.labels一致
# ========== Pod模板配置 ==========
  template:
    metadata:
      labels:
        app: redis-nfs-client  # 必须与selector.matchLabels一致
    spec:
      serviceAccountName: redis-nfs-client  # 关联之前创建的SA
      containers:
        - name: redis-nfs-client
          # 使用阿里云镜像仓库的NFS Provisioner
          image: registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0
          # ========== 存储卷挂载 ==========
          volumeMounts:
            - name: redis-nfs-client-root
              mountPath: /persistentvolumes  # Provisioner工作目录
          # ========== 关键环境变量 ==========
          env:
            - name: PROVISIONER_NAME   # 必须与StorageClass的provisioner字段完全一致
              value: my-redis-nfs
            - name: ENABLE_LEADER_ELECTION  # 即使单副本也建议开启(兼容后续扩展)
              value: "True"
            - name: NFS_SERVER  # NFS服务器地址(需替换为实际IP)
              value: 192.168.133.133
            - name: NFS_PATH    # NFS共享路径(需确保目录已存在且有权限)
              value: /data/nfs/redis
      # ========== NFS卷声明 ==========
      volumes:
        - name: redis-nfs-client-root
          nfs:
            server: 192.168.133.133  # 必须与env.NFS_SERVER一致
            path: /data/nfs/redis    # 必须与env.NFS_PATH一致
EOF

1-6. 创建SC(storeclass) 存储类

cat > redis-storeclass.yaml<<EOF
 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: redis-nfs-storage
  namespace: redis-cluster
provisioner: my-redis-nfs
EOF

1-7. 执行命令使上面1-4至1-6的内容生效

执行命令使其生效
kubectl apply -f  redis-nfs-client-sa.yaml
 
kubectl apply -f  redis-nfs-client.yaml
 
kubectl apply -f  redis-storeclass.yaml

1-8. 创建6个PV卷

rm -rf redis-pv.yaml
cat > redis-pv.yaml<<EOF
 
# ========== PV基础定义 ==========
apiVersion: v1
kind: PersistentVolume
metadata:
# PV资源名称,集群内唯一标识
  name: redis-nfs-pv1
# ========== 核心规格配置 ==========
spec:
  # 存储类名称,需与StorageClass的metadata.name匹配
  storageClassName: redis-nfs-storage
  
  # 存储容量配置(注意单位需使用Mi/Gi等标准格式)
  capacity:
  # 实际容量需小于NFS服务器可用空间
    storage: 500M
  # 访问模式:支持多节点同时读写
  accessModes:
  # 适用于NFS/Ceph等共享存储
    - ReadWriteMany
  # ========== NFS专属配置 ==========
  nfs:
  # NFS服务器IP地址
    server: 192.168.133.133
    path: "/data/nfs/redis/pv1"

---
 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv2
spec:
  storageClassName: redis-nfs-storage
  capacity:
    storage: 500M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.133.133
    path: "/data/nfs/redis/pv2"
 
---
 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv3
spec:
  storageClassName: redis-nfs-storage
  capacity:
    storage: 500M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.133.133
    path: "/data/nfs/redis/pv3"
 
---
 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv4
spec:
  storageClassName: redis-nfs-storage
  capacity:
    storage: 500M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.133.133
    path: "/data/nfs/redis/pv4"
 
---
 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv5
spec:
  storageClassName: redis-nfs-storage
  capacity:
    storage: 500M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.133.133
    path: "/data/nfs/redis/pv5"
 
---
 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv6
spec:
  storageClassName: redis-nfs-storage
  capacity:
    storage: 500M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.133.133
    path: "/data/nfs/redis/pv6"
EOF
# 使这些pv卷 创建出来
kubectl apply -f redis-pv.yaml

2. 创建redis-configmap.yaml

rm -rf redis-configmap.yaml
cat > redis-configmap.yaml<<EOF

apiVersion: v1
kind: ConfigMap

# 配置元数据
metadata:
  # 配置字典名称
  name: redis-conf
  # 必须与部署命名空间一致
  namespace: redis-cluster

# 配置数据
data:
 	# ===== Pod IP自动修复脚本 =====
  #fix-pod-ip.sh: |
    ##!/bin/sh
    #CLUSTER_CONFIG="/var/lib/redis/nodes.conf"  # 集群配置文件路径(需与redis.conf中cluster-config-file一致)
    #if [ -f ${CLUSTER_CONFIG} ]; then # 检查集群配置文件是否存在
    #  if [ -z "${POD_IP}" ]; then # 验证环境变量POD_IP是否设置
    #    echo "Unable to determine Pod IP address! 自主报错"
    #    exit 1
    #  fi
    #  # 使用sed替换nodes.conf中的IP地址(匹配myself行)
    #  echo "Updating pod IP to ${POD_IP} in ${CLUSTER_CONFIG}"
    #  sed -i -e '/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/'${POD_IP}'/' ${CLUSTER_CONFIG} 
    #  count=`grep -c ${POD_IP} ${CLUSTER_CONFIG}` # 验证IP更新结果
    #  if [[ $count > 0 ]];then
    #   echo "Successful updated pod Ip to ${POD_IP} in ${CLUSTER_CONFIG}"
    #  fi
    #fi
    #exec "$@"  # 执行容器原始启动命令
  # Redis核心配置文件
  redis.conf: |
    # 集群模式开关(必须开启)
    cluster-enabled yes
    
    # 网络配置
    bind 0.0.0.0
    port 6379
    
    # 安全配置
    protected-mode no
    
    # 超时设置
    timeout 300
    cluster-node-timeout 5000
    
    # 集群配置
    cluster-config-file /var/lib/redis/nodes.conf
    
    # 存储配置
    dir /var/lib/redis
    maxmemory 1000mb
    maxmemory-policy volatile-lru
    
    # 持久化配置
    appendonly yes
    appendfsync everysec
    appendfilename "appendonly.aof"
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    aof-rewrite-incremental-fsync yes
    
    # 性能优化
    maxclients 10000
    loglevel notice
    hash-max-ziplist-entries 512
    hash-max-ziplist-value 64kb
    hz 10
EOF
# 执行
kubectl apply -f redis-configmap.yaml -n redis-cluster

3. 创建redis-headless-svc.yaml

rm -rf redis-headless-svc.yaml
cat > redis-headless-svc.yaml<<EOF

# ========== 基础服务定义 ==========
apiVersion: v1
kind: Service

# 服务元数据配置
metadata:
  # 服务名称(需与StatefulSet中serviceName一致)
  name: redis-service
  # 必须与Pod部署命名空间一致
  namespace: redis-cluster
  # 服务标签(用于资源分类)
  labels:
    app: redis

# ========== 服务规格配置 ==========
spec:
  # 端口配置
  ports:
    # 客户端访问端口名称
    - name: redis-port
      # 标准Redis服务端口
      port: 6379
    # 集群总线端口名称
    - name: redis-cluster-port
      # Redis集群节点通信端口
      port: 16379

  # 无头服务配置(禁用自动分配ClusterIP)
  clusterIP: None

  # Pod选择器配置
  selector:
    # 必须匹配Pod的app标签
    app: redis
    # 必须匹配Pod的集群标识标签
    appCluster: redis-cluster

EOF
kubectl apply  -f  redis-headless-svc.yaml 

4. 创建redis-nodeport-svc.yaml (注意:外网访问的端口在这个地方设置)

rm -rf redis-nodeport-svc.yaml
cat >redis-nodeport-svc.yaml<<EOF
# ========== 基础服务定义 ==========

# ========== 基础服务定义 ==========
apiVersion: v1
kind: Service

# 服务元数据配置
metadata:
  # 服务资源名称(用于外部访问)
  name: redis-cluster-access-service
  # 必须与Pod部署命名空间一致
  namespace: redis-cluster
  # 服务标签(用于资源分类)
  labels:
    app: redis

# ========== 服务规格配置 ==========
spec:
  # 端口配置
  ports:
    # 端口名称标识
    - name: redis-cluster-port
      # 使用TCP协议
      protocol: TCP
      # 服务暴露的集群内部端口
      port: 6379
      # 容器实际监听端口(需与Pod定义一致)
      targetPort: 6379
      # 访问端口(30000-32767范围)
      nodePort: 30476

  # Pod选择器配置
  selector:
    # 匹配Pod的app标签
    app: redis
    # 匹配Pod的集群专用标签
    appCluster: redis-cluster

  # 服务类型(NodePort允许通过节点IP访问)
  type: NodePort
EOF

kubectl apply  -f  redis-nodeport-svc.yaml

5. 创建statefulset(状态集)

rm -rf redis-StatefulSet-svc.yaml
cat > redis-StatefulSet-svc.yaml<<EOF

# ========== 基础定义 ==========
# Kubernetes API版本
apiVersion: apps/v1
# 资源类型(适用于有状态应用)
kind: StatefulSet
metadata:
# StatefulSet名称
  name: redis-app
  # 命名空间(需与Service/ConfigMap一致)
  namespace: redis-cluster

# ========== 核心配置 ==========
spec:
# 关联的无头服务名称
  serviceName: "redis-service"
   # 6节点集群(3主3从架构)
  replicas: 6
  # ===== 选择器 =====
  selector:
    matchLabels:
      app: redis
      appCluster: redis-cluster
  # ===== Pod模板 =====
  template:
    metadata:
    # 必须与Service选择器匹配
      labels:
        app: redis
        appCluster: redis-cluster
    spec:
    # 优雅终止等待时间
      terminationGracePeriodSeconds: 20
      
      # 反亲和性配置(避免Pod部署到同一节点)
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values: ["redis"]
              topologyKey: kubernetes.io/hostname

      # ===== 容器配置 =====
      containers:
      - name: redis
      # 官方Redis镜像
        image: redis:6.2.12
        # 启动时执行IP修复脚本并加载配置
        command:
        #  - "/etc/redis/fix-pod-ip.sh"
          - "redis-server"
          - "/etc/redis/redis.conf"
        # 资源限制(根据业务需求调整)
        resources:
          requests:
            cpu: "100m"
            memory: "100Mi"
        
        # 环境变量(获取Pod IP用于集群通信)
        env:
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        
        # 端口定义
        ports:
        # 客户端访问端口
          - name: redis
            containerPort: 6379
            protocol: "TCP"
            # 集群总线端口
          - name: cluster
            containerPort: 16379
            protocol: "TCP"
        
        # 卷挂载
        volumeMounts:
        # 配置文件卷
          - name: "redis-conf"
            mountPath: "/etc/redis"
            # 数据持久化卷
          - name: "redis-data"
            mountPath: "/var/lib/redis"
      
      # ===== 卷配置 =====
      volumes:
      - name: "redis-conf"
      # 引用ConfigMap配置
        configMap:
          name: "redis-conf"
          # 文件权限设置 原0755
          defaultMode: 0755

  # ===== 持久化存储声明 =====
  volumeClaimTemplates:
  - metadata:
      name: redis-data
      namespace: redis-cluster
    spec:
    # 多节点读写模式
      accessModes: ["ReadWriteMany"]
      resources:
        requests:
        # 存储空间大小
          storage: 500M
          # 存储类名称
      storageClassName: redis-nfs-storage
EOF

kubectl apply  -f  redis-StatefulSet-svc.yaml

6. 获取集群node ip

#### 手动获取集群node ip
kubectl get pod -n redis-cluster -o wide 
#### 命令获取集群node ip
kubectl get pods -n redis-cluster -l app=redis -o jsonpath='{range.items[*]}{.status.podIP}:6379 ' | sed 's/ :6379//g'

7. 访问

ip:30476 这里笔者没有设置密码 所以可以直接链接

一些其他的方法

1. 重启pod

# 删除并重新创建 Pod,或触发 Pod 的滚动更新,以确保新的 ConfigMap 内容被加载。
kubectl delete pod -n redis-cluster redis-app-0
# 或者使用 StatefulSet 的滚动更新机制
kubectl rollout restart statefulset -n redis-cluster redis-app

一些问题:

1. 链接之后 切换数据库报错 ERR SELECT is not allowed in cluster mode

在Redis集群模式下,尝试使用SELECT命令切换数据库时遇到错误“ERR SELECT is not allowed in cluster mode”是因为Redis集群不支持多数据库的概念。以下是解决此问题的方法及步骤:

1. 理解Redis集群的数据库限制
  • 单一数据库‌:Redis集群在逻辑上被视为一个单一的数据库,不支持传统的多数据库索引(如0-15)。\
    • Redis Cluster设计上只支持db0,禁用SELECT命令是为了保证数据分片一致性
    • 所有键都分布在16384个槽位中,无法按数据库隔离数据
  • 数据分片‌:集群将数据自动分片到多个节点上,但所有节点共享相同的键值空间。
2. 修改应用逻辑
  • 移除SELECT命令‌:由于集群模式不支持SELECT命令,需要从应用程序中移除所有尝试切换数据库的代码。
  • 使用正确的键名‌:确保应用程序使用唯一的键名来避免数据冲突,因为所有节点共享相同的键值空间。
3. 重新配置Redis客户端
  • 配置为集群模式‌:确保Redis客户端配置为集群模式,而不是单节点模式。这通常涉及指定集群中多个节点的地址以及启用集群支持。
  • 更新连接字符串‌:如果客户端使用连接字符串,请更新它以包含集群中所有节点的地址,并使用集群特定的配置选项。
4. 测试和验证
  • 连接到集群‌:使用Redis CLI或其他客户端工具以集群模式连接到Redis集群。
  • 执行命令‌:尝试执行一些基本的读写命令,确保它们正常工作而不涉及SELECT命令。
  • 监控和日志‌:监控Redis集群的性能和日志,确保没有错误或异常行为。
5. 文档和培训
  • 更新文档‌:更新应用程序和Redis集群的文档,以反映集群模式的限制和配置要求。
  • 培训开发人员‌:确保所有相关人员都了解Redis集群的工作方式以及如何在应用程序中使用它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值