Kubernetes-Volume

1. 简介

我们都知道 Container 中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用 程序带来一些问题。

  1. 是当容器崩溃时文件丢失。(kubelet 会重新启动容器, 但容器会以干净的状态重启)
  2. 在同一 Pod 中运行多个容器如何共享文件

Kubernetes 卷(Volume)这一抽象概念能够解决这两个问题。

2. 背景

Docker 也有 卷(Volume)的概念,但对它只有少量且松散的管理。 Docker 卷是磁盘上或者另外一个容器内的一个目录。 Docker 提供卷驱动程序,但是其功能非常有限。

Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁 持久卷。对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。

卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放 的内容。

使用卷时, 在 .spec.volumes 字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。 容器中的进程看到的是由它们的 Docker 镜像和卷组成的文件系统视图。 Docker 镜像 位于文件系统层次结构的根部。各个卷则挂载在镜像内的指定路径上。 卷不能挂载到其他卷之上,也不能与其他卷有硬链接。Pod 配置中的每个容器必须独立指定各个卷的挂载位置。

3. emptyDir

当 Pod 分派到某个 Node 上时,emptyDir 卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。 就像其名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,这些容器都可以读写 emptyDir 卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会被永久删除。

说明: 容器崩溃并会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir 卷中的数据是安全的。

资源模板如下:

apiVersion: v1
kind: Pod
metadata:
  name: producer-consumer
spec:
  containers:
  - image: busybox
    name: producer
    volumeMounts:
    - mountPath: /producer_dir
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - echo "hello world" > /producer_dir/hello; sleep 3000
  - image: busybox
    name: consumer
    volumeMounts:
    - mountPath: /consumer_dir
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - cat /consumer_dir/hello; sleep 3000
  volumes:
  - name: shared-volume
    emptyDir: {}

创建一个pod,其中有两个container,一个是producer,另一个是consumer,producer负责生成hello文件并且写入内容hello world,consumer 则负责读取hello文件中的内容,验证同一pod中的container存储共享机制。

因为 emptyDir 是 Docker Host 文件系统里的目录,其效果相当于在k8s-woker-01上执行了 docker run -v /producer_dirdocker run -v /consumer_dir。通过 docker inspect 查看容器的详细配置信息,我们发现两个容器都 mount 了同一个目录:

$ docker inspect 52305cbb0dec

$ docker inspect c946762ccc8c

这里Source指定的路径就是 emptyDir 在 Host 上的真正路径。

emptyDir 是 Host 上创建的临时目录,其优点是能够方便地为 Pod 中的容器提供共享存储,不需要额外的配置。但它不具备持久性,如果 Pod 不存在了,emptyDir 也就没有了。根据这个特性,emptyDir 特别适合 Pod 中的容器需要临时共享存储空间的场景,比如前面的生产者消费者用例。

4. hostPath

警告:

HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。 当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。

hostPath 卷能将主机节点文件系统上的文件或目录挂载到你的 Pod 中。 虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃生舱。

例如,hostPath 的一些用法有:

  • 运行一个需要访问 Docker 内部机制的容器;可使用 hostPath 挂载 /var/lib/docker 路径。
  • 允许 Pod 指定给定的 hostPath 在运行 Pod 之前是否应该存在,是否应该创建以及应该以什么方式存在。

除了必需的 path 属性之外,用户可以选择性地为 hostPath 卷指定 type

支持的 type 值如下:

取值 行为
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate 如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。
Directory 在给定路径上必须存在的目录。
FileOrCreate 如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。
File 在给定路径上必须存在的文件。
Socket 在给定路径上必须存在的 UNIX 套接字。

hostPath 配置示例:

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # 宿主上目录位置
      path: /data
      # 此字段为可选
      type: Directory

注意: FileOrCreate 模式不会负责创建文件的父目录。 如果欲挂载的文件的父目录不存在,Pod 启动会失败。 为了确保这种模式能够工作,可以尝试把文件和它对应的目录分开挂载,如 FileOrCreate 配置所示。

hostPath FileOrCreate 配置示例

apiVersion: v1
kind: Pod
metadata:
  name: test-webserver
spec:
  containers:
  - name: test-webserver
    image: k8s.gcr.io/test-webserver:latest
    volumeMounts:
    - mountPath: /var/local/aaa
      name: mydir
    - mountPath: /var/local/aaa/1.txt
      name: myfile
  volumes:
  - name: mydir
    hostPath:
      # 确保文件所在目录成功创建。
      path: /var/local/aaa
      type: DirectoryOrCreate
  - name: myfile
    hostPath:
      path: /var/local/aaa/1.txt
      type: FileOrCreate

5. local

local 卷所代表的是某个被挂载的本地存储设备,例如磁盘、分区或者目录。

local 卷只能用作静态创建的持久卷。尚不支持动态配置。

hostPath 卷相比,local 卷能够以持久和可移植的方式使用,而无需手动将 Pod 调度到节点。系统通过查看 PersistentVolume 的节点亲和性配置,就能了解卷的节点约束。

然而,local 卷仍然取决于底层节点的可用性,并不适合所有应用程序。 如果节点变得不健康,那么local 卷也将变得不可被 Pod 访问。使用它的 Pod 将不能运行。 使用 local 卷的应用程序必须能够容忍这种可用性的降低,以及因底层磁盘的耐用性特征 而带来的潜在的数据丢失风险。

下面是一个使用 local 卷和 nodeAffinity 的持久卷示例:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - example-node

