title: k8s数据持久化之自动创建pv #标题tags: 数据持久化 #标签
date: 2020-08-09
categories: k8s # 分类
要k8s使用nfs实现数据持久化,流程上有些问题,手动配置的流程大概是: 搭建nfs底层存储===》创建PV====》创建PVC===》创建pod。最终pod中的container实现数据的持久化。
上述流程中,看似没什么问题,但细想一下,PVC在向PV申请存储空间的时候,是根据指定的pv名称、访问模式、容量大小来决定具体向哪个PV来申请空间的,如果PV的容量为20G,定义的访问模式是WRO(只允许以读写的方式挂载到单个节点),而PVC申请的存储空间为10G,那么一旦这个PVC是向上面的PV申请的空间,也就是说,那个PV有10个G的空间被浪费了,因为其只允许被单个节点挂载。就算不考虑这个问题,我们每次手动去创建PV也就比较麻烦的事情,这时,我们就需要一个自动化的工具来替我们创建PV。
这个东西就是阿里提供的一个开源镜像“nfs-client-provisioner”,这个东西是通过k8s内置的NFS驱动挂载远端的NFS服务器到本地目录(这里的本地目录指的是 nfs-client-provisioner 容器内部的目录,而不是k8s宿主机的本地目录),然后容器自身作为storage(存储)。
当然,PVC是无法直接去向nfs-client-provisioner申请使用的存储空间的,这时,就需要通过SC(storageClass)这个资源对象去申请了,SC的根本作用就是根据PVC定义的值去自动创建PV。
总结流程为:搭建nfs底层存储===》运行容器nfs-client-provisioner ====》创建SC ====》创建PVC(在此时,SC会自动创建PV) ===》创建pod
这篇博文将来写下具体实现过程,并用nginx及mysql来做数据持久化,废话不多说,开搞。
搭建nfs
yum -y install nfs-utilssystemctl enable rpcbindmkdir -p /datacat >> /etc/exports << EOF/data *(rw,sync,no_root_squash)EOFsystemctl start nfs-serversystemctl enable nfs-servershowmount -eExport list for master:/data *
创建RBAC授权
$ cat > rbac-rolebind.yaml << EOFapiVersion: v1kind: ServiceAccountmetadata:name: nfs-provisionernamespace: default---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:name: nfs-provisioner-runnernamespace: defaultrules:- apiGroups: [""]resources: ["persistentvolumes"]verbs: ["get", "list", "watch", "create", "delete"]- apiGroups: [""]resources: ["persistentvolumeclaims"]verbs: ["get", "list", "watch", "update"]- apiGroups: ["storage.k8s.io"]resources: ["storageclasses"]verbs: ["get", "list", "watch"]- apiGroups: [""]resources: ["events"]verbs: ["watch", "create", "update", "patch"]- apiGroups: [""]resources: ["services", "endpoints"]verbs: ["get","create","list", "watch","update"]- apiGroups: ["extensions"]resources: ["podsecuritypolicies"]resourceNames: ["nfs-provisioner"]verbs: ["use"]---kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: run-nfs-provisionersubjects:- kind: ServiceAccountname: nfs-provisionernamespace: defaultroleRef:kind: ClusterRolename: nfs-provisioner-runnerapiGroup: rbac.authorization.k8s.ioEOF$ kubectl apply -f rbac-rolebind.yaml
运行 nfs-client-provisioner 容器
$ cat > nfs-deployment.yaml << EOF---apiVersion: apps/v1kind: Deploymentmetadata:labels:app: nfs-client-provisionername: db-nfs-clientnamespace: defaultspec:replicas: 1revisionHistoryLimit: 10selector:matchLabels:app: nfs-client-provisionertemplate:metadata:labels:app: nfs-client-provisionerspec:containers:- env:- name: PROVISIONER_NAMEvalue: nfs-data- name: NFS_SERVERvalue: 192.168.20.2 # 指定nfs服务IP- name: NFS_PATHvalue: /data # 执行nfs服务共享目录image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisionerimagePullPolicy: IfNotPresentname: nfs-client-provisionervolumeMounts:- mountPath: /persistentvolumesname: nfs-client-rootrestartPolicy: AlwaysserviceAccount: nfs-provisionervolumes:- name: nfs-client-rootnfs: # 指定nfs共享目录及IPpath: /dataserver: 192.168.20.2EOF$ kubectl apply -f nfs-deployment.yaml
创建SC(StroageClass)
$ cat > stroageclass.yaml << EOFapiVersion: storage.k8s.io/v1kind: StorageClassmetadata:name: statefu-nfsnamespace: defaultprovisioner: nfs-data #这里要和nfs-client-provisioner的env环境变量中的value值对应。reclaimPolicy: RetainEOF$ kubectl apply -f stroageclass.yaml
ReclaimPolicy:PV的回收策略
- Recycle:重复利用。
- Retain:保留。
- Delete:删除
- PS:注意这里的回收策略是指,在PV被删除后,在这个PV下所存储的源文件是否删除。
Recycle
此回收策略将在PV回收时,执行一个基本的清除操作(rm -rf /thevolume/*),并使其再次被新的PVC绑定。
Retain
此回收策略最为保险,需要集群管理员手动回收该资源,当绑定的PVC被删除后,PV仍然存在,并被认为是“已释放”。但是此时该PV仍然不能被其他PVC绑定,因为前一个绑定的PVC对应的容器组数据还在其中。
若要回收,可以通过以下步骤来回收;
- 删除该PV,PV删除后,其数据仍然存在于对应的外部存储介质中(nfs、cefpfs、GFS等)。
- 手工删除对应存储介质上的数据。
- 手工删除对应的存储介质,也可以创建一个新的PV并再次使用该存储介质。
Delete
此策略将从k8s集群中移除PV以及其关联的外部存储介质(云环境中的 AWA EBS、GCE PD、Azure Disk 或 Cinder volume)。
注: 动态提供的 PV 将从其对应的 StorageClass 继承回收策略的属性。
至此,准备工作完成。
使用pvc存储运行nginx
创建PVC
$ cat > nginx-pvc.yaml << EOFapiVersion: v1kind: PersistentVolumeClaimmetadata:name: nginx-claimnamespace: defaultspec:storageClassName: statefu-nfs #定义存储类的名字,要和SC的名字对应accessModes:- ReadWriteMany #访问模式为RWMresources:requests:storage: 100MiEOF# PVC只能同时绑定到一个PV,但一个PV可以通过设置访问模式,被多个PVC绑定使用,关于PV的访问模式如下:# - ReadWriteOnce:只能以读写的方式挂载到单个节点(单个节点意味着只能被单个PVC声明使用)。# - ReadOnlyMany:能以只读的方式挂载到多个节点。# - ReadWriteMany:能以读写的方式挂载到多个节点。$ kubectl apply -f nginx-pvc.yaml$ kubectl get pv,pvc # 查看pv及pvc,确定pv,pvc状态都为 bound,表示关联成功。NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGEpersistentvolume/pvc-08f79600-01f8-4996-b7ac-c7d4d7707067 100Mi RWX Delete Bound default/nginx-claim statefu-nfs 3m48sNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGEpersistentvolumeclaim/nginx-claim Bound pvc-08f79600-01f8-4996-b7ac-c7d4d7707067 100Mi RWX statefu-nfs 3m48s
至此,已经实现了根据PVC的申请存储空间去自动创建PV(本地的nfs共享目录下已经生成了一个目录,名字挺长的,是pv+pvc名字定义的目录名),至于这个PVC申请的空间是给哪个pod使用,这已经无所谓了。
创建nginx容器
$ cat > nginx.yaml <<EOF---apiVersion: apps/v1kind: Deploymentmetadata:labels:app: webname: web-nginxnamespace: defaultspec:replicas: 3revisionHistoryLimit: 10selector:matchLabels:app: webstrategy:rollingUpdate:maxSurge: 25%maxUnavailable: 25%type: RollingUpdatetemplate:metadata:labels:app: webspec:containers:- image: 'nginx:latest'imagePullPolicy: IfNotPresentname: mywebvolumeMounts:- mountPath: /usr/share/nginx/html/name: myweb-persistent-storagednsPolicy: ClusterFirstrestartPolicy: Alwaysvolumes:- name: myweb-persistent-storagepersistentVolumeClaim:claimName: nginx-claim---apiVersion: v1kind: Servicemetadata:labels:app: webname: web-nginxnamespace: defaultspec:externalTrafficPolicy: Clusterports:- name: n5tdhhnodePort: 32565port: 80protocol: TCPtargetPort: 80selector:app: webtype: NodePortEOF$ kubectl apply -f nginx.yaml# 自定义网页内容(你的pvc生成的目录名字和我的应该不一样,请自行改动)$ echo 'test nginx pv pvc' >> /data/default-nginx-claim-pvc-08f79600-01f8-4996-b7ac-c7d4d7707067/index.html
浏览器访问:

至此,以后只要将你业务上的网页代码存放到本地的nfs目录下即可供容器使用。
使用pvc存储运行mysql
创建pvc
cat > mysql-pvc.yaml << EOFapiVersion: v1kind: PersistentVolumeClaimmetadata:name: mysql-claimnamespace: defaultspec:storageClassName: statefu-nfs #定义存储类的名字,要和SC的名字对应accessModes:- ReadWriteMany #访问模式为RWMresources:requests:storage: 200MiEOF$ kubectl apply -f mysql-pvc.yaml$ kubectl get pv,pvc # 查看pv及pvc
创建mysql容器
cat > mysql.yaml << EOF---apiVersion: apps/v1kind: StatefulSetmetadata:name: db-mysqlspec:selector:matchLabels:app: mysqlreplicas: 1serviceName: db-mysqltemplate:metadata:labels:app: mysqlspec:containers:- env:- name: MYSQL_ROOT_PASSWORDvalue: 123.comimage: 'mysql:5.7'imagePullPolicy: IfNotPresentname: mysqlvolumeMounts:- mountPath: /var/lib/mysqlname: vo1volumes:- name: vo1persistentVolumeClaim:claimName: mysql-claim---apiVersion: v1kind: Servicemetadata:name: mysqlspec:type: NodePortports:- port: 3306targetPort: 3306nodePort: 31111selector:app: mysqlEOF$ kubectl apply -f mysql.yaml
验证数据持久化效果
$ kubectl get pods -o wide # 查看容器详细信息NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdb-mysql-0 1/1 Running 0 7m27s 10.100.140.73 node02 <none> <none>db-nfs-client-745d766695-94zb6 1/1 Running 0 39m 10.100.140.70 node02 <none> <none>web-nginx-85c4748b9f-6sl5p 1/1 Running 0 23m 10.100.140.71 node02 <none> <none>web-nginx-85c4748b9f-xbj5j 1/1 Running 0 23m 10.100.196.136 node01 <none> <none>web-nginx-85c4748b9f-znwvh 1/1 Running 0 23m 10.100.196.135 node01 <none> <none>$ kubectl exec -it db-mysql-0 -- /bin/bash # 进入容器# 以下是创库,创表,写测试数据等操作。$ mysql> create database db_test;Query OK, 1 row affected (0.03 sec)$ mysql> use db_test;Database changed$ mysql> create table t1 (-> id int(4),-> name varchar(15)-> );Query OK, 0 rows affected (0.04 sec)mysql> insert into t1 value(1,'zhangsan');Query OK, 1 row affected (0.03 sec)mysql> insert into t1 value(1,'lisi');Query OK, 1 row affected (0.00 sec)# 退出容器后,删除mysql这个pod(目的是删除后,StatefulSet控制器自动拉起一个新的pod):$ kubectl delete pod/db-mysql-0 # 删除此podpod "db-mysql-0" deleted$ kubectl get pods -o wide # 再次查看,会发现pod的IP已经发生了变化,说明这是一个新的pod,之前的pod已经没了NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESdb-mysql-0 1/1 Running 0 19s 10.100.140.74 node02 <none> <none>db-nfs-client-745d766695-94zb6 1/1 Running 0 41m 10.100.140.70 node02 <none> <none>web-nginx-85c4748b9f-6sl5p 1/1 Running 0 25m 10.100.140.71 node02 <none> <none>web-nginx-85c4748b9f-xbj5j 1/1 Running 0 25m 10.100.196.136 node01 <none> <none>web-nginx-85c4748b9f-znwvh 1/1 Running 0 25m 10.100.196.135 node01 <none> <none># 进入pod,查看数据是否还在$ kubectl exec -it pod/db-mysql-0 -- /bin/bash$ mysql -uroot -p123.commysql> show databases; # 库还在+--------------------+| Database |+--------------------+| information_schema || db_test || mysql || performance_schema || sys |+--------------------+5 rows in set (0.02 sec)mysql> select * from db_test.t1; # 数据也还在+------+----------+| id | name |+------+----------+| 1 | zhangsan || 1 | lisi |+------+----------+2 rows in set (0.01 sec)
经此验证,可以保障mysql运行在k8s中,数据做到持久化,不管容器怎样,只要本地的持久化数据还在,或者说有备份,那么随时拉起一个pod,使用这个持久化的数据,都可以继续使用。
