转载:https://www.cnblogs.com/edisonchou/p/aspnet_core_on_k8s_deepstudy_part8.html
本篇已加入《.NET Core on K8S学习实践系列文章索引》,可以点击查看更多容器化技术相关系列文章。
在Docker中我们知道,要想实现数据的持久化(所谓Docker的数据持久化即数据不随着Container的结束而结束),需要将数据从宿主机挂载到容器中,常用的手段就是Volume数据卷。在K8S中,也提供了存储模型Volume,支持我们将应用中的数据持久化存储到容器中。
一、Volume
1.1 关于K8S Volume
为了持久化保存容器的数据,我们可以使用K8S Volume,其本质上也是一个目录,与Docker Volume没有什么区别。
需要注意的是:K8S Volume的生命周期独立于容器,Pod中的容器可能被销毁和重建,但Volume会被保留。
当Volume被mount到Pod中,Pod中的的所有容器都可以访问这个Volume。在K8S中,支持多种backend类型,例如emptyDir, hostPath, NFS, Ceph以及一些云服务商提供的存储服务等等。对Pod来说,它不需要关心到底数据会被存储在本地文件系统中还是远程云端硬盘中,它认为所有类型的Volume都只是一个目录而已。
1.2 使用K8S Volume
(1)emptyDir
作为K8S最基础的Volume类型,emptyDir提供了最基础的持久化方案,但是这个方案不怎么好。因为,emptyDir对于Pod来说并非持久的(它对于容器来说是持久化的),因为当Pod从节点删除时,Volume的内容也会被删除。但如果只是容器被销毁而Pod还在,则Volume不会受影响。
换句话说:emptyDir Volume的生命周期与Pod一致。鉴于此特性,不建议在实际中使用此类型Volume。
(2)hostPath
相对于emptyDir,hotPath则克服了其生命周期的弱点,如果Pod被销毁,hostPath对应的目录还是会被保留。不过,如果一旦Host崩溃,hostPath也就无法访问了。因为,hostPath是将Docker Host文件系统中已经存在的目录mount给Pod的容器,所以会依赖于Host。
在K8S中,那些需要访问K8S或Docker内部数据(配置文件和二进制库)的应用需要使用到hostPath,比如kube-apiserver和kube-controller-manager这样的应用。下面的配置就是kube-apiserver的持久化设置,其定义了3个hostPath:ca-certs, etc-pki以及k8s-certs,分别对应Host目录为/etc/ssl/certs, /etc/pki 以及 /etc/kubernetes/pki。
volumeMounts: - mountPath: /etc/ssl/certs name: ca-certs readOnly: true - mountPath: /etc/pki name: etc-pki readOnly: true - mountPath: /etc/kubernetes/pki name: k8s-certs readOnly: true dnsPolicy: ClusterFirst enableServiceLinks: true hostNetwork: true nodeName: k8s-master priority: 2000000000 nodeName: k8s-master priority: 2000000000 priorityClassName: system-cluster-critical restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 tolerations: - effect: NoExecute operator: Exists volumes: - hostPath: path: /etc/ssl/certs type: DirectoryOrCreate name: ca-certs - hostPath: path: /etc/pki type: DirectoryOrCreate name: etc-pki - hostPath: path: /etc/kubernetes/pki type: DirectoryOrCreate name: k8s-certs
(3)外部Storage Povider
如果我们的K8S是部署在AWS、GCE、Azure等公有云上,那么可以直接使用云硬盘做为Volume。这里由于我没有使用,所以跳过,有使用的朋友可以直接参考各云服务提供商的参考文档进行配置。
二、PersistentVolume与PersistentVolumeClaim
2.1 关于PersistentVolume与PersistentVolumeClaim
前面提到的几种方案在可管理性上均有不足,特别是当集群规模较大的时候,效率和安全性均有待提高。因此,K8S提供了一个解决方案:PersistentVolume 和 PersistentVolumeClaim,以下简称PV和PVC。
PV是外部存储系统中的一块存储空间,由管理员创建和维护。与Volume一样,PV具有持久性,生命周期独立于Pod。
PVC则是对PV的申请(Claim),PVC通常由普通用户创建和维护。当需要为Pod分配存储资源的时候,用户就可以创建一个PVC,指明存储资源的容量大小和访问方式(比如ReadOnly)等信息,K8S会查找并提供满足条件的PV。
了解ASP.NET Identity的童鞋应该对Claim这个词不陌生,如果把我们的认证信息看成一个Claims,那么其中的一个一个的键值对就是Claim。我们常用的ClaimTypes如下图所示,我们可以通过Claim定位到认证信息中的Value。
同理,我们知道了Claim也就可以定位到我们要使用的哪个PV的地址。
与K8S Volume一样,K8S PersistentVolume也支持多种类型的存储,比如NFS、AWS EBS、Ceph等等。
2.2 NFS PV的使用
NFS是网络文件系统 (Network File System), 它允许系统将本地目录和文件共享给网络上的其他系统。通过 NFS,用户和应用程序可以访问远程系统上的文件,就象它们是本地文件一样。
关于如何为CentOS配置NFS,请参考这一篇文章《CentOS7安装NFS服务》。
这里假设已经为我们的k8s-master节点搭建了一个NFS服务器,目录为/edc/k8s/nfsdata,如下图所示:
(1)创建一个PV
接下来我们就来创建一个PV,其yaml配置文件如下:
apiVersion: v1 kind: PersistentVolume metadata: name: edc-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: nfs nfs: path: /edc/k8s/nfsdata/edc-pv server: 192.168.2.100
其中:
- capacity指定了PV的容量为1GB
- accessModes指定访问模式为ReadWriteOnce,表示PV能够以Read-Write模式mount到单个节点。此外,还支持ReadOnlyMany和ReadWriteMany,分别代表PV能以Read-Only模式或者Read-Write模式mount到多个节点。这里ReadWriteOnce只mount到单个节点,即k8s-master(192.168.2.100)。
- persistentVolumeReclaimPolicy指定了此PV的回收策略为Recycle,表示清除PV中的数据。此外,还支持Retain和Delete,Retain表示需要管理员手动回收,类似于你用C/C++还需要手动写free代码释放空间。而Delete呢,表示删除Storage Provider中的对应存储资源,如果你使用的是外部云服务提供商的存储空间的话。
- storageClassName指定了PV的class为nfs。
- nfs配置项指定了PV在NFS服务器上对应的目录,如果没有可以事先创建一下。
理解了其中的配置项,我们创建该PV,可以看到其状态Status变为了Available,表示可以被PVC申请啦。
(2)创建一个PVC
与创建PV不同,创建PVC只需指定PV容量、访问模式以及class即可:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: edc-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs
有了配置文件,就可以创建PVC了:
可以看到,edc-pvc已经Bound到edc-pv了,申请PV成功。
申请成功之后,我们就可以在Pod中使用了,下面是一个示例Pod的配置文件:
apiVersion: v1 kind: Pod metadata: name: edc-pv-pod spec: containers: - name: edc-pv-pod image: busybox args: - /bin/sh - -c - sleep 30000 volumeMounts: - mountPath: "/mydata" name: mydata volumes: - name: mydata persistentVolumeClaim: claimName: edc-pvc
通过kubectl创建该pod,如下所示:
接下来验证一下PV是否可用:
可以看到,在Pod中创建的文件/mydata/hello已经保存到了NFS服务器目录的edc-pv目录下了。
2.2 NFS PV的回收
当我们不再需要某个PV时,也可以使用PVC来回收PV,如下所示:
kubectl delete pvc edc-pvc
当edc-pvc被删除后,我们会发现K8S启动了一个新Pod,这个Pod就是用来清除edc-pv的数据的。数据的清理需要一个过程,完成后edc-pv的状态会重新恢复为Available,此时可以被新的PVC申请。
此外,由于我们设置的回收策略为Recycle,所以Pod中的数据也被清除了:
如果希望能够保留这些数据,那么我们需要将回收策略改为Retain:
apiVersion: v1 kind: PersistentVolume metadata: name: edc-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /edc/k8s/nfsdata/edc-pv server: 192.168.2.100
这里就不再验证Retain的效果了。
三、MySQL持久化存储案例
3.1 准备工作
这里我们来演示一个MySQL持久化存储的案例:
(1)创建PV和PVC
准备PV和PVC的yaml:
-- mysql-pv apiVersion: v1 kind: PersistentVolume metadata: name: mysql-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /edc/k8s/nfsdata/mysql-pv server: k8s-master -- mysql-pvc apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs
通过kubectl apply创建PV和PVC:
kubectl apply -f mysql-pv.yaml kubectl apply -f mysql-pvc.yaml
可以看到,mysql-pvc已经申请到了mysql-pv。
(2)部署MySQL
准备yaml配置文件:
apiVersion: v1 kind: Service metadata: name: mysql-service spec: ports: - port: 3306 selector: app: mysql --- apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - image: mysql:5.6 name: mysql env: - name: MYSQL_ROOT_PASSWORD value: password ports: - containerPort: 3306 name: mysql-container volumeMounts: - name: mysql-storage mountPath: /var/lib/mysql volumes: - name: mysql-storage persistentVolumeClaim: claimName: mysql-pvc
重点关注其中的volumeMounts和volumes配置,其中mysql-pvc申请Bound的mysql-pv将会被mount到MySQL的数据目录/var/lib/mysql下。
通过kubectl创建MySQL:
kubectl apply -f mysql-service.yaml kubectl get pod -o wide
可以看到,MySQL被部署到了k8s-node1节点上。
(3)客户端访问MySQL
下面我们在k8s-master上通过客户端访问MySQL Service:
kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql-service -ppassword
如下图所示,进入了MySQL数据库:
接下来我们更新一下数据库,如下图所示:
新建了一张表edc_test,插入了一行数据1110.
3.2 快速验证
(1)模拟k8s-node1故障
接下来我们模拟一下k8s-node1宕机,这样在k8s-node1上运行的MySQL服务就会受到影响,不过根据之前的了解,K8S会帮我们将MySQL迁移到k8s-node2上从而保证服务可用。
首先,关闭k8s-node1:
shutdown now
其次,验证K8S迁移MySQL:
(2)验证数据一致性
虽然k8s-node1挂了,但是K8S帮我们迁移了MySQL到k8s-node2,而且数据也是完好无损,如下图所示:
(3)验证数据持久性
如果我们将部署的Service和Deployment删掉,那么其Pod也会停止被删除,但是由于我们的PV的回收策略是Retain,因此其数据不会被清除:
四、小结
本文探索了K8S的数据管理方案Volume,其中普通类型的Volume如emptyDir和hostPath虽然使用方便,但是可持久性不强,而外部云存储Volume Provider则提供了更好的持久化存储。PV和PVC的模式,更加适合于我们使用在实际环境中,最后还通过了一个MySQL持久化案例演示了如何应用PV和PVC实现持久化。