Kubernetes09-部署redis高可用集群(StatefulSet)

1.redis集群规划

1.1架构原理

架构原理:每个Master都可以拥有多个Slave。当Master下线后,Redis集群会从多个Slave中选举出一个新的Master作为替代,而旧Master重新上线后变成新Master的Slave。

1.2准备操作

本次部署主要基于该项目:
kubernetes-redis-cluster
其包含了两种部署Redis集群的方式:

  1. StatefulSet
  2. Service&Deployment

两种方式各有优劣,对于像Redis、Mongodb、Zookeeper等有状态的服务,使用StatefulSet是首选方式。本文将主要介绍如何使用StatefulSet进行Redis集群的部署。

1.3集群规划

查看下当前的k8s集群

[root@master ~]# kubectl get nodes -o wide 
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION          CONTAINER-RUNTIME
master   Ready    master   9d    v1.17.4   192.168.56.101   <none>        CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://1.13.1
node1    Ready    <none>   9d    v1.17.4   192.168.56.102   <none>        CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://1.13.1
node2    Ready    <none>   9d    v1.17.4   192.168.56.103   <none>        CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://1.13.1

当前k8s只有一个master两个node ,部署redis时我们采用三主三从来部署高可用redis集群。也就是说6个redis节点都会部署到node1和node2上。

2、StatefulSet简介

RC、Deployment、DaemonSet都是面向无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的,而StatefulSet是什么?顾名思义,有状态的集合,管理所有有状态的服务,比如MySQL、MongoDB集群等。
StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为GA版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序,在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。
在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。
除此之外,StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:

$(podname).(headless server name)   
FQDN: $(podname).(headless server name).namespace.svc.cluster.local

也即是说,对于有状态服务,我们最好使用固定的网络标识(如域名信息)来标记节点,当然这也需要应用程序的支持(如Zookeeper就支持在配置文件中写入主机域名)。
StatefulSet基于Headless Service(即没有Cluster IP的Service)为Pod实现了稳定的网络标志(包括Pod的hostname和DNS Records),在Pod重新调度后也保持不变。同时,结合PV/PVC,StatefulSet可以实现稳定的持久化存储,就算Pod重新调度后,还是能访问到原先的持久化数据。

以下为使用StatefulSet部署Redis的架构,无论是Master还是Slave,都作为StatefulSet的一个副本,并且数据通过PV进行持久化,对外暴露为一个Service,接受客户端请求。

3. 实战部署

3.1部署过程

本文参考kubernetes-redis-cluster的README中,简要介绍了基于StatefulSet的Redis创建步骤:

  1. 创建NFS存储
  2. 创建PV
  3. 创建ConfigMap
  4. 创建headlessService服务
  5. 创建redis集群节点
  6. 初始化Redis集群
  7. 创建用于访问的service
  8. 测试集群

3.1.1 创建NFS存储

创建NFS存储主要是为了给Redis提供稳定的后端存储,当Redis的Pod重启或迁移后,依然能获得原先的数据。这里,我们先要创建NFS,然后通过使用PV为Redis挂载一个远程的NFS路径。

  1. 安装NFS
yum -y install nfs-utils(主包提供文件系统)
yum -y install rpcbind(提供rpc协议)
  1. 然后,新增/etc/exports文件,用于设置需要共享的路径:
[root@master data]# cat /etc/exports
/root/data/redis_pv1 192.168.56.0/24(rw,sync,no_root_squash)
/root/data/redis_pv2 192.168.56.0/24(rw,sync,no_root_squash)
/root/data/redis_pv3 192.168.56.0/24(rw,sync,no_root_squash)
/root/data/redis_pv4 192.168.56.0/24(rw,sync,no_root_squash)
/root/data/redis_pv5 192.168.56.0/24(rw,sync,no_root_squash)
/root/data/redis_pv6 192.168.56.0/24(rw,sync,no_root_squash)
  1. 创建对应目录
[root@master data]# mkdir -p /root/data/redis_pv{1..6}
[root@master data]# ll
total 0
drwxr-xr-x 2 root root  6 Jul  5 07:25 redis_pv1
drwxr-xr-x 2 root root  6 Jul  5 07:25 redis_pv2
drwxr-xr-x 2 root root  6 Jul  5 07:25 redis_pv3
drwxr-xr-x 2 root root  6 Jul  5 07:25 redis_pv4
drwxr-xr-x 2 root root  6 Jul  5 07:25 redis_pv5
drwxr-xr-x 2 root root  6 Jul  5 07:25 redis_pv6
  1. 启动NFS和rpcbind服务:
[root@master data]# systemctl restart rpcbind
[root@master data]# systemctl restart nfs
[root@master data]# systemctl enable nfs

[root@master data]# exportfs -v
/root/data/redis_pv1
                192.168.56.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/root/data/redis_pv2
                192.168.56.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/root/data/redis_pv3
                192.168.56.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/root/data/redis_pv4
                192.168.56.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/root/data/redis_pv5
                192.168.56.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/root/data/redis_pv6
                192.168.56.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
  1. 安装客户端
yum -y install nfs-utils	
  1. 查看存储端共享
[root@master data]# showmount -e 192.168.56.101
Export list for 192.168.56.101:
/root/data/redis_pv6 192.168.56.0/24
/root/data/redis_pv5 192.168.56.0/24
/root/data/redis_pv4 192.168.56.0/24
/root/data/redis_pv3 192.168.56.0/24
/root/data/redis_pv2 192.168.56.0/24
/root/data/redis_pv1 192.168.56.0/24

3.1.2 创建PV

每一个Redis Pod都需要一个独立的PV来存储自己的数据,因此可以创建一个redis-pv.yaml文件,包含6个PV:

[root@master other]# cat redis-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv1
spec:
  capacity:
    storage: 200M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.56.101
    path: "/root/data/redis_pv1"

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv2
spec:
  capacity:
    storage: 200M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.56.101
    path: "/root/data/redis_pv2"

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv3
spec:
  capacity:
    storage: 200M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.56.101
    path: "/root/data/redis_pv3"

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv4
spec:
  capacity:
    storage: 200M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.56.101
    path: "/root/data/redis_pv4"

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv5
spec:
  capacity:
    storage: 200M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.56.101
    path: "/root/data/redis_pv5"

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-nfs-pv6
spec:
  capacity:
    storage: 200M
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.56.101
    path: "/root/data/redis_pv6"

执行创建

[root@master other]# kubectl create -f redis-pv.yaml 
persistentvolume/redis-nfs-pv1 created
persistentvolume/redis-nfs-pv2 created
persistentvolume/redis-nfs-pv3 created
persistentvolume/redis-nfs-pv4 created
persistentvolume/redis-nfs-pv5 created
persistentvolume/redis-nfs-pv6 created

3.1.3 创建ConfigMap

这里,我们可以直接将Redis的配置文件转化为Configmap,这是一种更方便的配置读取方式。配置文件redis.conf如下

[root@master redis]# cat redis.conf 
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
# 创建名为redis-conf的Configmap:
[root@master redis]# kubectl create configmap redis-conf --from-file=redis.conf 
configmap/redis-conf created
# 查看创建的configmap详情
[root@master redis]# kubectl describe cm redis-conf
Name:         redis-conf
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis.conf:
----
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379

Events:  <none>

如上,redis.conf中的所有配置项都保存到redis-conf这个Configmap中。

3.1.4 创建HeadlessService

HeadlessService是statefulSet实现稳定网络标识的基础,我们需要提前创建。准备文件redis-headless-service.yaml

[root@master redis]# cat redis-headless-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: redis-service
  labels:
    app: redis
spec:
  ports:
  - name: redis-port
    port: 6379
  clusterIP: None
  selector:
    app: redis
    appCluster: redis-cluster
[root@master redis]# kubectl create -f  redis-headless-service.yaml 
service/redis-service created
[root@master redis]# kubectl get svc 
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP        9d
nginx           NodePort    10.111.38.164   <none>        80:32529/TCP   9d
nginx-sts       ClusterIP   None            <none>        80/TCP         3d3h
#可以看到,服务名称为redis-service,其CLUSTER-IP为None,表示这是一个“无头”服务
redis-service   ClusterIP   None            <none>        6379/TCP       14s

3.1.5 创建redis集群节点

创建好Headless service后,就可以利用StatefulSet创建Redis 集群节点,这也是本文的核心内容。我们先创建redis.yml文件:

[root@master redis]# cat redis.yaml 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-app
spec:
  selector:
    matchLabels:
      app: redis
  serviceName: "redis-service"
  replicas: 6
  template:
    metadata:
      labels:
        app: redis
        appCluster: redis-cluster
    spec:
      terminationGracePeriodSeconds: 20
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - redis
              topologyKey: kubernetes.io/hostname
      containers:
      - name: redis
        image: redis
        command:
          - "redis-server"
        args:
          - "/etc/redis/redis.conf"
          - "--protected-mode"
          - "no"
        resources:
          requests:
            cpu: "100m"
            memory: "100Mi"
        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:
          name: "redis-conf"
          items:
            - key: "redis.conf"
              path: "redis.conf"
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: [ "ReadWriteMany" ]
      resources:
        requests:
          storage: 200M
#创建pod
[root@master redis]# kubectl create -f redis.yaml
statefulset.apps/redis-app created
#查看pod
[root@master redis]# kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
#根据StatefulSet的规则,我们生成的Redis的6个Pod的hostname会被依次命名为 $(statefulset名称)-$(序号)
redis-app-0   1/1     Running   0          2m13s
redis-app-1   1/1     Running   0          112s
redis-app-2   1/1     Running   0          105s
redis-app-3   1/1     Running   0          101s
redis-app-4   1/1     Running   0          98s
redis-app-5   1/1     Running   0          77s

总共创建了6个Redis节点(Pod),其中3个将用于master,另外3个分别作为master的slave;
Redis的配置通过volume将之前生成的redis-conf这个Configmap,挂载到了容器的/etc/redis/redis.conf;
Redis的数据存储路径使用volumeClaimTemplates声明(也就是PVC),其会绑定到我们先前创建的PV上。
这里有一个关键概念——Affinity,请参考官方文档详细了解。其中,podAntiAffinity表示反亲和性,其决定了某个pod不可以和哪些Pod部署在同一拓扑域,可以用于将一个服务的POD分散在不同的主机或者拓扑域中,提高服务本身的稳定性。
而PreferredDuringSchedulingIgnoredDuringExecution 则表示,在调度期间尽量满足亲和性或者反亲和性规则,如果不能满足规则,POD也有可能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。
在这里,matchExpressions规定了Redis Pod要尽量不要调度到包含app为redis的Node上,也即是说已经存在Redis的Node上尽量不要再分配Redis Pod了。但是,由于我们只有两个Node,而副本有6个,因此根据PreferredDuringSchedulingIgnoredDuringExecution,这些pod不得不得挤一挤,挤挤更健康~

可以看到这些Pods在部署时是以{0…N-1}的顺序依次创建的。注意,直到redis-app-0状态启动后达到Running状态之后,redis-app-1 才开始启动。
同时,每个Pod都会得到集群内的一个DNS域名,格式为 ( p o d n a m e ) . (podname). (podname).(service name).$(namespace).svc.cluster.local ,也即是:

[root@master redis]# for x in $(seq 0 5); do kubectl exec redis-app-$x  -- hostname -f; done   
redis-app-0.redis-service.default.svc.cluster.local
redis-app-1.redis-service.default.svc.cluster.local
redis-app-2.redis-service.default.svc.cluster.local
redis-app-3.redis-service.default.svc.cluster.local
redis-app-4.redis-service.default.svc.cluster.local
redis-app-5.redis-service.default.svc.cluster.local

查看pv绑定情况

[root@master redis]# kubectl get pv
NAME            CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                            STORAGECLASS   REASON   AGE                                             22h
redis-nfs-pv1   200M       RWX            Retain           Bound    default/redis-data-redis-app-2                           23m
redis-nfs-pv2   200M       RWX            Retain           Bound    default/redis-data-redis-app-4                           23m
redis-nfs-pv3   200M       RWX            Retain           Bound    default/redis-data-redis-app-0                           23m
redis-nfs-pv4   200M       RWX            Retain           Bound    default/redis-data-redis-app-1                           23m
redis-nfs-pv5   200M       RWX            Retain           Bound    default/redis-data-redis-app-3                           23m
redis-nfs-pv6   200M       RWX            Retain           Bound    default/redis-data-redis-app-5                           23m

3.1.6 初始化redis集群

创建好6个Redis Pod后,我们还需要利用常用的Redis-tribe工具进行集群的初始化

3.1.6.1 创建Ubuntu容器

由于Redis集群必须在所有节点启动后才能进行初始化,而如果将初始化逻辑写入Statefulset中,则是一件非常复杂而且低效的行为。这里,本人不得不称赞一下原项目作者的思路,值得学习。也就是说,我们可以在K8S上创建一个额外的容器,专门用于进行K8S集群内部某些服务的管理控制。
这里,我们专门启动一个Ubuntu的容器,可以在该容器中安装Redis-tribe,进而初始化Redis集群,执行:

[root@master redis]# kubectl run -it ubuntu --image=ubuntu --restart=Never /bin/bash
If you don't see a command prompt, try pressing enter.
# 注意这里进入到了ubuntu容器内
root@ubuntu:/#
#写入源
root@ubuntu:/# cat > /etc/apt/sources.list << EOF
> deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
> 
> deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
> 
> deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
> 
> deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
>  
> deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
> deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
> EOF
# 查看写入结果
root@ubuntu:/# cat /etc/apt/sources.list         
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse

deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse

deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse

deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
 
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse

成功后,原项目要求执行如下命令安装基本的软件环境:

apt-get update
apt-get install -y vim wget python2.7 python-pip redis-tools dnsutils
3.1.6.2 初始化集群
#直接安装。我这里报错了,如果你没有报错跳过后面的操作
root@ubuntu:/# pip install redis-trib==0.5.1
Collecting redis-trib==0.5.1
  Could not find a version that satisfies the requirement redis-trib==0.5.1 (from versions: )
No matching distribution found for redis-trib==0.5.1

root@ubuntu:/# pip --trusted-host pypi.python.org install redis-trib==0.5.1 -vvv
  1 location(s) to search for versions of redis-trib:
  * https://pypi.python.org/simple/redis-trib/
  Getting page https://pypi.python.org/simple/redis-trib/
  Starting new HTTPS connection (1): pypi.python.org
  https://pypi.python.org:443 "GET /simple/redis-trib/ HTTP/1.1" 301 122
  Looking up "https://pypi.org/simple/redis-trib/" in the cache
  No cache entry available
  Starting new HTTPS connection (1): pypi.org
  Could not fetch URL https://pypi.python.org/simple/redis-trib/: connection error: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:726) - skipping
  Could not find a version that satisfies the requirement redis-trib==0.5.1 (from versions: )
Cleaning up...
No matching distribution found for redis-trib==0.5.1
#使用这个命令 可以成功安装了
root@ubuntu:/#  pip install redis-trib==0.5.1 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

创建只有master节点的集群

