k8s部署官方redis cluster集群

redis官方的集群方案redis cluster,去中心化云云,看起来各种美好,但是他不支持域名,部署带状态,整体来说放到容器环境里就是蛋疼。本文要讲的,是在这种蛋疼的条件下,把他装到k8s上去。redis装到k8s上,比直接在物理机,大概有那么一点优势就是其中的实例可以在物理机到处飘,出点问题还能自己恢复,顺便带点live probe,资源限制规划之类的。

redis集群相关的一些基本概念

redis cluster是用Gossip在各个节点直接达成一致,在数据分布上,会讲数据的key做crc16,然后mod到某个slot,各个节点知道彼此负责那一段slot,然后各管各的。当然,如果发错了,他会提示你重定向。

在官方描述里,有关于实现细节的一些说法。

Redis Cluster implements all the single key commands available in the non-distributed version of Redis. Commands performing complex multi-key operations like Set type unions or intersections are implemented as well as long as the keys all belong to the same node.

Redis Cluster implements a concept called hash tags that can be used in order to force certain keys to be stored in the same node. However during manual reshardings, multi-key operations may become unavailable for some time while single key operations are always available.

Redis Cluster does not support multiple databases like the stand alone version of Redis. There is just database 0 and the SELECT command is not allowed.

简单翻译一下就是说,如果是要操作多个key,比如事务,mset之类的,必须都在一个节点上才好使,然后如果有用户需要,可以用hashtag来人为的控制把一堆key放到某个节点上去,然而在做重分片的过程中,多key操作可能用不了。还有就是集群模式只支持db0.

吐槽

不支持域名。要是支持的话,用k8s的statefulset就可以比较容易的用稳定的网络名称了。
实现高可用,居然要最少6个节点。
减节点,或者节点挂了,不能自己做平衡。加节点要人肉倒是勉强能理解了。

正文开始

我个人总结了一下,redis上容器,至少要解决3个问题:
部署
这个就是statefulset一把梭了,相关配置文件configmap挂上去
节点挂了迁移
这个环节下,要做到的就是保留集群信息,同时更新自己的ip,所以我们在部署的时候,将集群相关的配置文件,写到不会丢失的持久存储中。然后在每次启动的时候,更新自己的ip
节点新增
这个只能是手工啦。
节点缩容
这个只能是手工啦。谁叫redis太蠢了呢

不废话了,上配置文件

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
---
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-cluster
labels:
app: redis-cluster
data:
fix-ip.sh: |
#!/bin/sh
CLUSTER_CONFIG="/data/nodes.conf"
if [ -f ${CLUSTER_CONFIG} ]; then
if [ -z "${POD_IP}" ]; then
echo "Unable to determine Pod IP address!"
exit 1
fi
echo "Updating my IP to ${POD_IP} in ${CLUSTER_CONFIG}"
sed -i.bak -e "/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${POD_IP}/" ${CLUSTER_CONFIG}
fi
exec "$@"
redis.conf: |+
cluster-enabled yes
cluster-require-full-coverage no
cluster-node-timeout 15000
cluster-config-file /data/nodes.conf
cluster-migration-barrier 1
appendonly yes
protected-mode no
save ""
appendfsync no
---
apiVersion: v1
kind: Service
metadata:
name: redis-cluster
labels:
app: redis-cluster
spec:
ports:
- port: 6379
targetPort: 6379
name: client
- port: 16379
targetPort: 16379
name: gossip
clusterIP: None
selector:
app: redis-cluster
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
labels:
app: redis-cluster
spec:
serviceName: redis-cluster
replicas: 6
selector:
matchLabels:
app: redis-cluster
template:
metadata:
labels:
app: redis-cluster
spec:
containers:
- name: redis
image: 10.168.136.193:5000/redis:5.0.3-alpine3.9
ports:
- containerPort: 6379
name: client
- containerPort: 16379
name: gossip
command: ["/conf/fix-ip.sh", "redis-server", "/conf/redis.conf"]
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: "1"
memory: 1Gi
readinessProbe:
exec:
command:
- sh
- -c
- "redis-cli -h $(hostname) ping"
initialDelaySeconds: 15
timeoutSeconds: 5
livenessProbe:
exec:
command:
- sh
- -c
- "redis-cli -h $(hostname) ping"
initialDelaySeconds: 20
periodSeconds: 3
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
volumeMounts:
- name: conf
mountPath: /conf
readOnly: false
- name: data
mountPath: /data
readOnly: false
volumes:
- name: conf
configMap:
name: redis-cluster
defaultMode: 0755
volumeClaimTemplates:
- metadata:
name: data
labels:
name: redis-cluster
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
storageClassName: rbd

