第二章 营销型网站建设测验,dede手机网站仿站,wordpress获取用户注册时间,商用图片的网站------ 课程视频同步分享在今日头条和B站
大家好#xff0c;我是博哥爱运维#xff0c;本期课程将深入解析Kubernetes的持久化存储机制,包括PV、PVC、StorageClass等的工作原理、使用场景、最佳实践等,帮您构建稳定可靠的状态存储,确保应用和数据 100% 安全。
Volume
…------ 课程视频同步分享在今日头条和B站
大家好我是博哥爱运维本期课程将深入解析Kubernetes的持久化存储机制,包括PV、PVC、StorageClass等的工作原理、使用场景、最佳实践等,帮您构建稳定可靠的状态存储,确保应用和数据 100% 安全。
Volume
我们这里先来聊聊K8s的存储模型Volume来实践下如何将各种持久化的存储映射到Pod中的容器。
在我们上面的实战中大家如果细心的话会发现把nginx服务pod内的默认页面改了但当重启pod后这个页面又恢复成nginx容器初始的状态了所以这里要和大家说的是在没有配置持久化存储前任何新增的数据在pod发生重启时都是无法保留的而在K8s上Pod的生命周期可能是很短它们会被频繁地销毁和创建自然在容器销毁时里面运行时新增的数据如修改的配置及日志文件等也会被清除。
那么怎么解决这一现象呢我们可以用K8s volume来持久化保存容器的数据Volume的生命周期独立于容器Pod中的容器可能被销毁重建但Volume会被保留。
本质上K8s volume是一个目录这点和Docker volume差不多当Volume被mount到Pod上这个Pod中的所有容器都可以访问这个volume在生产场景中我们常用的类型有这几种
emptyDirhostPathPersistentVolume(PV) PersistentVolumeClaim(PVC)StorageClass
emptyDir
我们先开始讲讲emptyDir它是最基础的Volume类型pod内的容器发生重启不会造成emptyDir里面数据的丢失但是当pod被重启后emptyDir数据会丢失也就是说emptyDir与pod的生命周期是一致的那么大家可能有个疑问这个之前讲的没有配置它也没什么区别呀实际上在某些时候它的作用还是挺大的在生产中它的最实际实用是提供Pod内多容器的volume数据共享下面我会用一个实际的生产者消费者的例子来演示下emptyDir的作用相信大家动动手就会理解得更快了
# cat web.yamlapiVersion: apps/v1
kind: Deployment
metadata:labels:app: webname: webnamespace: default
spec:replicas: 1selector:matchLabels:app: webtemplate:metadata:labels:app: webspec:containers:- image: nginx:1.21.6name: nginxresources:limits:cpu: 50mmemory: 20Mirequests:cpu: 50mmemory: 20MivolumeMounts: # 准备将pod的目录进行卷挂载- name: html-files # 自定个名称容器内可以类似这样挂载多个卷mountPath: /usr/share/nginx/html- name: busybox # 在pod内再跑一个容器每秒把当时时间写到nginx默认页面上image: registry.cn-shanghai.aliyuncs.com/acs/busybox:v1.29.2args:- /bin/sh- -c- while :; doif [ -f /html/index.html ];thenecho [$(date %F\ %T)] hello /html/index.htmlsleep 1elsetouch /html/index.htmlfidonevolumeMounts:- name: html-files # 注意这里的名称和上面nginx容器保持一样这样才能相互进行访问mountPath: /html # 将数据挂载到当前这个容器的这个目录下volumes:- name: html-files # 最后定义这个卷的名称也保持和上面一样emptyDir: # 这就是使用emptyDir卷类型了medium: Memory # 这里将文件写入内存中保存这样速度会很快配置为medium: 就是代表默认的使用本地磁盘空间来进行存储sizeLimit: 10Mi # 因为内存比较珍贵注意限制使用大小更新这个web的配置
# kubectl apply -f web.yaml
deployment.apps/web configured# 可以看到READY下面容器数量变为2了
# kubectl get pod
NAME READY STATUS RESTARTS AGE
......
web-5bf769fdfc-44p7h 2/2 Running 0 2m4s# 接着创建一个service来请求测试下
kubectl expose deployment web --port 80 --target-port 80# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
......
web ClusterIP 10.68.229.231 none 80/TCP 4h36m# 可以看到每次访问都是被写入当前最新时间的页面内容
[rootnode-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:34] hello
[rootnode-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:35] hello
[rootnode-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:36] hello
[rootnode-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:38] hello我们来探究下原理
# 下面这个是docker容器运行时的记录
# 看下这个web的pod的描述信息
# kubectl describe pod web-5bf769fdfc-44p7h
......
Node: 10.0.1.203/10.0.1.203 # 找到这个pod运行在哪个node上
......
Containers:nginx:Container ID: docker://c1482a15f756ff3bc089973ec942a4e60f7ec34674ab8435a47a94d4b93411a7 # 找到pod内nginx容器的ID
......busybox:Container ID: docker://ecedf3b0ffa6b5101e84a21f8dbf6188179875b5db61980bc93b65195f558c6f # 找到pod内busybox容器的ID# 我们登陆10.0.1.203 这台node查看pod内这两个容器的volume挂载信息我们发现两个容器都 mount 了同一个目录
[rootnode-3 ~]# docker inspect c1482a15f756ff3bc089973ec942a4e60f7ec34674ab8435a47a94d4b93411a7|grep volume|grep html/var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files:/usr/share/nginx/html,Source: /var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files,[rootnode-3 ~]# docker inspect ecedf3b0ffa6b5101e84a21f8dbf6188179875b5db61980bc93b65195f558c6f|grep volume|grep html/var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files:/html,Source: /var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files, # Containerd运行时日志目录
# ll /var/log/containers/|grep web
hostPath
hostPath Volume 的作用是将容器运行的node上已经存在文件系统目录给mount到pod的容器。在生产中大部分应用是是不会直接使用hostPath的因为我们并不关心Pod在哪台node上运行而hostPath又恰好增加了pod与node的耦合限制了pod的使用这里我们只作一下了解知道有这个东西存在即可一般只是一些安装服务会用到比如下面我截取了网络插件calico的部分volume配置: volumeMounts:- mountPath: /host/drivername: flexvol-driver-host
......volumes:
......- hostPath:path: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~udstype: DirectoryOrCreatename: flexvol-driver-host
PersistentVolume(PV) PersistentVolumeClaim(PVC)
现在讲Volume里面在生产中用的最多的PersistentVolume(持久卷简称PV)和 PersistentVolumeClaim(持久卷消费简称PVC)通常在企业中Volume是由存储系统的管理员来维护他们来提供pvpv具有持久性生命周期独立于PodPod则是由应用的开发人员来维护如果要进行一卷挂载那么就写一个pvc来消费pv就可以了K8s会查找并提供满足条件的pv。
有了pvc我们在K8s进行卷挂载就只需要考虑要多少容量了而不用关心真正的空间是用什么存储系统做的等一些底层细节信息pv这些只有存储管理员才应用去关心它。
K8s支持多种类型的pv我们这里就以生产中常用的NFS来作演示在云上的话就用NAS生产中如果对存储要求不是太高的话建议就用NFS这样出问题也比较容易解决如果有性能需求可以看看rook的ceph以及Rancher的Longhorn这些我都在生产中用过如果有需求的同学可以在评论区留言我会单独做课程来讲解。
开始部署NFS-SERVER
# 我们这里在10.0.1.201上安装在生产中大家要提供作好NFS-SERVER环境的规划
# yum -y install nfs-utils
# ubuntu安装NFS服务端
# apt-get install nfs-kernel-server -y# 创建NFS挂载目录
# mkdir /nfs_dir
# chown nobody.nogroup /nfs_dir# 修改NFS-SERVER配置
# echo /nfs_dir *(rw,sync,no_root_squash) /etc/exports# 重启服务
# systemctl restart rpcbind.service
# systemctl restart nfs-kernel-server.service
# systemctl restart nfs-utils.service
# systemctl restart nfs-server.service # 增加NFS-SERVER开机自启动
# systemctl enable nfs-server.service
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.# 验证NFS-SERVER是否能正常访问
## 注如果查看不到目录可以到另一机器上挂载试试
## rootnode-2:~# mount.nfs 10.0.1.201:/nfs_dir /mnt/
# showmount -e 10.0.1.201
Export list for 10.0.1.201:
/nfs_dir *创建基于NFS的PV
首先在NFS-SERVER的挂载目录里面创建一个目录
# mkdir /nfs_dir/pv1接着准备好pv的yaml配置保存为pv1.yaml
# cat pv1.yaml
apiVersion: v1
kind: PersistentVolume
metadata:name: pv1labels:type: test-claim # 这里建议打上一个独有的标签方便在多个pv的时候方便提供pvc选择挂载
spec:capacity:storage: 1Gi # ---------- 1accessModes:- ReadWriteOnce # ---------- 2persistentVolumeReclaimPolicy: Recycle # ---------- 3storageClassName: nfs # ---------- 4nfs:path: /nfs_dir/pv1 # ---------- 5server: 10.0.1.201capacity 指定 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 服务器上对应的目录这里注意我测试的时候需要手动先创建好这个目录并授权好不然后面挂载会提示目录不存在 mkdir /nfsdata/pv1 chown -R nobody.nogroup /nfsdata 。
创建这个pv
# kubectl apply -f pv1.yaml
persistentvolume/pv1 created# STATUS 为 Available表示 pv1 就绪可以被 PVC 申请
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv1 1Gi RWO Recycle Available nfs 4m45s接着准备PVC的yaml保存为pvc1.yaml
# cat pvc1.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: pvc1
spec:accessModes:- ReadWriteOnceresources:requests:storage: 1GistorageClassName: nfsselector:matchLabels:type: test-claim创建这个pvc
# kubectl apply -f pvc1.yaml
persistentvolumeclaim/pvc1 created# 看下pvc的STATUS为Bound代表成功挂载到pv了
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc1 Bound pv1 1Gi RWO nfs 2s# 这个时候再看下pvSTATUS也是Bound了同时CLAIM提示被default/pvc1消费
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv1 1Gi RWO Recycle Bound default/pvc1 nfs 下面我们准备pod服务来挂载这个pvc这里就以上面最开始演示用的nginx的deployment的yaml配置来作修改
# cat nginx.yaml
apiVersion: v1
kind: Service
metadata:labels:app: nginxname: nginx
spec:ports:- port: 80protocol: TCPtargetPort: 80selector:app: nginx---
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: nginxname: nginx
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- image: nginx:1.21.6name: nginxvolumeMounts: # 我们这里将nginx容器默认的页面目录挂载- name: html-filesmountPath: /usr/share/nginx/htmlvolumes:- name: html-filespersistentVolumeClaim: # 卷类型使用pvc,同时下面名称处填先创建好的pvc1claimName: pvc1更新配置
# kubectl apply -f nginx.yaml
service/nginx unchanged
deployment.apps/nginx configured# 我们看到新pod已经在创建了
# kubectl get pod
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# 我们这里直接用svc地址测试一下
# kubectl get svc
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# 咦这里为什么是显示403了呢注意卷挂载后会把当前已经存在这个目录的文件给覆盖掉这个和传统机器上的磁盘目录挂载道理是一样的
[rootnode-1 ~]# curl 10.68.238.54
html
headtitle403 Forbidden/title/head
body
centerh1403 Forbidden/h1/center
hrcenternginx/1.19.5/center
/body
/html# 我们来自己创建一个index.html页面
# echo hello, world! /nfs_dir/pv1/index.html# 再请求下看看已经正常了
# curl 10.68.238.54
hello, world!# 我们来手动删除这个nginx的pod看下容器内的修改是否是持久的呢
# kubectl delete pod nginx-569546db98-4nmmg
pod nginx-569546db98-4nmmg deleted# 等待一会等新的pod被创建好
# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-569546db98-99qpq 1/1 Running 0 45s# 再测试一下可以看到容器内的修改现在已经被持久化了
# curl 10.68.238.54
hello, world!# 后面我们再想修改有两种方式一个是exec进到pod内进行修改还有一个是直接修改挂载在NFS目录下的文件
# echo 111 /nfs_dir/pv1/index.html
# curl 10.68.238.54
111下面讲下如何回收PVC以及PV
# 这里删除时会一直卡着我们按ctrlc看看怎么回事
# kubectl delete pvc pvc1
persistentvolumeclaim pvc1 deleted
^C# 看下pvc发现STATUS是Terminating删除中的状态我分析是因为服务pod还在占用这个pvc使用中
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc1 Terminating pv1 1Gi RWO nfs 21m# 先删除这个pod
# kubectl delete pod nginx-569546db98-99qpq
pod nginx-569546db98-99qpq deleted# 再看先删除的pvc已经没有了
# kubectl get pvc
No resources found in default namespace.# 根据先前创建pv时的数据回收策略为Recycle – 清除 PV 中的数据这时果然先创建的index.html已经被删除了在生产中要尤其注意这里的模式注意及时备份数据注意及时备份数据注意及时备份数据
# ll /nfs_dir/pv1/
total 0# 虽然此时pv是可以再次被pvc来消费的但根据生产的经验建议在删除pvc时也同时把它消费的pv一并删除然后再重启创建都是可以的我们先提到了K8s的设计是pv交给存储管理员来管理我们是管用pvc来消费就好但这里我们实际还是得一起管理pv和pvc在实际工作中我们存储管理员可以提前配置好pv的动态供给StorageClass来根据pvc的消费动态生成pv
StorageClass
我这是直接拿生产中用的实例来作演示利用nfs-client-provisioner来生成一个基于nfs的StorageClass部署配置yaml配置如下保持为nfs-sc.yaml
apiVersion: v1
kind: ServiceAccount
metadata:name: nfs-client-provisionernamespace: 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: ServiceAccountname: nfs-client-provisionernamespace: kube-system
roleRef:kind: ClusterRolename: nfs-client-provisioner-runnerapiGroup: rbac.authorization.k8s.io---
kind: Deployment
apiVersion: apps/v1
metadata:name: nfs-provisioner-01namespace: kube-system
spec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-provisioner-01template:metadata:labels:app: nfs-provisioner-01spec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisionerimage: bogeit/nfs-subdir-external-provisioner:v4.0.2imagePullPolicy: IfNotPresentvolumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAMEvalue: nfs-provisioner-01 # 此处供应者名字供storageclass调用- name: NFS_SERVERvalue: 10.0.1.201 # 填入NFS的地址- name: NFS_PATHvalue: /nfs_dir # 填入NFS挂载的目录volumes:- name: nfs-client-rootnfs:server: 10.0.1.201 # 填入NFS的地址path: /nfs_dir # 填入NFS挂载的目录---
# use aliyuns nas need configure: https://help.aliyun.com/document_detail/130727.html?spma2c4g.11174283.6.715.1aad2ceeUrijYZ
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: nfs-boge
provisioner: nfs-provisioner-01
# Supported policies: Delete、 Retain default is Delete
reclaimPolicy: Retain
开始创建这个StorageClass
# kubectl apply -f nfs-sc.yaml
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 createdorageclass.storage.k8s.io/nfs-boge created# 注意这个是在放kube-system的namespace下面这里面放置一些偏系统类的服务
# kubectl -n kube-system get pod -w
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# StorageClass已经创建好了
# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-boge nfs-provisioner-01 Retain Immediate false 37s我们来基于StorageClass创建一个pvc看看动态生成的pv是什么效果
# vim pvc-sc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: pvc-sc
spec:storageClassName: nfs-bogeaccessModes:- ReadWriteManyresources:requests:storage: 1Mi# kubectl apply -f pvc-sc.yaml
persistentvolumeclaim/pvc-sc created# kubectl get pvc
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# kubectl get pv
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
# vim nginx.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: nginxname: nginx
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- image: nginx:1.21.6name: nginxvolumeMounts: # 我们这里将nginx容器默认的页面目录挂载- name: html-filesmountPath: /usr/share/nginx/htmlvolumes:- name: html-filespersistentVolumeClaim:claimName: pvc-sc# kubectl apply -f nginx.yaml
service/nginx unchanged
deployment.apps/nginx configured# 这里注意下因为是动态生成的pv所以它的目录基于是一串随机字符串生成的这时我们直接进到pod内来创建访问页面
# kubectl exec -it nginx-57cdc6d9b4-n497g -- bash
rootnginx-57cdc6d9b4-n497g:/# echo storageClass used /usr/share/nginx/html/index.html
rootnginx-57cdc6d9b4-n497g:/# exit# curl 10.68.238.54
storageClass used# 我们看下NFS挂载的目录
# ll /nfs_dir/
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