root@ubuntu:/# redis-trib.py create \
`dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379       
Redis-trib 0.5.1 Copyright (c) HunanTV Platform developers
INFO:root:Instance at 10.244.2.53:6379 checked
INFO:root:Instance at 10.244.2.52:6379 checked
INFO:root:Instance at 10.244.1.77:6379 checked
INFO:root:Add 5462 slots to 10.244.2.53:6379
INFO:root:Add 5461 slots to 10.244.2.52:6379
INFO:root:Add 5461 slots to 10.244.1.77:6379

为每个master添加slave:

root@ubuntu:/# redis-trib.py replicate \
> --master-addr `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \
> --slave-addr `dig +short redis-app-3.redis-service.default.svc.cluster.local`:6379 
Redis-trib 0.5.1 Copyright (c) HunanTV Platform developers
INFO:root:Instance at 10.244.1.78:6379 has joined 10.244.1.77:6379; now set replica
INFO:root:Instance at 10.244.1.78:6379 set as replica to 07feeb4b72bd9c1c06a69091168a8aa4639eea23

root@ubuntu:/# redis-trib.py replicate --master-addr `dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 --slave-addr `dig +short redis-app-4.redis-service.default.svc.cluster.local`:6379  
Redis-trib 0.5.1 Copyright (c) HunanTV Platform developers
INFO:root:Instance at 10.244.2.54:6379 has joined 10.244.2.52:6379; now set replica
INFO:root:Instance at 10.244.2.54:6379 set as replica to 2dc05ff2b52a1eed728872a9a4fe3271b9c28bca

root@ubuntu:/# redis-trib.py replicate --master-addr `dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379 --slave-addr `dig +short redis-app-5.redis-service.default.svc.cluster.local`:6379  
Redis-trib 0.5.1 Copyright (c) HunanTV Platform developers
INFO:root:Instance at 10.244.1.79:6379 has joined 10.244.2.53:6379; now set replica
INFO:root:Instance at 10.244.1.79:6379 set as replica to 5b53a30a686909e94f5d26fedef4e774ee501883

至此,我们的Redis集群就真正创建完毕了,连到任意一个Redis Pod中检验一下:

[root@master ~]# kubectl exec -it redis-app-1 /bin/bash
root@redis-app-1:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> cluster nodes 
2dc05ff2b52a1eed728872a9a4fe3271b9c28bca 10.244.2.52:6379@16379 myself,master - 0 1656911837000 1 connected 5462-10922
0f425d5feb3a9956a658c1452ba1234ddd33fad3 10.244.1.78:6379@16379 slave 07feeb4b72bd9c1c06a69091168a8aa4639eea23 0 1656911837724 2 connected
64cc1c51c2820ed546d855423f640e23c4180923 10.244.1.79:6379@16379 slave 5b53a30a686909e94f5d26fedef4e774ee501883 0 1656911838864 5 connected
07feeb4b72bd9c1c06a69091168a8aa4639eea23 10.244.1.77:6379@16379 master - 0 1656911838342 2 connected 10923-16383
0acfbf921a73c141d9629d2ca0d41a95190e27c3 10.244.2.54:6379@16379 slave 2dc05ff2b52a1eed728872a9a4fe3271b9c28bca 0 1656911838755 1 connected
5b53a30a686909e94f5d26fedef4e774ee501883 10.244.2.53:6379@16379 master - 0 1656911837301 5 connected 0-5461
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_ping_sent:749
cluster_stats_messages_pong_sent:769
cluster_stats_messages_meet_sent:2
cluster_stats_messages_sent:1520
cluster_stats_messages_ping_received:769
cluster_stats_messages_pong_received:751
cluster_stats_messages_received:1520

回到宿主机查看NFS上查看redis挂载的数据:

[root@master ~]# cd /root/data/redis_pv2
[root@master redis_pv2]# ll
total 12
-rw-r--r-- 1 root root  92 Jul  5 10:40 appendonly.aof
-rw-r--r-- 1 root root 175 Jul  5 10:40 dump.rdb
-rw-r--r-- 1 root root 793 Jul  5 10:41 nodes.conf

3.1.7 创建用于访问的service

前面我们创建了用于实现StatefulSet的Headless Service,但该Service没有Cluster Ip,因此不能用于外界访问。所以,我们还需要创建一个Service,专用于为Redis集群提供访问和负载均衡:

[root@master redis]# cat redis-access-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: redis-access-service
  labels:
    app: redis
spec:
  ports:
  - name: redis-port
    port: 6379
    nodePort: 30011
    targetPort: 6379
  type: NodePort 
  selector:
    app: redis
    appCluster: redis-cluster

如上,该Service名称为 redis-access-service,在K8S集群中暴露6379端口,集群外暴露30011端口,并且会对labels nameapp: redisappCluster: redis-cluster的pod进行负载均衡。

[root@master redis]# kubectl get svc -o wide 
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE     SELECTOR
redis-access-service   NodePort    10.109.94.83    <none>        6379:30011/TCP   2m54s   app=redis,appCluster=redis-cluster
redis-service          ClusterIP   None            <none>        6379/TCP         3h8m    app=redis,appCluster=redis-cluster

3.1.8 测试集群

3.1.8.1 测试redis连接

宿主机上用rdm测试连接:
如上,在K8S集群中,所有应用都可以通过10.105.228.212 :6379来访问Redis集群。当然,为了方便测试,我们也可以为Service添加一个NodePort映射到物理机上,这里不再详细介绍。
并且103也是可以访问的:
在这里插入图片描述

3.1.8.2 测试主从切换

在K8S上搭建完好Redis集群后,我们最关心的就是其原有的高可用机制是否正常。这里,我们可以任意挑选一个Master的Pod来测试集群的主从切换机制,如redis-app-0:

#查看pod信息 用来对照下面命令的 slave ip
[root@master redis]# kubectl get pods -o wide 
NAME          READY   STATUS    RESTARTS   AGE    IP            NODE    NOMINATED NODE   READINESS GATES
redis-app-0   1/1     Running   0          113m   10.244.1.77   node1   <none>           <none>
redis-app-1   1/1     Running   0          113m   10.244.2.52   node2   <none>           <none>
redis-app-2   1/1     Running   0          113m   10.244.2.53   node2   <none>           <none>
redis-app-3   1/1     Running   0          113m   10.244.1.78   node1   <none>           <none>
redis-app-4   1/1     Running   0          112m   10.244.2.54   node2   <none>           <none>
redis-app-5   1/1     Running   0          112m   10.244.1.79   node1   <none>           <none>
ubuntu        1/1     Running   0          91m    10.244.1.80   node1   <none>           <none>

[root@master redis]# kubectl exec -it redis-app-0 /bin/bash
root@redis-app-0:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> role
1) "master"
2) (integer) 2702
3) 1) 1) "10.244.1.78"
      2) "6379"
      3) "2702"
127.0.0.1:6379> 

如上可以看到,app-0为master,slave为10.244.1.78即redis-app-3。

接着,我们手动删除redis-app-0:

# 首先另外启动一个窗口查看pod变化 
[root@master redis]# kubectl get pods -o wide -w
NAME          READY   STATUS      RESTARTS   AGE    IP            NODE    NOMINATED NODE   READINESS GATES
redis-app-0   1/1     Running     0          117m   10.244.1.77   node1   <none>           <none>
redis-app-1   1/1     Running     0          117m   10.244.2.52   node2   <none>           <none>
redis-app-2   1/1     Running     0          117m   10.244.2.53   node2   <none>           <none>
redis-app-3   1/1     Running     0          117m   10.244.1.78   node1   <none>           <none>
redis-app-4   1/1     Running     0          117m   10.244.2.54   node2   <none>           <none>
redis-app-5   1/1     Running     0          116m   10.244.1.79   node1   <none>           <none>
ubuntu        0/1     Completed   0          96m    10.244.1.80   node1   <none>           <none>
redis-app-0   1/1     Terminating   0          118m   10.244.1.77   node1   <none>           <none>
redis-app-0   0/1     Terminating   0          118m   10.244.1.77   node1   <none>           <none>
redis-app-0   0/1     Terminating   0          118m   10.244.1.77   node1   <none>           <none>
redis-app-0   0/1     Terminating   0          118m   10.244.1.77   node1   <none>           <none>
redis-app-0   0/1     Pending       0          0s     <none>        <none>   <none>           <none>
redis-app-0   0/1     Pending       0          0s     <none>        node1    <none>           <none>
redis-app-0   0/1     ContainerCreating   0          0s     <none>        node1    <none>           <none>
redis-app-0   1/1     Running             0          3s     10.244.1.81   node1    <none>           <none>

