持久化存储需要定义pod的volume,在容器中使用volumemouts挂载存储
emptyDir临时存储
创建一个有挂载卷的pod
在 Kubernetes 中,Pod 的容器中的 volumeMount
的 name
必须与 volumes
中定义的卷的 name
相同,以便正确地挂载卷到容器中。因此,在你的 YAML 文件中,volumeMounts
中的 name
和 volumes
中的 name
需要相同
apiVersion: v1
kind: Pod
metadata:
name: empty-pod
spec:
containers:
- name: empty-pod
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
mountPath: /volume
name: cache-volume
volumes:
- emptyDir:
{}
name: cache-volume
运行后查看pod的uid。可以看到pod运行在node1上
[root@master volume]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
client-5c75576967-l258d 1/1 Running 0 76m 10.244.166.183 node1 <none> <none>
empty-pod 1/1 Running 0 9s 10.244.166.185 node1 <none> <none>
[root@master volume]# kubectl get pods empty-pod -o yaml | grep uid
uid: 87622a4f-dcc0-4127-a51e-2bc1d3f24cb7
查看node1的文件目录,进入/var/lib/kubelet/pods目录下,通过uid进入该pod的目录
[root@node1 ~]# cd /var/lib/kubelet/pods
[root@node1 pods]# ls
3b5623f9-b593-431c-992d-a5672a6f3e43 781772e9-4825-4a5c-9639-ee14ca3ee328 929cb6a4-4194-4c1c-bd7a-6cbfa5bc7be3 e8b74cda-ce80-4e2b-9ef1-d2b9f534e99a
68280322-a7d6-4d63-8753-750d956229f4 87622a4f-dcc0-4127-a51e-2bc1d3f24cb7 d9a16ca2-e0b9-4ab0-bc6a-3b77f065ee6a
[root@node1 pods]# cd 87622a4f-dcc0-4127-a51e-2bc1d3f24cb7
[root@node1 87622a4f-dcc0-4127-a51e-2bc1d3f24cb7]# ls
containers etc-hosts plugins volumes
[root@node1 87622a4f-dcc0-4127-a51e-2bc1d3f24cb7]# cd volumes
[root@node1 volumes]# ls
kubernetes.io~empty-dir kubernetes.io~projected
[root@node1 volumes]# cd kubernetes.io~empty-dir/
[root@node1 kubernetes.io~empty-dir]# ls
cache-volume
[root@node1 kubernetes.io~empty-dir]# cd cache-volume/
[root@node1 cache-volume]# ls
[root@node1 cache-volume]#
进入emptyDir目录后,发现cache-volume为空,在该目录下新建目录aa,随后进入pod的内部,发现aa,也就是说yaml文件中的mountpath和物理节点上的emptyDir下的cache-volume是等价的,而name 相同是为了方便挂载的。
[root@master volume]# kubectl exec -it empty-pod -- /bin/sh
# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var volume
# cd volume
# ls
aa
#
此时删掉pod,node1上整个目录被删掉,aa自然也不复存在。
hostPath持久存储
写一个yaml文件,是在物理节点上的固定存储,这个pod包含两个container,他们两个都挂载在物理节点的/data1文件夹下
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- name: test-nginx
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /test-nginx
name: test-volume
- name: test-tomcat
iamge: tomcat:8.5-jre8-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /test-tomcat
name: test-volume
volumes:
name: test-volume
hostPath: /data1
type: DirectoryOrCreate
进入物理节点查看,注意这个data1文件夹的权限使得无法通过ls命令查看。在该目录下创建一个文件夹aa
[root@node1 ~]# ls -ld /data1
drwxr-xr-x 3 root root 16 6月 27 20:18 /data1
[root@node1 ~]# cd /data1
[root@node1 data1]# ls
aa
查看两个container的目录结构,发现aa的存在,在两个container目录中分别 创建两个文件夹,也会体现在/data1文件夹中
[root@master volume]# kubectl exec -it test-hostpath -c test-nginx -- /bin/bash
root@test-hostpath:/# ls
bin dev docker-entrypoint.sh home lib64 mnt proc run srv test-nginx usr
boot docker-entrypoint.d etc lib media opt root sbin sys tmp var
root@test-hostpath:/# cd test-nginx
root@test-hostpath:/test-nginx# ls
aa
root@test-hostpath:/test-nginx# mkdir nginx1
root@test-hostpath:/test-nginx# ls
aa nginx1
root@test-hostpath:/test-nginx# exit
exit
[root@master volume]# kubectl exec -it test-hostpath -c test-tomcat -- /bin/bash
bash-4.4# ls
BUILDING.txt LICENSE README.md RUNNING.txt conf lib native-jni-lib webapps
CONTRIBUTING.md NOTICE RELEASE-NOTES bin include logs temp work
bash-4.4# cd /test-tomcat/
bash-4.4# ls
aa nginx1
bash-4.4# mkdir tomcat1
bash-4.4# ls
aa nginx1 tomcat1
bash-4.4# ls -ld /test-tomcat
drwxr-xr-x 3 root root 16 Jun 27 12:18 /test-tomcat
bash-4.4# exit
exit
但是这个做法有一个弊端,就是如果删除pod后,新创建的pod没有调度到这个物理节点上,那么数据还是会丢失。
nfs网络存储
nfs全称network file system,是一种网络文件系统,允许多个客户端通过网络共享同一个文件系统,本身并不是持久化存储的实现,而是一种存储协议。
在 Kubernetes 集群中,只要一个节点上运行了 NFS 服务器,这个节点就可以对外提供 NFS 服务,所有共享的文件都存储在这个节点上。
先运行这个nfs服务,任何需要共享存储的node都需要把虚拟的存储目录挂载在自己本地的一个目录。
创建需要共享的目录:
[root@master ~]# mkdir /data/volumes -pv
mkdir: 已创建目录 "/data"
mkdir: 已创建目录 "/data/volumes"
在master节点上配置nfs服务器:
vim /etc/exports
>
/data/volumes *(rw,sync,no_subtree_check)
sudo exportfs -arv //重新导出共享目录,以应用新的配置
*代表允许所有客户端访问,可以规定只允许某个网段的客户端访问。
rw代表可以读写。
sync,强制数据同步写入磁盘,还有async,这个允许缓存数据从而提高性能,但是存在数据丢失的风险。
no_subtree_check: 禁用子树检查。子树检查是 NFS 服务器检查文件路径的完整性的一种机制,禁用它可以提高性能,尤其是在共享的目录不是根目录时。
然后把这个nfs目录挂载到不同的节点上。首先挂载在本身
[root@master ~]# mount 100.64.252.90:/data/volumes /nfs_mount/
然后挂载到node1
[root@node1 data1]# mount 100.64.252.90:/data/volumes /nfs_node1
在node1的nfs_node1目录下创建一个文件,可以看到nfs服务器的挂载点可以看到(master节点)
[root@node1 nfs_node1]# echo "hello,this is io,the machine" > index.html
[root@node1 nfs_node1]# ls
index.html
[root@master ~]# cd /data/volumes/
[root@master volumes]# ls
index.html
创建一个pod,可以看到这里挂载的是nfs的服务器的卷
[root@master ~]# cat nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-nfs-volume
spec:
containers:
- name: test-nfs
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- name: nfs-volumes
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-volumes
nfs:
path: /data/volumes
server: 100.64.252.90
启动后,进入pod,也可以看到这个文件
[root@master volumes]# kubectl exec -it test-nfs-volume -- /bin/bash
root@test-nfs-volume:/# cd /usr/share/nginx/html
root@test-nfs-volume:/usr/share/nginx/html# ls
index.html
pvc持久化存储
pvc全名PersistentVolumeClaim,是一个持久化存储的卷,在创建pod的时候可以定义这个类型的存储卷。PV是集群中的一块存储,由管理员配置或使用。PV是容量插件,其生命周期独立于使用PV的任何单个pod。
我们可以通过创建pod的方式创建PVC存储卷,pod消耗节点资源,PVC消耗PV资源。
如上面的yaml文件所示,如果创建的是pod文件或者是deployment文件,是可以挂载在nfs目录下,nfs目录在挂在物理节点上,实现持久化存储。但是如果每次都在pod中直接配置NFS挂载,可能会导致重复配置和管理困难。
PVC 提供了持久化存储卷的生命周期管理。即使 Pod 被删除或重新调度,PVC 仍然存在,并保持其绑定的 PV 和存储数据。使用 PVC,可以在一个地方定义存储请求,并在多个 Pod 中复用。
定义pv资源:
[root@master volume]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: v1
spec:
capacity:
storage: 1Gi
accessModes: ["ReadWriteOnce"]
nfs:
path: /data/volume_test/v1
server: 100.44.252.90
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v2
spec:
capacity:
storage: 2Gi
accessModes: ["ReadWriteMany"]
nfs:
path: /data/volume_test/v2
server: 100.44.252.90
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v3
spec:
capacity:
storage: 2Gi
accessModes: ["ReadWriteMany"]
nfs:
path: /data/volumes/aa
server: 100.64.252.90
查看pv的状态,这里的3个pv资源都是可用的,表示没有被绑定
[root@master volume]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
v1 1Gi RWO Retain Available 26m
v2 2Gi RWX Retain Available 26m
v3 2Gi RWX Retain Available 13s
创建pvc,用来绑定pv
[root@master volume]# cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 2Gi
应用以后,发现pvc自然绑定了一个pv,符合他的要求。如果再创建一个2.5Gi的pvc,则会绑定失败,这个未绑定的pvc会显示是pending
[root@master volume]# kubectl apply -f pvc.yaml
persistentvolumeclaim/my-pvc created
[root@master volume]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound v3 1Gi RWX 19h
pvc1 Bound v2 2Gi RWX 5s
创建一个pod,用来绑定这个pvc,yaml如下
[root@master volume]# cat pod_pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-pvc
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nginx-html
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-html
persistentVolumeClaim:
claimName: my-pvc
发现绑定失败,所以pod创建失败,查看具体的问题
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m8s default-scheduler Successfully assigned default/pod-pcv-1 to node1
Warning FailedMount 5s kubelet Unable to attach or mount volumes: unmounted volumes=[nginx-html], unattached volumes=[nginx-html kube-api-access-m2kwl]: timed out waiting for the condition
查看问题在哪里,查看nfs在本机的挂载点(发现一开始挂载目录不对):
[root@master volume]# mount | grep nfs
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw,relatime)
nfsd on /proc/fs/nfsd type nfsd (rw,relatime)
100.64.252.90:/data/volumes on /nfs_mount type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=100.64.252.90,local_lock=none,addr=100.64.252.90)
100.64.252.90:/data/volume_test on /nfs_mount1 type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=100.64.252.90,local_lock=none,addr=100.64.252.90)
可以看到本机的挂载点是/nfs_mount1和/nfs_mount, 而在nfs服务器上的挂载点是100.64.252.90,目录是/data/volumes 和/data/volume_test,因为nfs服务器就是本机,所以这两个目录也在本机上。
查看node1上的挂载点
[root@node1 ~]# mount | grep nfs
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw,relatime)
nfsd on /proc/fs/nfsd type nfsd (rw,relatime)
100.64.252.90:/data/volumes on /nfs_node1 type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=100.64.212.7,local_lock=none,addr=100.64.252.90)
100.64.252.90:/data/volumes/aa on /var/lib/kubelet/pods/04e4b76c-4f45-452a-a869-03632d80f1af/volumes/kubernetes.io~nfs/v3 type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=100.64.212.7,local_lock=none,addr=100.64.252.90)
100.64.252.90:/data/volume_test/v2 on /var/lib/kubelet/pods/7f1c4ae5-b779-430e-bc27-6c425a206912/volumes/kubernetes.io~nfs/v2 type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=100.64.212.7,local_lock=none,addr=100.64.252.90)
可以看到 这里提到了nfs服务器的/data/volumes的目录是挂载在/nfs_node1上,而后面两行显示/data/volumes/aa目录被挂载在pv的v3上,如下的v3的yaml所示,/data/volume_test/v2挂载在v2的pv上。
apiVersion: v1
kind: PersistentVolume
metadata:
name: v3
spec:
capacity:
storage: 1Gi
accessModes: ["ReadWriteMany"]
nfs:
path: /data/volumes/aa
server: 100.64.252.90
绑定pod成功后,查看pod:
[root@master volume]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-pcv-1 1/1 Running 0 19m 10.244.166.189 node1 <none> <none>
pod-pvc 1/1 Running 0 20h 10.244.166.188 node1 <none> <none>
test-nfs-volume 1/1 Running 0 2d13h 10.244.104.36 node2 <none> <none>
此时在pod里创建一个文件夹,然后把pod删除,再创建的pod只要绑定在原有的pvc上,那么这个文件夹还在pod里。
删除pv
删除一个 Pod 通常不会直接影响绑定到它的 PVC。PVC 和 Pod 的生命周期是分开的,当删除一个 Pod 时,PVC 仍然会保留在 Kubernetes 集群中,以及它所绑定的 PV 也不会受到影响。这样设计的目的是为了数据的持久性,即使应用被重新部署或删除,数据也能保持不变。
删除pvc,可以看到直接删除是无效的,pvc会处于terminating状态,这是一个保护机制,因此需要改变pvc的finalizers标签,这个标签可以保护pvc不被删除。
[root@master volume]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound v3 1Gi RWX 19h
pvc1 Terminating v2 2Gi RWX 27m
[root@master volume]# kubectl delete pvc pvc1
persistentvolumeclaim "pvc1" deleted
^C
[root@master volume]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound v3 1Gi RWX 19h
pvc1 Terminating v2 2Gi RWX 27m
[root@master volume]# kubectl patch pvc pvc1 -p '{"metadata":{"finalizers": []}}' --type=merge
persistentvolumeclaim/pvc1 patched
[root@master volume]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound v3 1Gi RWX 19h
删除pvc后,根据不同的回收策略,会对绑定的pv进行处理:
Retain
:PV 会保持存在并转为 "Released" 状态,但不会被自动回收。管理员需要手动处理这些 PV。Delete
:PV 和底层存储将会被自动删除。Recycle
(不推荐使用,已被废弃在许多 Kubernetes 版本):PV 会被清理并准备好重新使用,但这个选项依赖于底层存储系统的支持。
删除pv也是同理,如果删除一个没有被绑定的pv,那么直接删除,否则需要更改pv的filanlizer。
storageClass存储类
创建nfs供应商
存储类最关键的功能之一是支持动态卷供应。在没有存储类的情况下,管理员必须手动预先创建和配置持久卷(PV),然后这些卷才能被持久卷声明(PVC)使用。存储类允许用户通过 PVC 请求存储时,自动创建卷。这样,用户就不需要提前知道具体的卷如何和在哪里被创建。
用户提交一个pvc到kubernetes,然后kubernetes会检查pvc中是否指定了storageClass,如果指定了,kubernetes将使用这个storageClass,否则会使用集群默认的storageClass。storage查看provisioner字段,查看哪个存储插件应该被使用来创建卷,然后创建一个pv。
创建一个service Account用来和kubernetesAPI交互,这个account用来为nfs-provisioner提供权限和身份验证,以便在k8s集群内部提供操作权限和管理资源。这个简单的yaml文件只是定义了name,从而可以和nfs-provisioner绑定。
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
授权,将一个clusterRole(这里是cluster-admin)关联到上面的serviceAccount里面。
kubectl create clusterrolebinding nfs-provisioner-clusterrolebinding --clusterrole=cluster-admin --serviceaccount=default:nfs-provisioner
nfs的存储插件的deployment文件:其中image就是nfs的插件的镜像。将该pod与服务相连,通过spec里面的serviceAccount字段
其中环境变量中的provisioner_name为example.com/nfs
[root@master nfs]# cat nfs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-provisioner
image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentVolumes
env:
- name: PROVISIONER_NAME
value: example.com/nfs
- name: NFS_SERVER
value: 100.64.252.90
- name: NFS_PATH
value: /data/nfs_pro
volumes:
- name: nfs-client-root
nfs:
path: /data/nfs_pro
server: 100.64.252.90
第一次配置的时候写的container name为aa,但是发现如果这样写后面pvc会挂载失败,不知道为什么,后面改成和pod name一样就正常了。
然后将上面yaml文件里定义的挂载目录挂载到nfs服务器上
[root@master ~]# cat /etc/exports
/data/volumes *(rw,no_root_squash)
/data/volume_test/v1 *(rw,no_root_squash)
/data/volume_test/v2 *(rw,no_root_squash)
/data/nfs_pro *(rw,no_root_squash)
[root@master ~]# exportfs -var
exporting *:/data/nfs_pro
exporting *:/data/volume_test/v2
exporting *:/data/volume_test/v1
exporting *:/data/volumes
创建存储类
创建一个存储类,规定nfs-provisioner字段
[root@master nfs]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs
provisioner: example.com/nfs
[root@master nfs]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs example.com/nfs Delete Immediate false 13s
可以看到因为没有规定其他字段,所以它的回收策略是delete,也就是绑定的pvc删除后,pv也删除,而volumebindingmode是立即绑定,还有另一种方法:WaitForFirstConsumer:延迟绑定模式,PV 的创建和绑定会等到有 Pod 需要时才进行,以确保 PV 与 Pod 在同一区域。
另一个字段是是否允许在线扩容,默认为false。
创建pvc
创建一个pvc,发现直接与nfs绑定,这是因为在pvc的yaml文件中定义了storageClassName的字段为nfs。
[root@master nfs]# cat claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-claim
spec:
accessModes: ["ReadWriteMany"]
storageClassName: nfs
resources:
requests:
storage: 1Gi
[root@master nfs]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim Bound pvc-b921af0c-5e6d-4920-9607-472217483910 1Gi RWX nfs 6s
然后创建pod,和pvc绑定
[root@master nfs]# cat pod_claim.yaml
kind: Pod
apiVersion: v1
metadata:
name: read-pod
spec:
containers:
- name: read-pod
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-pvc
mountPath: /usr/share/nginx/html
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim