存储卷简述

  在k8s中部署的应用都是以pod容器的形式运行的,假如部署MySQL、Redis等数据库,需要对这些数据库产生的数据做备份。因为Pod是有生命周期的,如果pod不挂载数据卷,那pod被删除或重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到pod数据持久化存储。

  k8s提供的存储卷(Volume)属于pod资源级别,共享于pod内的所有容器,可用于在容器的文件系统之外存储应用程序的相关数据,甚至还可以独立于pod的生命周期之外实现数据持久化。

一、存储卷概述

  k8s系统上的存储卷是定义在pod资源之上、可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力则取决于存储卷自身是否支持持久化。

1. 存储卷类型

  查看k8s支持的存储类型,可以看到k8s支持多种存储卷类型,包括本地存储(节点)和网络存储系统中的诸多存储机制。

[root@k8s-master1 ~]# kubectl explain pod.spec.volumes
KIND:     Pod
VERSION:  v1

RESOURCE: volumes <[]Object>

DESCRIPTION:
     List of volumes that can be mounted by containers belonging to the pod.
     More info: https://kubernetes.io/docs/concepts/storage/volumes

     Volume represents a named volume in a pod that may be accessed by any
     container in the pod.

FIELDS:
   awsElasticBlockStore <Object>
     AWSElasticBlockStore represents an AWS Disk resource that is attached to a
     kubelet's host machine and then exposed to the pod. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore

   azureDisk    <Object>
     AzureDisk represents an Azure Data Disk mount on the host and bind mount to
     the pod.

   azureFile    <Object>
     AzureFile represents an Azure File Service mount on the host and bind mount
     to the pod.

   cephfs       <Object>
     CephFS represents a Ceph FS mount on the host that shares a pod's lifetime

   cinder       <Object>
     Cinder represents a cinder volume attached and mounted on kubelets host
     machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md

   configMap    <Object>
     ConfigMap represents a configMap that should populate this volume

   csi  <Object>
     CSI (Container Storage Interface) represents ephemeral storage that is
     handled by certain external CSI drivers (Beta feature).

   downwardAPI  <Object>
     DownwardAPI represents downward API about the pod that should populate this
     volume

   emptyDir     <Object>
     EmptyDir represents a temporary directory that shares a pod's lifetime.
     More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

   ephemeral    <Object>
     Ephemeral represents a volume that is handled by a cluster storage driver
     (Alpha feature). The volume's lifecycle is tied to the pod that defines it
     - it will be created before the pod starts, and deleted when the pod is
     removed.

     Use this if: a) the volume is only needed while the pod runs, b) features
     of normal volumes like restoring from snapshot or capacity tracking are
     needed, c) the storage driver is specified through a storage class, and d)
     the storage driver supports dynamic volume provisioning through a
     PersistentVolumeClaim (see EphemeralVolumeSource for more information on
     the connection between this volume type and PersistentVolumeClaim).

     Use PersistentVolumeClaim or one of the vendor-specific APIs for volumes
     that persist for longer than the lifecycle of an individual pod.

     Use CSI for light-weight local ephemeral volumes if the CSI driver is meant
     to be used that way - see the documentation of the driver for more
     information.

     A pod can use both types of ephemeral volumes and persistent volumes at the
     same time.

   fc   <Object>
     FC represents a Fibre Channel resource that is attached to a kubelet's host
     machine and then exposed to the pod.

   flexVolume   <Object>
     FlexVolume represents a generic volume resource that is
     provisioned/attached using an exec based plugin.

   flocker      <Object>
     Flocker represents a Flocker volume attached to a kubelet's host machine.
     This depends on the Flocker control service being running

   gcePersistentDisk    <Object>
     GCEPersistentDisk represents a GCE Disk resource that is attached to a
     kubelet's host machine and then exposed to the pod. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk

   gitRepo      <Object>
     GitRepo represents a git repository at a particular revision. DEPRECATED:
     GitRepo is deprecated. To provision a container with a git repo, mount an
     EmptyDir into an InitContainer that clones the repo using git, then mount
     the EmptyDir into the Pod's container.

   glusterfs    <Object>
     Glusterfs represents a Glusterfs mount on the host that shares a pod's
     lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md

   hostPath     <Object>
     HostPath represents a pre-existing file or directory on the host machine
     that is directly exposed to the container. This is generally used for
     system agents or other privileged things that are allowed to see the host
     machine. Most containers will NOT need this. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#hostpath

   iscsi        <Object>
     ISCSI represents an ISCSI Disk resource that is attached to a kubelet's
     host machine and then exposed to the pod. More info:
     https://examples.k8s.io/volumes/iscsi/README.md

   name <string> -required-
     Volume's name. Must be a DNS_LABEL and unique within the pod. More info:
     https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

   nfs  <Object>
     NFS represents an NFS mount on the host that shares a pod's lifetime More
     info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

   persistentVolumeClaim        <Object>
     PersistentVolumeClaimVolumeSource represents a reference to a
     PersistentVolumeClaim in the same namespace. More info:
     https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims

   photonPersistentDisk <Object>
     PhotonPersistentDisk represents a PhotonController persistent disk attached
     and mounted on kubelets host machine

   portworxVolume       <Object>
     PortworxVolume represents a portworx volume attached and mounted on
     kubelets host machine

   projected    <Object>
     Items for all in one resources secrets, configmaps, and downward API

   quobyte      <Object>
     Quobyte represents a Quobyte mount on the host that shares a pod's lifetime

   rbd  <Object>
     RBD represents a Rados Block Device mount on the host that shares a pod's
     lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md

   scaleIO      <Object>
     ScaleIO represents a ScaleIO persistent volume attached and mounted on
     Kubernetes nodes.

   secret       <Object>
     Secret represents a secret that should populate this volume. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#secret

   storageos    <Object>
     StorageOS represents a StorageOS volume attached and mounted on Kubernetes
     nodes.

   vsphereVolume        <Object>
     VsphereVolume represents a vSphere volume attached and mounted on kubelets
     host machine

  常用的存储类型:emptyDir,hostPath,nfs,persistentVolumeClaim,glusterfs,cephfs,configMap,secret等。

  其中,Secret用于向pod传递敏感信息,如:密码,私钥、证书文件等,用户可以将这些信息存储于集群中而后由pod进行挂载,从而实现将敏感信息与系统解耦。

    ConfigMap资源则用于向pod注入非敏感数据,使用时,用户将数据直接存储于ConfigMap对象中,而后直接在pod中使用ConfigMap卷引用它即可,它可以帮助实现容器配置文件集中化定义和管理。

