前言 在之前的博客’kubernetes集群部署‘ 中,我们搭建了一个kubernetes集群;而在博客‘kubernetes部署Nginx集群StatefulSet 部署一个有状态的Redis集群。
部署 Redis官方文件推荐的集群为3主3从6节点,我们现在的kubernetes集群中有1主3从4个节点,在资源允许的条件下可以再往kubernetes集群中添加两个worker节点,如果没有资源可以部署3主0从3节点的集群(这里多说一句,经过实际部署,kubernetes是不会在master节点中运行pod的,也就是说要部署3主3从6节点的redis集群,要6个worker节点);同时我们还需要一个虚拟机部署NFS文件服务器,这里简单介绍一下NFS:NFS(Network File System,网络文件系统)是一种分布式文件系统协议,允许客户端通过网络访问远程服务器上的文件,就像访问本地文件一样。我们用NFS来存储redis的数据,这样可以防止redis节点损坏时丢失数据。
部署NFS服务,创建数据共享文件夹 新建一个虚拟机节点,参考博客‘虚拟机的初始准备 ’
(1)资源为:2core、4G内存、50G硬盘,虚拟机安装的镜像为:CentOS-7-x86_64-Minimal-2009.iso;
(2)设置静态ip,我设置的ip为‘192.168.218.138’
(3)安装yum,设置yum源
(4)安装安装 NFS 服务
1 2 #  安装 NFS 服务器软件包 sudo yum install nfs-utils -y 
(5)创建数据共享文件夹
1 2 #  创建‘/data/nfs/redis/pv1’到‘/data/nfs/redis/pv6’ 共6个文件件 mkdir -p /data/nfs/redis/pv{1..6} 
(6)设置共享文件夹路径,编辑文件‘/etc/exports’,添加如下内容:
1 2 3 4 5 6 /data/nfs/redis/pv1     *(rw,no_root_squash,sync,insecure) /data/nfs/redis/pv2     *(rw,no_root_squash,sync,insecure) /data/nfs/redis/pv3     *(rw,no_root_squash,sync,insecure) /data/nfs/redis/pv4     *(rw,no_root_squash,sync,insecure) /data/nfs/redis/pv5     *(rw,no_root_squash,sync,insecure) /data/nfs/redis/pv6     *(rw,no_root_squash,sync,insecure) 
‘/data/nfs/redis/pv1’:共享目录 
‘*’:允许访问的客户端 IP 范围(192.168.1.0/24),*代表全部ip都可以访问 
‘rw’:客户端拥有读和写的权限 
‘no_root_squash’:允许客户端以 root 用户身份访问 
‘sync’:同步写入 
‘insecure’:表示允许客户端使用非特权端口 (大于 1024 的端口)进行访问 
 
