在 k8s 中为什么要做持久化存储?
在 k8s 中部署的应用都是以 pod 容器的形式运行的,假如我们部署 MySQL、Redis 等数据库,需
要对这些数据库产生的数据做备份。因为 Pod 是有生命周期的,如果 pod 不挂载数据卷,那 pod 被删 除或重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到 pod 数据持久化存储。
k8s 持久化存储方案:emptyDir
kubectl explain pods.spec.volumes
常用如下
emptyDir
hostPath
nfs persistentVolumeClaim glusterfs
cephfs configMap secret
我们想要使用存储卷,需要经历如下步骤
1、定义 pod 的 volume,这个 volume 指明它要关联到哪个存储上的
2、在容器中要使用 volume mounts 挂载对应的存储
经过以上两步才能正确的使用存储卷
emptyDir 类型的 Volume 是在 Pod 分配到 Node 上时被创建,Kubernetes 会在 Node 上自动 分配一个目录,因此无需指定宿主机 Node 上对应的目录文件。 这个目录的初始内容为空,当 Pod 从 Node 上移除时,emptyDir 中的数据会被永久删除。emptyDir Volume 主要用于某些应用程序无需永 久保存的临时目录,多个容器的共享目录等。
#创建一个 pod,挂载临时目录 emptyDir
cat emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-empty
spec:
containers:
- name: container-empty
image: nginx
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- emptyDir:
{}
name: cache-volume
kubectl apply -f emptydir.yaml
kubectl get pods -o wide | grep empty
#查看 pod 的 uid
kubectl get pods pod-empty -o yaml | grep uid
uid: ff865494-205a-4318-b8bc-072de1d8ea6f
工作节点看挂载目录
由上可知,临时目录在本地的/var/lib/kubelet/pods/ff865494-205a-4318-b8bc-072de1d8ea6f/volumes/kubernetes.io~empty-dir/cache-volume/下
k8s 持久化存储方案:hostPath
hostPath Volume 是指 Pod 挂载宿主机上的目录或文件。 hostPath Volume 使得容器可以使用 宿主机的文件系统进行存储,hostpath(宿主机路径):节点级别的存储卷,在 pod 被删除,这个存储 卷还是存在的,不会被删除,所以只要同一个 pod 被调度到同一个节点上来,在 pod 被删除重新被调度 到这个节点之后,对应的数据依然是存在的。
kubectl explain pods.spec.volumes.hostPath
FIELDS:
path -required-
type
tomcat.tar.gz 上传到工作节点
#创建一个 pod,挂载 hostPath 存储卷
如果文档里有#号注释复制的时候会乱. vim打开的时候加:set paste就不会乱了
cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- image: nginx
name: test-nginx
volumeMounts:
- mountPath: /test-nginx
name: test-volume
- image: tomcat
name: test-tomcat
volumeMounts:
- mountPath: /test-tomcat #挂载到容器内目录
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data1 #挂载到节点目录
type: DirectoryOrCreate
# DirectoryOrCreate 表示本地有/data1 目录,就用本地的,本地没有就会在 pod 调度到的节点 自动创建一个
kubectl apply -f hostpath.yaml
#查看 pod 调度到了哪个物理节点
kubectl get pods -o wide | grep hostpath
在 god62 上的/data1 下创建一个目录
# mkdir -p /data1/god
#测试存储卷是否可以正常使用,登录到 nginx 容器
kubectl exec -it test-hostpath -c test-nginx -- /bin/bash
#测试存储卷是否可以正常使用,登录到 tomcat 容器
kubectl exec -it test-hostpath -c test-tomcat -- /bin/bash root@test-hostpath:/usr/local/tomcat# cd /test-tomcat/ #/test-tomcat/目录存在,说明已经把宿主机目录挂载到了容器里 root@test-hostpath:/test-tomcat# ls
#通过上面测试可以看到,同一个 pod 里的 test-nginx 和 test-tomcat 这两个容器是共享存储卷 的。
hostpath 存储卷缺点:
单节点
pod 删除之后重新创建必须调度到同一个 node 节点,数据才不会丢失
可以用分布式存储: nfs,cephfs,glusterfs
k8s 持久化存储方案:NFS
hostPath 存储,存在单点故障,pod 挂载 hostPath 时,只有调度到同一个节点,数据 才不会丢失。那可以使用 nfs 作为持久化存储。
1、搭建 nfs 服务
#以 k8s 的控制节点作为 NFS 服务端
yum install nfs-utils -y
#在宿主机创建 NFS 需要的共享目录
mkdir /data/volumes -pv
mkdir: created directory ‘/data’
mkdir: created directory ‘/data/volumes’
#配置 nfs 共享服务器上的/data/volumes 目录
# systemctl start nfs
# vim /etc/exports
/data/volumes 192.168.1.0/24(rw,no_root_squash)
#no_root_squash: 用户具有根目录的完全管理访问权限 #使 NFS 配置生效
# exportfs -arv
exporting 192.168.172.0/24:/data/volumes
]# service nfs start
Redirecting to /bin/systemctl start nfs.service
#设置成开机自启动
]# systemctl enable nfs
#查看 nfs 是否启动成功
systemctl status nfs Active: active
看到 nfs 是 active,说明 nfs 正常启动了 #god62 和 god64 上也安装 nfs 驱动
yum install nfs-utils -y
service nfs start && systemctl enable nfs
在 god62 上手动挂载试试:
mount -t nfs 192.168.172.63:/data/volumes /data1
df -h 看一哈
#手动卸载:
umount /data1/
#创建 Pod,挂载 NFS 共享出来的目录
cat nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-nfs-volume
spec:
containers:
- name: test-nfs
image: nginx
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nfs-volumes
volumes:
- name: nfs-volumes
nfs:
server: 192.168.172.163
path: /data/volumes
注:path: /data/volumes #nfs 的共享目录
server:192.168.172.63 是 god63 机器的 ip,这个是安装 nfs 服务的地址
kubectl get pods -o wide | grep nfs
test-nfs-volume 1/1 Running 0 59s 10.244.187.124 god62 #登录到 nfs 服务器(master),在共享目录创建一个 index.html cd /data/volumes/ echo "hello,welcome to pod" > inde.html curl 10.244.187.124 hello,welcome to pod #通过上面可以看到,在共享目录创建的 index.html 已经被 pod 挂载了 #登录到 pod 验证下 # kubectl exec -it test-nfs-volume -- /bin/bash root@test-nfs-volume:/# cat /usr/share/nginx/html/index.html hello, Welcome to pod #上面说明挂载 nfs 存储卷成功了,nfs 支持多个客户端挂载,可以创建多个 pod,挂载同一个 nfs 服务器共享出来的目录;但是 nfs 如果宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分布 式存储有 glusterfs 和 cephfs 参考官网:https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes k8s PV 是什么? PersistentVolume(PV)是群集中的一块存储,由管理员配置或使用存储类动态配置。 它是集群中的资源,就像 pod 是 k8s 集群资源一样。 PV 是容量插件,如 Volumes,其生命周期独立于使用 PV 的任何单个 pod。 k8s PVC 是什么? PersistentVolumeClaim(PVC)是一个持久化存储卷,我们在创建 pod 时可以定义这个类型的存储卷。 它类似于一个 pod。 Pod 消耗节点资源,PVC 消耗 PV 资源。 Pod 可以请求特定级别的资源 (CPU 和内存)。 pvc 在申请 pv 的时候也可以请求特定的大小和访问模式(例如,可以一次读写或多次 只读)。 k8s PVC 和 PV 工作原理 PV 是群集中的资源。 PVC 是对这些资源的请求。 PV 和 PVC 之间的相互作用遵循以下生命周期: (1)pv 的供应方式 可以通过两种方式配置 PV:静态或动态。 静态的: 集群管理员创建了许多 PV。它们包含可供群集用户使用的实际存储的详细信息。它们存在于Kubernetes API 中,可供使用。 动态的: 当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,群集可能会尝试为 PVC 专门动态配置卷。此配置基于 StorageClasses,PVC 必须请求存储类,管理员必须创建并配置该类,以便 进行动态配置。 2)绑定 用户创建 pvc 并指定需要的资源和访问模式。在找到可用 pv 之前,pvc 会保持未绑定状态 3)使用 a)需要找一个存储服务器,把它划分成多个存储空间; b)k8s 管理员可以把这些存储空间定义成多个 pv; c)在 pod 中使用 pvc 类型的存储卷之前需要先创建 pvc,通过定义需要使用的 pv 的大小和对应 的访问模式,找到合适的 pv; d)pvc 被创建之后,就可以当成存储卷来使用了,我们在定义 pod 时就可以使用这个 pvc 的存 储卷 e)pvc 和 pv 它们是一一对应的关系,pv 如果被 pvc 绑定了,就不能被其他 pvc 使用了; f)我们在创建 pvc 的时候,应该确保和底下的 pv 能绑定,如果没有合适的 pv,那么 pvc 就会 处于 pending 状态。 4)回收策略 当我们创建 pod 时如果使用 pvc 做为存储卷,那么它会和 pv 绑定,当删除 pod,pvc 和 pv 绑定 就会解除,解除之后和 pvc 绑定的 pv 卷里的数据需要怎么处理,目前,卷可以保留,回收或删除: Retain Recycle (不推荐使用,1.15 可能被废弃了) Delete 1、Retain 当删除 pvc 的时候,pv 仍然存在,处于 released 状态,但是它不能被其他 pvc 绑定使用,里面的 数据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略 Delete 删除 pvc 时即会从 Kubernetes 中移除 PV,也会从相关的外部设施中删除存储资产 创建 pod,使用 pvc 作为持久化存储卷 1、创建 nfs 共享目录 #在宿主机创建 NFS 需要的共享目录 # mkdir /data/volume_test/v{1,2,3,4,5,6,7,8,9,10} -p #配置 nfs 共享宿主机上的/data/volume_test/v1..v10 目录 cat /etc/exports /data/volumes 192.168.172.0/24(rw,no_root_squash) /data/volumes 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v1 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v2 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v3 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v4 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v5 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v6 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v7 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v8 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v9 192.168.172.0/24(rw,no_root_squash) /data/volume_test/v10 192.168.172.0/24(rw,no_root_squash) # 冒号替换一下保存 :1,$s/192.168.1.0/192.168.172.0/g #重新加载配置,使配置成效 exportfs -arv systemctl restart nfs #查看定义 pv 需要的字段 kubectl explain pv.spec.nfs path readOnly server PersistentVolume可以通过资源提供者支持的任何方式安装在主机上。如下表所示,提供商将具有不同的功能,并且每个PV的访问模式都将设置为该特定卷支持的特定模式。例如,NFS可以支持多个读/写客户端,但是特定的NFS PV可能以只读方式在服务器上导出。每个PV都有自己的一组访问模式,用于描述该特定PV的功能。 访问方式为: ReadWriteOnce-可以通过单个节点以读写方式安装该卷 ReadOnlyMany-该卷可以被许多节点只读挂载 ReadWriteMany-该卷可以被许多节点读写安装 在CLI中,访问模式缩写为: RWO-ReadWriteOnce ROX-ReadOnlyMany RWX-ReadWriteMany 重要的!即使一次卷支持多个卷,也只能一次使用一种访问模式挂载该卷。例如,GCEPersistentDisk可以由单个节点安装为ReadWriteOnce,也可以由多个节点安装为ReadOnlyMany,但不能同时安装。 cat pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: v1 spec: capacity: storage: 1Gi #pv的存储空间容量 accessModes: ["ReadWriteOnce"] nfs: path: /data/volume_test/v1 #把nfs的存储空间创建成pv server: 192.168.172.163 #nfs服务器的地址 --- apiVersion: v1 kind: PersistentVolume metadata: name: v2 spec: capacity: storage: 2Gi accessModes: ["ReadWriteMany"] nfs: path: /data/volume_test/v2 server: 192.168.40.130 --- apiVersion: v1 kind: PersistentVolume metadata: name: v3 spec: capacity: storage: 3Gi accessModes: ["ReadOnlyMany"] nfs: path: /data/volume_test/v3 server: 192.168.40.130 --- apiVersion: v1 kind: PersistentVolume metadata: name: v4 spec: capacity: storage: 4Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v4 server: 192.168.40.130 --- apiVersion: v1 kind: PersistentVolume metadata: name: v5 spec: capacity: storage: 5Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v5 server: 192.168.40.130 --- apiVersion: v1 kind: PersistentVolume metadata: name: v6 spec: capacity: storage: 6Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v6 server: 192.168.40.130 --- apiVersion: v1 kind: PersistentVolume metadata: name: v7 spec: capacity: storage: 7Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v7 server: 192.168.40.130 --- apiVersion: v1 kind: PersistentVolume metadata: name: v8 spec: capacity: storage: 8Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v8 server: 192.168.40.130 --- apiVersion: v1 kind: PersistentVolume metadata: name: v9 spec: capacity: storage: 9Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v9 server: 192.168.172.163 --- apiVersion: v1 kind: PersistentVolume metadata: name: v10 spec: capacity: #pv的存储空间容量 storage: 10Gi accessModes: ["ReadWriteOnce","ReadWriteMany"] nfs: path: /data/volume_test/v10 #把nfs的存储空间创建成pv server: 192.168.172.163 --- #更新资源清单文件 ]# kubectl apply -f pv.yaml #查看 pv 资源 #STATUS 是 Available,表示 pv 是可用的 创建 pvc,和符合条件的 pv 绑定 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: ["ReadWriteMany"] resources: requests: storage: 2Gi kubectl apply -f pvc.yaml #查看 pv 和 pvc # kubectl get pv #STATUS是Bound,表示这个pv已经被my-pvc绑定了 kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v2 2Gi RWX 2m25s #. pvc 的名字-绑定到 pv-绑定的是 v2 这个 pv-pvc 可使用的容量是 2G# 5、创建 pod,挂载 pvc cat pod_pvc.yaml apiVersion: v1 kind: Pod metadata: name: pod-pvc spec: containers: - name: nginx image: nginx volumeMounts: - name: nginx-html mountPath: /usr/share/nginx/html volumes: - name: nginx-html persistentVolumeClaim: claimName: my-pvc kubectl get pods | grep pod-pvc pod-pvc 1/1 Running 0 71s #通过上面可以看到 pod 处于 running 状态,正常运行 注:使用 pvc 和 pv 的注意事项 1、我们每次创建 pvc 的时候,需要事先有划分好的 pv,这样可能不方便,那么可以在创建 pvc 的 时候直接动态创建一个 pv 这个存储类,pv 事先是不存在的 2、pvc 和 pv 绑定,如果使用默认的回收策略 retain,那么删除 pvc 之后,pv 会处于 released 状 态,我们想要继续使用这个 pv,需要手动删除 pv,kubectl delete pv pv_name,删除 pv,不会删除 pv 里的数据,当我们重新创建 pvc 时还会和这个最匹配的 pv 绑定,数据还是原来数据,不会丢失。 上面介绍的 PV 和 PVC 模式都是需要先创建好 PV,然后定义好 PVC 和 pv 进行一对一的 Bond, 但是如果 PVC 请求成千上万,那么就需要创建成千上万的 PV,对于运维人员来说维护成本很高, Kubernetes 提供一种自动创建 PV 的机制,叫 StorageClass,它的作用就是创建 PV 的模板。k8s 集 群管理员通过创建 storageclass 可以动态生成一个存储卷 pv 供 k8s pvc 使用。 每个 StorageClass 都包含字段 provisioner,parameters 和 reclaimPolicy。 具体来说,StorageClass 会定义以下两部分: 1、PV 的属性 ,比如存储的大小、类型等; 2、创建这种 PV 需要使用到的存储插件,比如 Ceph、NFS 等 有了这两部分信息,Kubernetes 就能够根据用户提交的 PVC,找到对应的 StorageClass,然后 Kubernetes 就会调用 StorageClass 声明的存储插件,创建出需要的 PV。 kubectl explain storageclass allowVolumeExpansion allowedTopologies <[]Object> mountOptions <[]string> parameters provisioner reclaimPolicy volumeBindingMode provisioner:供应商,storageclass 需要有一个供应者,用来确定我们使用什么样的存储来创 建 pv,常见的 provisioner 如下 (https://kubernetes.io/zh/docs/concepts/storage/storage-classes/): provisioner 既可以由内部供应商提供,也可以由外部供应商提供,如果是外部供应商可以参考 https://github.com/kubernetes-incubator/external-storage/下提供的方法创建。 以 NFS 为例,要想使用 NFS,我们需要一个 nfs-client 的自动装载程序,称之为 provisioner,这 个程序会使用我们已经配置好的 NFS 服务器自动创建持久卷,也就是自动帮我们创建 PV。 reclaimPolicy:回收策略 allowVolumeExpansion:允许卷扩展,PersistentVolume 可以配置成可扩展。将此功能设置为 true 时,允许用户通过编辑相应的 PVC 对象来调整卷大小。当基础存储类的 allowVolumeExpansion 字段设置为 true 时,以下类型的卷支持卷扩展。 注意:此功能仅用于扩容卷,不能用于缩小卷。 1、创建运行 nfs-provisioner 需要的 sa 账号 cat serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: nfs-provisioner kubectl apply -f serviceaccount.yaml what. sa? sa 的全称是 serviceaccount。 serviceaccount 是为了方便 Pod 里面的进程调用 Kubernetes API 或其他外部服务而设计的。指定了 serviceaccount 之后,我们把 pod 创建出来了,我们在使用这个 pod 时,这个 pod 就 有了我们指定的账户的权限了。 2、对 sa 授权 #kubectl create clusterrolebinding nfs-provisioner --clusterrole=cluster-admin --serviceaccount=default:nfs-provisioner 3、k8s1.20 版本通过 nfs provisioner 动态生成 pv 会报错 Unexpected error getting claim reference to claim "default/test-claim1": selfLink was empty, can't make reference,报错原因是 1.20 版本仅用了 selfLink,解决方法如下; vim /etc/kubernetes/manifests/kube-apiserver.yaml 在这里: spec: containers: - command: - kube-apiserver 添加这一行: - --feature-gates=RemoveSelfLink=false 然后应用它,即可: kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml 二进制安装的 k8s,按如下修改: vi /etc/systemd/system/kube-apiserver.service --feature-gates=RemoveSelfLink=false \ --v=2 systemctl datemon-reload systemctl restart apiserver 4、安装 nfs-provisioner 程序 # mkdir /data/nfs_pro -p #把/data/nfs_pro 变成 nfs 共享的目录 cat /etc/exports /data/nfs_pro 192.168.172.0/24(rw,no_root_squash) #exportfs -arv systemctl restart nfs cat nfs-deployment.yaml kind: Deployment apiVersion: apps/v1 metadata: name: nfs-provisioner spec: selector: matchLabels: app: nfs-provisioner replicas: 1 strategy: type: Recreate template: metadata: labels: app: nfs-provisioner spec: serviceAccount: nfs-provisioner containers: - name: nfs-provisioner image: registry.cn-hangzhou.aliyuncs.com/god/nfs-client-provisioner:v1 volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: example.com/nfs #提供人表示提供人的类型。 - name: NFS_SERVER value: 192.168.172.163 - name: NFS_PATH value: /data/nfs_pro/ volumes: - name: nfs-client-root nfs: server: 192.168.172.163 path: /data/nfs_pro/ kubectl apply -f nfs-deployment.yaml 1,$s/文档内容/替换内容/g #查看 nfs-provisioner 是否正常运行 kubectl get pods | grep nfs nfs-provisioner-6cb84f5d49-nwvtj 1/1 Running 0 22s cat nfs-storageclass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs provisioner: example.com/nfs kubectl apply -f nfs-storageclass.yaml #查看 storageclass 是否创建成功 kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE nfs example.com/nfs Delete Immediate false 2s 注意:provisioner 处写的 example.com/nfs 应该跟安装 nfs provisioner 时候的 env 下的 PROVISIONER_NAME 的 value 值保持一致,如下: env: - name: PROVISIONER_NAME value: example.com/nfs 创建 pvc,通过 storageclass 动态生成 pv cat claim.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim1 annotations: volume.beta.kubernetes.io/storage-class: "nfs" spec: accessModes: ["ReadWriteMany"] resources: requests: storage: 1Gi storageClassName: nfs kubectl apply -f claim.yaml #查看是否动态生成了 pv,pvc 是否创建成功,并和 pv 绑定 kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v2 2Gi RWX 22h test-claim1 Bound pvc-b03288b7-13f8-4e6d-aba7-ff6d23a5c500 1Gi RWX nfs 7m16s #通过上面可以看到 test-claim1 的 pvc 已经成功创建了,绑定的 pv 是 pvc-b03288b7-13f8-4e6d-aba7-ff6d23a5c500,这个 pv 是由 storageclass 调用 nfs provisioner 自动生成的。 总结: 1、供应商:创建一个 nfs provisioner 2、创建 storageclass,storageclass 指定刚才创建的供应商 3、创建 pvc,这个 pvc 指定 storageclass 创建 pod,挂载 storageclass 动态生成的 pvc:test-claim1 cat read-pod.yaml apiVersion: v1 kind: Pod metadata: name: read-pod spec: containers: - name: read-pod image: nginx volumeMounts: - name: nfs-pvc mountPath: /usr/share/nginx/html restartPolicy: volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim1 #查看 pod 是否创建成功 kubectl get pods | grep read read-pod 1/1 Running 0 29sk8s 持久化存储方案: PVC
3、创建 pv
存取模式
k8s 存储类:storageclass
安装 nfs provisioner,用于配合存储类动态生成 pv
#把 nfs-provisioner-v1.tar.gz 工作节点上上,手动解压
创建 storageclass,动态供给 pv