深入理解StatefulSet(二)

前言

上一篇介绍了 StatefulSet 的基础原理,但是只使用了应用之一的拓扑状态。这一篇将会介绍使用存储状态。

应用背景

应用的多个实例分别绑定了不同的存储数据。典型例子就是一个数据库应用的多个存储实例。StatefulSet 的核心功能就是通过某种方式记录这些状态,然后在Pod被重新创建时,能够为新Pod恢复这些状态。比较典型之一,Redis的主从架构。

知识点解析

主从架构

1649409054685.png

当我们启动多个redis实例的时候,他们相互之间就可以通过slaveof 命令形成主库和从库关系,之后会按照三个阶段完成数据的第一次同步。

  1. 第一阶段是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立连接,并告诉主库即将建立连接,主库确认回复后,主从库间就可以开始同步了。
  2. 在第二阶段,主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照RDB。
  3. 第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成RDB文件发送后,就会把此时replocation buffer中修改操作发送给从库,从库再执行这些操作。这样一来,主从库就实现同步了。

之后的同步如果不出现意外,都会采用增量的方式进行同步。

此外,有一些其他注意事项:

  • 为避免数据混乱,从节点是默认不允许写的
  • 首次连接需要同步全量 RDB ,此后执行基于长连接的命令传播
  • 建议使用 主-从-从 的级联架构,减轻每一个从节点连进来主节点都要进行一次生成RDB和传输RDB的压力
  • 从节点断连后,如短时间内重新连入,则只需要进行增量更新。如未同步的数据过多 (repl_backlog_buffer中未同步的数据已被覆盖),则需要进行一次全量同步

综上所述,Redis主从架构的基本原理在于同步和保持可用性的持久化。

PV/PVC

PV (PersistentVolume)是设置的存储,群集的一部分,也是一种资源。场景的PV有很多种,比如NAS、NFS和ceph等等。

PVC 是用户的存储请求,和Pod类似。Pod消耗节点资源,PVC消耗PV资源。并且可以设置请求特定的大小和模式。其主要作用是确保相关Pod正在使用的PVC不会从系统中删除。因为如果被移除的话会导致数据丢失。

PV访问模式:

  1. ReadWriteOnce——该卷可以被单个节点以读/写模式挂载
  2. ReadOnlyMany——该卷可以被多个节点以只读模式挂载
  3. ReadWriteMany——该卷可以被多个节点以读/写模式挂载

在命令行中,访问模式缩写为:

  • RWO - ReadWriteOnce
  • ROX - ReadOnlyMany
  • RWX - ReadWriteMany

