持久卷使用(nfs存储数据)
Kubernetes 为了使应用程序及其开发人员能够正常请求存储资源,避免处理存储设施细节,引入了 PV 和 PVC。创建 PV 有两种方式:
- 集群管理员通过手动方式静态创建应用所需要的 PV;
- 用户手动创建 PVC 并由 Provisioner 组件动态创建对应的 PV。
搭建nfs服务器(ip:192.168.0.29)
找一台服务器搭建nfs服务端, 我以centos7为例
安装nfs yum -y install nfs-utils #创建nfs目录 mkdir -p /nfs/data/ #修改权限 chmod -R 777 /nfs/data #编辑export文件 vim /etc/exports /nfs/data *(rw,no_root_squash,sync) (“*“代表所有人都能连接,建议换成具体ip或ip段,如192.168.20.0/24) #配置生效 exportfs -r #查看生效 exportfs #启动rpcbind、nfs服务 systemctl restart rpcbind && systemctl enable rpcbind systemctl restart nfs && systemctl enable nfs #查看 RPC 服务的注册状况 rpcinfo -p localhost #showmount测试 showmount -e 192.168.92.56 #所有node节点安装nfs客户端 yum -y install nfs-utils systemctl start nfs && systemctl enable nfs
静态创建PV卷
添加pv卷对应目录,这里创建2个pv卷,则添加2个pv卷的目录作为挂载点。
#nfs服务器操作 #创建pv卷对应的目录 mkdir -p /nfs/data/pv001 mkdir -p /nfs/data/pv002 #配置exportrs vim /etc/exports /nfs/data *(rw,no_root_squash,sync) /nfs/data/pv001 *(rw,no_root_squash,sync) /nfs/data/pv002 *(rw,no_root_squash,sync) #配置生效 exportfs -r #重启rpcbind、nfs服务 systemctl restart rpcbind && systemctl restart nfs
第一步:集群管理员创建 NFS PV,NFS 属于 K8s 原生支持的 in-tree 存储类型。yaml 文件如下:
# cat nfs-pv1.yaml apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv001 labels: pv: nfs-pv001 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: nfs nfs: path: /nfs/data/pv001 server: 192.168.0.29 # cat nfs-pv2.yaml apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv002 labels: pv: nfs-pv002 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: nfs nfs: path: /nfs/data/pv002 server: 192.168.0.29
配置说明
配置说明: ① capacity 指定 PV 的容量为 1G。 ② 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 服务器上对应的目录。
[root@vm192-168-0-79 ~]# kubectl apply -f pv1.yaml persistentvolume/nfs-pv001 created [root@vm192-168-0-79 ~]# kubectl apply -f pv2.yaml persistentvolume/nfs-pv002 created [root@vm192-168-0-79 ~]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv001 1Gi RWO Recycle Available nfs 10m nfs-pv002 1Gi RWO Recycle Available nfs 3m2s # STATUS 为 Available,表示 pv就绪,可以被 PVC 申请。
第二步:用户创建 PVC,yaml 文件如下:
接下来创建一个名为pvc001,pvc002的pvc
# cat nfs-pvc1.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc001 spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs selector: matchLabels: pv: nfs-pv001 # cat nfs-pvc2.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc002 spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs selector: matchLabels: pv: nfs-pv002
[root@vm192-168-0-79 ~]# kubectl apply -f pvc1.yaml persistentvolumeclaim/nfs-pvc001 created [root@vm192-168-0-79 ~]# kubectl apply -f pvc2.yaml persistentvolumeclaim/nfs-pvc002 created [root@vm192-168-0-79 ~]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv001 1Gi RWO Recycle Bound default/nfs-pvc001 nfs 12m nfs-pv002 1Gi RWO Recycle Bound default/nfs-pvc002 nfs 5m [root@vm192-168-0-79 ~]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE nfs-pvc001 Bound nfs-pv001 1Gi RWO nfs 15s nfs-pvc002 Bound nfs-pv002 1Gi RWO nfs 15s
从 kubectl get pvc 和 kubectl get pv 的输出可以看到 pvc001 和pvc002分别绑定到pv001和pv002,申请成功。注意pvc绑定到对应pv通过labels标签方式实现,也可以不指定,将随机绑定到pv。
第三步:用户创建应用,并使用第二步创建的 PVC。
# cat pod1.yaml kind: Pod apiVersion: v1 metadata: name: nfs-pod001 spec: containers: - name: myfrontend image: nginx:latest volumeMounts: - mountPath: "/var/www/html" name: nfs-pv001 volumes: - name: nfs-pv001 persistentVolumeClaim: claimName: nfs-pvc001 # cat pod2.yaml kind: Pod apiVersion: v1 metadata: name: nfs-pod002 spec: containers: - name: myfrontend image: nginx:latest volumeMounts: - mountPath: "/var/www/html" name: nfs-pv002 volumes: - name: nfs-pv002 persistentVolumeClaim: claimName: nfs-pvc002
与使用普通 Volume 的格式类似,在 volumes 中通过 persistentVolumeClaim 指定使用nfs-pvc001和nfs-pvc002申请的 Volume。
[root@vm192-168-0-79 ~]# kubectl apply -f pod1.yaml pod/nfs-pod001 created [root@vm192-168-0-79 ~]# kubectl apply -f pod2.yaml pod/nfs-pod002 created [root@vm192-168-0-79 ~]# kubectl get po NAME READY STATUS RESTARTS AGE nfs-pod001 1/1 Running 0 62s nfs-pod002 1/1 Running 0 7s [root@vm192-168-0-79 ~]# kubectl exec nfs-pod001 touch /var/www/html/index001.html [root@vm192-168-0-79 ~]# kubectl exec nfs-pod002 touch /var/www/html/index002.html # 在nfs服务器验证 [root@vm192-168-0-29 pv001]# ls /nfs/data/pv001 index001.html [root@vm192-168-0-29 pv001]# ls /nfs/data/pv002 index002.html
进入pod查看挂载情况
[root@vm192-168-0-79 ~]# kubectl exec -ti nfs-pod001 /bin/bash root@nfs-pod001:/# df -h ... 192.168.0.29:/nfs/data/pv001 197G 2.8G 186G 2% /var/www/html ...
删除pv
删除pod,pv和pvc不会被删除,nfs存储的数据不会被删除。
[root@vm192-168-0-79 ~]# kubectl delete -f pod1.yaml pod "nfs-pod001" deleted [root@vm192-168-0-79 ~]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv001 1Gi RWO Recycle Bound default/nfs-pvc001 nfs 38m nfs-pv002 1Gi RWO Recycle Bound default/nfs-pvc002 nfs 31m [root@vm192-168-0-79 ~]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE nfs-pvc001 Bound nfs-pv001 1Gi RWO nfs 26m nfs-pvc002 Bound nfs-pv002 1Gi RWO nfs 26m # nfs服务器查看 [root@vm192-168-0-29 pv001]# ls /nfs/data/pv001 index001.html
继续删除pvc,pv将被释放,处于 Available 可用状态,并且nfs存储中的数据被删除。
[root@vm192-168-0-79 ~]# kubectl delete -f pvc1.yaml persistentvolumeclaim "nfs-pvc001" deleted [root@vm192-168-0-79 ~]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv001 1Gi RWO Recycle Available nfs 40m nfs-pv002 1Gi RWO Recycle Bound default/nfs-pvc002 nfs 32m # nfs服务器查看 [root@vm192-168-0-29 pv001]# ls /nfs/data/pv001 [root@vm192-168-0-29 pv001]#
继续删除pv
[root@vm192-168-0-79 ~]# kubectl delete -f pv1.yaml persistentvolume "nfs-pv001" deleted
动态创建pv卷
项目地址: https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client
External NFS驱动的工作原理
K8S的外部NFS驱动,可以按照其工作方式(是作为NFS server还是NFS client)分为两类:
1.nfs-client:
也就是我们接下来演示的这一类,它通过K8S的内置的NFS驱动挂载远端的NFS服务器到本地目录;然后将自身作为storage provider,关联storage class。当用户创建对应的PVC来申请PV时,该provider就将PVC的要求与自身的属性比较,一旦满足就在本地挂载好的NFS目录中创建PV所属的子目录,为Pod提供动态的存储服务。
2.nfs:
与nfs-client不同,该驱动并不使用k8s的NFS驱动来挂载远端的NFS到本地再分配,而是直接将本地文件映射到容器内部,然后在容器内使用ganesha.nfsd来对外提供NFS服务;在每次创建PV的时候,直接在本地的NFS根目录中创建对应文件夹,并export出该子目录。
利用NFS动态提供Kubernetes后端存储卷
本文将介绍使用nfs-client-provisioner这个应用,利用NFS Server给Kubernetes作为持久存储的后端,并且动态提供PV。前提条件是有已经安装好的NFS服务器,并且NFS服务器与Kubernetes的Slave节点都能网络连通。将nfs-client驱动做一个deployment部署到K8S集群中,然后对外提供存储服务。
nfs-client-provisioner 是一个Kubernetes的简易NFS的外部provisioner,本身不提供NFS,需要现有的NFS服务器提供存储
部署nfs-client-provisioner
部署nfs-client-provisioner 首先克隆仓库获取yaml文件 git clone https://github.com/kubernetes-incubator/external-storage.git cp -R external-storage/nfs-client/deploy/ $HOME cd deploy
部署rbac.yaml
apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default --- 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: ["create", "update", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed 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 # replace with namespace where provisioner is deployed 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 # replace with namespace where provisioner is deployed namespace: default subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
修改deployment.yaml文件
这里修改的参数包括NFS服务器所在的IP地址(192.168.0.29),以及NFS服务器共享的路径(/nfs/data),两处都需要修改为你实际的NFS服务器和共享目录。另外修改nfs-client-provisioner镜像从dockerhub拉取。
apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner # replace with namespace where provisioner is deployed 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: quay.io/external_storage/nfs-client-provisioner:latest volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: fuseim.pri/ifs - name: NFS_SERVER value: 192.168.0.29 - name: NFS_PATH value: /nfs/data volumes: - name: nfs-client-root nfs: server: 192.168.0.29 path: /nfs/data
部署deployment.yaml
[root@vm192-168-0-79 deploy]# kubectl apply -f deployment.yaml deployment.apps/nfs-client-provisioner created [root@vm192-168-0-79 deploy]# kubectl get po NAME READY STATUS RESTARTS AGE nfs-client-provisioner-66569dbf98-6q9n6 1/1 Running 0 14s
第一步:创建StorageClass
storage class的定义,需要注意的是:provisioner属性要等于驱动所传入的环境变量PROVISIONER_NAME的值。否则,驱动不知道知道如何绑定storage class。
此处可以不修改,或者修改provisioner的名字,需要与上面的deployment的PROVISIONER_NAME名字一致。
# cat class.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME' parameters: archiveOnDelete: "false"
查看创建的storageclass
[root@vm192-168-0-79 deploy]# kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storage fuseim.pri/ifs Delete Immediate false 5s
第二步:创建 PVC,此处 PVC 的 storageClassName 指定为上面 NFS 的 storageclass 名称
kubectl create -f test-claim.yaml
这里指定了其对应的storage-class的名字为managed-nfs-storage,如下:
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi storageClassName: managed-nfs-storage
集群中的 nfs-client-provisioner 会动态创建相应 PV。此时可看到环境中 PV 已创建,并与 PVC 已绑定。
查看创建的PVC
可以看到PVC状态为Bound,绑定的volume为pvc-b237bffa-e57f-4360-bfee-806571532df1。
[root@vm192-168-0-79 deploy]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE test-claim Bound pvc-b237bffa-e57f-4360-bfee-806571532df1 1Mi RWX managed-nfs-storage 41s
查看自动创建的PV
[root@vm192-168-0-79 deploy]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-b237bffa-e57f-4360-bfee-806571532df1 1Mi RWX Delete Bound default/test-claim managed-nfs-storage 105s
然后,我们进入到NFS的export目录,可以看到对应该volume name的目录已经创建出来了。
其中volume的名字是namespace,PVC name以及uuid的组合:
[root@vm192-168-0-29 ~]# cd /nfs/data [root@vm192-168-0-29 data]# ll total 12 drwxrwxrwx 2 root root 4096 Oct 9 17:12 default-test-claim-pvc-b237bffa-e57f-4360-bfee-806571532df1
第三步:用户创建应用,并使用第二步创建的 PVC,同静态创建存储卷的第三步。
kind: Pod apiVersion: v1 metadata: name: test-pod spec: containers: - name: test-pod image: gcr.io/google_containers/busybox:1.24 command: - "/bin/sh" args: - "-c" - "touch /mnt/SUCCESS && exit 0 || exit 1" volumeMounts: - name: nfs-pvc mountPath: "/mnt" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim
如果attach到pod中执行一些文件的读写操作,就可以确定pod的/mnt已经使用了NFS的存储服务了。
查看创建的测试pod
[root@vm192-168-0-79 deploy]# kubectl get po NAME READY STATUS RESTARTS AGE nfs-client-provisioner-66569dbf98-6q9n6 1/1 Running 0 22m test-pod 0/1 Completed 0 6s
在NFS服务器上的共享目录下的卷子目录中检查创建的NFS PV卷下是否有"SUCCESS" 文件。
[root@vm192-168-0-29 ~]# cd /nfs/data [root@vm192-168-0-29 data]# ll total 12 drwxrwxrwx 2 root root 4096 Oct 9 17:27 default-test-claim-pvc-b237bffa-e57f-4360-bfee-806571532df1 drwxr-xr-x 2 root root 4096 Oct 9 15:13 pv001 drwxr-xr-x 2 root root 4096 Oct 9 17:12 pv002 [root@vm192-168-0-29 data]# cd default-test-claim-pvc-b237bffa-e57f-4360-bfee-806571532df1/ [root@vm192-168-0-29 default-test-claim-pvc-b237bffa-e57f-4360-bfee-806571532df1]# ll total 0 -rw-r--r-- 1 root root 0 Oct 9 17:27 SUCCESS