前言 在之前的博客’kubernetes集群部署
‘ 中,我们搭建了一个kubernetes集群;而在博客‘kubernetes部署Nginx集群
’中,我们使用Deployment部署了一个无状态的集群;在本文中我们在这个kubernetes集群中使用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集群
测试连接成功