使用 local 卷时,你需要设置 PersistentVolume 对象的 nodeAffinity 字段。 Kubernetes 调度器使用 PersistentVolume 的 nodeAffinity 信息来将使用 local 卷的 Pod 调度到正确的节点。

PersistentVolume 对象的 volumeMode 字段可被设置为 "Block" (而不是默认值 "Filesystem"),以将 local 卷作为原始块设备暴露出来。

使用 local 卷时,建议创建一个 StorageClass 并将其 volumeBindingMode 设置为 WaitForFirstConsumer。 延迟卷绑定的操作可以确保 Kubernetes 在为 PersistentVolumeClaim 作出绑定决策时, 会评估 Pod 可能具有的其他节点约束,例如:如节点资源需求、节点选择器、Pod 亲和性和 Pod 反亲和性。

你可以在 Kubernetes 之外单独运行静态驱动以改进对 local 卷的生命周期管理。 请注意,此驱动尚不支持动态配置。

说明: 如果不使用外部静态驱动来管理卷的生命周期,用户需要手动清理和删除 local 类型的持久卷。

6. subPath

  1. 同一个pod中多容器挂载同一个卷时提供隔离
  2. 将configMap和secret作为文件挂载到容器中而不覆盖挂载目录下的文件

6.1 同一pod中多容器挂载同一个卷时提供隔离

先创建一个共享资源的 pod 用于 设置subPath的前后对照比较

暂未设置subPath,通过hostPath的方式将文件挂载到了集群节点上

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-test
spec:
  containers:
  - image: busybox
    name: test-c-01
    volumeMounts:
    - mountPath: /opt/test
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - sleep 3000
  - image: busybox
    name: test-c-02
    volumeMounts:
    - mountPath: /opt/test
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - sleep 3000
  volumes:
  - name: shared-volume
    hostPath:
     path: /test
     type: DirectoryOrCreate

演示步骤如图所示:

此时pod 中容器挂载的卷是共享的

宿主机上的挂载情况如下

接下来加入subPath配置,再来查看卷共享的情况

资源文件内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-test
spec:
  containers:
  - image: busybox
    name: test-c-01
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /opt/test
      name: shared-volume
      subPath: c01
    args:
    - /bin/sh
    - -c
    - sleep 3000
  - image: busybox
    name: test-c-02
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /opt/test
      name: shared-volume
      subPath: c02
    args:
    - /bin/sh
    - -c
    - sleep 3000
  volumes:
  - name: shared-volume
    hostPath:
     path: /test02
     type: DirectoryOrCreate

演示步骤如图所示:

此时pod 中容器挂载的卷是不共享的

宿主机上的挂载情况如下

通过查看宿主机上的挂载目录可以判断出,其实是通过制定的subPath的创建了各自的子目录,每个容器都是用各自的subPath,所以实现了挂载的隔离。

6.2 将configMap/secret作为文件挂载到容器中而不覆盖挂载目录下的文件

首先我们run一个普通的nginx作为前后的对照

apiVersion: v1
kind: Pod
metadata:
  name: helloworld
spec:
  containers:
  - image: nginx
    name: helloworld
    imagePullPolicy: IfNotPresent

演示步骤如下:

可以看到 正常的nginx配置文件有很多

接下来 我们通过ConfigMap方式挂载一下nginx.conf文件

  1. 创建一个cm资源

    普普通通,极其简单的一个配置文件

    apiVersion: v1
    data:
      nginx-conf: |
        worker_processes  1;
        events {
          worker_connections  1024;
        }
        http {
          server {
            listen       80;
            location / {
                root   html;
                index  index.html index.htm;
            }
          }
        }
    kind: ConfigMap
    metadata:
      name: conf-nginx
    
  2. 创建nginx-pod

    也是一个很简单的nginx-pod,挂载了上面的cm资源

    apiVersion: v1
    kind: Pod
    metadata:
      name: helloworld
    spec:
      containers:
      - image: nginx
        name: helloworld
        imagePullPolicy: IfNotPresent
        volumeMounts:
          - name: config-vol
            # 这里不能写成 /etc/nginx/nginx.conf 是因为这样写会被解析成nginx.conf文件夹,从而导致启动失败
            mountPath: /etc/nginx
      volumes:
        - name: config-vol
          configMap:
            name: conf-nginx
            items:
              - key: nginx-conf
                path: nginx.conf
    
  3. 演示步骤如下

这时就发现直接通过cm挂载配置文件其实是有问题的,自己想挂载的文件虽然挂载成功了,但是挂载点目录下的其他资源会丢失,这其实是个很大的隐患,因为有的服务少了部分配置文件会导致启动失败。

如果我们既想要挂载cm资源,又不想把挂载点中的其他配置文件丢失掉,这时subPath就有作用了

apiVersion: v1
kind: Pod
metadata:
  name: helloworld
spec:
  containers:
  - image: nginx
    name: helloworld
    imagePullPolicy: IfNotPresent
    volumeMounts:
      - name: config-vol
        # 修改挂载点
        mountPath: /etc/nginx/nginx.conf
        # 添加subPath信息
        subPath: nginx.conf
  volumes:
    - name: config-vol
      configMap:
        name: conf-nginx
        items:
          - key: nginx-conf
            path: nginx.conf

演示步骤如下:

posted @ 2022-01-16 00:55  张铁牛  阅读(629)  评论(0编辑  收藏  举报