一、K8s的存储的基础知识与分类
参考官方文档:https://kubernetes.io/zh/docs/concepts/storage/
1、基础知识:
K8s的基本单位为Pod,但是一个Pod中实际工作的是容器containers,所以要挂载的卷其实也是给容器使用的;K8s卷的设计:
-
容器Container:申请容器内的哪个目录/文件将被挂载出来;(但是如何挂载,他不做细节干涉)
-
Pod:为其中的每个容器声明出来的挂载,指定详细的挂载详情;(有很多种挂载模式,可以是本地的、可以是远程的、甚至还可以是云服务上提供的对象存储服务)
2、数据卷分类:
数据卷实现分类有很多,可通过explain查看所有暂时支持的实现分类(多达30种)
kubectl explain pod.spec.volumes
但是我们大概可以粗分为以下三大类:
配置信息:类似于独立环境变量文件的方式,将文件种的键值对挂载到容器内;
临时存储:这些存储虽然挂出来了,但是如果容器被删除,或者在其他机器上被拉起,这些挂载文件将无法继续被读取到;
持久化存储:真正的持久化,K8s将自己不擅长的存储实现,交给了别的擅长于存储的服务商或软件;——这也是我们以后学习的重点难点!
二、NFS网络文件系统的安装与调试
NFS(Network File System)即网络文件系统,它允许网络中的计算机之间通过网络共享资源。将NFS主机分享的目录,挂载到本地客户端当中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,在客户端端看起来,就像访问本地文件一样。
NFS设计成C/S架构,通过RPC远程过程调用的方式实现数据同步!
1、在Master节点安装NFS Server(当然,其他节点也可以)
yum install -y nfs-utils #执行命令 vi /etc/exports,创建 exports 文件,文件内容如下: echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports # 执行以下命令,启动 nfs 服务;创建共享目录 mkdir -p /nfs/data systemctl enable rpcbind systemctl enable nfs-server systemctl start rpcbind systemctl start nfs-server exportfs -r #检查配置是否生效 [root@k8s-01 /]# exportfs /nfs/data <world> ## 说明已经nfs-server服务已经启动,生效
2、在我们两台需要共享nfs server的机器上,安装NFS Client
yum install -y nfs-utils #执行以下命令检查 nfs 服务器端是否有设置共享目录 # showmount -e $(nfs服务器的IP) showmount -e 192.168.56.20 # 输出结果如下所示 Export list for 192.168.56.20 /nfs/data * #执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /root/nfsmount mkdir /root/nfsmount # mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount mount -t nfs 192.168.56.20:/nfs/data /root/nfsmount
3、测试NFS服务是否在3台服务器上已经生效
#1、 client节点执行命令,看本地的文件系统列表: [root@k8s-02 ~]# df -Th Filesystem Type Size Used Avail Use% Mounted on devtmpfs devtmpfs 1.9G 0 1.9G 0% /dev tmpfs tmpfs 1.9G 0 1.9G 0% /dev/shm tmpfs tmpfs 1.9G 9.4M 1.9G 1% /run tmpfs tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup /dev/sda1 xfs 40G 11G 30G 27% / tmpfs tmpfs 379M 0 379M 0% /run/user/0 192.168.56.20:/nfs/data nfs4 40G 6.3G 34G 16% /root/nfsmount ## 可以看到,有一个是远程的网络文件系统,挂载了我们自己本地的/root/nfsmount 目录上 #2、在server上目录种创建文件aaa.txt #3、在clinet上目录中创建文件bbb.txt
可以看到,3台主机上对应的文件已经立即得到了同步!
4、补充,有时候,我们即使安装了nfs-utils,依然无法使用showmount -e ip
我们可以直接在客户端配置fstab挂载:
vi /etc/fstab ##追加nfs master的信息 10.130.59.50:/nfs/data /nfs/data nfs defaults 0 0
执行以下命令,让修改后的fstab生效:
mount -a
之后查看:
df -Th ##存在以下行: 10.130.59.50:/nfs/data nfs4 50G 4.4G 46G 9% /nfs/data
之后创建文件,测试正常!
三、使用一个Nginx测试Pod使用NFS进行挂载
1、创建nginxnfs.yaml文件:
#测试Pod直接挂载NFS了 apiVersion: v1 kind: Pod metadata: name: vol-nfs namespace: mytest spec: containers: - name: mynginx image: nginx volumeMounts: - name: html mountPath: /usr/share/nginx/html/ volumes: - name: html nfs: path: /nfs/data server: 192.168.56.20
2、执行此yaml文件,部署一台nginx:
[root@k8s-01 mytest]# kubectl apply -f nginxnfs.yaml pod/vol-nfs created [root@k8s-01 mytest]# kubectl get pod -n mytest -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES vol-nfs 1/1 Running 0 68s 10.244.165.226 k8s-03 <none> <none>
3、nfs server的对应目录下,创建一个index.html文件,然后访问:
[root@k8s-01 mytest]# echo 12345 > /nfs/data/index.html [root@k8s-01 mytest]# curl 10.244.165.226 12345
可以看到,明明部署在k8s-03节点上面的pod,依然可以正常使用k8s-01节点上的文件系统进行文件挂载!
四、PV、PVC、StorageClass的基础知识
背景:为什么要把一个存储事情分成这么多层?如此复杂?
答:**存储的管理**是一个与**计算实例的管理**完全不同的问题。
因为整个Pod部署的yaml文件可能都是由我们开发人员编写的,但是我们以后部署的服务器什么情况我们肯定是不知道的,服务器上能支持的文件存储方式我们也是不知道的,那我们的Pod肯定没有办法一气呵成地完成所有的事情!
有了PV(Persistent Volume)、PVC(Persistent Volume Claim)后,我们就可以让运维人员提前准备好很多的PV,这样我们在部署Pod的yaml文件种,只需要添加一个PVC(PV的使用申请),这样K8s就能够自动地为Pod匹配上可以使用的PV存储卷了;
程序员:负责Pod + PVC的yaml文件即可;
K8s运维人员:负责准备好PV;—— 这里还分为静态供应、自动供应 两类;
1、什么是PV(Persistent Volume 持久化存储卷)?
PV 卷的供应有两种方式:静态供应或动态供应。
-
静态供应:所有的事情,尤其是PV的创建,我是由管理员手动创建的,如果没有现成的可用的PV存在,我们的PVC申请将一直处于Pending状态;
-
动态供应:PV的创建是自动化完成的,主要依赖于底层的StorageClass来实现;—— 后面详述
手动创建PV(mypv.yaml)—— 我一次创建了3个PV:
apiVersion: v1 kind: PersistentVolume metadata: name: mypv-10m labels: type: local spec: storageClassName: my-nfs-storage capacity: storage: 10m accessModes: - ReadWriteOnce nfs: #使用nfs存储系统,挂载的目录为192.168.56.20:/nfs/data/one server: 192.168.56.20 path: /nfs/data/one --- apiVersion: v1 kind: PersistentVolume metadata: name: mypv-20m labels: type: local spec: storageClassName: my-nfs-storage capacity: storage: 20m accessModes: - ReadWriteOnce nfs: #使用nfs存储系统,挂载的目录为192.168.56.20:/nfs/data/one server: 192.168.56.20 path: /nfs/data/two --- apiVersion: v1 kind: PersistentVolume metadata: name: mypv-50m labels: type: local spec: storageClassName: my-nfs-storage capacity: storage: 50m accessModes: - ReadWriteOnce nfs: #使用nfs存储系统,挂载的目录为192.168.56.20:/nfs/data/one server: 192.168.56.20 path: /nfs/data/three
应用此yaml文件后,将产生3个pv存储卷:
[root@k8s-01 mytest]# kubectl apply -f mypv.yaml persistentvolume/mypv-10m created persistentvolume/mypv-20m created persistentvolume/mypv-50m created [root@k8s-01 mytest]# kubectl get pv -n mytest NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE mypv-10m 10m RWO Retain Available my-nfs-storage 11s mypv-20m 20m RWO Retain Available my-nfs-storage 11s mypv-50m 50m RWO Retain Available my-nfs-storage 11s
注:其中重要的字段:
ACCESS MODES:访问模式
RWO – ReadWriteOnce:允许单节点读写
ROX – ReadOnlyMany:允许多节点只读(读写分离时可用)
RWX – ReadWriteMany:允许多节点读写(集群常用)
RWOP – ReadWriteOncePod:只允许单Pod读写(比RWO更精细)
RECLAIM POLICY:回收策略
Retain — 手动回收,PVC被删除,PV纹丝不动,自己手动控制,最安全;
Recycle — 基本擦除 (rm -rf /thevolume/*),卷里面的内容将会被清除,卷变为Released状态,依然无法被其他其他PVC申领;
Delete — 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除;
目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。: pv自己也跟着删除
STATUS:当前状态
Available(可用)– 卷是一个空闲资源,尚未绑定到任何申领;
Bound(已绑定)– 该卷已经绑定到某申领;
Released(已释放)– 所绑定的PVC已被删除,但是资源尚未被集群回收;
Failed(失败)– 卷的自动回收操作失败。
CLAIM:当前正被哪个PVC绑定 —— 当某个PV被PVC申领之后,该列就会有数值了!
STORAGECLASS:存储类——动态供应的重点,每个StorageClass都对应着自己的一套自己的存储实现;
2、什么是PVC(Persistent Volume Claim 持久化存储卷申明/申请)?
PVC其实就是我们程序员编写的,对于PV资源的一个申请,当有合适的PV的时候,我们即可以进行绑定,如果没有合适的PV,我们申请的PVC将一直处于Pending状态,直到有合适的PV被创建出来!
手动创建一个PVC(mypvc.yaml)
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mypvc namespace: mytest labels: app: nginx-pvc spec: storageClassName: my-nfs-storage #必须与PV的该项对应,K8s将会从该SC对应的pv资源种为我们匹配合适的资源 accessModes: - ReadWriteOnce resources: requests: storage: 15m #我故意写的15m,看看K8s是否会帮我们挑选一个最合适的PV资源
我们应用此yaml文件后,查看pvc和pv的状态:
[root@k8s-01 mytest]# kubectl apply -f mypvc.yaml persistentvolumeclaim/mypvc created [root@k8s-01 mytest]# kubectl get pvc -n mytest NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mypvc Bound mypv-20m 20m RWO my-nfs-storage 14s [root@k8s-01 mytest]# kubectl get pv -n mytest NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE mypv-10m 10m RWO Retain Available my-nfs-storage 2m36s mypv-20m 20m RWO Retain Bound mytest/mypvc my-nfs-storage 2m36s mypv-50m 50m RWO Retain Available my-nfs-storage 2m36s
可以看到,pv和pvc的状态都变为了Bound状态;同时,K8s为我们挑选的时最合适的PV资源;
3、我们再编写一个Nginx的Pod,来使用上面创建的pvc申请(pvcnginx.yaml)
apiVersion: v1 kind: Pod metadata: name: "mypvc-nginx" namespace: mytest labels: app: "mypvc-nginx" spec: containers: - name: mypvc-nginx image: "nginx" ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime - name: html mountPath: /usr/share/nginx/html volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: html persistentVolumeClaim: claimName: mypvc #我自己创建的pvc的名字 restartPolicy: Always
我们应用此yaml后,对应的nginx即可被创建,然后我们在对应的two文件夹种创建index.html文件即可看到效果:
[root@k8s-01 mytest]# kubectl get all -n mytest NAME READY STATUS RESTARTS AGE pod/mypvc-nginx 1/1 Running 0 3m55s pod/vol-nfs 1/1 Running 0 5h25m [root@k8s-01 mytest]# echo mypvc-nginx > /nfs/data/two/index.html [root@k8s-01 mytest]# curl 10.244.165.227 mypvc-nginx
这样,整个”静态供应“的流程就完成了!
4、什么是StorageClass(存储类)?
整个静态供应的过程中,我们好像都没有 StorageClass 什么事情,除了就用它来隔绝PV资源一样,没有了其他作用!
每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。
provisioner:我们一般称之为 供应商;
当我们创建StorageClass的时候,会指定使用哪个provisioner来为我们供应服务(自动创建PV)
而在我们的PVC中,我们会选择使用哪一个StorageClass来为我们服务;
这样,整个”动态供应“的模型就出来了!我们程序员只需要编写 Pod、PVC的yaml文件,在PVC中指定StorageClassName即可,至于StorageClass对应的是自动供应PV,还是手动创建PV,都跟我们没有关系了!
五、借助NFS存储服务实现一个动态供应案例
再来回顾一下”动态供应“的流程:(其中重点就是:当没有合适的PV时候,K8s会获取StorageClass信息,帮我们创建合适的PV资源)
1、参考 nfs-subdir-external-provisioner 动态供应的文档创建yaml
官方文档地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
我们把这三个文件写在同一个 pvc-provisioner.yaml 文件中,便于移植:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: "true" --- apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: mynfs 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-hangzhou.aliyuncs.com/jgqk8s/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: 10.206.114.4 - name: NFS_PATH value: /nfs/data volumes: - name: nfs-client-root nfs: server: 10.206.114.4 path: /nfs/data --- apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: mynfs --- 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 # replace with namespace where provisioner is deployed namespace: mynfs 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: mynfs 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: mynfs subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: mynfs roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
2、执行上面的yaml文件:
[root@k8s-01 mytest]# kubectl apply -f pvc-provisioner.yaml storageclass.storage.k8s.io/managed-nfs-storage created deployment.apps/nfs-client-provisioner created 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 role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created [root@k8s-01 mytest]# kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storage (default) k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 94s
其中的default,就代表该StorageClass为默认SC;
如果刚开始的Yaml文件中配置配置为 default 默认SC,我们可以通过以下两种方式修改;
-
通过 kubectl edit 命令修改:
kubectl edit sc managed-nfs-storage
-
通过 kubectl patch 命令修改:
kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
3、创建一个Pod + PVC,使用上面的 SC 完成动态供应
apiVersion: v1 kind: Pod metadata: name: "auto-pv-nginx" namespace: mytest labels: app: "auto-pv-nginx" spec: containers: - name: auto-pv-nginx image: "nginx" ports: - containerPort: 80 name: http volumeMounts: - name: localtime mountPath: /etc/localtime - name: html mountPath: /usr/share/nginx/html volumes: - name: localtime hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: html persistentVolumeClaim: claimName: auto-pvc #下方对应的pvc的名字 restartPolicy: Always --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: auto-pvc namespace: mytest labels: app: auto-pvc spec: storageClassName: managed-nfs-storage #如果我们已经配置了默认的StorageClass,该行可以缺省 accessModes: - ReadWriteOnce resources: requests: storage: 30m #待会看看是不是会自动创建一个30m的PV资源
应用该yaml后,看看是否自动创建了一个30m的PV资源:
[root@k8s-01 mytest]# kubectl apply -f auto-pv-nginx.yaml pod/auto-pv-nginx created persistentvolumeclaim/auto-pvc created [root@k8s-01 mytest]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE mypv-10m 10m RWO Retain Available my-nfs-storage 78m mypv-20m 20m RWO Retain Bound mytest/mypvc my-nfs-storage 78m mypv-50m 50m RWO Retain Available my-nfs-storage 78m pvc-b7ecbdff-89f4-4135-91b9-cf8d227ce86f 30m RWO Delete Bound mytest/auto-pvc managed-nfs-storage 26s
4、我们将刚刚部署的测试用的pod和pvc删除,检验该SC的回收策略?
[root@k8s-01 mytest]# ls /nfs/data/ aaa.txt bbb.txt index.html mytest-auto-pvc-pvc-b7ecbdff-89f4-4135-91b9-cf8d227ce86f one three two [root@k8s-01 mytest]# kubectl delete -f auto-pv-nginx.yaml pod "auto-pv-nginx" deleted persistentvolumeclaim "auto-pvc" deleted [root@k8s-01 mytest]# ls /nfs/data/ aaa.txt archived-mytest-auto-pvc-pvc-b7ecbdff-89f4-4135-91b9-cf8d227ce86f bbb.txt index.html one three two
上面的现象足以证明:
我们此StorageClass创建出来的PV的回收策略为DELETE(PVC删除时候,PV也被删除)
但是虽然PV被删除了,但是删除前,该SC帮我们做了数据备份;(原文件夹前增加了archived-前缀)—— archiveOnDelete 字段为true