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

  1. yum -y install nfs-utils
  2. systemctl enable rpcbind
  3. mkdir -p /data
  4. cat >> /etc/exports << EOF
  5. /data *(rw,sync,no_root_squash)
  6. EOF
  7. systemctl start nfs-server
  8. systemctl enable nfs-server
  9. showmount -e
  10. Export list for master:
  11. /data *

创建RBAC授权

  1. $ cat > rbac-rolebind.yaml << EOF
  2. apiVersion: v1
  3. kind: ServiceAccount
  4. metadata:
  5. name: nfs-provisioner
  6. namespace: default
  7. ---
  8. apiVersion: rbac.authorization.k8s.io/v1
  9. kind: ClusterRole
  10. metadata:
  11. name: nfs-provisioner-runner
  12. namespace: default
  13. rules:
  14. - apiGroups: [""]
  15. resources: ["persistentvolumes"]
  16. verbs: ["get", "list", "watch", "create", "delete"]
  17. - apiGroups: [""]
  18. resources: ["persistentvolumeclaims"]
  19. verbs: ["get", "list", "watch", "update"]
  20. - apiGroups: ["storage.k8s.io"]
  21. resources: ["storageclasses"]
  22. verbs: ["get", "list", "watch"]
  23. - apiGroups: [""]
  24. resources: ["events"]
  25. verbs: ["watch", "create", "update", "patch"]
  26. - apiGroups: [""]
  27. resources: ["services", "endpoints"]
  28. verbs: ["get","create","list", "watch","update"]
  29. - apiGroups: ["extensions"]
  30. resources: ["podsecuritypolicies"]
  31. resourceNames: ["nfs-provisioner"]
  32. verbs: ["use"]
  33. ---
  34. kind: ClusterRoleBinding
  35. apiVersion: rbac.authorization.k8s.io/v1
  36. metadata:
  37. name: run-nfs-provisioner
  38. subjects:
  39. - kind: ServiceAccount
  40. name: nfs-provisioner
  41. namespace: default
  42. roleRef:
  43. kind: ClusterRole
  44. name: nfs-provisioner-runner
  45. apiGroup: rbac.authorization.k8s.io
  46. EOF
  47. $ kubectl apply -f rbac-rolebind.yaml

运行 nfs-client-provisioner 容器

  1. $ cat > nfs-deployment.yaml << EOF
  2. ---
  3. apiVersion: apps/v1
  4. kind: Deployment
  5. metadata:
  6. labels:
  7. app: nfs-client-provisioner
  8. name: db-nfs-client
  9. namespace: default
  10. spec:
  11. replicas: 1
  12. revisionHistoryLimit: 10
  13. selector:
  14. matchLabels:
  15. app: nfs-client-provisioner
  16. template:
  17. metadata:
  18. labels:
  19. app: nfs-client-provisioner
  20. spec:
  21. containers:
  22. - env:
  23. - name: PROVISIONER_NAME
  24. value: nfs-data
  25. - name: NFS_SERVER
  26. value: 192.168.20.2 # 指定nfs服务IP
  27. - name: NFS_PATH
  28. value: /data # 执行nfs服务共享目录
  29. image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
  30. imagePullPolicy: IfNotPresent
  31. name: nfs-client-provisioner
  32. volumeMounts:
  33. - mountPath: /persistentvolumes
  34. name: nfs-client-root
  35. restartPolicy: Always
  36. serviceAccount: nfs-provisioner
  37. volumes:
  38. - name: nfs-client-root
  39. nfs: # 指定nfs共享目录及IP
  40. path: /data
  41. server: 192.168.20.2
  42. EOF
  43. $ kubectl apply -f nfs-deployment.yaml

创建SC(StroageClass)

  1. $ cat > stroageclass.yaml << EOF
  2. apiVersion: storage.k8s.io/v1
  3. kind: StorageClass
  4. metadata:
  5. name: statefu-nfs
  6. namespace: default
  7. provisioner: nfs-data #这里要和nfs-client-provisioner的env环境变量中的value值对应。
  8. reclaimPolicy: Retain
  9. EOF
  10. $ 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

  1. $ cat > nginx-pvc.yaml << EOF
  2. apiVersion: v1
  3. kind: PersistentVolumeClaim
  4. metadata:
  5. name: nginx-claim
  6. namespace: default
  7. spec:
  8. storageClassName: statefu-nfs #定义存储类的名字,要和SC的名字对应
  9. accessModes:
  10. - ReadWriteMany #访问模式为RWM
  11. resources:
  12. requests:
  13. storage: 100Mi
  14. EOF
  15. # PVC只能同时绑定到一个PV,但一个PV可以通过设置访问模式,被多个PVC绑定使用,关于PV的访问模式如下:
  16. # - ReadWriteOnce:只能以读写的方式挂载到单个节点(单个节点意味着只能被单个PVC声明使用)。
  17. # - ReadOnlyMany:能以只读的方式挂载到多个节点。
  18. # - ReadWriteMany:能以读写的方式挂载到多个节点。
  19. $ kubectl apply -f nginx-pvc.yaml
  20. $ kubectl get pv,pvc # 查看pv及pvc,确定pv,pvc状态都为 bound,表示关联成功。
  21. NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
  22. persistentvolume/pvc-08f79600-01f8-4996-b7ac-c7d4d7707067 100Mi RWX Delete Bound default/nginx-claim statefu-nfs 3m48s
  23. NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
  24. persistentvolumeclaim/nginx-claim Bound pvc-08f79600-01f8-4996-b7ac-c7d4d7707067 100Mi RWX statefu-nfs 3m48s

至此,已经实现了根据PVC的申请存储空间去自动创建PV(本地的nfs共享目录下已经生成了一个目录,名字挺长的,是pv+pvc名字定义的目录名),至于这个PVC申请的空间是给哪个pod使用,这已经无所谓了。

创建nginx容器

  1. $ cat > nginx.yaml <<EOF
  2. ---
  3. apiVersion: apps/v1
  4. kind: Deployment
  5. metadata:
  6. labels:
  7. app: web
  8. name: web-nginx
  9. namespace: default
  10. spec:
  11. replicas: 3
  12. revisionHistoryLimit: 10
  13. selector:
  14. matchLabels:
  15. app: web
  16. strategy:
  17. rollingUpdate:
  18. maxSurge: 25%
  19. maxUnavailable: 25%
  20. type: RollingUpdate
  21. template:
  22. metadata:
  23. labels:
  24. app: web
  25. spec:
  26. containers:
  27. - image: 'nginx:latest'
  28. imagePullPolicy: IfNotPresent
  29. name: myweb
  30. volumeMounts:
  31. - mountPath: /usr/share/nginx/html/
  32. name: myweb-persistent-storage
  33. dnsPolicy: ClusterFirst
  34. restartPolicy: Always
  35. volumes:
  36. - name: myweb-persistent-storage
  37. persistentVolumeClaim:
  38. claimName: nginx-claim
  39. ---
  40. apiVersion: v1
  41. kind: Service
  42. metadata:
  43. labels:
  44. app: web
  45. name: web-nginx
  46. namespace: default
  47. spec:
  48. externalTrafficPolicy: Cluster
  49. ports:
  50. - name: n5tdhh
  51. nodePort: 32565
  52. port: 80
  53. protocol: TCP
  54. targetPort: 80
  55. selector:
  56. app: web
  57. type: NodePort
  58. EOF
  59. $ kubectl apply -f nginx.yaml
  60. # 自定义网页内容(你的pvc生成的目录名字和我的应该不一样,请自行改动)
  61. $ echo 'test nginx pv pvc' >> /data/default-nginx-claim-pvc-08f79600-01f8-4996-b7ac-c7d4d7707067/index.html

浏览器访问:

k8s数据持久化之自动创建pv - 图1

至此,以后只要将你业务上的网页代码存放到本地的nfs目录下即可供容器使用。

使用pvc存储运行mysql

创建pvc

  1. cat > mysql-pvc.yaml << EOF
  2. apiVersion: v1
  3. kind: PersistentVolumeClaim
  4. metadata:
  5. name: mysql-claim
  6. namespace: default
  7. spec:
  8. storageClassName: statefu-nfs #定义存储类的名字,要和SC的名字对应
  9. accessModes:
  10. - ReadWriteMany #访问模式为RWM
  11. resources:
  12. requests:
  13. storage: 200Mi
  14. EOF
  15. $ kubectl apply -f mysql-pvc.yaml
  16. $ kubectl get pv,pvc # 查看pv及pvc

创建mysql容器

  1. cat > mysql.yaml << EOF
  2. ---
  3. apiVersion: apps/v1
  4. kind: StatefulSet
  5. metadata:
  6. name: db-mysql
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: mysql
  11. replicas: 1
  12. serviceName: db-mysql
  13. template:
  14. metadata:
  15. labels:
  16. app: mysql
  17. spec:
  18. containers:
  19. - env:
  20. - name: MYSQL_ROOT_PASSWORD
  21. value: 123.com
  22. image: 'mysql:5.7'
  23. imagePullPolicy: IfNotPresent
  24. name: mysql
  25. volumeMounts:
  26. - mountPath: /var/lib/mysql
  27. name: vo1
  28. volumes:
  29. - name: vo1
  30. persistentVolumeClaim:
  31. claimName: mysql-claim
  32. ---
  33. apiVersion: v1
  34. kind: Service
  35. metadata:
  36. name: mysql
  37. spec:
  38. type: NodePort
  39. ports:
  40. - port: 3306
  41. targetPort: 3306
  42. nodePort: 31111
  43. selector:
  44. app: mysql
  45. EOF
  46. $ kubectl apply -f mysql.yaml

验证数据持久化效果

  1. $ kubectl get pods -o wide # 查看容器详细信息
  2. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
  3. db-mysql-0 1/1 Running 0 7m27s 10.100.140.73 node02 <none> <none>
  4. db-nfs-client-745d766695-94zb6 1/1 Running 0 39m 10.100.140.70 node02 <none> <none>
  5. web-nginx-85c4748b9f-6sl5p 1/1 Running 0 23m 10.100.140.71 node02 <none> <none>
  6. web-nginx-85c4748b9f-xbj5j 1/1 Running 0 23m 10.100.196.136 node01 <none> <none>
  7. web-nginx-85c4748b9f-znwvh 1/1 Running 0 23m 10.100.196.135 node01 <none> <none>
  8. $ kubectl exec -it db-mysql-0 -- /bin/bash # 进入容器
  9. # 以下是创库,创表,写测试数据等操作。
  10. $ mysql> create database db_test;
  11. Query OK, 1 row affected (0.03 sec)
  12. $ mysql> use db_test;
  13. Database changed
  14. $ mysql> create table t1 (
  15. -> id int(4),
  16. -> name varchar(15)
  17. -> );
  18. Query OK, 0 rows affected (0.04 sec)
  19. mysql> insert into t1 value(1,'zhangsan');
  20. Query OK, 1 row affected (0.03 sec)
  21. mysql> insert into t1 value(1,'lisi');
  22. Query OK, 1 row affected (0.00 sec)
  23. # 退出容器后,删除mysql这个pod(目的是删除后,StatefulSet控制器自动拉起一个新的pod):
  24. $ kubectl delete pod/db-mysql-0 # 删除此pod
  25. pod "db-mysql-0" deleted
  26. $ kubectl get pods -o wide # 再次查看,会发现pod的IP已经发生了变化,说明这是一个新的pod,之前的pod已经没了
  27. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
  28. db-mysql-0 1/1 Running 0 19s 10.100.140.74 node02 <none> <none>
  29. db-nfs-client-745d766695-94zb6 1/1 Running 0 41m 10.100.140.70 node02 <none> <none>
  30. web-nginx-85c4748b9f-6sl5p 1/1 Running 0 25m 10.100.140.71 node02 <none> <none>
  31. web-nginx-85c4748b9f-xbj5j 1/1 Running 0 25m 10.100.196.136 node01 <none> <none>
  32. web-nginx-85c4748b9f-znwvh 1/1 Running 0 25m 10.100.196.135 node01 <none> <none>
  33. # 进入pod,查看数据是否还在
  34. $ kubectl exec -it pod/db-mysql-0 -- /bin/bash
  35. $ mysql -uroot -p123.com
  36. mysql> show databases; # 库还在
  37. +--------------------+
  38. | Database |
  39. +--------------------+
  40. | information_schema |
  41. | db_test |
  42. | mysql |
  43. | performance_schema |
  44. | sys |
  45. +--------------------+
  46. 5 rows in set (0.02 sec)
  47. mysql> select * from db_test.t1; # 数据也还在
  48. +------+----------+
  49. | id | name |
  50. +------+----------+
  51. | 1 | zhangsan |
  52. | 1 | lisi |
  53. +------+----------+
  54. 2 rows in set (0.01 sec)

经此验证,可以保障mysql运行在k8s中,数据做到持久化,不管容器怎样,只要本地的持久化数据还在,或者说有备份,那么随时拉起一个pod,使用这个持久化的数据,都可以继续使用。