2. 存储卷的使用方式

  在pod中定义使用存储卷的配置由两部分组成:一是通过.spec.volumes字段定义在pod之上的存储卷列表,其支持使用多种不同类型的存储卷且配置参数差别很大;另一个是通过.spec.containers.volumeMounts字段定义在容器上定义的存储卷挂载列表,它只能挂载当前pod资源中定义的具体存储卷,当然,也可以不挂载任何存储卷。

  简单来说要使用存储卷,需要经历如下步骤:

  1)定义pod的volume,这个volume指明它要关联到哪个存储上的

  2)在容器中要使用volumemounts挂载对应的存储

  在pod级别定义存储卷时,.spec.volumes字段的值是对象列表格式,每个对象为一个存储卷的定义。下面的资源清单片段中定义了由两个存储卷组成的卷列表,一个是emptyDir类型,一个是gitRepo类型:

spec:
...
  volumes:
  - name: logdata
    emptyDir: {}
  - name: example
    gitRepo:
	  repository: https//github.com/iKubernetes/k8s_book.gitRepo
	  revision: master
	  directory: .

  定义好的存储卷可由当前pod资源内的各容器进行挂载。事实上,也只有多个容器挂载同一个存储卷时,“共享”才有具体意义。当pod中只有一个容器时,使用存储卷的目的通常在于数据持久化。.spec.containers.volumeMounts字段的值也是对象列表格式,由一到多个存储卷挂载定义组成。无论何种类型的存储卷,它们的挂载格式基本上都是相同的。下面的代码段是在容器内定义挂载卷时通用的语法格式:

[root@k8s-master1 ~]# kubectl explain pod.spec.containers.volumeMounts
KIND:     Pod
VERSION:  v1

RESOURCE: volumeMounts <[]Object>

DESCRIPTION:
     Pod volumes to mount into the container's filesystem. Cannot be updated.

     VolumeMount describes a mounting of a Volume within a container.

FIELDS:
   mountPath    <string> -required- #挂载点路径,容器文件系统上的路径,必须字段
     Path within the container at which the volume should be mounted. Must not
     contain ':'.

   mountPropagation     <string>
     mountPropagation determines how mounts are propagated from the host to
     container and the other way around. When not set, MountPropagationNone is
     used. This field is beta in 1.10.

   name <string> -required-  #指定要挂载的存储名称,必选字段
     This must match the Name of a Volume.

   readOnly     <boolean>  #是否挂载为只读卷
     Mounted read-only if true, read-write otherwise (false or unspecified).
     Defaults to false.

   subPath      <string> #挂载存储卷时使用的子路径,即在mountPath指定的路径下使用一个子路径作为其挂载点
     Path within the volume from which the container's volume should be mounted.
     Defaults to "" (volume's root).

   subPathExpr  <string>
     Expanded path within the volume from which the container's volume should be
     mounted. Behaves similarly to SubPath but environment variable references
     $(VAR_NAME) are expanded using the container's environment. Defaults to ""
     (volume's root). SubPathExpr and SubPath are mutually exclusive.

  下面是一个挂载示例,容器myapp将logdata存储卷挂载于/var/log/myapp目录下,将example挂载到/webdata/example目录下:

spec:
  containers:
  - name:  myapp
    image: nginx:latest
    imagePullPolicy: IfNotPresent
	volumeMounts:
	- name: logdata
	  mountPath: /var/log/myapp/
	- name: example
	  mountPath: /webdata/example/

二、临时存储卷

  k8s支持存储卷类型中,emptyDir存储卷的生命周期与其所属的pod对象相同,它无法脱离pod对象的生命周期提供数据存储功能,因此emptyDir通常仅用于数据缓存或临时存储。不过,基于emptyDir构建的gitRepo存储卷可以在pod对象的生命周期起始时从相应的Git仓库中复制相应的数据文件到底层的emptyDir中,从而使得它具有了一定意义上的持久性。

1. emptyDir存储卷

  emptyDir存储卷是pod对象生命周期中的一个临时目录,在pod启动时即被创建,而在pod对象被移除时会被一并删除。不具有持久能力的emptyDir存储卷只能用于某些特殊场景中,例如:同一个pod内的多个容器间文件的共享,或者作为容器数据的临时存储目录用于数据缓存系统等。

  emptyDir存储卷则定义于.spec.volumes.emptyDir嵌套的字段中,可用字段主要包括两个,具体如下:

[root@k8s-master1 ~]# kubectl explain pod.spec.volumes.emptyDir
KIND:     Pod
VERSION:  v1

RESOURCE: emptyDir <Object>

DESCRIPTION:
     EmptyDir represents a temporary directory that shares a pod's lifetime.
     More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

     Represents an empty directory for a pod. Empty directory volumes support
     ownership management and SELinux relabeling.

FIELDS:
   medium       <string> #此目录所在的存储介质的类型,可取值为"default"或"Memory",默认为default,表示使用节点的默认存储介质;"Memory"表示使用基于RAM的临时文件系统tmpfs,空间受限于内存,但性能非常好,通常用于为容器中的应用提供缓存空间。
     What type of storage medium should back this directory. The default is ""
     which means to use the node's default medium. Must be an empty string
     (default) or Memory. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#emptydir

   sizeLimit    <string> #当前存储卷的空间限额,默认值为nil,表示不受限制;不过在medium字段值为Memory时建议务必定义此限额。
     Total amount of local storage required for this EmptyDir volume. The size
     limit is also applicable for memory medium. The maximum usage on memory
     medium EmptyDir would be the minimum value between the SizeLimit specified
     here and the sum of memory limits of all containers in a pod. The default
     is nil which means that the limit is undefined. More info:
     http://kubernetes.io/docs/user-guide/volumes#emptydir

  下面是一个使用了emptyDir存储卷的简单示例:

[root@k8s-master1 pod]# vim emptydir-pod.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 pod]# cat emptydir-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-empty
spec:
  containers:
  - name: nginx
    ports:
    - containerPort: 80
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: html
  - name: pagegen
    image: alpine
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: html
      mountPath: /html
    command: ["/bin/sh", "-c"]
    args:
    - while true; do
        echo $(hostname) $(date) >> /html/index.html;
        sleep 10;
      done
  volumes:
  - name: html
    emptyDir: {}
[root@k8s-master1 pod]# kubectl apply -f emptydir-pod.yaml
pod/pod-empty created
[root@k8s-master1 pod]# kubectl get pods -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
pod-empty   2/2     Running   0          7s    10.244.36.76   k8s-node1   <none>           <none>

  上面示例中定义的存储卷名称为html,挂载于容器nginx的/usr/share/nginx/html目录,及容器pagen的/html目录。容器pagegen每隔10秒向存储卷上的index.html文件中追加一行信息,而容器nginx中的nginx进程则以其为站点主页。

     查看pod的uid,

[root@k8s-master1 pod]# kubectl get pods pod-empty -o yaml | grep uid
  uid: 70687bdc-d439-4ae5-81b7-a47037e9bff6

  登录k8s-node1节点上,查看存储位置,可以看到在/var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6/volumes/kubernetes.io~empty-dir/html/ 目录下

[root@k8s-node1 ~]# tree /var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6
/var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6
├── containers
│   ├── nginx
│   │   └── 85d0ba7e
│   └── pagegen
│       └── 87b4296f
├── etc-hosts
├── plugins
│   └── kubernetes.io~empty-dir
│       ├── html
│       │   └── ready
│       └── wrapped_default-token-5n29f
│           └── ready
└── volumes
    ├── kubernetes.io~empty-dir
    │   └── html
    │       └── index.html
    └── kubernetes.io~secret
        └── default-token-5n29f
            ├── ca.crt -> ..data/ca.crt
            ├── namespace -> ..data/namespace
            └── token -> ..data/token

12 directories, 9 files
[root@k8s-node1 ~]# ls -lrt  /var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6/volumes/kubernetes.io~empty-dir/
total 0
drwxrwxrwx 2 root root 24 Sep 18 23:31 html
[root@k8s-node1 ~]# ls -lrt  /var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6/volumes/kubernetes.io~empty-dir/html
total 4
-rw-r--r-- 1 root root 1131 Sep 18 23:36 index.html
[root@k8s-node1 ~]# cat  /var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6/volumes/kubernetes.io~empty-dir/html/index.html
pod-empty Sun Sep 18 15:31:26 UTC 2022
pod-empty Sun Sep 18 15:31:36 UTC 2022
pod-empty Sun Sep 18 15:31:46 UTC 2022
pod-empty Sun Sep 18 15:31:56 UTC 2022
pod-empty Sun Sep 18 15:32:06 UTC 2022
...

  pod资源的详细信息中会显示存储卷的相关状态,包括其是否创建成功,相关类型及参数以及容器中的挂载状态等信息。如下面命令结果所示:

[root@k8s-master1 pod]# kubectl describe pods pod-empty
Name:         pod-empty
Namespace:    default
Priority:     0
Node:         k8s-node1/10.0.0.132
Start Time:   Sun, 18 Sep 2022 23:31:24 +0800
Labels:       <none>
Annotations:  cni.projectcalico.org/podIP: 10.244.36.76/32
              cni.projectcalico.org/podIPs: 10.244.36.76/32
Status:       Running
IP:           10.244.36.76
IPs:
  IP:  10.244.36.76
Containers:
  nginx:
    Container ID:   docker://250d903443ede6b8e6594b815cd1b815e079d149d91eefb7cd26cdf5737811ec
    Image:          nginx:latest
    Image ID:       docker-pullable://nginx@sha256:b95a99feebf7797479e0c5eb5ec0bdfa5d9f504bc94da550c2f58e839ea6914f
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sun, 18 Sep 2022 23:31:26 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from html (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5n29f (ro)
  pagegen:
    Container ID:  docker://c5e9b7de94315d7e194110c66e38dc7a15ded921fea7e9c3aeace9e3fb372828
    Image:         alpine
    Image ID:      docker-pullable://alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad
    Port:          <none>
    Host Port:     <none>
    Command:
      /bin/sh
      -c
    Args:
      while true; do echo $(hostname) $(date) >> /html/index.html; sleep 10; done
    State:          Running
      Started:      Sun, 18 Sep 2022 23:31:26 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /html from html (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5n29f (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  html:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
  default-token-5n29f:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-5n29f
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  7m5s  default-scheduler  Successfully assigned default/pod-empty to k8s-node1
  Normal  Pulled     7m4s  kubelet            Container image "nginx:latest" already present on machine
  Normal  Created    7m4s  kubelet            Created container nginx
  Normal  Started    7m4s  kubelet            Started container nginx
  Normal  Pulled     7m4s  kubelet            Container image "alpine" already present on machine
  Normal  Created    7m4s  kubelet            Created container pagegen
  Normal  Started    7m4s  kubelet            Started container pagegen

  而后,可以为其创建Service资源并进行访问测试,或者在集群中直接对pod的地址发起访问请求,以测试pod中的两个容器通过emptyDir卷共享数据的结果状态:

[root@k8s-master1 pod]# curl -kv 10.244.36.76
* About to connect() to 10.244.36.76 port 80 (#0)
*   Trying 10.244.36.76...
* Connected to 10.244.36.76 (10.244.36.76) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.244.36.76
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.23.1
< Date: Sun, 18 Sep 2022 15:40:50 GMT
< Content-Type: text/html
< Content-Length: 2223
< Last-Modified: Sun, 18 Sep 2022 15:40:47 GMT
< Connection: keep-alive
< ETag: "63273bff-8af"
< Accept-Ranges: bytes
<
pod-empty Sun Sep 18 15:31:26 UTC 2022
pod-empty Sun Sep 18 15:31:36 UTC 2022
pod-empty Sun Sep 18 15:31:46 UTC 2022
pod-empty Sun Sep 18 15:31:56 UTC 2022
pod-empty Sun Sep 18 15:32:06 UTC 2022
pod-empty Sun Sep 18 15:32:16 UTC 2022
pod-empty Sun Sep 18 15:32:26 UTC 2022
...

  作为边车(sidecar)的容器pagegen,其每隔10秒生成一行信息追加到存储卷上的index.html文件中,因此,通过主容器nginx的应用访问到的内容也会处于不停的变动中。

       在pod所在的节点上的存储目录下 /var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6/volumes/kubernetes.io~empty-dir/html/的index.html文件也在不断的变动。若修改 /var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6/volumes/kubernetes.io~empty-dir/html/的index.html文件,那么测试访问的内容也将发生变化

[root@k8s-node1 ~]# echo "hello world" >  /var/lib/kubelet/pods/70687bdc-d439-4ae5-81b7-a47037e9bff6/volumes/kubernetes.io~empty-dir/html/index.html
#测试访问
[root@k8s-master1 pod]# curl -kv 10.244.36.76
* About to connect() to 10.244.36.76 port 80 (#0)
*   Trying 10.244.36.76...
* Connected to 10.244.36.76 (10.244.36.76) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.244.36.76
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.23.1
< Date: Sun, 18 Sep 2022 15:47:05 GMT
< Content-Type: text/html
< Content-Length: 12
< Last-Modified: Sun, 18 Sep 2022 15:47:02 GMT
< Connection: keep-alive
< ETag: "63273d76-c"
< Accept-Ranges: bytes
<
hello world
* Connection #0 to host 10.244.36.76 left intact

  若将pod-empty对象删除,pod所在节点的存储目录也将被删除

[root@k8s-master1 pod]# kubectl delete pods pod-empty
pod "pod-empty" deleted
[root@k8s-node1 ~]# ls -lrt /var/lib/kubelet/pods/
total 0
drwxr-x--- 5 root root 71 Aug  1 20:48 2eafe33f-ef87-453b-99f5-6793f27be642
drwxr-x--- 5 root root 71 Aug  1 21:12 7ab91121-203f-41f5-81c4-8801c1383fcf
drwxr-x--- 5 root root 71 Sep 18 23:31 70687bdc-d439-4ae5-81b7-a47037e9bff6
[root@k8s-node1 ~]# ls -lrt /var/lib/kubelet/pods/
total 0
drwxr-x--- 5 root root 71 Aug  1 20:48 2eafe33f-ef87-453b-99f5-6793f27be642
drwxr-x--- 5 root root 71 Aug  1 21:12 7ab91121-203f-41f5-81c4-8801c1383fcf
[root@k8s-node1 ~]#

  emptyDir卷简单易用,但是仅能用于临时存储。

2. gitRepo存储卷

  gitRepo存储卷可以看作是emptyDIr存储卷的一种实际应用,使用该存储卷的pod资源可以通过挂载目录访问指定的代码仓库中的数据。使用gitRepo存储卷的pod资源在创建时,会首先创建一个空目录并克隆一份指定的Git仓库中的数据值该目录,而后在创建容器并挂载该存储卷。

   定义该gitRepo类型的存储卷时,其可嵌套使用的字段具体包含如下三个:

[root@k8s-master1 ~]# kubectl explain pod.spec.volumes.gitRepo
KIND:     Pod
VERSION:  v1

RESOURCE: gitRepo <Object>

DESCRIPTION:
     GitRepo represents a git repository at a particular revision. DEPRECATED:
     GitRepo is deprecated. To provision a container with a git repo, mount an
     EmptyDir into an InitContainer that clones the repo using git, then mount
     the EmptyDir into the Pod's container.
	 #使用gitRepo类型的存储卷的pod资源运行的工作节点上必须安装git程序,否则克隆仓库的操作将无从完成。另外,自Kubernetes 1.12起,gitRepo存储卷已经被废弃

     Represents a volume that is populated with the contents of a git
     repository. Git repo volumes do not support ownership management. Git repo
     volumes support SELinux relabeling.

     DEPRECATED: GitRepo is deprecated. To provision a container with a git
     repo, mount an EmptyDir into an InitContainer that clones the repo using
     git, then mount the EmptyDir into the Pod's container.

FIELDS:
   directory    <string> #目标目录名称,名称中不能包含".."字符;"."表示将仓库中的数据直接复制到卷目录中,否则,即为复制到卷目录中以用户指定的字符串为名称的子目录
     Target directory name. Must not contain or start with '..'. If '.' is
     supplied, the volume directory will be the git repository. Otherwise, if
     specified, the volume will contain the git repository in the subdirectory
     with the given name.

   repository   <string> -required-  #Git仓库的URL,必选字段
     Repository URL

   revision     <string>  #特定revision的提交哈希码
     Commit hash for the specified revision.

  下面示例中的pod资源在创建时,首先会创建一个空目录,将指定的Git仓库https://github.com/cnych/presentation-gitlab-k8s.git 中的数据复制一份直接保存于此目录中,而后将此目录创建为存储卷html,最后由容器nginx将此存储卷挂载于/usr/share/nginx/html 目录上。

[root@k8s-master1 pod]# vim vol-gitrepo.yaml
You have new mail in /var/spool/mail/root
[root@k8s-master1 pod]# cat vol-gitrepo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-gitrepo
spec:
  containers:
  - name: nginx
    ports:
    - containerPort: 80
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: html
  volumes:
  - name: html
    gitRepo:
      repository: https://github.com/cnych/presentation-gitlab-k8s.git
      revision: "master"
[root@k8s-master1 pod]# kubectl apply -f vol-gitrepo.yaml
pod/pod-gitrepo created
[root@k8s-master1 pod]# kubectl get pods
NAME          READY   STATUS              RESTARTS   AGE
pod-gitrepo   0/1     ContainerCreating   0          5s
[root@k8s-master1 pod]# kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE    IP             NODE        NOMINATED NODE   READINESS GATES
pod-gitrepo   1/1     Running   0          4m6s   10.244.36.80   k8s-node1   <none>           <none>

  访问此pod资源中的nginx服务即可看到其来自于Git仓库中的页面资源。不过,gitRepo存储卷在其创建完成后不会再与指定的仓库指定的仓库执行同步操作,这就意味着在pod资源运行期间,如果仓库中的数据发生变化,那么gitRepo存储卷不会同步到这些内容。

  可以登录到pod中查看目录中是否有克隆下仓库的数据

[root@k8s-master1 pod]# kubectl exec -it pod-gitrepo -- /bin/sh
# ls /usr/share/nginx/html
presentation-gitlab-k8s
# ls /usr/share/nginx/html/presentation-gitlab-k8s
Dockerfile  Gopkg.lock  Gopkg.toml  LICENSE  Makefile  README.md  VERSION  main.go  manifests  vendor
#

  在pod所在的k8s-node1节点上查看存储位置中,可以看到克隆到仓库中的数据

[root@k8s-node1 ~]# ll /var/lib/kubelet/pods/58af7421-5454-4d03-9888-0414d2ce1bd1/volumes/kubernetes.io~git-repo/
total 0
drwxrwxrwx 3 root root 37 Sep 21 22:33 html
[root@k8s-node1 ~]# ll /var/lib/kubelet/pods/58af7421-5454-4d03-9888-0414d2ce1bd1/volumes/kubernetes.io~git-repo/html/
total 0
drwxr-xr-x 5 root root 221 Sep 21 22:33 presentation-gitlab-k8s
[root@k8s-node1 ~]# ll /var/lib/kubelet/pods/58af7421-5454-4d03-9888-0414d2ce1bd1/volumes/kubernetes.io~git-repo/html/presentation-gitlab-k8s/
total 32
-rw-r--r-- 1 root root   86 Sep 21 22:33 Dockerfile
-rw-r--r-- 1 root root 2093 Sep 21 22:33 Gopkg.lock
-rw-r--r-- 1 root root  843 Sep 21 22:33 Gopkg.toml
-rw-r--r-- 1 root root 1072 Sep 21 22:33 LICENSE
-rw-r--r-- 1 root root 2314 Sep 21 22:33 main.go
-rw-r--r-- 1 root root  724 Sep 21 22:33 Makefile
drwxr-xr-x 2 root root   86 Sep 21 22:33 manifests
-rw-r--r-- 1 root root 3875 Sep 21 22:33 README.md
drwxr-xr-x 4 root root   42 Sep 21 22:33 vendor
-rw-r--r-- 1 root root    7 Sep 21 22:33 VERSION

  gitRepo存储卷建构于emptyDir存储卷上,它的生命周期与隶属pod对象相同,因此使用时不建议在此类存储卷上保存由容器生成的重要数据。其次,自Kubernetes 1.12起,gitRepo存储卷已经被废弃,所以在之后的版本中如要使用它配置pod对象,建议借助初始化容器,将仓库中的数据复制到emptyDir存储卷上,并在主容器中使用此存储卷。

二、节点存储卷hostPath

  hostPath类型的存储卷是指将工作节点上的某个文件系统目录或文件挂载于pod中的一种存储卷,它可独立于pod资源的生命周期,在pod被删除,这个存储卷还是存在的,不会被删除,所以只要同一个pod被调度到同一个节点上来,在pod被删除重新被调度到这个节点之后,对应的数据依然是存在的,因而具有持久性。但它是工作节点本地的存储空间,仅适用于特定情况下的存储卷使用需求,例如,将工作节点上的文件系统关联为pod的存储卷,从而使得容器访问节点文件系统上的数据。这一点在运行由管理任务的系统级pod资源需要访问节点上的文件尤为有用。

  配置hostPath存储卷的嵌套字段共有两个:一个是用于指定工作节点上的目录路径的必须字段path,一个是指定存储卷的类型type,

[root@k8s-master1 pod]# kubectl explain pod.spec.volumes.hostPath
KIND:     Pod
VERSION:  v1

RESOURCE: hostPath <Object>

DESCRIPTION:
     HostPath represents a pre-existing file or directory on the host machine
     that is directly exposed to the container. This is generally used for
     system agents or other privileged things that are allowed to see the host
     machine. Most containers will NOT need this. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#hostpath

     Represents a host path mapped into a pod. Host path volumes do not support
     ownership management or SELinux relabeling.

FIELDS:
   path <string> -required-  #工作节点上的目录路径
     Path of the directory on the host. If the path is a symlink, it will follow
     the link to the real path. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#hostpath

   type <string> #存储卷类型
     Type for HostPath Volume Defaults to "" More info:
     https://kubernetes.io/docs/concepts/storage/volumes#hostpath

  支持使用的卷类型包含以下几种:

DirectoryOrCreate	如果给定路径不存在任何内容,则会根据需要在此处创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权
Directory	目录必须存在于给定路径
FileOrCreate	如果给定路径不存在任何内容,则会根据需要在此处创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。
File	文件必须存在于给定路径
Socket	给定路径中必须存在 UNIX 套接字
CharDevice	字符设备必须存在于给定路径中
BlockDevice	块设备必须存在于给定路径

  下面为hostPath 配置示例

[root@k8s-master1 pod]# vim hostPath-pod.yaml
[root@k8s-master1 pod]# cat hostPath-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-hostpath
spec:
  containers:
  - name: nginx
    ports:
    - containerPort: 80
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: html
  - name:  tomcat
    image: tomcat:8.5-jre8-alpine
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: html
      mountPath: /html
  volumes:
  - name: html
    hostPath:
      path: /data1
      type: DirectoryOrCreate   #表示本地有/data1目录,就用本地的,本地没有的,就会在pod调度的节点上自动创建该目录
[root@k8s-master1 pod]# kubectl apply -f hostPath-pod.yaml
pod/pod-hostpath created
[root@k8s-master1 pod]# kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
pod-hostpath   2/2     Running   0          6s    10.244.36.78   k8s-node1   <none>           <none>

   由上面可以知道pod调度到了k8s-node1上,登录到k8s-node1机器,查看是否在这台机器创建了存储目录

[root@k8s-node1 ~]# ll /data1
total 0
[root@k8s-node1 ~]#

  上面可以看到已经创建了存储目录/data1,这个/data1会作为pod的持久化存储目录

  在k8s-node1上的/data1下创建一个文件index.html

[root@k8s-node1 ~]# cd /data1
[root@k8s-node1 data1]# echo "hello world" >> index.html
[root@k8s-node1 data1]# cat index.html
hello world
[root@k8s-node1 data1]# ll
total 4
-rw-r--r-- 1 root root 12 Sep 22 21:54 index.html

  测试存储卷是否可以正常使用,登录到nginx容器

[root@k8s-master1 pod]# kubectl exec -it pod-hostpath -c nginx -- /bin/sh
# ls /usr/share/nginx/html
index.html
# cat /usr/share/nginx/html/index.html
hello world
#

  测试nginx的访问页面可以正常访问:

[root@k8s-master1 ~]# kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE     IP             NODE        NOMINATED NODE   READINESS GATES
pod-hostpath   2/2     Running   0          8m35s   10.244.36.78   k8s-node1   <none>           <none>
[root@k8s-master1 ~]# curl -kv http://10.244.36.78:80
* About to connect() to 10.244.36.78 port 80 (#0)
*   Trying 10.244.36.78...
* Connected to 10.244.36.78 (10.244.36.78) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.244.36.78
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.23.1
< Date: Thu, 22 Sep 2022 13:58:57 GMT
< Content-Type: text/html
< Content-Length: 12
< Last-Modified: Thu, 22 Sep 2022 13:54:43 GMT
< Connection: keep-alive
< ETag: "632c6923-c"
< Accept-Ranges: bytes
<
hello world
* Connection #0 to host 10.244.36.78 left intact

  同时我们也登录到tomcat容器,查看存储卷是否可以正常使用

[root@k8s-master1 pod]# kubectl exec -it pod-hostpath -c tomcat -- /bin/sh
/usr/local/tomcat # ls /html
index.html
/usr/local/tomcat # cat /html/index.html
hello world

  通过上面测试可以看到,同一个pod里的nginx和tomcat这两个容器是共享存储卷的。

  hostpath存储卷缺点:单节点。即pod删除之后重新创建必须调度到同一个node节点,数据才不会丢失。可以用分布式存储:nfs,cephfs,glusterfs

  另外,在节点中创建的文件或目录默认仅有root可写,若期望容器内的进程拥有写权限,则要么将它运行为特权容器,要么修改该节点上的目录路劲的权限。

三、NFS存储卷

  NFS即网络文件系统,它是一种分布式文件系统协议,其功能旨在允许客户端主机可以像访问本地存储一样通过网络访问服务器端文件。

1. 搭建NFS服务

  1)安装nfs插件

[root@k8s-master1 ~]# yum install nfs-utils -y

   2)在宿主机创建NFS需要的共享目录

[root@k8s-master1 ~]# mkdir -pv /data/volumes

  3)配置nfs共享服务器上的/data/volumes目录

[root@k8s-master1 ~]# vim /etc/exports
[root@k8s-master1 ~]# cat /etc/exports
/data/volumes 10.0.0.131/24(rw,no_root_squash)
[root@k8s-master1 ~]#

  其中,no_root_squash: 用户具有根目录的完全管理访问权限

  4)使NFS配置生效

[root@k8s-master1 ~]# exportfs -arv
exporting 10.0.0.131/24:/data/volumes

  5)设置成开机自启动

[root@k8s-master1 ~]# systemctl start nfs
You have new mail in /var/spool/mail/root
[root@k8s-master1 ~]# systemctl enable nfs
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.

  6)查看nfs是否启动成功

[root@k8s-master1 ~]# systemctl status nfs
● nfs-server.service - NFS server and services
   Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)
  Drop-In: /run/systemd/generator/nfs-server.service.d
           └─order-with-mounts.conf
   Active: active (exited) since Sat 2022-09-24 10:37:33 CST; 41s ago
 Main PID: 27955 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/nfs-server.service

Sep 24 10:37:33 k8s-master1 systemd[1]: Starting NFS server and services...
Sep 24 10:37:33 k8s-master1 systemd[1]: Started NFS server and services.
[root@k8s-master1 ~]# netstat -lntup |grep 111
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      563/rpcbind
tcp6       0      0 :::111                  :::*                    LISTEN      563/rpcbind
udp        0      0 0.0.0.0:111             0.0.0.0:*                           563/rpcbind
udp6       0      0 :::111                  :::*                                563/rpcbind
[root@k8s-master1 ~]#

   可以看到nfs是active,说明nfs正常启动了

  7)测试手动挂载是否正常,可以看到可以正常挂载

[root@k8s-node1 ~]# mkdir /test
[root@k8s-node1 ~]# mount 10.0.0.131:/data/volumes /test/
[root@k8s-node1 ~]# df -hT |grep /test
10.0.0.131:/data/volumes nfs4       40G  5.9G   34G  15% /test
[root@k8s-node1 ~]#

  手动卸载命令如下:

[root@k8s-node1 ~]# umount /test
[root@k8s-node1 ~]# df -hT |grep /test
[root@k8s-node1 ~]#

2.  NFS存储卷挂载

  kubernetes的NFS存储卷用于将某事先存在的NFS服务器上导出的存储空间挂载到pod中以供容器使用。与emptyDir不同的是,NFS存储卷在pod对象终止后仅是被卸载而非删除。另外,NFS是文件系统级共享服务,它支持同时存在的多路挂载请求。定义NFS存储卷时,用到以下字段:

  

[root@k8s-master1 ~]# kubectl explain pod.spec.volumes.nfs
KIND:     Pod
VERSION:  v1

RESOURCE: nfs <Object>

DESCRIPTION:
     NFS represents an NFS mount on the host that shares a pod's lifetime More
     info: https://kubernetes.io/docs/concepts/storage/volumes#nfs

     Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do
     not support ownership management or SELinux relabeling.

