k8s存储之emptyDir、hostPath和nfs存储卷
Volume
容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet会重启它,但是容器中的文件将丢失——容器以干净点状态(镜像最初点状态)重新启动。其次,在pod中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes中的volume就能很好的解决了这些问题。
1.背景
Docker中也有一个volume的概念,尽管它稍微宽松一些,管理也很少。在Docker中,卷就像是磁盘或是另一个容器中的一个目录。它的生命周期不受管理,直到最近才有了local-disk-backed卷。Docker现在提供来卷驱动程序,但是功能还非常有限(例如Docker1.7只允许每个容器使用一个卷驱动,并且无法给卷传递参数)。
另一方面,Kubernetes中的卷有明确的寿命——与封装他的Pod相同。所以,卷的生命比Pod中点所有容器都长,当这个容器重启时数据仍然得以保存。当然,当pod不再存在时,卷也将不复存在。也许更重要点的是,Kubernetes支持多种类型的卷,Pod可以同时使用任意数量的卷。
卷的核心是目录,可能还包含了一些数据,可以通过pod中的容器来访问。该目录是如何形成的、支持该目录的介质以及内容取决于所使用的特定卷类型。
容器中的进程看到的是其由Docker镜像和卷组成点文件系统视图。Docker镜像位于文件系统层次结构的根目录,任何卷都被挂载在镜像的指定路径中。卷无法挂载到其他卷上或与其他卷有硬链接。Pod中的每个容器都必须独立指定每个卷的挂载位置。
Pod使用Volume步骤:
1、在Pod上定义存储卷,并关联至目标存储服务上;
2、在需要用到存储卷的容器上,挂载其所属Pod的存储卷。
volume的资源清单详解:
spec: volumes: - name <string> #存储卷名称标识,仅可使用DNS标签格式的字符,在当前Pod中必须唯一 VOL_TYPE <Object> #存储卷插件及具体的目标存储供给方的相关配置 containers: - name: ... image: ... volumeMounts: - name <string> #要挂载的存储卷的名称,必须匹配存储卷列表中的某项定义 mountPatch <string> #容器文件系统上的挂载点路径 readOnly <boolean> #是否挂载为只读模式,默认为"否" subPath <string> #挂载存储卷上的一个子目录至指定的挂载点 subPathExpr <string> #挂载由指定的模式匹配到的存储卷的文件或目录至挂载点 mountPropagation <string> #挂载卷的传播模式
2.卷的类型
Kubernetes 1.19.4 ⽀持以下类型的卷:
awsElasticBlockStore
azureDisk
azureFile
cephfs
cinder
configMap
csi
downwardAPI
emptyDir
ephemeral
fc
flexVolume
flocker
gcePersistentDisk
gitRepo
glusterfs
hostPath
iscsi
name
nfs
persistentVolumeClaim
photonPersistentDisk
portworxVolume
projected
quobyte
rbd
scaleIO
secret
storageos
vsphereVolume
上述类型中,emptyDir与hostPath属于节点级别的卷类型,emptyDir的生命周期与Pod资源相同,而使用了hostPath卷的Pod一旦被重新调度至其他节点,那么它将无法在使用此前的数据。因此,这两张类型都不具有持久性。要想使用持久类型的存储卷,就得使用网络存储系统,如NFS,Ceph、GlusterFS等,或者云端存储,如awsElasticBlockStore、gcePersistentDisk。
然而,网络存储系统通常都不太容易使用,有的甚至很复杂,以至于对大多数用户来说它是一个难以逾越点障碍。Kubernetes为此专门设计了一种集群级别的资源“persistentVolume”(简称PV),它借由管理员配置存储系统,而后由用户通过“persistentVolumeClaim”(简称PVC)存储卷直接申请使用的机制大大简化了终端存储用户的配置过程,有效降低了使用难度。
再者,Secret和ConfigMap算得上是两种特殊的卷类型。
1)Secret用于向Pod传递敏感信息,如密码、私钥、证书文件等,这些信息如果直接定义在镜像中很容易导致泄露,有了Secre资源,用户可以将这些信息存储于集群中而后由Pod进行挂载,从而实现将敏感数据与系统解耦。
2)ConfigMap资源则用于向Pod注入非敏感数据,使用时,用户将数据直接存储于ConfigMap对象中,而后直接在Pod中使用ConfigMap卷引用它即刻,它可以帮助实现容器配置文件集中化定义和管理。
3.常用卷详解
3.1 emptyDir(临时存储卷)
当 Pod 被分配给节点时,⾸先创建 emptyDir 卷,并且只要该 Pod 在该节点上运⾏,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写⼊ emptyDir 卷中的相同⽂件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时, emptyDir 中的数据将被永久删除。
注意:容器崩溃不会从节点中移除pod,因此emptyDir卷中的数据在容器崩溃时是安全的。
emptyDir的用法有:
-
暂存空间,例如用于基于磁盘的合并排序
-
用作长时间计算崩溃恢复时的检查点
-
Web服务器容器提供数据时,保存内容管理器容器提取的文件
总结:
-
emptyDir只是一个临时挂载的文件,pod删除后,该目录也会在node节点上被删除;但是容器崩溃时,该文件还存在.
范例:
#在master上编写emptyDir资源清单 [root@ubuntu-200 ~]# cat volume-emptyDir.yaml apiVersion: v1 kind: Pod metadata: name: emptydir-pod #注意此处只能是小写字母或数字,大写字母会报错 spec: containers: - name: nginx-test image: nginx imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /cache name: cache-volume volumes: - name: cache-volume emptyDir: {} #创建pod [root@ubuntu-200 ~]# kubectl apply -f volume-emptyDir.yaml #查看Pod [root@ubuntu-200 ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES emptydir-pod 1/1 Running 0 17s 10.244.2.62 ubuntu-210 <none> <none> #进入容器并在容器中创建文件测试 [root@ubuntu-200 ~]# kubectl exec -it emptydir-pod -- bash root@emptydir-pod:/# ls -l /cache/ total 0 root@emptydir-pod:/cache# touch /cache/nginx-emptydir.log root@emptydir-pod:/cache# echo nginx-emptydir > /cache/nginx-emptydir.log root@emptydir-pod:/cache# cat /cache/nginx-emptydir.log nginx-emptydir #在node节点上查找临时存储的文件位置 [root@ubuntu-210 ~]# find / -name nginx-emptydir.log /var/lib/kubelet/pods/95dcb0a8-61f7-42e3-aa35-e7f1a1872b20/volumes/kubernetes.io~empty-dir/cache-volume/nginx-emptydir.log #查看文件内容 [root@ubuntu-210 ~]# cat /var/lib/kubelet/pods/95dcb0a8-61f7-42e3-aa35-e7f1a1872b20/volumes/kubernetes.io~empty-dir/cache-volume/nginx-emptydir.log nginx-emptydir #在master节点上删除pod [root@ubuntu-200 ~]# kubectl delete -f volume-emptyDir.yaml pod "emptydir-pod" deleted #在node节点上查看临时存储是否还存在 [root@ubuntu-210 ~]# cat /var/lib/kubelet/pods/95dcb0a8-61f7-42e3-aa35-e7f1a1872b20/volumes/kubernetes.io~empty-dir/cache-volume/nginx-emptydir.log cat: /var/lib/kubelet/pods/95dcb0a8-61f7-42e3-aa35-e7f1a1872b20/volumes/kubernetes.io~empty-dir/cache-volume/nginx-emptydir.log: No such file or directory [root@ubuntu-210 ~]# ll /var/lib/kubelet/pods/95dcb0a8-61f7-42e3-aa35-e7f1a1872b20/volumes/kubernetes.io~empty-dir/cache-volume/ ls: cannot access '/var/lib/kubelet/pods/95dcb0a8-61f7-42e3-aa35-e7f1a1872b20/volumes/kubernetes.io~empty-dir/cache-volume/': No such file or directory
3.2 hostPath(节点存储卷)
hostPath类型的存储卷是指将工作节点上某个文件系统的目录或文件挂载于Pod中的一种存储卷,它独立于Pod资源的生命周期,因而具有持久性。但它是工作节点本地的存储空间,仅适用于特定情况下的存储卷使用要求,例如,将工作节点上的文件系统关联为Pod的存储卷,从而使得容器访问节点文件系统上的数据。这一点在运行有管理任务的系统级Pod资源需要访问节点上的文件时尤为有用。
hostPath的用途如如下:
-
运行需要访问Docker内部的容器;使用/var/lib/docker的hostPath
-
在容器中运行cAdvisor;使用/dev/cgroups的hostPath
-
运行pod指定给定的hostPath是否应该在pod运行之前存在,是否应该创建,以及它应该以什么形式存在
配置hostPath存储卷点嵌套字段共有两个:一个是用于指定工作节点上点目录路径的必须字段path;另一个是指定存储卷类型的type,它支持使用的卷类型包含以下几种:
-
DirectoryCreate:如果在给定的路径上没有任何东⻄存在,那么将根据需要在那⾥创建⼀个空⽬录,权限设置为 0755,与 Kubelet 具有相同的组和所有权;
-
Directory:给定的路径下必须存在⽬录;
-
FileOrCreate:如果在给定的路径上没有任何东⻄存在,那么会根据需要创建⼀个空⽂件,权限设置为0644,与 Kubelet 具有相同的组和所有权;
-
File:给定的路径下必须存在⽂件;
-
Socket:给定的路径下必须存在 UNIX 套接字;
-
CharDevice:给定的路径下必须存在字符设备;
-
BlockDevice:给定的路径下必须存在块设备;
- “”:空字符串,默认配置,在关联hostPath存储卷之前不进行任何检查。
注意:
-
如资源清单中未配置type字段则为默认选项(默认的值为空字符串),空字符串(默认)⽤于向后兼容,这意味着在挂载 hostPath 卷之前不会执⾏任何检查;就是不检查path的类型
-
由于每个节点上点文件都不同,具有相同配置(例如从podTemplate创建的)的pod在不同节点上的行为可能会有所不同
-
当Kubetnetes安装计划添加资源感知调度时,将无法考虑hostPath使用的资源
-
在底层主机上创建的文件或目录只能由root写入。需要在特权容器中以root身份运行进程,或修改主机上的文件权限以便写入hostPath卷。
范例:
[root@ubuntu-200 ~]# cat volume-hostPath-test.yaml apiVersion: v1 kind: Pod metadata: name: hostpath-test-pod spec: containers: - name: nginx-hostpath image: nginx volumeMounts: - mountPath: /var/logs/ name: data-log #定义hostPath卷名,和宿主机共享的路径 volumes: - name: data-log hostPath: path: /data/logs #创建pod [root@ubuntu-200 ~]# kubectl apply -f volume-hostPath-test.yaml [root@ubuntu-200 ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES hostpath-test-pod 1/1 Running 0 66m 10.244.2.65 ubuntu-210 <none> <none> #在node节点上的共享目录上创建文件 [root@ubuntu-210 ~]# echo 111 > /data/logs/1.txt [root@ubuntu-210 ~]# cat /data/logs/1.txt 111 #在master节点上进入pod,查看挂载卷的文件内容 [root@ubuntu-200 ~]# kubectl exec -it hostpath-test-pod -- bash root@hostpath-test-pod:/# cat /var/logs/1.txt 111 #在pod内查看和创建创建文件 root@hostpath-test-pod:/# ls /var/logs/ 1.txt root@hostpath-test-pod:/# echo 222 > /var/logs/2.txt root@hostpath-test-pod:/# cat /var/logs/2.txt 222 root@hostpath-test-pod:/# ls /var/logs/ 1.txt 2.txt #在Node节点上查看pod内部创建的文件 [root@ubuntu-210 ~]# cat /data/logs/2.txt 222 #删除Pod [root@ubuntu-200 ~]# kubectl delete -f volume-hostPath-test.yaml pod "hostpath-test-pod" deleted #Node节点上查看文件是否存在 [root@ubuntu-210 ~]# ll /data/logs/ total 16 drwxr-xr-x 2 root root 4096 Dec 8 08:46 ./ drwxr-xr-x 4 root root 4096 Dec 8 07:34 ../ -rw-r--r-- 1 root root 4 Dec 8 08:43 1.txt -rw-r--r-- 1 root root 4 Dec 8 08:46 2.txt #可看到在Pod内部创建的文件依然存在,没有随pod的删除而删除,已初步实现了持久化. [root@ubuntu-210 ~]# cat /data/logs/2.txt 222
3.3 nfs存储卷
nfs卷能将NFS(网络文件系统)挂载到你的pod中。不像emptyDir,当删除Pod时,nfs卷的内容被保留,卷仅仅是被卸载。另外,NFS是文件系统级共享服务,它支持同时存在的多路挂载请求。
注意:要使用nfs,必须先拥有自己的NFS服务器,然后才能使用。
定义NFS存储卷时,常用到以下字段:
-
server<string>:NFS服务器的IP地址或主机名,必选字段。
-
path<string>:NFS服务器导出(共享)的文件系统路径,必选字段。
-
readOnly<boolean>:是否以只读方式挂载,默认为false。
范例1:
注意:所有node节点上都需要安装nfs-utils包,否则pod无法正常挂载,pod无法正常运行。
#先配置nfs服务器 [root@C8_58 test]# yum -y install nfs-utils [root@C8_58 test]# cat /etc/exports /data/test *(rw) [root@C8_58 test]# mkdir -p /data/test [root@C8_58 test]# echo nginx web > /data/test/index.html [root@C8_58 test]# systemctl restart nfs-server #测试能否正常挂载 [root@ubuntu-220 ~]# apt -y install nfs-server [root@ubuntu-220 ~]# showmount -e 10.0.0.58 Export list for 10.0.0.58: /data/test * [root@ubuntu-220 ~]# mount -t nfs 10.0.0.58:/data/test /mnt/ [root@ubuntu-220 ~]# df -h Filesystem Size Used Avail Use% Mounted on udev 955M 0 955M 0% /dev tmpfs 198M 13M 185M 7% /run /dev/sda2 98G 5.4G 88G 6% / tmpfs 986M 0 986M 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 986M 0 986M 0% /sys/fs/cgroup /dev/loop1 97M 97M 0 100% /snap/core/9665 /dev/loop0 98M 98M 0 100% /snap/core/10444 /dev/sda3 976M 146M 764M 16% /boot /dev/sda5 95G 61M 91G 1% /data tmpfs 198M 0 198M 0% /run/user/0 10.0.0.58:/data/test 50G 390M 50G 1% /mnt #编写资源清单 [root@ubuntu-200 ~]# cat volume-nfs.yaml apiVersion: v1 kind: Pod metadata: name: nginx-nfs labels: app: nginx-nfs spec: containers: - name: nginx-nfs image: nginx ports: - containerPort: 80 volumeMounts: - mountPath: /usr/share/nginx/html name: my-nfs-volume volumes: - name: my-nfs-volume nfs: server: 10.0.0.58 path: /data/test --- apiVersion: v1 kind: Service metadata: name: nginx-service spec: ports: - name: http port: 81 targetPort: 80 nodePort: 30016 protocol: TCP type: NodePort selector: app: nginx-nfs #创建Pod及service [root@ubuntu-200 ~]# kubectl apply -f volume-nfs.yaml [root@ubuntu-200 ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-nfs 1/1 Running 0 24m 10.244.2.67 ubuntu-210 <none> <none> [root@ubuntu-200 ~]# kubectl get service nginx-service -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx-service NodePort 10.104.177.209 <none> 81:30016/TCP 25m app=nginx-nfs #进入Pod查看是否挂载成功 [root@ubuntu-200 ~]# kubectl exec -it nginx-nfs -- bash root@nginx-nfs:/# cat /usr/share/nginx/html/index.html nginx web
访问测试
范例2:
#编辑资源清单 [root@ubuntu-200 ~]# cat volume-nfs-redis.yaml apiVersion: v1 kind: Pod metadata: name: volume-nfs-redis labels: app: redis spec: containers: - name: redis image: redis:alpine securityContext: #配置了这项,需要在nfs服务器让id为999的账户对共享的命令有读写写权限 runAsUser: 999 ports: - containerPort: 6379 name: redisport volumeMounts: - mountPath: /data name: redisdata volumes: - name: redisdata nfs: server: 10.0.0.58 path: /data/redis readOnly: false #创建pod [root@ubuntu-200 ~]# kubectl apply -f volume-nfs-redis.yaml #进入Pod [root@ubuntu-200 ~]# kubectl exec -it volume-nfs-redis -- /bin/sh #登录redis并创建 /data $ redis-cli 127.0.0.1:6379> set name yang OK 127.0.0.1:6379> get name "yang" 127.0.0.1:6379> bgsave #查看创建的redis文件 /data $ ls -l total 4 -rw-r--r-- 1 1099 nobody 120 Dec 9 13:19 dump.rdb #在nfs服务器主机上也能看到 [root@C8_58 ~]# ll /data/redis/ total 4 -rw-r--r-- 1 redis nobody 120 Dec 9 21:19 dump.rdb #删除镜像,重新创建一次 [root@ubuntu-200 ~]# kubectl delete -f volume-nfs-redis.yaml pod "volume-nfs-redis" deleted [root@ubuntu-200 ~]# kubectl apply -f volume-nfs-redis.yaml pod/volume-nfs-redis created [root@ubuntu-200 ~]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES volume-nfs-redis 1/1 Running 0 8s 10.244.2.71 ubuntu-210 <none> <none> #再次进入pod,可看到数据仍然存在,已实现持久化. [root@ubuntu-200 ~]# kubectl exec -it volume-nfs-redis -- /bin/sh /data $ ls dump.rdb /data $ redis-cli 127.0.0.1:6379> get name "yang"