Volume 在K8s上,Pod的生命周期可能是很短,它们会被频繁地销毁和创建,自然在容器销毁时,里面运行时新增的数据,如修改的配置及日志文件等也会被清除。解决这一问题时可以用K8s volume来持久化保存容器的数据,Volume的生命周期独立于容器,Pod中的容器可能被销毁重建,但Volume会被保留。
本质上,K8s volume是一个目录,这点和Docker volume差不多,当Volume被mount到Pod上,这个Pod中的所有容器都可以访问这个volume,在生产场景中,我们常用的类型有这几种:
emptyDir
hostPath
PersistentVolume(PV) & PersistentVolumeClaim(PVC)
StorageClass
emptyDir emptyDir是最基础的Volume类型,pod内的容器发生重启不会造成emptyDir里面数据的丢失,但是当pod被重启后,emptyDir数据会丢失,也就是说emptyDir与pod的生命周期是一致的,这个使用场景实际上是在生产环境某些时候,它的最实际实用是提供Pod内多容器的volume数据共享,下面用一个实际的生产者,消费者的栗子来演示下emptyDir的作用:
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 apiVersion: apps/v1 kind: Deployment metadata: labels: app: web name: web namespace: default spec: replicas: 1 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - image: nginx name: nginx resources: limits: cpu: "50m" memory: 20Mi requests: cpu: "50m" memory: 20Mi volumeMounts: - name: html-files mountPath: "/usr/share/nginx/html" - name: busybox image: busybox args: - /bin/sh - -c - > while :; do if [ -f /html/index.html ];then echo "[$(date +%F\ %T)] hello" >> /html/index.html sleep 1 else touch /html/index.html fi done volumeMounts: - name: html-files mountPath: "/html" volumes: - name: html-files emptyDir: medium: Memory sizeLimit: 10Mi --- apiVersion: v1 kind: Service metadata: name: web namespace: default spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: web type: ClusterIP //busybox运行时生产数据 //nginx运行时消费数据 [root@node1 app ] NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-7d647b7fc8-s7blv 2 /2 Running 0 5h26m 10.100 .104 .11 node2 <none> <none> [root@node1 app ] [2021-06-09 13:16:53 ] hello [2021-06-09 13:16:54 ] hello [2021-06-09 13:16:55 ] hello [2021-06-09 13:16:56 ] hello [root@node1 ~ ] Container ID: containerd://cb51edc831de2197840c1cd647877c2def9fa504ae24a55597455cc311f884b7 Container ID: containerd://ae90836c6708eb44445238e73146f20c1be7244353ebc7062499ca992e666e36
hostPath hostPath Volume 的作用是将容器运行的node上已经存在文件系统目录给mount到pod的容器。在生产中大部分应用是是不会直接使用hostPath的,因为我们并不关心Pod在哪台node上运行,而hostPath又恰好增加了pod与node的耦合,限制了pod的使用,这里只作一下了解,知道有这个东西存在即可,一般只是一些安装服务会用到,比如下面我截取了网络插件calico的部分volume配置:
1 2 3 4 5 6 7 8 9 10 volumeMounts: - mountPath: /host/driver name: flexvol-driver-host ...... volumes: ...... - hostPath: path: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~uds type: DirectoryOrCreate name: flexvol-driver-host
PV&PVC Volume里面在生产中用的最多的PersistentVolume(持久卷,简称PV)和 PersistentVolumeClaim(持久卷消费,简称PVC),在企业中,Volume是由存储系统的管理员来维护,他们来提供pv,pv具有持久性,生命周期独立于Pod;Pod则是由应用的开发人员来维护,如果要进行一卷挂载,那么就写一个pvc来消费pv就可以了,K8s会查找并提供满足条件的pv。
有了pvc,我们在K8s进行卷挂载就只需要考虑要多少容量了,而不用关心真正的空间是用什么存储系统做的等一些底层细节信息,pv这些只有存储管理员才应用去关心它。
K8s支持多种类型的pv,我们这里就以生产中常用的NFS来作演示(在阿里等云上的话就用NAS),生产中如果对存储要求不是太高的话,建议就用NFS,这样出问题也比较容易解决,如果有性能需求,也可以看看rook的ceph,以及Rancher的Longhorn。
部署NFS-SERVER 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service. Export list for 10.0.1.201: /nfs_dir *
创建基于NFS的PV 首先在NFS-SERVER的挂载目录里面创建一个目录
接着准备好pv的yaml配置,保存为pv1.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 apiVersion: v1 kind: PersistentVolume metadata: name: pv1 labels: type : test-claim spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: nfs nfs: path: /nfs_dir/pv1 server: 10.0.1.201
capacity 指定 PV 的容量为 1G。单位以1000进制时使用(E, P, T, G, M, K, m),以1024进制时使用(Ei, Pi, Ti, Gi, Mi, Ki)
accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。
persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:
Retain – 需要管理员手工回收。*
Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*。
Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。
storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。
指定 PV 在 NFS 服务器上对应的目录,这里注意,我测试的时候,需要手动先创建好这个目录并授权好,不然后面挂载会提示目录不存在 mkdir /nfsdata/pv1 && chown -R nobody.nogroup /nfsdata 。
1 2 3 4 5 6 7 8 9 persistentvolume/pv1 created NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Available nfs 4m45s
创建基于NFS的PVC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc1 spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs selector: matchLabels: type: test-claim
1 2 3 4 5 6 7 8 9 10 11 12 persistentvolumeclaim/pvc1 created NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc1 Bound pv1 1Gi RWO nfs 2s NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc1 nfs
准备pod服务来挂载这个pvc,这里就以上面最开始演示用的nginx的deployment的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 apiVersion: v1 kind: Service metadata: labels: app: nginx name: nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: nginx --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx name: nginx volumeMounts: - name: html-files mountPath: "/usr/share/nginx/html" volumes: - name: html-files persistentVolumeClaim: claimName: pvc1
更新配置
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 service/nginx unchanged deployment.apps/nginx configured NAME READY STATUS RESTARTS AGE nginx-569546db98-4nmmg 0/1 ContainerCreating 0 5s nginx-f89759699-6vgr8 1/1 Running 1 23h web-5bf769fdfc-44p7h 2/2 Running 0 113m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.68.0.1 <none> 443/TCP 23h nginx ClusterIP 10.68.238.54 <none> 80/TCP 23h web ClusterIP 10.68.229.231 <none> 80/TCP 6h27m [root@node-1 ~] <html> <head ><title>403 Forbidden</title></head> <body> <center><h1>403 Forbidden</h1></center> <hr><center>nginx/1.19.5</center> </body> </html> hello, world! pod "nginx-569546db98-4nmmg" deleted NAME READY STATUS RESTARTS AGE nginx-569546db98-99qpq 1/1 Running 0 45s hello, world! 111 下面讲下如何回收PVC以及PV persistentvolumeclaim "pvc1" deleted ^C NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc1 Terminating pv1 1Gi RWO nfs 21m pod "nginx-569546db98-99qpq" deleted No resources found in default namespace. total 0
K8s对于存储解耦的设计是,pv交给存储管理员来管理,运维人员只管用pvc来消费就好,但这里我们实际还是得一起管理pv和pvc,在实际工作中,我们(存储管理员)可以提前配置好pv的动态供给StorageClass,来根据pvc的消费动态生成pv。
StorageClass 我这是直接拿生产中用的实例来作演示,利用nfs-client-provisioner来生成一个基于nfs的StorageClass,部署配置yaml配置如下,保持为nfs-sc.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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner namespace: kube-system --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - 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: ["list" , "watch" , "create" , "update" , "patch" ] - apiGroups: ["" ] resources: ["endpoints" ] verbs: ["get" , "list" , "watch" , "create" , "update" , "patch" ] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: kube-system roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Deployment apiVersion: apps/v1 metadata: name: nfs-provisioner-01 namespace: kube-system spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: nfs-provisioner-01 template: metadata: labels: app: nfs-provisioner-01 spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: jmgao1983/nfs-client-provisioner:latest imagePullPolicy: IfNotPresent volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: nfs-provisioner-01 - name: NFS_SERVER value: 10.0 .1 .201 - name: NFS_PATH value: /nfs_dir volumes: - name: nfs-client-root nfs: server: 10.0 .1 .201 path: /nfs_dir --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-boge provisioner: nfs-provisioner-01 reclaimPolicy: Retain
创建这个StorageClass:
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 serviceaccount/nfs-client-provisioner created clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created deployment.apps/nfs-provisioner-01 created orageclass.storage.k8s.io/nfs-boge created NAME READY STATUS RESTARTS AGE calico-kube-controllers-7fdc86d8ff-dpdm5 1/1 Running 1 24h calico-node-8jcp5 1/1 Running 1 24h calico-node-m92rn 1/1 Running 1 24h calico-node-xg5n4 1/1 Running 1 24h calico-node-xrfqq 1/1 Running 1 24h coredns-d9b6857b5-5zwgf 1/1 Running 1 24h metrics-server-869ffc99cd-wfj44 1/1 Running 2 24h nfs-provisioner-01-5db96d9cc9-qxlgk 0/1 ContainerCreating 0 9s nfs-provisioner-01-5db96d9cc9-qxlgk 1/1 Running 0 21s NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE nfs-boge nfs-provisioner-01 Retain Immediate false 37s
基于StorageClass创建一个pvc,动态生成的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 kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-sc spec: storageClassName: nfs-boge accessModes: - ReadWriteMany resources: requests: storage: 1Mi persistentvolumeclaim/pvc-sc created NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc-sc Bound pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623 1Mi RWX nfs-boge 3s pvc1 Bound pv1 1Gi RWO nfs 24m NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc1 nfs 49m pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623 1Mi RWX Retain Bound default/pvc-sc nfs-boge 7s
修改下nginx的yaml配置,将pvc的名称换成上面的pvc-sc:
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 --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx name: nginx volumeMounts: - name: html-files mountPath: "/usr/share/nginx/html" volumes: - name: html-files persistentVolumeClaim: claimName: pvc-sc service/nginx unchanged deployment.apps/nginx configured root@nginx-57cdc6d9b4-n497g:/ root@nginx-57cdc6d9b4-n497g:/ storageClass used total 0 drwxrwxrwx 2 root root 24 Nov 27 17:52 default-pvc-sc-pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623 drwxr-xr-x 2 root root 6 Nov 27 17:25 pv1