前言 上一篇介绍了 StatefulSet 的基础原理,但是只使用了应用之一的拓扑状态。这一篇将会介绍使用存储状态。
应用背景 应用的多个实例分别绑定了不同的存储数据。典型例子就是一个数据库应用的多个存储实例。StatefulSet 的核心功能就是通过某种方式记录这些状态,然后在Pod被重新创建时,能够为新Pod恢复这些状态。比较典型之一,Redis的主从架构。
知识点解析 主从架构
当我们启动多个redis实例的时候,他们相互之间就可以通过slaveof 命令形成主库和从库关系,之后会按照三个阶段完成数据的第一次同步。
第一阶段是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立连接,并告诉主库即将建立连接,主库确认回复后,主从库间就可以开始同步了。 在第二阶段,主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照RDB。 第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成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访问模式:
ReadWriteOnce——该卷可以被单个节点以读/写模式挂载 ReadOnlyMany——该卷可以被多个节点以只读模式挂载 ReadWriteMany——该卷可以被多个节点以读/写模式挂载 在命令行中,访问模式缩写为:
RWO - ReadWriteOnce ROX - ReadOnlyMany RWX - ReadWriteMany PVC回收策略:
Retain(保留)——手动回收 Recycle(回收)——基本擦除( rm -rf /thevolume/* ) Delete(删除)——关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷) 将被删除当前,只有 NFS 和 HostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。
PVC的状态:
Available(可用)——一块空闲资源还没有被任何声明绑定 Bound(已绑定)——卷已经被声明绑定 Released(已释放)——声明被删除,但是资源还未被集群重新声明 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 spec: storageClassName: redis persistentVolumeReclaimPolicy: Retain capacity: storage: 1Gi accessModes: - ReadWriteOnce nfs: path: /Public/redis-master server: 192.168.1.10 ---- 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: accessModes: [ "ReadWriteOnce" ] storageClassName: "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: accessModes: [ "ReadWriteOnce" ] storageClassName: "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 ~] 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 ~] 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 ~] root@redis-master-0:/data total 4 -rw-r--r-- 1 root root 196 Apr 8 09:01 dump.rdb root@redis-master-0:/data /data root@redis-master-0:/data 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 total 4 -rw-r--r-- 1 root root 196 Apr 8 09:01 dump.rdb root@redis-master-0:/data 127.0.0.1:6379> info 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 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 / 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 / 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不变 有序部署和扩展 有序收缩