全部的 K8S学习笔记总目录 ,请点击查看。
Kubernetes 中的持久卷(Persistent Volume)是一种存储卷,它的生命周期独立于 Pod,这意味着 Pod 被删除后,持久卷中的数据不会被删除。持久卷可以在多个 Pod 之间共享,但是同一时间只能被一个 Pod 挂载。持久卷的类型可以是 NFS、iSCSI、GlusterFS、Ceph 等。
持久化卷方式 k8s存储的目的就是保证Pod重建后,数据不丢失。简单的数据持久化有以下几种方式:
emptyDir
Pod内的容器共享卷的数据
存在于Pod的生命周期,Pod销毁,数据丢失
Pod内的容器自动重建后,数据不会丢失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: v1 kind: Pod metadata: name: test-pod spec: containers: - image: k8s.gcr.io/test-webserver name: webserver volumeMounts: - mountPath: /cache name: cache-volume - image: k8s.gcr.io/test-redis name: redis volumeMounts: - mountPath: /data name: cache-volume volumes: - name: cache-volume emptyDir: {}
hostPath 指定节点上的目录作为卷,这种方式不适合生产环境,因为Pod可能会被调度到其他节点,数据会不一致或者丢失,因此通常配合nodeSelector使用,保证Pod一直调度到指定的节点上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 apiVersion: v1 kind: Pod metadata: name: test-pod spec: nodeSelector: kubernetes.io/hostname: k8s-node1 containers: - image: k8s.gcr.io/test-webserver name: test-container volumeMounts: - mountPath: /test-pod name: test-volume volumes: - name: test-volume hostPath: path: /data type: Directory
nfs存储 通过nfs挂载远程存储
1 2 3 4 5 6 7 8 ... volumes: - name: redisdata nfs: server: 192.168 .100 .1 path: /data/redis readOnly: false ...
存储支持 volume支持的种类众多(参考 https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes )每种对应不同的存储后端实现。
为了屏蔽后端存储的细节,同时使得Pod在使用存储的时候更加简洁和规范,k8s引入了两个新的资源类型,PV和PVC。
PV PersistentVolume(持久化卷),是对底层的存储的一种抽象,它和具体的底层的共享存储技术的实现方式有关,比如 Ceph、GlusterFS、NFS 等,都是通过插件机制完成与共享存储的对接。如使用PV对接NFS存储:
pv的声明式清单如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: path: /data/k8s server: 192.168 .100 .1
capacity,存储能力, 目前只支持存储空间的设置, 就是我们这里的 storage=1Gi,不过未来可能会加入 IOPS、吞吐量等指标的配置。
persistentVolumeReclaimPolicy,pv的回收策略, 目前只有 NFS 和 HostPath 两种类型支持回收策略
Retain(保留)- 保留数据,需要管理员手工清理数据
Recycle(回收)- 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
Delete(删除)- 与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务,比如 ASW EBS。
accessModes,访问模式, 是用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
ReadWriteMany(RWX):读写权限,可以被多个节点挂载
PVC 因为PV是直接对接底层存储的,就像集群中的Node可以为Pod提供计算资源(CPU和内存)一样,PV可以为Pod提供存储资源。因此PV不是namespaced的资源,属于集群层面可用的资源。Pod如果想使用该PV,需要通过创建PVC挂载到Pod中。
PVC全写是PersistentVolumeClaim(持久化卷声明),PVC 是用户存储的一种声明,创建完成后,可以和PV实现一对一绑定。对于真正使用存储的用户不需要关心底层的存储实现细节,只需要直接使用 PVC 即可。
1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi
然后Pod中通过如下方式去使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... spec: containers: - name: nginx image: nginx:alpine imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumes: - name: www persistentVolumeClaim: claimName: pvc-nfs ...
PV是集群层面的资源,PVC是命名空间层面的资源,因此PVC定义的时候需要指定命名空间,而且PVC只能给同一个命名空间下的Pod使用。
PV与PVC管理NFS存储卷实践 环境准备 服务端:192.168.100.1
1 2 3 4 5 6 7 8 9 10 11 12 $ yum -y install nfs-utils rpcbind $ mkdir -p /data/k8s && chmod 755 /data/k8s $ echo '/data/k8s *(insecure,rw,sync,no_root_squash,fsid=0)' >>/etc/exports $ systemctl enable rpcbind && systemctl start rpcbind $ systemctl enable nfs-server && systemctl start nfs-server $ showmount -e 127.0.0.1
客户端:k8s集群slave节点
1 2 3 $ yum -y install nfs-utils $ mkdir /nfsdata $ mount -t nfs 192.168.100.1:/ /nfsdata
创建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 $ mkdir /data/k8s/nginx $ cat pv-nfs.yaml apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: path: /nginx server: 192.168.100.1 $ kubectl create -f pv-nfs.yaml $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS nfs-pv 1Gi RWO Retain Available
一个 PV 的生命周期中,可能会处于4中不同的阶段:
Available(可用):表示可用状态,还未被任何 PVC 绑定
Bound(已绑定):表示 PV 已经被 PVC 绑定
Released(已释放):PVC 被删除,但是资源还未被集群重新声明
Failed(失败): 表示该 PV 的自动回收失败
创建PVC 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 $ cat pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi $ kubectl create -f pvc.yaml $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc-nfs Bound nfs-pv 1Gi RWO 3s $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM nfs-pv 1Gi RWO Retain Bound default/pvc-nfs $ ls /nfsdata
创建Pod挂载PVC 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 $ cat deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nfs-pvc spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:alpine imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumes: - name: www persistentVolumeClaim: claimName: pvc-nfs $ kubectl create -f deployment.yaml
StorageClass动态挂载 上述的PV和PVC都是静态的,需要手动创建,且PV与PVC一一对应,手动创建很繁琐。因此,通过 storageClass + provisioner 的方式来实现通过PVC自动创建并绑定PV。
一些关于nfs的StorageClass可以查看github仓库 (sig-storage-lib-external-provisioner)[https://github.com/kubernetes-sigs/sig-storage-lib-external-provisioner]
准备yaml文件 在github仓库中下载配置文件nfs-subdir-external-provisioner/deploy/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 43 44 apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner namespace: default spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: nfs-client-provisioner template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.2 volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: k8s-sigs.io/nfs-subdir-external-provisioner - name: NFS_SERVER value: 192.168 .100 .1 - name: NFS_PATH value: / volumes: - name: nfs-client-root nfs: server: 192.168 .100 .1 path: /
再下载nfs-subdir-external-provisioner/deploy/rbac.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 apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - apiGroups: ["" ] resources: ["nodes" ] verbs: ["get" , "list" , "watch" ] - 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: ["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: default roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner namespace: default rules: - apiGroups: ["" ] resources: ["endpoints" ] verbs: ["get" , "list" , "watch" , "create" , "update" , "patch" ] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner namespace: default subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
最后,下载nfs-subdir-external-provisioner/deploy/class.yaml文件,然后进行修改。
1 2 3 4 5 6 7 8 9 10 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: "false"
创建资源 1 2 3 4 5 6 7 8 9 $ kubectl create -f rbac.yaml $ kubectl create -f deployment.yaml $ kubectl create -f class.yaml $ kubectl -n nfs-provisioner get pod nfs-client-provisioner-6cfb58c597-rmc45 1/1 Running 0 27s
验证效果 验证使用storageclass
自动创建并绑定pv
创建一个 pvc.yaml
1 2 3 4 5 6 7 8 9 10 11 kind: PersistentVolumeClaim apiVersion: v1 metadata: name: registry-claim spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi storageClassName: nfs-client
创建资源
1 $ kubectl create -f pvc.yaml
查看pvc和pv是否绑定成功
1 2 3 4 5 6 7 $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE registry-claim Bound pvc-eb692323-553e-4350-8749-d972ff785567 1Gi RWX nfs-client 8s $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-eb692323-553e-4350-8749-d972ff785567 1Gi RWX Delete Bound default/registry-claim nfs-client 8s
查看nfs-server上的数据目录
1 2 3 $ ls -l /data/k8s/nginxtotal 0 drwxrwxrwx 2 root root 6 Nov 23 10:28 default-test-claim-pvc-eb692323-553e-4350-8749-d972ff785567
总结 我们所说的容器的持久化,实际上应该理解为宿主机中volume的持久化,因为Pod是支持销毁重建的,所以只能通过宿主机volume持久化,然后挂载到Pod内部来实现Pod的数据持久化。
宿主机上的volume持久化,因为要支持数据漂移,所以通常是数据存储在分布式存储中,宿主机本地挂载远程存储(NFS,Ceph,OSS),这样即使Pod漂移也不影响数据。
k8s的pod的挂载盘通常的格式为:
1 /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>