PVC回收策略:

  1. Retain(保留)——手动回收
  2. Recycle(回收)——基本擦除( rm -rf /thevolume/* )
  3. Delete(删除)——关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)

将被删除当前,只有 NFS 和 HostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。

PVC的状态:

  1. Available(可用)——一块空闲资源还没有被任何声明绑定
  2. Bound(已绑定)——卷已经被声明绑定
  3. Released(已释放)——声明被删除,但是资源还未被集群重新声明
  4. Failed(失败)——该卷的自动回收失败

使用PVC时需要注意,在每个node节点上都需要安装nfs-utis,不然会报错,比如:

1
2
3
4
5
6
7
8
9
10
  Warning  FailedMount  54s (x8 over 117s)  kubelet            MountVolume.SetUp failed for volume "redis01" : mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t nfs 192.168.1.10:/Public/redis-master /var/lib/kubelet/pods/0645dd81-4bba-4fd5-bf08-ffeb72367254/volumes/kubernetes.io~nfs/redis01
Output: mount: wrong fs type, bad option, bad superblock on 192.168.1.10:/Public/redis-master,
missing codepage or helper program, or other error
(for several filesystems (e.g. nfs, cifs) you might
need a /sbin/mount.<type> helper program)

In some cases useful info is found in syslog - try
dmesg | tail or so.

应用部署

创建PV

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
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis01 # PV名字
spec:
storageClassName: redis #卷的名字
persistentVolumeReclaimPolicy: Retain #回收模式
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce # 访问模式
nfs:
path: /Public/redis-master # NFS目录
server: 192.168.1.10 # nfs 信息
----
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis02
spec:
storageClassName: redis
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
nfs:
path: /Public/redis2
server: 192.168.1.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis03
spec:
storageClassName: redis
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
nfs:
path: /Public/redis3
server: 192.168.1.10

查看

1
2
3
4
5
[root@uat-master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
redis01 1Gi RWO Retain Bound default/redis-data-redis-master-0 redis 60m
redis02 1Gi RWO Retain Bound default/redis-data-redis-slave-0 redis 60m
redis03 1Gi RWO Retain Bound default/redis-data-redis-slave-1 redis 60m

redis配置文件

redis-cm.yaml

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
data:
master.conf: |
port 6379
slave.conf: |
port 6379
slaveof redis-master-0.redis-master 6379

redis-master-sts.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
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-master
spec:
selector:
matchLabels:
app: redis-master
serviceName: redis-master
template:
metadata:
labels:
app: redis-master
spec:
containers:
- name: redis
image: redis
imagePullPolicy: IfNotPresent
command:
- sh
args:
- -c
- redis-server /usr/local/etc/redis/redis.conf
ports:
- containerPort: 6379
name: masterport
protocol: TCP
volumeMounts:
- mountPath: /usr/local/etc/redis
name: conf
- name: redis-data
mountPath: /data
volumes:
- name: conf
configMap:
name: redis-config
items:
- key: master.conf
path: redis.conf
volumeClaimTemplates:
- metadata:
name: redis-data
spec: # 必须满足下面两个,pod才会绑定到pv上面
accessModes: [ "ReadWriteOnce" ] #读取模式
storageClassName: "redis" # 绑定pv名字为redis
resources:
requests:
storage: 1Gi

redis-slave-sts.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
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-slave
spec:
selector:
matchLabels:
app: redis-slave
serviceName: redis-slave
template:
metadata:
labels:
app: redis-slave
spec:
containers:
- name: redis
image: redis
imagePullPolicy: IfNotPresent
command:
- sh
args:
- -c
- redis-server /usr/local/etc/redis/redis.conf
ports:
- containerPort: 6379
name: redis-slave
protocol: TCP
volumeMounts:
- mountPath: /usr/local/etc/redis
name: conf
- name: redis-data
mountPath: /data
volumes:
- name: conf
configMap:
name: redis-config
items:
- key: slave.conf
path: redis.conf
volumeClaimTemplates:
- metadata:
name: redis-data
spec: # 必须满足下面两个,pod才会绑定到pv上面
accessModes: [ "ReadWriteOnce" ] # 读取模式
storageClassName: "redis" # 绑定pv名字为redis
resources:
requests:
storage: 1Gi

master-head.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: redis-master
spec:
selector:
app: redis-master
clusterIP: None
ports:
- port: 6379
targetPort: 6379
name: redis

slave-head.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: redis-slave
spec:
ports:
- port: 6379
targetPort: 6379
protocol: TCP
selector:
app: redis-slave
clusterIP: None

上述编排文件都apply后,检查:

1
2
3
4
5
6
7
8
9
10
[root@uat-master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
redis01 1Gi RWO Retain Bound default/redis-data-redis-master-0 redis 72m
redis02 1Gi RWO Retain Bound default/redis-data-redis-slave-0 redis 72m
redis03 1Gi RWO Retain Bound default/redis-data-redis-slave-1 redis 72m
[root@uat-master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
redis-data-redis-master-0 Bound redis01 1Gi RWO redis 54m
redis-data-redis-slave-0 Bound redis02 1Gi RWO redis 51m
redis-data-redis-slave-1 Bound redis03 1Gi RWO redis 40m

登录master pod

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
[root@uat-master ~]# kubectl exec -it po/redis-master-0 -- bash
root@redis-master-0:/data# ls -l
total 4
-rw-r--r-- 1 root root 196 Apr 8 09:01 dump.rdb
root@redis-master-0:/data# pwd
/data
root@redis-master-0:/data# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 46G 15G 31G 33% /
tmpfs 64M 0 64M 0% /dev
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
192.168.1.10:/Public/redis-master 1.3T 505G 816G 39% /data
/dev/mapper/centos-root 46G 15G 31G 33% /etc/hosts
shm 64M 0 64M 0% /dev/shm
tmpfs 2.0G 12K 2.0G 1% /run/secrets/kubernetes.io/serviceaccount
tmpfs 2.0G 0 2.0G 0% /proc/acpi
tmpfs 2.0G 0 2.0G 0% /proc/scsi
tmpfs 2.0G 0 2.0G 0% /sys/firmware
root@redis-master-0:/data# ls -l
total 4
-rw-r--r-- 1 root root 196 Apr 8 09:01 dump.rdb
root@redis-master-0:/data# redis-cli
127.0.0.1:6379> info Replication
# Replication
role:master
connected_slaves:2
slave0:ip=10.200.96.163,port=6379,state=online,offset=3668,lag=1
slave1:ip=10.200.96.159,port=6379,state=online,offset=3668,lag=1
master_failover_state:no-failover
master_replid:33c2abd3e8ee089411772f5b64790d9463400872
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3668
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3668
127.0.0.1:6379> info Keyspace
# Keyspace
db0:keys=2,expires=0,avg_ttl=0

在显示信息中看到了slave节点的信息,而且生成了rdb文件。之后检查nfs上也存在对应的文件,无论是master pod还是slave pod对应的目录。

解析DNS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ # nslookup redis-slave.default.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name: redis-slave.default.svc.cluster.local
Address 1: 10.200.128.144 redis-slave-0.redis-slave.default.svc.cluster.local
Address 2: 10.200.96.162 redis-slave-1.redis-slave.default.svc.cluster.local

/ # nslookup redis-master.default.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name: redis-master.default.svc.cluster.local
Address 1: 10.200.128.143 redis-master-0.redis-master.default.svc.cluster.local

总结

  • 稳定的持久化存储,Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  • 稳定的网络标识符,Pod重新调度后PodName和HostName不变
  • 有序部署和扩展
  • 有序收缩