Kubernetes存储管理
容器中的磁盘生命周期是短暂的,也带来了一系列的问题:
- 当一个容器损坏后,Kubernetes会重启容器,但是文件会丢失
- 很多容器在同一Pod中运行的时候,数据文件需要共享
Kubernetes Volume的到来解决了上述问题,Kubernetes集群中的存储跟Docker的存储卷有些类似,只不过Docker的存储卷作用范围为一个容器,Kubernetes的作用范围是Pod,每个Pod中声明的存储卷由Pod中的所有容器共享。
1、EmptyDir
emptyDir:当Pod分配到Node上时,将会创建emptyDir,pod中的容器都可以读写这个目录,这个目录可以被挂载到各个容器相同或不同的路径下,并且只要Node上的Pod一直运行,Volume就会一直存在,当Pod从Node上被删除时,emptyDir也会被同时删除,存储的数据也将永久删除。(容器损坏不影响emptyDir)
1.1 创建命名空间
创建名为nsvolume的命名空间,使用kubens切换命名空间
[root@master ~]# kubectl create namespace nsvolume
[root@master ~]# kubens nsvolume
1.2 创建并进入新的目录
[root@master ~]# mkdir volume
[root@master ~/volume]# cd volume/
1.3 创建一个pod的yaml文件
[root@master ~/volume]# kubectl run demo --image=busybox --image-pull-policy=IfNotPresent --dry-run=client -o yaml -- sh -c 'sleep 5000' > emp.yaml
查看该文件
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: demo
name: demo
spec:
containers:
- args:
- sh
- -c
- sleep 5000
image: busybox
imagePullPolicy: IfNotPresent
name: demo
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
修改该文件至如下
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: demo
name: demo
spec:
# 创建一个Volume1和Volume2
volumes:
- name: volume1
emptyDir: {}
- name: volume2
emptyDir: {}
containers:
# 两个容器命名为demo1、demo2并且使用volume1的卷
- command: ['sh','-c','sleep 5000;']
image: busybox
imagePullPolicy: IfNotPresent
name: demo1
volumeMounts:
- mountPath: /mmx
name: volume1
- command: ['sh','-c','sleep 5000;']
image: busybox
imagePullPolicy: IfNotPresent
name: demo2
volumeMounts:
- mountPath: /mmx
name: volume1
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
1.4 执行编排文件
[root@master ~/volume]# kubectl apply -f emp.yaml
pod/demo configured
[root@master ~/volume]# kubectl get pods
NAME READY STATUS RESTARTS AGE
demo 2/2 Running 2 134m
1.5 查看pod具体位置
根据命令,发现pod在node3上
[root@master ~/volume]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo 2/2 Running 2 135m 10.244.135.16 node3 <none> <none>
查看容器
[root@node3 ~]# docker ps --format "{{.ID}} {{.Names}}" | grep nsvolume
d4d2106b6983 k8s_demo2_demo_nsvolume_d3d5c156-76ca-437f-9a29-ee11a897eb96_1
ba418b395532 k8s_demo1_demo_nsvolume_d3d5c156-76ca-437f-9a29-ee11a897eb96_1
ab6552464c41 k8s_POD_demo_nsvolume_d3d5c156-76ca-437f-9a29-ee11a897eb96_0
查看是否存在挂载目录/mmx,发现容器共享一个卷
[root@node3 ~]# docker inspect ba418b395532 | grep /mmx
"/var/lib/kubelet/pods/d3d5c156-76ca-437f-9a29-ee11a897eb96/volumes/kubernetes.io~empty-dir/volume1:/mmx:Z",
"Destination": "/mmx",
[root@node3 ~]# docker inspect d4d2106b6983 | grep /mmx
"/var/lib/kubelet/pods/d3d5c156-76ca-437f-9a29-ee11a897eb96/volumes/kubernetes.io~empty-dir/volume1:/mmx:Z",
"Destination": "/mmx",
1.6 使用kubectl命令复制操作
[root@master ~/volume]# kubectl cp /etc/hosts demo:/mmx -c demo1
[root@master ~/volume]# kubectl cp /etc/fstab demo:/mmx -c demo2
1.7 查看pod上是否存在文件
由于容器是共享一个目录的,所以hosts和fstab文件均存在
[root@master ~/volume]# kubectl exec demo -c demo1 -- ls /mmx
fstab
hosts
进入特定目录查看
[root@node3 ~]# ls /var/lib/kubelet/pods/d3d5c156-76ca-437f-9a29-ee11a897eb96/volumes/kubernetes.io~empty-dir/volume1
fstab hosts
1.8 移除Pod
[root@master ~/volume]# kubectl delete -f emp.yaml --force
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "demo" force deleted
查看挂载卷
# 由于其特性,挂载目录也随之消失
[root@node3 ~]# ls /var/lib/kubelet/pods/d3d5c156-76ca-437f-9a29-ee11a897eb96/volumes/kubernetes.io~empty-dir/volume1
ls: cannot access '/var/lib/kubelet/pods/d3d5c156-76ca-437f-9a29-ee11a897eb96/volumes/kubernetes.io~empty-dir/volume1': No such file or directory
2、HostPath
hostPath允许挂载Node上的文件系统到Pod里面去。如果Pod需要使用Node上的文件,可以使用hostPath。相比于EmptyDir,此方式在删除Pod后,数据依旧保留,需要给挂载目录指定对应的物理目录,很类似于Docker中的挂载操作。
2.1 创建编排文件
[root@master ~/volume]# kubectl run demo --image=busybox --image-pull-policy=IfNotPresent --dry-run=client -o yaml -- sh -c 'sleep 5000' > host.yml
编排文件默认如下所示
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: demo
name: demo
spec:
containers:
- args:
- sh
- -c
- sleep 5000
image: busybox
imagePullPolicy: IfNotPresent
name: demo
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
修改成hostPath方式
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: demo
name: demo
spec:
# 指定本地目录为/root/mmx
volumes:
- name: volume
hostPath:
path: /root/mmx
containers:
# 两个容器命名为demo1、demo2并且使用volume1的卷
- command: ['sh','-c','sleep 5000;']
image: busybox
imagePullPolicy: IfNotPresent
name: demo1
volumeMounts:
- mountPath: /mmx
name: volume
- command: ['sh','-c','sleep 5000;']
image: busybox
imagePullPolicy: IfNotPresent
name: demo2
volumeMounts:
- mountPath: /mmx
name: volume
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
2.2 执行编排文件
[root@master ~/volume]# kubectl apply -f host.yml
pod/demo created
查看编排文件具体位置
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo 2/2 Running 0 41s 10.244.135.17 node3 <none> <none>
查看node3上是否已经创建/root/mmx目录
[root@node3 ~]# ls /root/mmx/
2.3 使用kubectl命令复制操作
[root@master ~/volume]# kubectl cp /etc/hosts demo:/mmx -c demo1
[root@master ~/volume]# kubectl cp /etc/fstab demo:/mmx -c demo2
2.4 验证操作是否成功
本地目录查看
[root@node3 ~]# ls mmx/
fstab hosts
容器挂载点查看
[root@master ~]# kubectl exec demo -c demo1 -- ls /mmx/
fstab
hosts
[root@master ~]# kubectl exec demo -c demo2 -- ls /mmx/
fstab
hosts
2.5 删除Pod并验证
[root@master ~]# kubectl delete pod demo --force
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "demo" force deleted
验证本地目录文件是否被删除
# 由于hostpath特性,本地文件并未删除
[root@node3 ~]# ls mmx/
fstab hosts
3、NFS存储
NFS网络文件系统(network file system)服务可以将远程Linux系统上的文件共享资源挂载到本地主机的目录上,从而使得本地主机(Linux客户端)基于TCP/IP协议,像使用本地主机上的资源那样读写远程Linux系统上的共享文件。
Kubernetes中通过简单地配置就可以挂载NFS到Pod中,而NFS中的数据是可以永久保存的,同时NFS支持同时写操作。Pod被删除时,Volume被卸载,内容被保留。这就意味着NFS能够允许我们提前对数据进行处理,而且这些数据可以在Pod之间相互传递。
NFS卷能将 NFS (网络文件系统) 挂载到你的 Pod 中。 不像 emptyDir那样会在删除 Pod 的同时也会被删除,NFS卷的内容在删除 Pod 时会被保存,卷只是被卸载。 这意味着 NFS卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
3.1 配置NFS服务器
使用master节点作为nfs服务器
# 安装软件包
yum -y install nfs-utils
# 开启NFS服务
systemctl enable nfs-server.service --now
# 放行防火墙
firewall-cmd --add-service=nfs --permanent
firewall-cmd --reload
# 创建共享文件夹
mkdir /nfs_share
# 编辑nfs配置文件
echo "/nfs_share 192.168.0.0/24(rw,sync,no_root_squash)" > /etc/exports
3.2 配置NFS客户端
在node1~node3上配置
# 安装软件包
yum -y install nfs-utils
# 编辑挂载文件
mkdir /share
echo "192.168.0.100:/nfs_share /share nfs defaults 0 0" >> /etc/fstab
mount -a
# 验证
df -Th /share
3.3 通过Pod访问NFS
[root@master ~]# kubectl run nfs --image=nginx --image-pull-policy=IfNotPresent --dry-run=client -o yaml -- sh -c 'echo hello; sleep 10' > nfs.yaml
生成编排文件如下
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nfs
name: nfs
spec:
containers:
- args:
- sh
- -c
- echo hello; sleep 10
image: nginx
imagePullPolicy: IfNotPresent
name: nfs
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
修改成如下格式
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nfs
name: nfs
spec:
volumes:
- name: volume1
nfs:
server: 192.168.0.100
path: '/nfs_share'
containers:
- command: ['sh','-c','echo hello; sleep 5000']
image: busybox
imagePullPolicy: IfNotPresent
name: nfs
volumeMounts:
- mountPath: /mmx
name: volume1
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
3.4 应用编排文件
[root@master ~/nfs]# kubectl apply -f nfs.yaml
pod/nfs created
# 查看pod具体位置
[root@master ~/nfs]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs 1/1 Running 1 30s 10.244.104.20 node2 <none> <none>
验证是否在nfs目录下
[root@master ~/nfs]# kubectl cp /etc/fstab nfs:/mmx
[root@master ~/nfs]# kubectl cp /etc/hosts nfs:/mmx -c nfs
[root@master ~/nfs]# ls /nfs_share/
fstab hosts
验证在pod中是否能查看该文件
[root@master ~/nfs]# kubectl exec nfs -- ls /mmx
fstab
hosts
3.5 移除Pod
[root@master ~/nfs]# kubectl delete -f nfs.yaml
pod "nfs" deleted
验证Pod移除后nfs共享目录文件是否存在
# 由于NFS是网络共享文件系统,移除Pod文件依旧存在
[root@master ~/nfs]# ls /nfs_share/
fstab hosts
4、持久性存储
PV和PVC使得K8s集群具备了存储的逻辑抽象能力,使得在配置Pod的逻辑里可以忽略对实际后台存储技术的配置,而把这项配置的工作交给PV的配置者,即集群的管理者。存储的PV和PVC的这种关系,跟计算的Node和Pod的关系是非常类似的;PV和Node是资源的提供者,根据集群的基础设施变化而变化,由K8S集群管理员配置;而PVC和Pod是资源的使用者,根据业务服务的需求变化而变化,有K8s集群的使用者即服务的管理员来配置。
4.1 PV(PersistentVolume)
持久卷(PersistentVolume,PV)是集群中的一块存储,可以由管理员事先制备,或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的Volume一样,也是使用卷插件来实现的,只是它们拥有独立于任何使用PV的Pod的生命周期。
4.2 PVC(PersistentVolumeClaim)
持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而PVC申领会耗用PV资源。Pod可以请求特定数量的资源(CPU 和内存);同样PVC申领也可以请求特定的大小和访问模式(例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载)。
访问模式 | 说明 |
ReadWriteOnce | 单节点读写映射 |
ReadOnlyMany | 多节点只读映射 |
ReadWriteMany | 多节点读写映射 |
4.3 各个生命周期说明
PV和PVC之间的相互作用遵循这个生命周期:
- Provisioning(供应)
- Binding(绑定)
- Using(使用)
- Releasing(发布)
- Recycling(回收)
Provisioning
Provisioning
有两种PV提供的方式:静态和动态
静态PV:集群管理员创建多个PV,它们携带着真实存储的详细信息,这些存储对于集群用户是可用的。它们存在于Kubernetes API中,并可用于存储使用。
动态PV:当管理员创建的静态PV都不匹配用户的PVC时,集群可能会尝试专门地供给volume给PVC。这种供给基于StorageClass,PVC必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。
PVC与PV的绑定是一对一的映射。没找到匹配的PV,那么PVC会无限期的处于unbound未绑定状态。
Binding
在动态配置的情况下,用户创建或已经创建了具有特定数量的存储请求和特定访问模式的PVC。 主机中的控制回路监视新的PVC,找到匹配的PV(如果可能),并将它们绑定在一起。 如果为新的PVC动态配置PV,则循环将始终将该PV绑定到PVC。
Using Pod使用PVC就像使用volume一样。集群检查PVC,查找绑定的PV,并映射PV给Pod。对于支持多种访问模式的PV,用户可以指定想用的模式。一旦用户拥有了一个PVC,并且PVC被绑定,那么只要用户还需要,PV就一直属于这个用户。用户调度Pod,通过在Pod的volume块中包含PVC来访问PV。
Pod使用PVC作为卷, 集群检查声明以找到绑定的卷并挂载该卷的卷。 对于支持多种访问模式的卷,用户在将其声明用作pod中的卷时指定所需的模式。
Releasing
当用户使用PV完毕后,他们可以通过API来删除PVC对象。当PVC被删除后,对应的PV就被认为是已经是“released”了,但还不能再给另外一个PVC使用。
Recycling
PV的回收策略告诉集群,在PV被释放之后集群应该如何处理该PV。当前,PV可以被Retained(保留)、 Recycled(再利用)或者Deleted(删除)。保留允许手动地再次声明资源。对于支持删除操作的PV卷,删除操作会从Kubernetes中移除PV对象,还有对应的外部存储(如AWS EBS,GCE PD,Azure Disk,或者Cinder volume)。动态供给的卷总是会被删除。
4.4 创建PV卷
使用NFS共享文件系统,分配一个大小为10G的空间
# 生成编排文件
kubectl run pv1 --image=nginx --dry-run=client -o yaml > pv1.yml
生成编排文件如下
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pv1
name: pv1
spec:
containers:
- image: nginx
name: pv1
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
将yaml文件修改如下所示
apiVersion: v1
# 将kind修改为PersistentVolume(持久卷)
kind: PersistentVolume
metadata:
name: pv1
spec:
# 定义容量
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
# 持久卷回收策略:回收
persistentVolumeReclaimPolicy: Recycle
# 使用NFS提供服务
nfs:
path: /nfs_share
server: 192.168.0.100
创建PV卷并验证
[root@master ~]# kubectl apply -f pv1.yml
persistentvolume/pv1 created
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv1 10Gi RWO Recycle Available 11s
4.5 创建PVC
通过PVC来调用已经存在的PV空间
# 生成编排文件
kubectl run pvc1 --image=nginx --dry-run=client -o yaml > pvc1.yaml
将生成yaml文件修改如下所示
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc1
spec:
# 设置访问方式为单节点读写
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
# 请求存储容量为5G,还剩5G
resources:
requests:
storage: 5Gi
查看PVC
[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc1 Bound pv1 10Gi RWO 15s
4.6 storageClassName
该参数用于给PV打上标记,通过该标记,可以让pod通过标记使用PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
# 打上标记mmx
storageClassName: mmx
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
nfs:
path: /nfs_share
server: 192.168.0.100
对应的PVC文件
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc2
spec:
# 使用对应的标记mmx
storageClassName: mmx
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
执行并查看
[root@master ~]# kubectl apply -f pv2.yml
persistentvolume/pv2 created
[root@master ~]# kubectl apply -f pvc2.yml
persistentvolumeclaim/pvc2 created
# 此时发现mmx的标记能够查看到
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv1 10Gi RWO Recycle Bound nsvolume/pvc1 4m1s
pv2 5Gi RWO Recycle Bound nsvolume/pvc2 mmx 78s
[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc1 Bound pv1 10Gi RWO 3m53s
pvc2 Bound pv2 5Gi RWO mmx 7s
4.8 删除PV、PVC
先删除PVC再删除PV(重要!)
# 删除pvc
kubectl delete -f pvc2.yml
kubectl delete -f pvc1.yml
# 删除pv
kubectl delete -f pv2.yml
kubectl delete -f pv1.yml
4.9 Pod使用持久化存储
# 生成编排文件
kubectl run pv_pod --image=nginx --image-pull-policy=IfNotPresent --dry-run=client -o yaml > pv_pod.yml
生成文件如下
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
volumes:
- name: mypv
persistentVolumeClaim:
claimName: pvc1
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: mmx
volumeMounts:
- mountPath: /mnt
name: mypv
执行yaml编排文件
[root@master ~]# kubectl apply -f pv1.yml
[root@master ~]# kubectl apply -f pvc1.yml
[root@master ~]# kubectl apply -f pv_pod.yml
# 验证pv、pvc、pod是否均已创建
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv1 10Gi RWO Recycle Bound nsvolume/pvc1 14m
[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc1 Bound pv1 10Gi RWO 13m
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 9m13s
Pod放入文件并验证
# 放入常用的测试文件
[root@master ~]# kubectl cp /etc/hosts nginx:/mnt
[root@master ~]# kubectl cp /etc/fstab nginx:/mnt
# 查看NFS提供的目录
[root@master ~]# cat /etc/exports
/nfs_share 192.168.0.0/24(rw,sync,no_root_squash)
# 查看文件是否存在
[root@master ~]# ls /nfs_share/
fstab hosts