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中的容器来访问。该目录是如何形成的、支持该目录的介质以及内容取决于所使用的特定卷类型。

要使用卷,需要为pod指定为卷(spec.volumes 字段)以及将它挂载到容器的位置(spec.containers.volumeMounts 字段)。

容器中的进程看到的是其由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"

 

posted @ 2020-12-08 19:34  yt丶独自  阅读(5333)  评论(0编辑  收藏  举报