#然后执行删除
[root@master redis]# kubectl delete pod redis-app-0
pod "redis-app-0" deleted

发现集群在删除redis-app-0的时候立刻启动了 一个新的redis-app-0
在进入redis-app-0查看:

[root@master redis]# kubectl delete pod redis-app-0
pod "redis-app-0" deleted
[root@master redis]# kubectl exec -it redis-app-0 /bin/bash
root@redis-app-0:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> role
1) "slave"
2) "10.244.1.78"
3) (integer) 6379
4) "connected"
5) (integer) 3248

如上,redis-app-0变成了slave,从属于它之前的从节点10.244.1.78即redis-app-3。
在来看下redis-app-3的角色:

[root@master redis]# kubectl exec -it redis-app-3 /bin/bash 
root@redis-app-3:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> role
1) "master"
2) (integer) 3682
3) 1) 1) "10.244.1.81"
      2) "6379"
      3) "3682"
127.0.0.1:6379> 

至此,大家可能会疑惑,那为什么没有使用稳定的标志,Redis Pod也能正常进行故障转移呢?这涉及了Redis本身的机制。因为,Redis集群中每个节点都有自己的NodeId(保存在自动生成的nodes.conf中),并且该NodeId不会随着IP的变化和变化,这其实也是一种固定的网络标志。也就是说,就算某个Redis Pod重启了,该Pod依然会加载保存的NodeId来维持自己的身份。我们可以在NFS上查看redis-app-0的nodes.conf文件:

[root@master redis]# cat /root/data/redis_pv1/nodes.conf 
5b53a30a686909e94f5d26fedef4e774ee501883 10.244.2.53:6379@16379 myself,master - 0 1656913875000 5 connected 0-5461
0acfbf921a73c141d9629d2ca0d41a95190e27c3 10.244.2.54:6379@16379 slave 2dc05ff2b52a1eed728872a9a4fe3271b9c28bca 0 1656913875893 1 connected
64cc1c51c2820ed546d855423f640e23c4180923 10.244.1.79:6379@16379 slave 5b53a30a686909e94f5d26fedef4e774ee501883 0 1656913875000 5 connected
07feeb4b72bd9c1c06a69091168a8aa4639eea23 10.244.1.81:6379@16379 slave 0f425d5feb3a9956a658c1452ba1234ddd33fad3 0 1656913876412 6 connected
0f425d5feb3a9956a658c1452ba1234ddd33fad3 10.244.1.78:6379@16379 master - 0 1656913874000 6 connected 10923-16383
2dc05ff2b52a1eed728872a9a4fe3271b9c28bca 10.244.2.52:6379@16379 master - 0 1656913875269 1 connected 5462-10922
vars currentEpoch 6 lastVoteEpoch 6

如上,第一列为NodeId,稳定不变;第二列为IP和端口信息,可能会改变。

这里,我们介绍NodeId的两种使用场景:

  1. 当某个Slave Pod断线重连后IP改变,但是Master发现其NodeId依旧, 就认为该Slave还是之前的Slave。

  2. 当某个Master Pod下线后,集群在其Slave中选举重新的Master。待旧Master上线后,集群发现其NodeId依旧,会让旧Master变成新Master的slave。

对于这两种场景,大家有兴趣的话还可以自行测试,注意要观察Redis的日志。

转载声明:
本文为CSDN博主「[普通网友](https://blog.csdn.net/sebeefe/article/details/124473706)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sebeefe/article/details/124473706
转载目的只为本人学习记录,如有侵权请联系删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值