配置中configmap redis-cluster中,redis.conf是redis固定配置部分,不包含cluster的字段,根据自己在单机版中的经验,以实际情况写。 fixip.sh是修改本机ip的脚本

第二部分是一个headless的服务,是statefulset必须的。没啥好说的

第三的主体部分。将pod的ip地址,作为环境变量注入进去,同时,将容器的启动命令改为自己的脚本,这样,在启动前就能把cluster配置里的ip地址修改为正确的值。同时我们配置了健康检查,再配置一个ceph rbd的持久存储,来报错集群配置。

部署

等到全部pod都启动完毕后,用下面的命令初始化集群

1
2
kubectl exec -it redis-cluster-0 -- redis-cli --cluster create --cluster-replicas 1 \
$(kubectl get pods -l app=redis-cluster -o jsonpath='{range.items[*]}{.status.podIP}:6379 ')

# 扩容

先把pod的数量加上去

1
kubectl scale statefulset redis-cluster --replicas=8

然后添加一个主节点

1
kubectl exec redis-cluster-0 -- redis-cli --cluster add-node  $(kubectl get pod redis-cluster-6 -o jsonpath='{.status.podIP}'):6379  $(kubectl get pod redis-cluster-0 -o jsonpath='{.status.podIP}'):6379

再添加一个从节点

1
kubectl exec redis-cluster-0 -- redis-cli --cluster add-node --cluster-slave  (kubectl get pod redis-cluster-7 -o jsonpath='{.status.podIP}'):6379  (kubectl get pod redis-cluster-0 -o jsonpath='{.status.podIP}'):6379

最后就是数据重新分片,注意,重新分片可能会影响业务哦

1
kubectl exec redis-cluster-0 -- redis-cli --cluster rebalance --cluster-use-empty-masters $(kubectl get pod redis-cluster-0 -o jsonpath='{.status.podIP}'):6379

缩容

这个就是扩容反过来啦
先删从,再删主,最后把pod减少。但是要注意删主之前,要把里面的数据弄走

从节点删除

先检查自己的角色,要有slave字样

1
2
 kubectl exec redis-cluster-7 -- redis-cli cluster nodes | grep myself
#d960b6891bc3c80b4b71b23d730068ef6f05be9c 172.30.15.7:6379@16379 myself,slave b5809f532c946171e0694ceaede084a25ae52b6a 0 1551858798000 6 connected

删了,命令行的最后一个参数就是id

1
kubectl exec redis-cluster-0 -- redis-cli --cluster del-node  (kubectl get pod redis-cluster-0 -o jsonpath='{.status.podIP}'):6379  960b6891bc3c80b4b71b23d730068ef6f05be9c

主节点删除

主节点要挪走数据再迁移,先看看其他的主

1
kubectl exec redis-cluster-6 -- redis-cli cluster nodes | grep master | grep -v myself

挪走

1
2
3
4
5
kubectl exec redis-cluster-0 -- redis-cli --cluster reshard --cluster-yes \
--cluster-from ccbcfa696a0e0e54df55dfa23060ed28b44c393f \
--cluster-to f6d0d02e8124d55f2ad933f52200fc8d55f9389f \
--cluster-slots 16384 \
$(kubectl get pod redis-cluster-0 -o jsonpath='{.status.podIP}'):6379


1
2
3
kubectl exec redis-cluster-0 -- redis-cli --cluster del-node \
$(kubectl get pod redis-cluster-0 -o jsonpath='{.status.podIP}'):6379 \
ccbcfa696a0e0e54df55dfa23060ed28b44c393f

数据重新平衡一下

1
2
kubectl exec redis-cluster-0 -- redis-cli --cluster rebalance --cluster-use-empty-masters \
$(kubectl get pod redis-cluster-0 -o jsonpath='{.status.podIP}'):6379

最后终于可以快乐的干掉pod了

1
kubectl scale statefulset redis-cluster --replicas=6