使用容器化方式搭建Redis集群具有简化部署、灵活扩展、资源隔离等优势,可以提高Redis集群的部署、管理、扩展和维护效率,降低运维成本。本文介绍如何基于ACK Serverless集群,使用ECI来搭建Redis集群。
背景信息
Redis支持以集群模式运行,在该模式下,Redis将所有存储空间分为16384个哈希槽,集群中的每个Master节点负责N个哈希槽(一个数据分片),当用户写入一条数据时,Redis计算其哈希槽,然后将数据写在负责该哈希槽的节点上。且每个Master节点可以添加一个或多个Slave节点,当某个Master节点不可用时,其Slave节点自动代替Master节点继续工作。
由此可见,在Redis集群模式下,可以获得更高的吞吐量和一定程度的可用性。需要注意的是,在集群模式下,Redis仍不能保证数据零丢失,更多信息,请参见Redis官方文档。
前提条件
已创建ACK Serverless集群,且集群满足以下条件:
集群具备公网环境,可以通过公网拉取镜像。
集群中已部署存储插件。
建议使用CSI插件,确保集群已部署阿里云CSI-Provisioner组件。
集群中已部署CoreDNS。
搭建Redis集群
本文以Redis官方镜像6.0.8版本作为示例,创建一个6个节点的Redis集群,其中3个Master节点,3个Slave节点。因为每个节点有自己的状态和标识,所以使用Statefulset来创建Pod,此外需要为每个节点挂载一个云盘,用于持久化存储节点数据。
建议选择较新Redis版本,如果Redis版本低于5.0,则初始化集群所用的命令可能会有所不同。
创建一个ConfigMap,用来存储和管理Redis集群的配置。
kubectl create -f redis-config.yaml
redis-config.yaml的内容示例如下:
apiVersion: v1 kind: ConfigMap metadata: name: redis-cluster data: redis.conf: | bind 0.0.0.0 port 6379 cluster-announce-bus-port 16379 cluster-enabled yes appendonly yes cluster-node-timeout 5000 dir /data cluster-config-file /data/nodes.conf requirepass pass123 masterauth pass123
字段
dir
为Redis节点数据的持久化存储目录,所以Pod的/data
目录应当是一个持久化存储目录。字段
cluster-config-file
是Redis集群的节点信息,由Redis节点自动生成和修改,该配置也应当是一个持久化存储目录,以便节点宕机恢复后能够找到集群中的其他节点,并继续工作。
创建一个headless类型的Service。
kubectl create -f redis-service.yaml
redis-service.yaml的内容示例如下:
apiVersion: v1 kind: Service metadata: name: redis-cluster-svc spec: clusterIP: None selector: app: redis-cluster
创建一个Statefulset,用于部署Redis。
创建Statefulset时引用之前创建的Service,将ConfigMap挂载到每一个Pod的
/config
,并且为每一个Pod创建一个PVC,挂载到/data
。kubectl create -f redis.yaml
redis.yaml的内容示例如下:
apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-cluster spec: selector: matchLabels: app: redis-cluster serviceName: redis-cluster-svc replicas: 6 template: metadata: labels: app: redis-cluster alibabacloud.com/eci: "true" spec: terminationGracePeriodSeconds: 10 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - redis-cluster topologyKey: kubernetes.io/hostname weight: 100 containers: - name: redis image: redis:6.0.8 command: ["redis-server", "/config/redis.conf"] ports: - name: redis containerPort: 6379 protocol: TCP - name: election containerPort: 16379 protocol: TCP volumeMounts: - name: redis-conf mountPath: /config - name: pvc-essd-redis-data mountPath: /data volumes: - name: redis-conf configMap: name: redis-cluster items: - key: redis.conf path: redis.conf volumeClaimTemplates: - metadata: name: pvc-essd-redis-data spec: accessModes: [ "ReadWriteOnce" ] storageClassName: alicloud-disk-essd resources: requests: storage: 20Gi
创建后,等待Statefulset中的所有Pod达到Ready状态。
kubectl get statefulset redis-cluster -o wide
预期返回:
NAME READY AGE CONTAINERS IMAGES redis-cluster 6/6 8m52s redis redis:6.0.8
初始化集群。
获取每个节点的IP地址。
目前Redis还不支持以hostname方式初始化集群,所以需要先获取每个节点的IP地址。
kubectl get pods -l app=redis-cluster -o wide
返回类似以下信息:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES redis-cluster-0 1/1 Running 0 9m37s 172.16.55.6 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-1 1/1 Running 0 9m5s 172.16.55.7 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-2 1/1 Running 0 8m30s 172.16.55.8 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-3 1/1 Running 0 7m46s 172.16.55.9 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-4 1/1 Running 0 7m7s 172.16.55.10 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-5 1/1 Running 0 6m30s 172.16.55.11 virtual-kubelet-cn-beijing-k <none> <none>
登录到其中一个Redis节点。
kubectl exec -ti redis-cluster-0 bash
为6个节点执行初始化命令,当选项
--cluster-replicas
指定为1时,表示为每个Master节点分配一个Slave节点,这样集群中刚好3个Master节点和3个Slave节点。redis-cli -a pass123 --cluster create 172.16.55.6:6379 172.16.55.7:6379 172.16.55.8:6379 172.16.55.9:6379 172.16.55.10:6379 172.16.55.11:6379 --cluster-replicas 1
返回类似以下信息表示初始化成功。
[OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
使用Redis,
由于之前创建了一个headless类型的Service,kubernetes集群会为该服务分配一个DNS A记录,格式为
{service name}.{service namespace}.svc.{domain}
,可以映射到后端Pod IP列表,即每次访问该服务名时,将随机解析到其中一个Redis节点上。因此,进入集群中的任意一个Pod中均可以访问Redis服务。
redis-cli -a pass123 -c -h redis-cluster-svc.default.svc.cluster.local -p 6379
登录后的测试示例如下:
172.16.55.8> set k1 v1 OK 172.16.55.8> get k1 "v1"
扩容Redis集群
目前的部署方式不支持动态扩容集群,主要的问题是每次新增节点都需要为集群中的所有节点重新分配哈希槽,此时可以在Redis镜像中添加脚本,以便每个Pod启动时自动完成该操作,但是当集群中的数据非常多时,连续的重新分片会导致扩容操作非常缓慢,并且有可能在重新分片期间因为耗尽Redis集群带宽而导致依赖此服务的所有客户端超时。另一个问题是没有好的策略确定新启动的Pod应该作为Master节点还是Slave节点。
基于上述问题,接下来以手动分片演示集群扩容。
修改Statefulset副本,将Statefulset的副本数从6改为8。
kubectl scale statefulsets redis-cluster --replicas=8
获取新节点的IP。
等待所有Pod达到Ready状态后,执行以下命令获取新节点的IP地址。
kubectl get pods -l app=redis-cluster -o wide
预期返回如下,其中redis-cluster-6和redis-cluster-7为新增加的节点。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES redis-cluster-0 1/1 Running 0 88m 172.16.55.6 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-1 1/1 Running 0 88m 172.16.55.7 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-2 1/1 Running 0 87m 172.16.55.8 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-3 1/1 Running 0 87m 172.16.55.9 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-4 1/1 Running 0 86m 172.16.55.10 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-5 1/1 Running 0 85m 172.16.55.11 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-6 1/1 Running 0 52s 172.16.55.16 virtual-kubelet-cn-beijing-k <none> <none> redis-cluster-7 1/1 Running 0 32s 172.16.55.17 virtual-kubelet-cn-beijing-k <none> <none>
添加Master节点。
登录到其中一个节点(以redis-cluster-0节点为例),执行以下命令将redis-cluster-6添加为集群的Master节点。
kubectl exec -ti redis-cluster-0 bash
redis-cli -a pass123 --cluster add-node 172.16.55.16:6379 172.16.55.6:6379
说明172.16.55.6:6379
为任意一个旧节点的访问地址,连接成功后,redis-cli将自动获取其他所有节点。查看redis-cluster-6节点的ID。
redis-cli -a pass123 -c cluster nodes | grep 172.16.55.16:6379 | awk '{print $1}'
预期返回:
47879390ecc7635f5c57d3e324e2134b24******
重新分配哈希槽。
现在共有4个Master节点,共同分担16384个哈希槽,平均每个节点分担的哈希槽数量为
16384 / 4 = 4096
,即需要从之前的3个Master分出4096个哈希槽给新节点。执行以下命令开始分配哈希槽,其中
172.16.55.6:6379
为任意一个旧节点的访问地址,连接成功后,redis-cli将自动获取其他所有节点。redis-cli -a pass123 --cluster reshard 172.16.55.6:6379
以上命令是交互式的,请根据提示依次输入信息:
How many slots do you want to move (from 1 to 16384)? 4096 What is the receiving node ID? 47879390ecc7635f5c57d3e324e2134b24****** Source node #1: all Do you want to proceed with the proposed reshard plan (yes/no)? yes
准备为新节点(redis-cluster-6)分配的哈希槽数量:4096
接收节点的ID,即新节点的ID:47879390ecc7635f5c57d3e324e2134b24******
来源节点的ID,从所有节点平均抽取哈希槽给新节点:all
确认分配:yes
操作后,需等待分配完毕,在此期间Redis集群可以正常提供服务。
添加Slave节点。
执行以下命令将redis-cluster-7添加为redis-cluster-6的Slave节点。
redis-cli -a pass123 --cluster add-node 172.16.55.17:6379 172.16.55.6:6379 --cluster-slave --cluster-master-id 47879390ecc7635f5c57d3e324e2134b24******
说明172.16.55.6:6379
为任意一个旧节点的访问地址,连接成功后,redis-cli将自动获取其他所有节点。47879390ecc7635f5c57d3e324e2134b24******
为该Slave节点要跟随的Master节点ID,即redis-cluster-6节点的ID。
添加后可以看到有4个Master节点,并且每个Master有一个副本节点。
redis-cli -a pass123 -c cluster nodes
返回示例如下:
47879390ecc7635f5c57d3e324e2134b24****** 172.16.55.16:6379@16379 master - 0 1667383406000 7 connected 0-1364 5461-6826 10923-12287 75a7398a45f9696066eaa6ac7968b13a47****** 172.16.55.11:6379@16379 slave b8f6b826241b47f29a4bdde14104b28fe8****** 0 1667383406514 2 connected b7849f8577e43d5a6da51bc78ae809bbb1****** 172.16.55.17:6379@16379 slave 47879390ecc7635f5c57d3e324e2134b24****** 0 1667383406314 7 connected b8f6b826241b47f29a4bdde14104b28fe8****** 172.16.55.7:6379@16379 master - 0 1667383406815 2 connected 6827-10922 ffce547186e0be179830cb0dca47c203f6****** 172.16.55.6:6379@16379 myself,master - 0 1667383406000 1 connected 1365-5460 833a939cde93991c8a16c41fb2568b8642****** 172.16.55.8:6379@16379 master - 0 1667383406514 3 connected 12288-16383 a4df1a26394bfcb833914278744219db01****** 172.16.55.9:6379@16379 slave 833a939cde93991c8a16c41fb2568b8642****** 0 1667383407515 3 connected c4234d71343ca4dbe07822e17f7572ac30****** 172.16.55.10:6379@16379 slave ffce547186e0be179830cb0dca47c203f6****** 0 1667383405813 1 connected
缩容Redis集群
集群缩容时,由于StatefulSet只能以和创建Pod时相反的顺序逐个删除Pod,即如果要删除一个Master节和一个Slave节点,就只能删除redis-cluster-7和redis-cluster-6。其次,删除这两个节点前必须先把该节点上所有哈希槽移动到其他节点,否则将会有数据丢失,并且Redis集群将拒绝服务。
重新分配哈希槽。
为了避免剩下的3个Master节点出现负载倾斜的情况,需要将redis-cluster-6的哈希槽平均分配给其它Master节点,即需要执行三次重新分片的操作,操作方法与扩容时执行的分片操作类似,但接收节点需变更为其它Master节点中的一个,来源节点为redis-cluster-6的节点ID。示例如下:
redis-cli -a pass123 --cluster reshard 172.16.55.6:6379
以上命令是交互式的,请根据提示依次输入信息:
How many slots do you want to move (from 1 to 16384)? 4096 What is the receiving node ID? ffce547186e0be179830cb0dca47c203f6****** Source node #1: 47879390ecc7635f5c57d3e324e2134b24****** Source node #2: done Do you want to proceed with the proposed reshard plan (yes/no)? yes
准备给旧节点分配的哈希槽数量:4096
接收节点的ID,即旧节点的ID:ffce547186e0be179830cb0dca47c203f6******
来源节点的ID,从redis-cluster-6节点抽取哈希槽给旧节点:47879390ecc7635f5c57d3e324e2134b24******
确认分配:yes
修改Statefulset副本,将Statefulset的副本数从8改为6。
kubectl scale statefulsets redis-cluster --replicas=6
查看Redis集群的Pod信息,可以看到集群缩容回6个节点。
kubectl get pods -l app=redis-cluster -o wide
删除Redis集群
删除Redis集群时需要删除StatefulSet,Service和ConfigMap。
kubectl delete statefulset redis-cluster
kubectl delete svc redis-cluster-svc
kubectl delete cm redis-cluster
需要注意的是,StatefulSet删除后,相关PVC并不会被自动删除,您需要单独删除PVC。PVC删除后,相应的云盘和数据也将被删除。
kubectl delete pvc -l app=redis-cluster