(7)启动服务
1 2 3 4 5 6 7 8 #  启动nfs-server服务 sudo systemctl start nfs-server #  设置nfs-server服务开机自启 sudo systemctl enable nfs-server #  启动rpcbind服务(提供rpc协议) systemctl start rpcbind #  设置rpcbind服务开机自启 systemctl enable rpcbind 
(8)查看设置是否生效
(9)在客户端(即kubernetes节点)安装nfs-utils
1 yum -y install nfs-utils 
(10)在客户端查看共享文件夹是否生效
创建 Namespace 我们依旧为这个集群创建一个单独的命名空间
1 kubectl create namespace redis-namespace 
查看创建的命名空间
创建 PV 我们前面已经部署了NFS文件服务器并且创建了6个共享文件夹,现在我们使用这6个共享文件夹,创建6个PV(PersistentVolume);在适当目录创建文件redis-pv.yaml,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 apiVersion:  v1 kind:  PersistentVolume metadata:   name:  nfs-pv1    namespace:  redis-namespace  spec:   capacity:      storage:  1Gi    accessModes:      -  ReadWriteMany    persistentVolumeReclaimPolicy:  Retain    storageClassName:  nfs    nfs:      server:  192.168 .218 .138      path:  "/data/nfs/redis/pv1"  --- apiVersion:  v1 kind:  PersistentVolume metadata:   name:  nfs-pv2    namespace:  redis-namespace  spec:   capacity:      storage:  1Gi    accessModes:      -  ReadWriteMany    persistentVolumeReclaimPolicy:  Retain    storageClassName:  nfs    nfs:      server:  192.168 .218 .138      path:  "/data/nfs/redis/pv2"  --- apiVersion:  v1 kind:  PersistentVolume metadata:   name:  nfs-pv3    namespace:  redis-namespace  spec:   capacity:      storage:  1Gi    accessModes:      -  ReadWriteMany    persistentVolumeReclaimPolicy:  Retain    storageClassName:  nfs    nfs:      server:  192.168 .218 .138      path:  "/data/nfs/redis/pv3"  --- apiVersion:  v1 kind:  PersistentVolume metadata:   name:  nfs-pv4    namespace:  redis-namespace  spec:   capacity:      storage:  1Gi    accessModes:      -  ReadWriteMany    persistentVolumeReclaimPolicy:  Retain    storageClassName:  nfs    nfs:      server:  192.168 .218 .138      path:  "/data/nfs/redis/pv4"  --- apiVersion:  v1 kind:  PersistentVolume metadata:   name:  nfs-pv5    namespace:  redis-namespace  spec:   capacity:      storage:  1Gi    accessModes:      -  ReadWriteMany    persistentVolumeReclaimPolicy:  Retain    storageClassName:  nfs    nfs:      server:  192.168 .218 .138      path:  "/data/nfs/redis/pv5"  --- apiVersion:  v1 kind:  PersistentVolume metadata:   name:  nfs-pv6    namespace:  redis-namespace  spec:   capacity:      storage:  1Gi    accessModes:      -  ReadWriteMany    persistentVolumeReclaimPolicy:  Retain    storageClassName:  nfs    nfs:      server:  192.168 .218 .138      path:  "/data/nfs/redis/pv6"  
1 2 3 4 #  应该该文件创建PV kubectl apply -f redis-pv.yaml #  查看PV是否已经成功创建 kubectl get pv 
创建 ConfigMap 我们之前部署单体redis,redis的配置文件为redis.conf,而现在的ConfigMap就相当于redis在kubernetes中的配置文件;我们合适的目录创建redis-config.yaml文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion:  v1 kind:  ConfigMap metadata:   name:  redis-conf    namespace:  redis-namespace  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  
这里的|符号表示后面的内容作为一个完整的字符串,这样我们就不需要设置每一个配置用到的key了,在部署redis实例的时候直接使用该配置即可。
1 2 3 4 5 6 #  应用该yaml文件创建ConfigMap kubectl apply -f redis-config.yaml #  查看命名空间中的ConfigMap kubectl get configmap -n redis-namespace #  查看configmap ‘redis-conf’的详细信息 kubectl describe configmap redis-conf -n redis-namespace 
创建 HeadlessService 
普通 Service :提供稳定的访问入口和负载均衡,适用于无状态应用。Headless Service :不提供 ClusterIP 和负载均衡,直接返回后端 Pod 的 IP 地址,适用于有状态应用和需要直接访问 Pod 的场景。 
因为Redis集群是有状态集群,我们采用无头HeaderLess的Service方式,不需要有具体的ClusterIp;在合适的目录创建redis-service.yaml文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion:  v1 kind:  Service metadata:   name:  redis-service    namespace:  redis-namespace    labels:      app:  redis  spec:   ports:    -  name:  redis-port      port:  6379    clusterIP:  None    selector:      app:  redis      appCluster:  redis-cluster  
1 2 3 4 #  应用该yaml文件创建service kubectl apply -f redis-service.yaml #  查看创建的service kubectl get service -n redis-namespace 
使用StatefulSet创建Redis集群节点 创建文件‘redis.yaml’,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 apiVersion:  apps/v1 kind:  StatefulSet metadata:   name:  redis-app    namespace:  redis-namespace  spec:   serviceName:  "redis-service"    replicas:  6    selector:      matchLabels:        app:  redis    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"                                                        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"  ]       storageClassName:  "nfs"          resources:          requests:            storage:  1Gi  
1 2 3 4 5 6 #  使用命令应用这个配置文件 kubectl apply -f redis.yaml #  查看集群情况 kubectl get statefulset -n redis-namespace -o wide #  查看pod情况 kubectl get pods -n redis-namespace -o wide 
StatefulSet部署成功 
集群编排 测试Pod间的DNS通信 可以看到我们已经成功创建了StatefulSet集群,集群中一共有6个Pod,但是这6个Pod还没有进行编排,也就是说谁是主谁是从还不知道,也不能一起协作;接下来我们要进行编排工作,但是首先,我们要确定这些redis实例之间是否能通信;相信大家注意到了每个pod都有自己的IP(这个IP是kubernetes分配的),那么有的小伙伴就会想,直接用这个IP通信不就行了吗,但是这个IP是动态分配的,也就是说每当Pod重启,kubernetes就会收回IP,下次重新分配到的就不一定是这个IP了;那怎么办呢,不用慌,由于我们采用了HeadlessService,所以各个Pod之间采用DNS通信,每个Pod都具有了DNS格式的IP,具体为:
1 <Pod名称>.<Service名称>.<namespace名称>.svc.cluster.lcoal 
我们可以随意进入一个Pod中的redis容器(没错,还是docker容器)
1 kubectl exec -it redis-app-0 -n redis-namespace -- /bin/sh 
在其中用DNS格式IP尝试连接其他节点的redis
1 redis-cli -h redis-app-4.redis-service.redis-namespace.svc.cluster.local 
成功使用 DNS格式的IP访问其他节点的redis实例 
使用redis-trib初始化redis集群 在确定了能Pod间能用DNS格式IP进行通信后,我们就可以进行下一步:集群编排,redis中为我们提供了redis-trib来进行这个工作
(1)创建一个ubuntu临时容器 1 kubectl run -it temp-ubuntu --image=ubuntu:18.04 --restart=Never -- /bin/bash 
(2)修改镜像源 接下来我们要修改镜像源,要不然无法安装必要的软件(并不是非要修改,根据你的网络环境选择);使用命令‘cat /etc/os-release’查看你安装的临时容器的版本
查看ubuntu的版本 
	在阿里云 中找到对应版本的内容,将其写入文件‘/etc/apt/sources.list’,这是ubuntu 18.04 LTS 版本的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 cat > /etc/apt/sources.list << EOF deb https://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src https://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb https://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb-src https://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb https://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb-src https://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse # deb https://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse # deb-src https://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse deb https://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse deb-src https://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse EOF 
