关于如何搭建 K8s 集群,教程很多,我是参考的此文完成搭建:
Centos7.6部署k8s v1.16.4高可用集群(主备模式)_Kubernetes中文社区
https://www.kubernetes.org.cn/6632.html

最近我负责完成了公司 Pass 平台(内核部分)单机 Redis 升级改造为 Redis Cluster,并将相关代码和功能进行适配,业务平滑过渡。继而领导交代了后续任务,提供基于:

  • 物理机或者虚拟机直接构建 Redis Cluster 部署文档
  • 一台或多台物理机或虚拟机基于 Docker 构建 Redis Cluster 部署文档
  • K8s 集群上构建 Redis Cluster 部署文档

并实现高可用和故障转移,根据ZAP理论,Redis 强调数据一致性,否则拒绝对外服务 (敲黑板,这是重点,稳定压倒一切)
目前文档完成了交付,在开发环境通过了测试。准备投入预生产环境。

一共是三份,前面两份文档没啥难度,重点记录第三份操作。

选择 K8s 来部署 Redis Cluster 集群,要点是理解 K8s 的功能,运用这些特性,再结合 Redis Cluster 本身的广播协议 Gossip,选择合适方案。

K8s部分

K8s的工作负载模式:

  • Deployment(StateFulLess) 无状态
  • StateFulSet 有状态
  • 定时
  • ...........
    这里我选择是 StateFulSet 模式,适用于有状态应用,如 Redis, Zookeeper
    这里需要注意的是,在 K8s 的 V1.18 版本后,StateFulSet 才从实验性功能,成为稳定功能,当然像 V1.10版本,都是可以使用此模式的。

数据挂载

计划使用分布式数据\对象存储,将 Redis Cluster 集群持久化的文件,进行保存。这里直接使用一台 NFS 做 PV,PVC,最好是用 MinIo 这种

关键需求:

  • 集群内外均可访问
    我查找了很多资料,CSDN,思否,51CTO,stackoverflow、后来直接找 K8s 开发者的博客(提供了思路)。没有找到实测可以集群外访问的
  • 支持 node.conf 文件的内容重建
    node.conf 文件,是 Redis Cluster 集群搭建后,自动生成的配置文件,如果机器非抗力的断电重启,虽然由 StateFulSet 进行维护状态,但可能节点ID、IP 发生了变化,从而丢失部分 Slot。 因此需要重建

Redis Cluster 部分

Gossip 协议里面,ping-pong 这种机制,直接绑定了 pod 的IP,所以也反推出了,K8s 应该采用的网络模式。是 Ingress-Nginx 还是 普通的 Service,是绑定Pod IP, 还是域名。这里我直接暴露 K8s 的 Node 机器节点 IP + redis 服务端口号,

上菜!

有了以上梳理,则已经完成了调研,直接再查具体文档,完成实现。

机器 和 节点规划

这里使用10台服务器(K8s集群内,1台 client, 3台 master, 6台 worker node),是的,我就是这么豪横。当然了,建议的最小数量机器是:(1台 master, 3台 work),再少就不合适了。
Redi Cluster 节点为 6个,则 每个 worker 节点,运行一个Redis 实例。

操作环境说明:基于 VMware Esxi 并通过 vSphere Client 管理 十台 Linux 服务器,组成 K8s 集群
主机列表:
VIP:192.168.50.222 ( VIP 通过Etcd 实现故障转移,默认在 Master1 上,虚拟节点)


Client:192.168.50.7(client主节点)
 
Master1:192.168.50.122(control plane)
Master2:192.168.50.167(control plane)
Master3:192.168.50.176(control plane)

Work1: 192.168.50.161(worker node)  
Work2: 192.168.50.180(worker node)
Work3: 192.168.50.105(worker node)  
Work4: 192.168.50.109(worker node)
Work5: 192.168.50.224(worker node)  
Work6: 192.168.50.109(worker node)

  1. Centos 版本和配置:CentOS Linux release 7.8.2003 (Core):4颗CPU + 4GB (每台)
  2. Redis 版本:
    V5.0.9 (本文档兼容任意 V5 版本,对于V4 及其以下,或V6等更高版本,不确定可行性,V4集群需要使用 Ruby 语法;V6 版本则需要对 Centos 7 系统设置进行更多的调整)