FIELDS:
   path <string> -required-  #NFS服务器导出(共享)的文件系统路径,必选字段
     Path that is exported by the NFS server. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#nfs

   readOnly     <boolean>  #是否以只读方式挂载,默认是false
     ReadOnly here will force the NFS export to be mounted with read-only
     permissions. Defaults to false. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#nfs

   server       <string> -required-   #NFS服务器IP地址或主机名,必选字段
     Server is the hostname or IP address of the NFS server. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#nfs

  Redis是一个著名的高性能key-value存储系统,应用非常广泛,将其部署运行于kubernetes系统之上时,需要持久化存储卷的支持。下面是简单使用redis的一个示例:

[root@k8s-master1 pod]# vim nfs-pod.yaml
[root@k8s-master1 pod]# cat nfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nfs
  labels:
    app: redis
spec:
  containers:
  - name: redis
    ports:
    - containerPort: 6379
    image: redis:4-alpine
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /data
      name: redisdata
  volumes:
  - name: redisdata
    nfs:
      path: /data/volumes
      server: 10.0.0.131

  上面示例定义在资源配置清单文件nfs-pod.yaml中,其中pod资源拥有一个关联至NFS服务器:10.0.0.131的存储卷,redis容器将其挂载于/data目录上,它是运行于容器中的redis-server数据的持久保存位置。

  使用下列命令创建资源,并查看资源的详细信息

