NFS实现k8s存储
K8s存储的几个重要概念: 卷(Volume),持久卷(PV),持久卷申领(PVC),存储类(StorageClass)
卷(VOLUME)
持久卷(PV)
持久卷申领(PVC)
存储类(StorageClass)
PV/PVC/StorageClass的关系
NFS实现K8S存储
NFS静态供应
NFS flex-volume动态供应
NFS csi动态供应
参考
K8s存储的几个重要概念: 卷(Volume),持久卷(PV),持久卷申领(PVC),存储类(StorageClass)
卷(VOLUME)
容器中的文件在磁盘上是临时存放的,这给在容器中运行较重要的应用带来一些问题。 当容器崩溃或停止时会出现一个问题。此时容器状态未保存, 因此在容器生命周期内创建或修改的所有文件都将丢失。 在崩溃期间,kubelet 会以干净的状态重新启动容器。 当多个容器在一个 Pod 中运行并且需要共享文件时,会出现另一个问题。 跨所有容器设置和访问共享文件系统具有一定的挑战性 ——来自官方文档
- 由于卷属于 Pod 内部共享资源存储,生命周期和 Pod 相同,与 Container 无关,即使 Pod 上的容器停止或者重启,Volume 不会受到影响,但是如果Pod终止,那么这个 Volume 的生命周期也将结束。
- 卷的类型比较多:hostPath,configMap,emptyDir,local,nfs,iscsi, 具体可以参考官方文档(点我):
持久卷(PV)
持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。背后可能是 NFS、iSCSI、特定于云平台的存储系统等。
以下是来自官网的一个例子:
apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
请注意,这是hostPath类型的PV,可以参考官方文档hostPath
root@master:/home/guanwu/k8s/pvtest# k apply -f task-pv-volume.yaml
persistentvolume/task-pv-volume created
root@master:/home/guanwu/k8s/pvtest# k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
task-pv-volume 10Gi RWO Retain Available manual 53s
创建PVC(下文会介绍)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
root@master:/home/guanwu/k8s/pvtest# k apply -f task-pv-claim.yaml
persistentvolumeclaim/task-pv-claim created
root@master:/home/guanwu/k8s/pvtest# k get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
task-pv-claim Bound task-pv-volume 10Gi RWO manual 3s
创建pod,这里请注意,这个pod需要被schedule到对应的提供hostPath卷的主机中,可以自行添加属性
spec.nodeSelector选择调度的节点,也可以参考节点调度和污点和容忍度
请注意,/mnt/data目录创建在master 节点中,要使用这个pv的pod需要被调度到当前工作节点,可以使用NodeSelector和label确保pod被调度到当前节点
root@master:/home/guanwu/k8s/pvtest# find /mnt/data
/mnt/data
请注意观察,我当前环境的master节点加上了type=ssd的标签
root@master:/home/guanwu/k8s/pvtest# k get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master Ready control-plane,master 92d v1.28.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,nfs=app,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=,type=ssd
worker1 Ready worker1 92d v1.28.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssy,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker1,kubernetes.io/os=linux,node-role.kubernetes.io/worker1=
worker2 Ready worker2 92d v1.28.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker2,kubernetes.io/os=linux,node-role.kubernetes.io/worker2=
root@master:/home/guanwu/k8s/pvtest#
以下是我的pod文件配置
apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
volumes:
- name: task-pv-storage
persistentVolumeClaim:
claimName: task-pv-claim
containers:
- name: task-pv-container
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: task-pv-storage
nodeSelector:
type: ssd #请注意这里,缺少这个配置会schedule该Pod到正确的节点
检查pod是否正常运行
root@master:/home/guanwu/k8s/pvtest# k apply -f pv-pod.yaml
pod/task-pv-pod created
root@master:/home/guanwu/k8s/pvtest# k get pod task-pv-pod
NAME READY STATUS RESTARTS AGE
task-pv-pod 1/1 Running 0 12s
root@master:/home/guanwu/k8s/pvtest# k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
task-pv-volume 10Gi RWO Retain Bound test/task-pv-claim manual 24m
root@master:/home/guanwu/k8s/pvtest# k get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
task-pv-claim Bound task-pv-volume 10Gi RWO manual 20m
验证挂载的文件(注意k命令是本地kubectl命令的简写)
root@master:/home/guanwu/k8s/pvtest# kubectl exec -it task-pv-pod -- /bin/bash
root@task-pv-pod:/# echo "hello world" > /usr/share/nginx/html/index.html
root@task-pv-pod:/# curl localhost
hello world
root@master:/home/guanwu/k8s/pvtest# k get pod task-pv-pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
task-pv-pod 1/1 Running 0 15m 10.10.0.80 master <none> <none>
循环10次修改 /mnt/data/index.html文件的内容,并使用curl 10.10.0.80访问
root@master:/home/guanwu/k8s/pvtest# for ((i=1; i<=10; i++)); do s=$(date); echo $s > /mnt/data/index.html; sleep 1s; curl 10.10.0.80 ; done
2024年 01月 05日 星期五 22:52:42 CST
2024年 01月 05日 星期五 22:52:43 CST
2024年 01月 05日 星期五 22:52:44 CST
2024年 01月 05日 星期五 22:52:45 CST
2024年 01月 05日 星期五 22:52:46 CST
2024年 01月 05日 星期五 22:52:47 CST
2024年 01月 05日 星期五 22:52:49 CST
2024年 01月 05日 星期五 22:52:50 CST
2024年 01月 05日 星期五 22:52:51 CST
2024年 01月 05日 星期五 22:52:52 CST
root@master:/home/guanwu/k8s/pvtest#
清除资源
$ kubectl delete pod task-pv-pod
$ kubectl delete pvc task-pv-claim
$ kubectl delete pv task-pv-volume
持久卷申领(PVC)
表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载。为了满足这类需求,就有了存储类(StorageClass) 资源。
存储类(StorageClass)
StorageClass 为管理员提供了一种描述他们提供的存储的“类”的方法。不同的类可能映 射到服务质量级别,或备份策略,或者由群集管理员确定的任意策略。 Kubernetes 本身对 于什么类别代表是不言而喻的。 这个概念有时在其他存储系统中称为“配置文件”
PV/PVC/StorageClass的关系
- PV 是运维人员来创建的,开发操作 PVC,可是大规模集群中可能会有很多 PV,如果这 些 PV 都需要运维手动来处理这也是一件很繁琐的事情,所以就有了动态供给概念,也就是 Dynamic Provisioning,动态供给的关键就是 StorageClass,它的作用就是创建 PV 模板。创 建 StorageClass 里面需要定义 PV 属性比如存储类型、大小等;另外创建这种 PV 需要用到 存储插件。最终效果是,用户提交PVC,里面指定存储类型,如果符合我们定义的StorageClass, 则会为其自动创建 PV 并进行绑定。
它们的关系可以参考下图 - PVC 和 PV 是一一对应的。 PV 和 PVC 中的 spec 关键字段要匹配,比如存储(storage)大小。 PV 和 PVC 中的 storageClassName 字段必须一致
NFS实现K8S存储
NFS安装就不再展示了,网上资料很多,本人挂载的目录是 /usr/share/nfs
root@master:/home/guanwu/k8s/pvtest# ls /usr/share/nfs
pvc-324d5c94-27c6-4d6d-8af0-a08adc9a8de6
root@master:/home/guanwu/k8s/pvtest# showmount -e
Export list for master:
/usr/share/nfs *
请注意!请注意!请注意!k8s集群的节点必须安装nfs客户端,否则pod挂载pv会出现异常的问题,作者本人宿主机环境是Ubuntu20.04 LTS,使用如下命令(其他环境的读者可以自行查找)
sudo apt install nfs-common
NFS静态供应
pv文件
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0001
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle
nfs:
path: "/usr/share/nfs"
server: 192.168.201.129
pvc文件
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
pod文件
apiVersion: v1
kind: Pod
metadata:
name: nfs-test
spec:
containers:
- name: my-busybox
image: busybox
volumeMounts:
- mountPath: "/data"
name: sample-volume
command: ["sleep", "1000000"]
imagePullPolicy: IfNotPresent
volumes:
- name: sample-volume
persistentVolumeClaim:
claimName: nfs-pvc #请注意这里,要和 pvc文件name对应
演示效果
root@master:/home/guanwu/k8s/nfs/staticprovisor# k apply -f pv1.yaml
persistentvolume/pv0001 created
root@master:/home/guanwu/k8s/nfs/staticprovisor# k get pv pv0001
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv0001 5Gi RWX Recycle Available 5s
root@master:/home/guanwu/k8s/nfs/staticprovisor# k apply -f pvc1.yaml
persistentvolumeclaim/nfs-pvc created
root@master:/home/guanwu/k8s/nfs/staticprovisor# k get pvc nfs-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound pv0001 5Gi RWX 9s
root@master:/home/guanwu/k8s/nfs/staticprovisor# k apply -f pvc-pod.yaml
pod/nfs-test created
root@master:/home/guanwu/k8s/nfs/staticprovisor# k get pod nfs-test
NAME READY STATUS RESTARTS AGE
nfs-test 1/1 Running 0 16s
root@master:/home/guanwu/k8s/nfs/staticprovisor# ls /usr/share/nfs/
pvc-324d5c94-27c6-4d6d-8af0-a08adc9a8de6
root@master:/home/guanwu/k8s/nfs/staticprovisor# k exec -it nfs-test -- sh
/ #
/ # echo $(date) > /data/date.txt
/ # cat /data/date.txt
Fri Jan 5 15:31:42 UTC 2024
/ # exit
root@master:/home/guanwu/k8s/nfs/staticprovisor# cat /usr/share/nfs/date.txt
Fri Jan 5 15:31:42 UTC 2024
root@master:/home/guanwu/k8s/nfs/staticprovisor#
测试多个容器共享volume的情况,创建对应deployment文件
root@master:/home/guanwu/k8s/nfs/staticprovisor# cat test-nfs-static.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: test-nfs-static
name: test-nfs-static
spec:
replicas: 3
selector:
matchLabels:
app: test-nfs-static
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: test-nfs-static
spec:
containers:
- image: nginx:1.9
name: nginx
resources: {}
volumeMounts:
- mountPath: "/data"
name: sample-volume
imagePullPolicy: IfNotPresent
volumes:
- name: sample-volume
persistentVolumeClaim:
claimName: nfs-pvc
创建deployment
root@master:/home/guanwu/k8s/nfs/staticprovisor# k apply -f test-nfs-static.yaml
deployment.apps/test-nfs-static created
root@master:/home/guanwu/k8s/nfs/staticprovisor# k get pod -l app==test-nfs-static -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-nfs-static-6bd96c8f59-jt5v9 1/1 Running 0 114s 10.10.0.82 master <none> <none>
test-nfs-static-6bd96c8f59-sglqf 1/1 Running 0 114s 10.10.1.123 worker1 <none> <none>
test-nfs-static-6bd96c8f59-tjfmb 1/1 Running 0 114s 10.10.2.122 worker2 <none> <none>
验证各个pod节点共享同一个卷,获取所有pod列表,使用podList保存,并对每个Pod执行以下命令
kubectl exec -t $pod -- bash -c 'echo "start>>>" && echo "$(hostname) has dir:" && ls /data && echo "end<<<"'
root@master:/home/guanwu/k8s/nfs/staticprovisor# podList=$(k get pods -l app=test-nfs-static | sed 1d | awk -F ' ' '{print $1}')
root@master:/home/guanwu/k8s/nfs/staticprovisor# echo $podList
test-nfs-static-6bd96c8f59-jt5v9 test-nfs-static-6bd96c8f59-sglqf test-nfs-static-6bd96c8f59-tjfmb
root@master:/home/guanwu/k8s/nfs/staticprovisor# for pod in $podList; do kubectl exec -t $pod -- bash -c 'echo "start>>>" && echo "$(hostname) has dir:" && ls /data && echo "end<<<"'; done;
start>>>
test-nfs-static-6bd96c8f59-jt5v9 has dir:
date.txt
pvc-324d5c94-27c6-4d6d-8af0-a08adc9a8de6
end<<<
start>>>
test-nfs-static-6bd96c8f59-sglqf has dir:
date.txt
pvc-324d5c94-27c6-4d6d-8af0-a08adc9a8de6
end<<<
start>>>
test-nfs-static-6bd96c8f59-tjfmb has dir:
date.txt
pvc-324d5c94-27c6-4d6d-8af0-a08adc9a8de6
end<<<
root@master:/home/guanwu/k8s/nfs/staticprovisor#
可以看到,每个pvc的挂载目录都是同一个,这样就实现了调度到不同的Node节点的pod容器共享相同的存储卷