机器概览
k8s集群概览

节点信息

节点

部署 NFS 存储


创建NFS是给Redis提供稳定后端存储,当Redis的Pod重启或迁移后,依然能获得原先的数据。这里使用最简单的 NFS 进行示例

# 在Master2 的磁盘做共享存储服务器,当前操作机器为 Master2

$  yum -y install nfs-utils rpcbind

$  mkdir -p /data/redis-cluster-example/cluster{1..6}

# 追加配置
$  cat >> /etc/exports <<EOF
/data/redis-cluster-example/cluster1 192.168.50.167/24(rw,sync,no_root_squash)
/data/redis-cluster-example/cluster2 192.168.50.167/24(rw,sync,no_root_squash)
/data/redis-cluster-example/cluster3 192.168.50.167/24(rw,sync,no_root_squash)
/data/redis-cluster-example/cluster4 192.168.50.167/24(rw,sync,no_root_squash)
/data/redis-cluster-example/cluster5 192.168.50.167/24(rw,sync,no_root_squash)
/data/redis-cluster-example/cluster6 192.168.50.167/24(rw,sync,no_root_squash)
EOF

$  chmod -R 755 /data/redis-cluster- example/

$  exportfs -arv

$  systemctl enable rpcbind && systemctl start rpcbind

$  systemctl enable nfs && systemctl start nfs


创建 PersistentVolumes


# 我们在 client 节点上进行相关操作。如果没有client节点,则在任意 master 节点进行操作

# 当前操作目录:/usr/local/soft/k8s/redis-cluster-example
$ mkdir -p /usr/local/soft/k8s/redis-cluster-example  && cd /usr/local/soft/k8s/redis-cluster-example

$  cat <<EOF> /usr/local/soft/k8s/redis-cluster-example/pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv1
spec:
  capacity:
    storage: 5Gi
  accessModes: 
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: "redis-cluster"
  nfs:
    path: /data/redis-cluster-example/cluster1
    server: 192.168.50.167
---  
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv2
spec:
  capacity:
    storage: 5Gi
  accessModes: 
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: "redis-cluster"
  nfs:
    path: /data/redis-cluster-example/cluster2
    server: 192.168.50.167
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv3
spec:
  capacity:
    storage: 5Gi
  accessModes: 
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: "redis-cluster"
  nfs:
    path: /data/redis-cluster-example/cluster3
    server: 192.168.50.167
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv4
spec:
  capacity:
    storage: 5Gi
  accessModes: 
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: "redis-cluster"
  nfs:
    path: /data/redis-cluster-example/cluster4
    server: 192.168.50.167
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv5
spec:
  capacity:
    storage: 5Gi
  accessModes: 
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: "redis-cluster"
  nfs:
    path: /data/redis-cluster-example/cluster5
    server: 192.168.50.167
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv6
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: "redis-cluster"
  nfs:
    path: /data/redis-cluster-example/cluster6
    server: 192.168.50.167
EOF

#  执行命令,创建 pv
$  kubectl apply -f pv.yaml


查看 PV 状态

kubectl get pv

查看 PV 状态

创建 NameSpace


这里引入nameSpace 命名空间的好处是,便于集群的管理,类似于标签标记和分组

$  cat <<EOF> /usr/local/soft/k8s/redis-cluster/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: redis-cluster-example
EOF

#  创建执行 命名空间
$  kubectl apply -f namespace.yaml

创建 Headless Service

Headless service是StatefulSet实现稳定网络标识的基础,我们需要提前创建


$  cat <<EOF> /usr/local/soft/k8s/redis-cluster/headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis-cluster
  namespace: redis-cluster-example
  labels:
    app: redis-cluster
spec:
  selector:
    app: redis-cluster
    appCluster: redis-cluster
  ports:
  - name: redis-cluster
    port: 6379
  clusterIP: None 
EOF

#  执行创建命令	这里指定并且引用了上方定义的 nameSpace
$  kubectl create -f headless-service.yaml

#  查看刚创建的 svc 
kubectl get svc -n redis-cluster-example -o wide


创建 Redis 集群节点


$  cat <<EOF> /usr/local/soft/k8s/redis-redis-example/redis-sts.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-cluster
data:
  update-node.sh: |
    #!/bin/sh
    REDIS_NODES="/data/nodes.conf"
    sed -i -e "/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${POD_IP}/" ${REDIS_NODES}
    exec "[email protected]"
  redis.conf: |+
    cluster-enabled yes
    cluster-require-full-coverage no
    cluster-node-timeout 15000
    cluster-config-file /data/nodes.conf
    cluster-migration-barrier 1
    appendonly yes
    protected-mode no
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  namespace: redis-cluster-example
spec:
  serviceName: redis-cluster
  replicas: 6
  selector:
    matchLabels:
      app: redis-cluster
      namespace: redis-cluster-example
  template:
    metadata:
      labels:
        app: redis-cluster
        namespace: redis-cluster-example
    spec:
      hostNetwork: true
      containers:
      - name: redis
        image: redis:5.0.9
        ports:
        - containerPort: 6379
          hostPort: 6379
          name: client
        - containerPort: 16379
          name: gossip
        command: ["/conf/update-node.sh", "redis-server", "/conf/redis.conf"]
        env:
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        volumeMounts:
        - name: conf
          mountPath: /conf
          readOnly: false
        - name: data
          mountPath: /data
          readOnly: false
      volumes:
      - name: conf
        configMap:
          name: redis-cluster
          defaultMode: 0755
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi
      storageClassName: redis-cluster
EOF

#  执行创建命令
$  kubectl apply -f redis-sts.yaml

#  查看 redis pod 情况
$  kubectl get pod -n redis-cluster-example -o wide

#  查看 pvc 绑定使用情况
$  kubectl get pvc -n redis-cluster- example -o wide


查看使用情况

pvc 绑定情况

初始化 Redis Cluster

因为我们选择的 Redis 版本是大于4.0的,redis-cli 已经集成了部署集群的功能


# 全部复制,一次执行

$  kubectl exec -it redis-cluster-0 -n redis-cluster-example \
		-- redis-cli --cluster create \
		--cluster-replicas 1 \
$(kubectl get pods -l app=redis-cluster -n redis-cluster-example -o jsonpath='{range.items[*]}{.status.podIP}:6379 ')



执行初始化
执行初始化

查看集群信息


#  连接到 redis-0 容器
$  kubectl exec -it redis-cluster-0 -n redis-cluster-example -- /bin/bash
#  使用 redis-cli 工具连接到任意节点
$  redis-cli -c -p 6379
#  查看集群节点
$  cluster nodes	  和   cluster info


节点

查看数据挂载情况

在master2 NFS 上可以看到,持久化文件,使用 tree 树形查看

节点

重启集群,校验数据是否丢失

# 单独删除某一个 pod,集群会自动重建 pod,并且数据可以正常访问
# 如,我们set foo bar ,集群重定向到 192.168.50.224:6379,删除 Ip 对应的Pod,重新 get foo


仔细看下方的图,他解释了所有问题,224号节点删除后,K8s 自动建立的,实现了高可用
$  kubectl delete pod redis-2  -n  redis-cluster-example

#  这个命令是强制删除命令,会导致集群数据丢失,切勿随意执行。 但本部署方案,理论上已经规避该操作风险,可以正常执行
$  kubectl replace --force -f  redis-sts.yaml

# 在重启物理机后,依然做到自动数据恢复,测试了4次宕机,均可恢复。如确实丢失身份节点,则需要手动添加节点,并分配 slot 

集群容灾

还有其他的集群管理命令,就看请我其他的博客文章吧

展望

  • 参数调优
  • 负载均衡处理
    我翻阅了很多资料,Redis Cluster 模式下,很容易出现的问题,是某个节点Cpu 负载过高;以及后续需要通过对请求进行一致性哈希运算,直接让不同的节点,处理固定的请求(类似于 CDN 策略)。这需要对 StateFulSet 模式更深入的了解
  • 继续改造,并通过 Helm 进行包管理,和 Chart 化,方便部署
  • Rancher化(已完成)

Rancher

GitHub:基于K8s集群,搭建 redis-cluster 集群