[root@k8s-master1 pod]# kubectl apply -f nfs-pod.yaml
pod/pod-nfs created
You have new mail in /var/spool/mail/root
[root@k8s-master1 pod]# kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
pod-nfs   1/1     Running   0          5s
[root@k8s-master1 pod]# kubectl describe pod pod-nfs
Name:         pod-nfs
Namespace:    default
Priority:     0
Node:         k8s-node1/10.0.0.132
Start Time:   Sat, 24 Sep 2022 11:15:28 +0800
Labels:       app=redis
Annotations:  cni.projectcalico.org/podIP: 10.244.36.74/32
              cni.projectcalico.org/podIPs: 10.244.36.74/32
Status:       Running
IP:           10.244.36.74
IPs:
  IP:  10.244.36.74
Containers:
  redis:
    Container ID:   docker://ce6445f678dee398b2d0cb321ea8a24097cc680b0fad05d290e4fe2e9c4e1ad8
    Image:          redis:4-alpine
    Image ID:       docker-pullable://redis@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20
    Port:           6379/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sat, 24 Sep 2022 11:15:29 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /data from redisdata (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5n29f (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  redisdata:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    10.0.0.131
    Path:      /data/volumes
    ReadOnly:  false
  default-token-5n29f:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-5n29f
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  19s   default-scheduler  Successfully assigned default/pod-nfs to k8s-node1
  Normal  Pulled     18s   kubelet            Container image "redis:4-alpine" already present on machine
  Normal  Created    18s   kubelet            Created container redis
  Normal  Started    18s   kubelet            Started container redis
[root@k8s-master1 pod]#

  资源创建完成之后,可通过其命令客户端redis-cli创建测试数据,并手动触发其同步与存储系统中

[root@k8s-master1 pod]# kubectl exec -it pod-nfs -- redis-cli
127.0.0.1:6379> set mykey "hello nfs"
OK
127.0.0.1:6379> get mykey
"hello nfs"
127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> exit

  可以看到在nfs服务器共享目录中已有相应的数据,说明挂载nfs存储卷成功。

[root@k8s-master1 ~]# ll /data/volumes/
total 4
-rw-r--r-- 1 polkitd 1000 115 Sep 24 11:19 dump.rdb
[root@k8s-master1 ~]#

  为了测试其数据持久化效果,下面删除pod资源nfs-pod,并于再次重建后检测数据是否依然能够访问

[root@k8s-master1 pod]# kubectl delete pods pod-nfs
pod "pod-nfs" deleted
[root@k8s-master1 pod]# kubectl apply -f nfs-pod.yaml
pod/pod-nfs created
[root@k8s-master1 pod]# kubectl get pods -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
pod-nfs   1/1     Running   0          7s    10.244.36.85   k8s-node1   <none>           <none>

  待其重建完成后,通过再一次创建的pod资源详细信息,可以观察到它挂载使用NFS存储卷的相关信息。接下来再次检查redis-server中是否还保存有此前存储的数据:

[root@k8s-master1 pod]# kubectl describe pods pod-nfs
Name:         pod-nfs
Namespace:    default
Priority:     0
Node:         k8s-node1/10.0.0.132
Start Time:   Sat, 24 Sep 2022 11:24:22 +0800
Labels:       app=redis
Annotations:  cni.projectcalico.org/podIP: 10.244.36.85/32
              cni.projectcalico.org/podIPs: 10.244.36.85/32
Status:       Running
IP:           10.244.36.85
IPs:
  IP:  10.244.36.85
Containers:
  redis:
    Container ID:   docker://6b0b4374818686c7bea5fc64cdcc969ae493928bcea7e18e4a3c9cfe28abca89
    Image:          redis:4-alpine
    Image ID:       docker-pullable://redis@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20
    Port:           6379/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sat, 24 Sep 2022 11:24:24 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /data from redisdata (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5n29f (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  redisdata:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    10.0.0.131
    Path:      /data/volumes
    ReadOnly:  false
  default-token-5n29f:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-5n29f
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  2m25s  default-scheduler  Successfully assigned default/pod-nfs to k8s-node1
  Normal  Pulled     2m24s  kubelet            Container image "redis:4-alpine" already present on machine
  Normal  Created    2m24s  kubelet            Created container redis
  Normal  Started    2m23s  kubelet            Started container redis

[root@k8s-master1 pod]# kubectl get pods -o wide
NAME      READY   STATUS    RESTARTS   AGE     IP             NODE        NOMINATED NODE   READINESS GATES
pod-nfs   1/1     Running   0          2m48s   10.244.36.85   k8s-node1   <none>           <none>
[root@k8s-master1 pod]# kubectl exec -it pod-nfs -- redis-cli
127.0.0.1:6379> get mykey
"hello nfs"
127.0.0.1:6379>

  从上面的命令结果可以看出,此前创建的键mykey及其数据再pod资源重建后依然存在,这表明在删除pod资源时,其关联的外部存储卷并不会被一同删除。如果需要清除此类的数据,需要用户通过存储系统的管理接口手动进行。如果nfs宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分布式存储有glusterfs和cephfs。

posted @ 2022-09-24 11:35  出水芙蓉·薇薇  阅读(253)  评论(0编辑  收藏  举报