(3)安装必要的软件 1 2 3 apt-get update #  可以看到这里安装了python环境、dns工具等 apt-get install -y vim wget python2.7 python-pip redis-tools dnsutils 
(4)利用python-pip安装redis-trib 1 pip install redis-trib==0.5.1 
(5)利用redis-trib创建3个master节点 1 2 3 4 redis-trib.py create \   `dig +short redis-app-0.redis-service.redis-namespace.svc.cluster.local`:6379 \   `dig +short redis-app-1.redis-service.redis-namespace.svc.cluster.local`:6379 \   `dig +short redis-app-2.redis-service.redis-namespace.svc.cluster.local`:6379 
(6)为每个master节点配置一个slave节点 1 2 3 4 5 6 7 8 9 10 11 redis-trib.py replicate \   --master-addr `dig +short redis-app-0.redis-service.redis-namespace.svc.cluster.local`:6379 \   --slave-addr `dig +short redis-app-3.redis-service.redis-namespace.svc.cluster.local`:6379 redis-trib.py replicate \   --master-addr `dig +short redis-app-1.redis-service.redis-namespace.svc.cluster.local`:6379 \   --slave-addr `dig +short redis-app-4.redis-service.redis-namespace.svc.cluster.local`:6379 redis-trib.py replicate \   --master-addr `dig +short redis-app-2.redis-service.redis-namespace.svc.cluster.local`:6379 \   --slave-addr `dig +short redis-app-5.redis-service.redis-namespace.svc.cluster.local`:6379 
(7)验证 	至此,我们的redis集群就初始化完成了,我们可以退出临时ubuntu容器,然后进入其中的一个pod,登录redis-cli,用命令‘cluster nodes’和‘cluster info’查看一下集群信息
1 2 3 4 5 6 7 #  进入2号节点 kubectl exec -it redis-app-2 -n redis-namespace -- /bin/sh #  登录redis-cli /usr/local/bin/redis-cli -c #  查看集群节点和信息 cluster nodes cluster info 
集群完成初始化 
注:有人会问,为什么要创建ubuntu临时容器呢,我们直接进入kubernetes集群中建好的redis节点,在redis-cli中使用redis-tools、dnsutils不好吗?我实践后发现容器中有权限问题,你不能安装dnsutils,这大概也是redis官方提供redis-trib的原因吧 
 
创建用于外界访问Service 前面我们创建了用于实现StatefulSet的Headless Service,但该Service没有Cluster Ip,因此不能用于外界访问。所以,我们还需要创建一个Service,专用于为Redis集群提供访问和负载均衡;
创建配置文件‘redis-access-service.yaml’,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 apiVersion:  v1 kind:  Service metadata:   name:  redis-access-service    namespace:  redis-namespace    labels:      app:  redis  spec:   type:  NodePort    ports:    -  name:  redis-port      protocol:  "TCP"      port:  6379      targetPort:  6379      nodePort:  30010    selector:      app:  redis      appCluster:  redis-cluster  
应用该文件,创建Service
1 kubectl apply -f redis-access-service.yaml 
查看创建的Service
1 kubectl get service -n redis-namespace -o wide 
Service创建成功 
此时我们已经可以使用redis集群中的任一节点的IP(并不是kubernetes集群节点的IP,master节点就不在redis集群中)和Service中暴露的端口30010访问这个redis集群
测试连接成功