使用容器化方式搭建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