Kubernetes 存储卷Pod 中的容器提供了一种通过文件系统访问和共享数据的方法。您可以根据不同用途使用多种不同类型的存储卷,例如:

  • 基于 ConfigMapSecret 填充配置文件
  • 为 Pod 提供临时的暂存空间
  • 在同一个 Pod 中的两个不同容器之间共享文件系统
  • 在两个不同的 Pod 之间共享文件系统(即使这些 Pod 运行在不同的节点上)
  • 持久化存储数据,确保即使在 Pod 重启或被替换后数据依然可用
  • 将配置信息传递给运行在容器中的应用,这些配置基于容器所在的 Pod 的详细信息(例如:告知 边车容器 (sidecar container) 该 Pod 正在哪个命名空间中运行)
  • 提供对不同容器镜像中数据的只读访问

数据共享可以在容器内的不同本地进程之间、不同容器之间或不同 Pod 之间进行。

为什么存储卷很重要

  • 数据持久性: 容器中的磁盘文件是临时的,这给在容器中运行的非简单应用带来了一些问题。一个问题发生在容器崩溃或停止时;容器状态不会被保存,因此在容器生命周期内创建或修改的所有文件都会丢失。崩溃后,kubelet 会以干净的状态重启容器。

  • 共享存储: 另一个问题发生在多个容器运行在同一个 Pod 中并需要共享文件时。在所有容器中设置和访问共享文件系统可能具有挑战性。

Kubernetes 的 存储卷 (volume) 抽象可以帮助您解决这两个问题。

在学习存储卷、PersistentVolumes (持久卷) 和 PersistentVolumeClaims (持久卷声明) 之前,您应该先阅读有关 Pods 的内容,并确保了解 Kubernetes 如何使用 Pod 来运行容器。

存储卷的工作原理

Kubernetes 支持多种类型的存储卷。一个 Pod 可以同时使用任意数量的存储卷类型。临时卷 (Ephemeral volume) 的生命周期与特定的 Pod 相关联,但 持久卷 (persistent volumes) 的存在独立于任何单个 Pod 的生命周期。当 Pod 不再存在时,Kubernetes 会销毁临时卷;但是,Kubernetes 不会销毁持久卷。对于给定 Pod 中的任何类型的存储卷,数据在容器重启期间都会得到保留。

从核心上讲,存储卷是一个目录,可能包含一些数据,可供 Pod 中的容器访问。该目录是如何产生的、支撑它的媒介是什么,以及它的内容是什么,由所使用的特定存储卷类型决定。

要使用存储卷,请在 .spec.volumes 中指定要为 Pod 提供的存储卷,并在 .spec.containers[*].volumeMounts 中声明将这些卷挂载到容器中的位置。

当 Pod 启动时,容器中的进程会看到一个文件系统视图,该视图由 容器镜像 的初始内容以及挂载在容器内的存储卷(如果已定义)组合而成。进程看到的根文件系统最初与容器镜像的内容一致。如果允许,对该文件系统层次结构内的任何写入操作,都会影响该进程在执行后续文件系统访问时所看到的内容。存储卷被挂载在容器文件系统内的 指定路径。对于 Pod 内定义的每个容器,您必须独立指定容器使用的每个存储卷的挂载位置。

存储卷不能挂载在其他存储卷内(但请参阅 使用 subPath 以了解相关机制)。此外,存储卷不能包含指向不同存储卷中任何内容的硬链接。

存储卷的类型

Kubernetes 支持多种类型的存储卷。

configMap

ConfigMap 提供了一种将配置数据注入 Pod 的方法。存储在 ConfigMap 中的数据可以在 configMap 类型的存储卷中引用,然后由运行在 Pod 中的容器化应用使用。

在引用 ConfigMap 时,您需要在存储卷中提供 ConfigMap 的名称。您可以自定义用于 ConfigMap 中特定条目的路径。以下配置展示了如何将 log-config ConfigMap 挂载到名为 configmap-pod 的 Pod 上:

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox:1.28
      command: ['sh', '-c', 'echo "The app is running!" && tail -f /dev/null']
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: log-config
        items:
          - key: log_level
            path: log_level.conf

log-config ConfigMap 被挂载为一个存储卷,其 log_level 条目中存储的所有内容都被挂载到 Pod 的 /etc/config/log_level.conf 路径下。请注意,此路径派生自存储卷的 mountPath 以及键为 log_levelpath

说明

  • 在使用 ConfigMap 之前,您必须先 创建它

  • ConfigMap 始终以 readOnly(只读)方式挂载。

  • 使用 ConfigMap 作为 subPath 存储卷挂载的容器在 ConfigMap 发生变更时不会收到更新。

  • 文本数据使用 UTF-8 字符编码作为文件公开。对于其他字符编码,请使用 binaryData

downwardAPI

downwardAPI 存储卷使得 向下 API (downward API) 数据可供应用程序使用。在存储卷内,您可以以纯文本格式的只读文件形式找到所公开的数据。

说明

使用向下 API 作为 subPath 存储卷挂载的容器在字段值发生变化时不会收到更新。

请参阅 通过文件向容器公开 Pod 信息 以了解更多信息。

emptyDir

对于定义了 emptyDir 存储卷的 Pod,该卷会在 Pod 被分配到节点时创建。顾名思义,emptyDir 存储卷最初是空的。Pod 中的所有容器都可以读取和写入 emptyDir 存储卷中的相同文件,尽管该存储卷可以在每个容器中挂载到相同或不同的路径。当 Pod 由于任何原因从节点中移除时,emptyDir 中的数据会被永久删除。

说明

容器崩溃并不会从节点上移除 Pod。emptyDir 存储卷中的数据在容器崩溃后是安全的。

emptyDir 的一些用途包括:

  • 暂存空间,例如用于基于磁盘的归并排序
  • 对长时间运行的计算进行检查点设置,以便从崩溃中恢复
  • 保存内容管理器容器提取的文件,同时 Web 服务器容器提供这些数据

emptyDir.medium 字段控制 emptyDir 存储卷的存储位置。默认情况下,emptyDir 存储卷存储在支持节点的任何媒介上,例如磁盘、SSD 或网络存储,具体取决于您的环境。如果将 emptyDir.medium 字段设置为 "Memory",Kubernetes 会为您挂载 tmpfs(基于 RAM 的文件系统)。虽然 tmpfs 非常快,但请注意,与磁盘不同,您写入的文件会占用写入该文件的容器的内存限制。

可以为默认媒介指定大小限制,这会限制 emptyDir 存储卷的容量。存储空间是从 节点临时存储 分配的。如果该空间被其他源(例如日志文件或镜像叠加层)填满,emptyDir 可能会在此限制之前耗尽容量。如果未指定大小,基于内存的存储卷大小将设为节点可分配内存的大小。

注意

请检查 此处 以了解使用基于内存的 emptyDir 时资源管理方面的注意事项。

emptyDir 配置示例

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: registry.k8s.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: 500Mi

emptyDir 内存配置示例

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: registry.k8s.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: 500Mi
      medium: Memory

fc (光纤通道)

fc 存储卷类型允许将现有的光纤通道块存储卷挂载到 Pod 中。您可以使用 Volume 配置中的 targetWWNs 参数指定单个或多个目标全球名称 (WWN)。如果指定了多个 WWN,targetWWNs 期望这些 WWN 来自多路径连接。

说明

您必须预先配置 FC SAN 分区以将这些 LUN(卷)分配并掩码到目标 WWN,以便 Kubernetes 主机能够访问它们。

gcePersistentDisk (已弃用)

在 Kubernetes 1.36 中,树内(in-tree)gcePersistentDisk 类型的所有操作都会被重定向到 pd.csi.storage.gke.io CSI 驱动程序。

树内 gcePersistentDisk 存储驱动程序在 Kubernetes v1.17 版本中被弃用,并在 v1.28 版本中被彻底移除。

Kubernetes 项目建议您改用 Google Compute Engine Persistent Disk CSI 第三方存储驱动程序。

gitRepo (已禁用)

警告

Kubernetes 1.36 包含 gitRepo 存储卷驱动程序。提供此驱动程序的最后一个版本是 Kubernetes v1.35,并且自 v1.11 小版本以来它一直处于弃用状态。

要配置挂载 Git 仓库的 Pod,您可以将 emptyDir 存储卷挂载到使用 Git 克隆仓库的 初始化容器 (init container) 中,然后将该 EmptyDir 挂载到 Pod 的容器中。


您可以使用 策略(例如 ValidatingAdmissionPolicy)限制集群中 gitRepo 存储卷的使用。您可以将以下通用表达式语言 (CEL) 表达式作为策略的一部分,以拒绝 gitRepo 存储卷的使用:

!has(object.spec.volumes) || !object.spec.volumes.exists(v, has(v.gitRepo))

hostPath

hostPath 存储卷将宿主节点文件系统上的文件或目录挂载到您的 Pod 中。大多数 Pod 不需要此功能,但它为某些应用程序提供了强大的逃生舱口。

警告

使用 hostPath 存储卷存在许多安全风险。如果可以避免使用 hostPath 存储卷,应该尽量避免。例如,定义一个 local PersistentVolume 并改用它。

如果您通过准入时验证来限制对节点上特定目录的访问,那么只有在您同时要求任何 hostPath 存储卷的挂载必须为只读时,该限制才有效。如果您允许不受信任的 Pod 对任何主机路径进行读写挂载,该 Pod 中的容器可能会绕过主机读写挂载的限制。


无论 hostPath 存储卷是作为只读挂载还是读写挂载,使用时都请务必小心,因为:

  • 对宿主机文件系统的访问可能会暴露特权系统凭据(例如 kubelet 的凭据)或特权 API(例如容器运行时 socket),这些凭据或 API 可被用于容器逃逸或攻击集群的其他部分。
  • 由于节点上的文件不同,配置相同(例如从 PodTemplate 创建)的 Pod 在不同的节点上可能会表现出不同的行为。
  • hostPath 存储卷的使用不会被视为临时存储的使用。您需要自行监控磁盘使用情况,因为过度的 hostPath 磁盘使用会导致节点面临磁盘压力。

hostPath 的一些用途包括:

  • 运行需要访问节点级系统组件的容器(例如,通过只读挂载 /var/log 访问日志来将系统日志传输到集中位置的容器)
  • 使存储在主机系统上的配置文件以只读方式供 静态 Pod (static Pod) 使用;与普通 Pod 不同,静态 Pod 无法访问 ConfigMap

hostPath 存储卷类型

除了必需的 path 属性外,您还可以选择性地为 hostPath 存储卷指定 type

type 的可用值包括:

行为
‌""空字符串(默认)表示向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate如果给定的路径下什么都不存在,则会根据需要创建一个空的目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。
Directory给定的路径下必须存在一个目录。
FileOrCreate如果给定的路径下什么都不存在,则会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。
文件给定的路径下必须存在一个文件。
Socket给定的路径下必须存在一个 UNIX socket。
CharDevice(仅限 Linux 节点) 给定的路径下必须存在一个字符设备。
BlockDevice(仅限 Linux 节点) 给定的路径下必须存在一个块设备。

注意

FileOrCreate 模式不会创建文件的父目录。如果挂载文件的父目录不存在,Pod 将无法启动。为确保此模式有效,您可以尝试分别挂载目录和文件,如 hostPathFileOrCreate 示例 所示。

在底层主机上创建的某些文件或目录可能只能由 root 用户访问。在这种情况下,您需要以 特权容器 身份运行进程,或者修改主机上的文件权限以从 hostPath 存储卷进行读取或写入。

hostPath 配置示例


---
# This manifest mounts /data/foo on the host as /foo inside the
# single container that runs within the hostpath-example-linux Pod.
#
# The mount into the container is read-only.
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-example-linux
spec:
  os: { name: linux }
  nodeSelector:
    kubernetes.io/os: linux
  containers:
  - name: example-container
    image: registry.k8s.io/test-webserver
    volumeMounts:
    - mountPath: /foo
      name: example-volume
      readOnly: true
  volumes:
  - name: example-volume
    # mount /data/foo, but only if that directory already exists
    hostPath:
      path: /data/foo # directory location on host
      type: Directory # this field is optional


---
# This manifest mounts C:\Data\foo on the host as C:\foo, inside the
# single container that runs within the hostpath-example-windows Pod.
#
# The mount into the container is read-only.
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-example-windows
spec:
  os: { name: windows }
  nodeSelector:
    kubernetes.io/os: windows
  containers:
  - name: example-container
    image: microsoft/windowsservercore:1709
    volumeMounts:
    - name: example-volume
      mountPath: "C:\\foo"
      readOnly: true
  volumes:
    # mount C:\Data\foo from the host, but only if that directory already exists
  - name: example-volume
    hostPath:
      path: "C:\\Data\\foo" # directory location on host
      type: Directory       # this field is optional

hostPath FileOrCreate 配置示例

以下清单定义了一个将 /var/local/aaa 挂载到 Pod 中单个容器里的 Pod。如果节点上尚不存在 /var/local/aaa 路径,kubelet 会将其创建为目录,然后将其挂载到 Pod 中。

如果 /var/local/aaa 已经存在但不是目录,则 Pod 会失败。此外,kubelet 会尝试在该目录内(从主机角度看)创建一个名为 /var/local/aaa/1.txt 的文件;如果该路径下已存在内容且不是常规文件,则 Pod 会失败。

示例清单如下:

apiVersion: v1
kind: Pod
metadata:
  name: test-webserver
spec:
  os: { name: linux }
  nodeSelector:
    kubernetes.io/os: linux
  containers:
  - name: test-webserver
    image: registry.k8s.io/test-webserver:latest
    volumeMounts:
    - mountPath: /var/local/aaa
      name: mydir
    - mountPath: /var/local/aaa/1.txt
      name: myfile
  volumes:
  - name: mydir
    hostPath:
      # Ensure the file directory is created.
      path: /var/local/aaa
      type: DirectoryOrCreate
  - name: myfile
    hostPath:
      path: /var/local/aaa/1.txt
      type: FileOrCreate

image

特性状态: Kubernetes v1.36 [稳定](默认启用)

image 存储卷源代表一个 OCI 对象(容器镜像或制品),该对象在 kubelet 的宿主机上可用。

使用 image 存储卷源的示例如下:

apiVersion: v1
kind: Pod
metadata:
  name: image-volume
spec:
  containers:
  - name: shell
    command: ["sleep", "infinity"]
    image: debian
    volumeMounts:
    - name: volume
      mountPath: /volume
  volumes:
  - name: volume
    image:
      reference: quay.io/crio/artifact:v2
      pullPolicy: IfNotPresent

存储卷在 Pod 启动时进行解析,具体取决于提供的 pullPolicy 值:

Always
kubelet 始终尝试拉取该引用。如果拉取失败,kubelet 会将 Pod 设置为 Failed
永不
kubelet 从不拉取该引用,仅使用本地镜像或制品。如果镜像的任何层尚未存在于本地,或者该镜像的清单尚未缓存,Pod 将变为 Failed
IfNotPresent
如果引用尚未存在于磁盘上,kubelet 会进行拉取。如果引用不存在且拉取失败,Pod 将变为 Failed

如果 Pod 被删除并重新创建,存储卷会重新解析,这意味着新的远程内容将在 Pod 重新创建时变得可用。在 Pod 启动期间解析或拉取镜像失败将阻止容器启动,并可能增加显著的延迟。失败将使用正常的存储卷回退机制进行重试,并将在 Pod 的原因 (reason) 和消息 (message) 中报告。

该存储卷可以挂载的对象类型由宿主机上的容器运行时实现定义。至少,它们必须包含容器镜像字段支持的所有有效类型。OCI 对象被挂载在一个单一的目录中 (spec.containers[*].volumeMounts[*].mountPath) 并且将以只读方式挂载。

除此之外:

  • 容器的 subPathsubPathExpr 挂载 (spec.containers[*].volumeMounts[*].subPath, spec.containers[*].volumeMounts[*].subPathExpr) 仅从 Kubernetes v1.33 开始支持。
  • 字段 spec.securityContext.fsGroupChangePolicy 对此存储卷类型无效。
  • AlwaysPullImages 准入控制器 同样适用于此存储卷源,就像适用于容器镜像一样。

对于 image 类型,可以使用以下字段:

reference
要使用的制品引用。例如,您可以指定 registry.k8s.io/conformance:v1.36.0 以从 Kubernetes 一致性测试镜像中加载文件。其行为方式与 pod.spec.containers[*].image 相同。拉取密钥将以与容器镜像相同的方式组装,即通过查找节点凭据、服务帐户镜像拉取密钥以及 Pod 规范镜像拉取密钥。此字段是可选的,允许更高级别的配置管理工具为工作负载控制器(如 Deployments 和 StatefulSets)中的容器镜像设置默认值或覆盖它们。关于容器镜像的更多信息
pullPolicy
拉取 OCI 对象的策略。可能的值为:AlwaysNeverIfNotPresent。如果指定了 :latest 标签,则默认为 Always;否则默认为 IfNotPresent

请参阅 在 Pod 中使用镜像卷 示例以获取有关如何使用该存储卷源的更多详细信息。

iscsi

iscsi 存储卷允许将现有的 iSCSI (SCSI over IP) 存储卷挂载到您的 Pod 中。与在 Pod 被删除时会被擦除的 emptyDir 不同,iscsi 存储卷的内容会得到保留,且该卷仅会被卸载。这意味着 iSCSI 存储卷可以预先填充数据,并且该数据可以在 Pod 之间共享。

说明

在能够使用它之前,您必须运行自己的 iSCSI 服务器并创建好存储卷。

iSCSI 的一个特性是它可以由多个使用者同时以只读方式挂载。这意味着您可以预先填充带有数据集的存储卷,然后从您需要的任意多个 Pod 中并行提供服务。遗憾的是,iSCSI 存储卷只能由单个使用者以读写模式挂载。不允许同时写入。

local

local 存储卷代表一个已挂载的本地存储设备,例如磁盘、分区或目录。

本地存储卷只能用作静态创建的 PersistentVolume。不支持动态配置。

hostPath 存储卷相比,local 存储卷以一种持久且可移植的方式使用,而无需手动将 Pod 调度到节点上。系统通过查看 PersistentVolume 上的节点亲和性(node affinity)来知晓存储卷的节点约束。

然而,local 存储卷受底层节点可用性的限制,并不适合所有应用程序。如果节点变得不健康,则 local 存储卷对 Pod 而言将变得不可访问。使用该存储卷的 Pod 将无法运行。使用 local 存储卷的应用程序必须能够容忍这种可用性降低,以及潜在的数据丢失(取决于底层磁盘的耐用性特性)。

以下示例展示了一个使用 local 存储卷和 nodeAffinity 的 PersistentVolume:

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 将这些 Pod 调度到正确的节点。

PersistentVolume 的 volumeMode 可以设置为 "Block"(而不是默认值 "Filesystem"),以将本地存储卷作为裸块设备公开。

使用本地存储卷时,建议创建一个将 volumeBindingMode 设置为 WaitForFirstConsumer 的 StorageClass。有关详细信息,请参见本地 StorageClass 示例。延迟存储卷绑定可确保 PersistentVolumeClaim 绑定决策也会根据 Pod 可能具有的任何其他节点约束(例如节点资源需求、节点选择器、Pod 亲和性和 Pod 反亲和性)进行评估。

为了改善本地存储卷生命周期的管理,可以单独运行外部静态配置器 (external static provisioner)。请注意,此配置器尚不支持动态配置。有关如何运行外部本地配置器的示例,请参见 本地存储卷配置器用户指南

说明

如果未使用外部静态配置器来管理存储卷生命周期,则本地 PersistentVolume 需要用户进行手动清理和删除。

nfs

nfs 存储卷允许将现有的 NFS (网络文件系统) 共享挂载到 Pod 中。与在 Pod 被删除时会被擦除的 emptyDir 不同,nfs 存储卷的内容会得到保留,且该卷仅会被卸载。这意味着 NFS 存储卷可以预先填充数据,并且该数据可以在 Pod 之间共享。NFS 可以由多个写入者同时挂载。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: registry.k8s.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /my-nfs-data
      name: test-volume
  volumes:
  - name: test-volume
    nfs:
      server: my-nfs-server.example.com
      path: /my-nfs-volume
      readOnly: true

说明

您必须运行自己的 NFS 服务器并导出共享,然后才能使用它。

另请注意,您不能在 Pod 规范中指定 NFS 挂载选项。您可以设置服务器端的挂载选项或使用 /etc/nfsmount.conf。您也可以通过 PersistentVolumes 挂载 NFS 存储卷,这允许您设置挂载选项。

persistentVolumeClaim

persistentVolumeClaim 存储卷用于将 PersistentVolume 挂载到 Pod 中。PersistentVolumeClaims 是用户“声明”持久存储(例如 iSCSI 存储卷)的一种方式,而无需知道特定云环境的详细信息。

有关更多详细信息,请参阅关于 PersistentVolumes 的信息。

portworxVolume (已弃用)

特性状态: Kubernetes v1.25 [弃用]

portworxVolume 是一种弹性块存储层,与 Kubernetes 超融合运行。Portworx 对服务器中的存储进行指纹识别,根据能力进行分层,并聚合跨多个服务器的容量。Portworx 以客户机身份在虚拟机或裸金属 Linux 节点上运行。

portworxVolume 可以通过 Kubernetes 动态创建,也可以预先配置并在 Pod 内引用。以下是引用预配置 Portworx 存储卷的示例 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: test-portworx-volume-pod
spec:
  containers:
  - image: registry.k8s.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /mnt
      name: pxvol
  volumes:
  - name: pxvol
    # This Portworx volume must already exist.
    portworxVolume:
      volumeID: "pxvol"
      fsType: "<fs-type>"

说明

在使用 PortworxVolume 之前,请确保您拥有一个名为 pxvol 的现有 PortworxVolume。

Portworx CSI 迁移

特性状态: Kubernetes v1.33 [稳定](默认启用)

在 Kubernetes 1.36 中,树内 Portworx 存储卷的所有操作默认重定向到 pxd.portworx.com 容器存储接口 (CSI) 驱动程序。
必须在集群上安装 Portworx CSI 驱动程序

projected

映射卷 (projected volume) 将多个现有的存储卷源映射到同一个目录中。有关详细信息,请参阅 映射卷 (projected volumes)

secret

secret 存储卷用于将敏感信息(例如密码)传递给 Pod。您可以将密钥存储在 Kubernetes API 中,并将其挂载为文件供 Pod 使用,而无需直接与 Kubernetes 耦合。secret 存储卷由 tmpfs(基于 RAM 的文件系统)支持,因此它们永远不会被写入非易失性存储。

说明

  • 在使用 Secret 之前,您必须在 Kubernetes API 中创建一个 Secret。

  • Secret 始终以 readOnly(只读)方式挂载。

  • 使用 Secret 作为 subPath 存储卷挂载的容器不会收到 Secret 的更新。

有关更多详细信息,请参阅 配置密钥 (Secrets)

使用 subPath

有时,在单个 Pod 中将一个存储卷用于多种用途是很有用的。volumeMounts[*].subPath 属性指定了引用存储卷内的子路径,而不是其根目录。

以下示例展示了如何配置一个使用单个共享存储卷的 LAMP 堆栈(Linux、Apache、MySQL、PHP)Pod。不建议在生产环境中使用此 subPath 配置示例。

PHP 应用的代码和资产映射到存储卷的 html 文件夹,MySQL 数据库存储在存储卷的 mysql 文件夹中。例如:

apiVersion: v1
kind: Pod
metadata:
  name: my-lamp-site
spec:
    containers:
    - name: mysql
      image: mysql
      env:
      - name: MYSQL_ROOT_PASSWORD
        value: "rootpasswd"
      volumeMounts:
      - mountPath: /var/lib/mysql
        name: site-data
        subPath: mysql
    - name: php
      image: php:7.0-apache
      volumeMounts:
      - mountPath: /var/www/html
        name: site-data
        subPath: html
    volumes:
    - name: site-data
      persistentVolumeClaim:
        claimName: my-lamp-site-data

使用包含展开环境变量的 subPath

功能状态: Kubernetes v1.17 [稳定]

使用 subPathExpr 字段可以从向下 API 的环境变量中构建 subPath 目录名称。subPathsubPathExpr 属性是互斥的。

在此示例中,Pod 使用 subPathExprhostPath 存储卷 /var/log/pods 内创建一个目录 pod1hostPath 存储卷从 downwardAPI 获取 Pod 名称。主机目录 /var/log/pods/pod1 被挂载到容器内的 /logs

apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  containers:
  - name: container1
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    image: busybox:1.28
    command: [ "sh", "-c", "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ]
    volumeMounts:
    - name: workdir1
      mountPath: /logs
      # The variable expansion uses round brackets (not curly brackets).
      subPathExpr: $(POD_NAME)
  restartPolicy: Never
  volumes:
  - name: workdir1
    hostPath:
      path: /var/log/pods

资源

emptyDir 存储卷的存储媒介(如磁盘或 SSD)由持有 kubelet 根目录(通常为 /var/lib/kubelet)的文件系统的媒介决定。对于 emptyDirhostPath 存储卷可以消耗多少空间没有限制,容器或 Pod 之间也没有隔离。

要了解如何使用资源规范请求空间,请参阅 如何管理资源

树外(Out-of-tree)存储卷插件

树外存储卷插件包括 容器存储接口 (CSI) 以及 FlexVolume(已弃用)。这些插件使存储供应商能够创建自定义存储插件,而无需将其插件源代码添加到 Kubernetes 仓库中。

以前,所有的存储卷插件都是“树内 (in-tree)”的。“树内”插件随核心 Kubernetes 二进制文件一起构建、链接、编译和发布。这意味着将新的存储系统添加到 Kubernetes(一个存储卷插件)需要将代码检入核心 Kubernetes 代码仓库。

CSI 和 FlexVolume 都允许独立于 Kubernetes 代码库开发存储卷插件,并作为扩展部署(安装)到 Kubernetes 集群上。

对于希望创建树外存储卷插件的存储供应商,请参考 存储卷插件 FAQ

csi

容器存储接口 (CSI) 定义了一个标准接口,用于容器编排系统(如 Kubernetes)将任意存储系统公开给其容器工作负载。

请阅读 CSI 设计建议 以获取更多信息。

说明

对 CSI 规范版本 0.2 和 0.3 的支持在 Kubernetes v1.13 中被弃用,并将在未来版本中移除。

说明

CSI 驱动程序可能无法在所有 Kubernetes 版本之间兼容。请检查特定 CSI 驱动程序的文档,了解每个 Kubernetes 版本的支持部署步骤和兼容性矩阵。

一旦 CSI 兼容的存储卷驱动程序部署到 Kubernetes 集群上,用户就可以使用 csi 存储卷类型来挂载或附加 CSI 驱动程序公开的存储卷。

csi 存储卷可以通过三种不同的方式在 Pod 中使用:

存储管理员可以使用以下字段来配置 CSI 持久卷:

  • driver:一个字符串值,指定要使用的存储卷驱动程序的名称。此值必须对应于 CSI 驱动程序在 CSI 规范(定义在 CSI 规范)中定义的 GetPluginInfoResponse 中返回的值。它被 Kubernetes 用来识别要调用的 CSI 驱动程序,也被 CSI 驱动程序组件用来识别哪些 PV 对象属于该 CSI 驱动程序。
  • volumeHandle:一个唯一标识存储卷的字符串值。此值必须对应于 CSI 驱动程序在 CSI 规范(定义在 CSI 规范)中定义的 CreateVolumeResponsevolume.id 字段中返回的值。在引用该存储卷时,该值作为 volume_id 传递给所有对 CSI 存储卷驱动程序的调用。
  • readOnly:一个可选的布尔值,指示存储卷是否应以只读方式进行“ControllerPublished”(附加)。默认为 false。此值通过 ControllerPublishVolumeRequest 中的 readonly 字段传递给 CSI 驱动程序。
  • fsType:如果 PV 的 VolumeModeFilesystem,则此字段可用于指定挂载存储卷时应使用的文件系统。如果该卷尚未格式化且支持格式化,则该值将用于格式化该卷。此值通过 ControllerPublishVolumeRequestNodeStageVolumeRequestNodePublishVolumeRequestVolumeCapability 字段传递给 CSI 驱动程序。
  • volumeAttributes:一个字符串到字符串的映射,指定存储卷的静态属性。此映射必须对应于 CSI 驱动程序在 CSI 规范(定义在 CSI 规范)中定义的 CreateVolumeResponsevolume.attributes 字段中返回的映射。此映射通过 ControllerPublishVolumeRequestNodeStageVolumeRequestNodePublishVolumeRequest 中的 volume_context 字段传递给 CSI 驱动程序。
  • controllerPublishSecretRef:对 secret 对象的引用,该对象包含要传递给 CSI 驱动程序以完成 CSI ControllerPublishVolumeControllerUnpublishVolume 调用的敏感信息。此字段是可选的,如果不需要密钥,可以为空。如果 Secret 包含多个密钥,则所有密钥都会被传递。
  • nodeExpandSecretRef:对包含敏感信息的 secret 的引用,该信息将传递给 CSI 驱动程序以完成 CSI NodeExpandVolume 调用。此字段是可选的,如果不需要密钥,可以为空。如果对象包含多个密钥,则所有密钥都会被传递。当您为节点发起的存储卷扩容配置了 secret 数据时,kubelet 会通过 NodeExpandVolume() 调用将该数据传递给 CSI 驱动程序。所有受支持的 Kubernetes 版本都提供 nodeExpandSecretRef 字段,且默认可用。v1.25 之前的 Kubernetes 版本不包含此支持。
  • 为每个 kube-apiserver 以及每个节点上的 kubelet 启用名为 CSINodeExpandSecret功能门 (feature gate)。自 Kubernetes 1.27 版本以来,此功能已默认启用,无需显式启用该功能门。您还必须使用支持或需要在节点发起的存储重置操作期间使用密钥数据的 CSI 驱动程序。
  • nodePublishSecretRef:对 secret 对象的引用,该对象包含要传递给 CSI 驱动程序以完成 CSI NodePublishVolume 调用的敏感信息。此字段是可选的,如果不需要密钥,可以为空。如果 secret 对象包含多个密钥,则所有密钥都会被传递。
  • nodeStageSecretRef:对 secret 对象的引用,该对象包含要传递给 CSI 驱动程序以完成 CSI NodeStageVolume 调用的敏感信息。此字段是可选的,如果不需要密钥,可以为空。如果 Secret 包含多个密钥,则所有密钥都会被传递。

CSI 裸块存储卷支持

功能状态: Kubernetes v1.18 [稳定]

具有外部 CSI 驱动程序的供应商可以在 Kubernetes 工作负载中实现对裸块存储卷的支持。

您可以像往常一样设置您的 PersistentVolume/PersistentVolumeClaim 并支持裸块存储卷,无需进行任何特定于 CSI 的更改。

CSI 临时卷

功能状态: Kubernetes v1.25 [稳定]

您可以直接在 Pod 规范内配置 CSI 存储卷。以这种方式指定的存储卷是临时的,不会在 Pod 重启后保留。有关更多信息,请参阅 临时卷 (Ephemeral Volumes)

有关如何开发 CSI 驱动程序的更多信息,请参考 kubernetes-csi 文档

Windows CSI 代理

功能状态: Kubernetes v1.22 [稳定]

CSI 节点插件需要执行各种特权操作,例如扫描磁盘设备和挂载文件系统。这些操作对于每个主机操作系统都有所不同。对于 Linux 工作节点,容器化的 CSI 节点插件通常部署为特权容器。对于 Windows 工作节点,容器化 CSI 节点插件的特权操作通过 csi-proxy 得到支持,这是一个由社区管理的独立二进制文件,需要在每个 Windows 节点上预先安装。

有关详细信息,请参考您希望部署的 CSI 插件的部署指南。

从树内插件迁移到 CSI 驱动程序

功能状态: Kubernetes v1.25 [稳定]

CSIMigration 功能将针对现有树内插件的操作重定向到相应的 CSI 插件(这些插件需要已安装和配置)。因此,当过渡到取代树内插件的 CSI 驱动程序时,操作员无需对现有的 StorageClass、PersistentVolume 或 PersistentVolumeClaim(引用树内插件)进行任何配置更改。

说明

即使在针对该存储卷类型完成了 CSI 迁移后,或者在升级到不再支持该类存储编译的 Kubernetes 版本后,由树内存储卷插件创建的现有 PV 仍然可以在未来继续使用,而无需任何配置更改。

作为迁移的一部分,您或其他集群管理员必须安装并配置适用于该存储的 CSI 驱动程序。Kubernetes 核心不会为您安装该软件。


迁移后,您还可以定义引用遗留的、内置存储集成的新 PVC 和 PV。只要您安装并配置了适当的 CSI 驱动程序,PV 的创建就会继续工作,即使对于全新的存储卷也是如此。实际的存储管理现在通过 CSI 驱动程序进行。

支持的操作和功能包括:卷的供应/删除、挂载/卸载以及卷的扩容。

支持 CSIMigration 且已实现相应 CSI 驱动程序的树内插件列在 存储卷类型 中。

flexVolume (已弃用)

特性状态: Kubernetes v1.23 [弃用]

FlexVolume 是一种树外插件接口,它使用基于 exec 的模型与存储驱动程序进行接口。FlexVolume 驱动程序二进制文件必须安装在每个节点上预定义的存储卷插件路径中,在某些情况下,也必须安装在控制平面节点上。

Pod 通过树内 flexVolume 存储卷插件与 FlexVolume 驱动程序进行交互。

以下以 PowerShell 脚本形式部署在主机上的 FlexVolume 插件 支持 Windows 节点:

说明

FlexVolume 已弃用。使用树外 CSI 驱动程序是将外部存储与 Kubernetes 集成的方法。

FlexVolume 驱动程序的维护者应实现 CSI 驱动程序,并帮助将 FlexVolume 驱动程序的用户迁移到 CSI。FlexVolume 的用户应将工作负载移动到使用相应的 CSI 驱动程序。

挂载传播 (Mount propagation)

注意

挂载传播 (Mount propagation) 是一种底层功能,并非在所有存储卷类型上都能一致工作。Kubernetes 项目建议仅将挂载传播与 hostPath 或基于内存的 emptyDir 存储卷一起使用。请参阅 Kubernetes issue #95049 以获取更多上下文。

挂载传播允许共享由容器挂载到 Pod 中其他容器的存储卷,甚至可以共享给同一节点上的其他 Pod。

存储卷的挂载传播由 containers[*].volumeMounts 中的 mountPropagation 字段控制。其值包括:

  • None - 此存储卷挂载不会接收主机挂载到此存储卷或其任何子目录中的任何后续挂载。同样,容器创建的挂载在主机上也不会可见。这是默认模式。

    此模式等同于 mount(8) 中描述的 rprivate 挂载传播。

    然而,当 rprivate 传播不适用时,CRI 运行时可能会选择 rslave 挂载传播(即 HostToContainer)。众所周知,当挂载源包含 Docker 守护进程的根目录 (/var/lib/docker) 时,cri-dockerd (Docker) 会选择 rslave 挂载传播。

  • HostToContainer - 此存储卷挂载将接收所有后续挂载到此存储卷或其任何子目录中的挂载。

    换句话说,如果主机在此存储卷挂载内挂载了任何内容,容器将看到它挂载在那里。

    同样,如果任何具有 Bidirectional 挂载传播到相同存储卷的 Pod 在那里挂载了任何内容,具有 HostToContainer 挂载传播的容器将看到它。

    此模式等同于 mount(8) 中描述的 rslave 挂载传播。

  • Bidirectional - 此存储卷挂载的行为与 HostToContainer 挂载相同。此外,容器创建的所有存储卷挂载都将传播回主机以及使用相同存储卷的所有 Pod 的所有容器。

    此模式的典型用例是带有 FlexVolume 或 CSI 驱动程序的 Pod,或者需要使用 hostPath 存储卷在主机上挂载某些内容的 Pod。

    此模式等同于 mount(8) 中描述的 rshared 挂载传播。

    警告

    Bidirectional 挂载传播可能很危险。它可能会损坏主机操作系统,因此仅允许在特权容器中使用。强烈建议熟悉 Linux 内核行为。此外,容器在 Pod 中创建的任何存储卷挂载都必须在终止时由容器销毁(卸载)。

只读挂载

通过将 .spec.containers[*].volumeMounts[*].readOnly 字段设置为 true,可以将挂载设置为只读。这不会使存储卷本身变为只读,但该特定容器将无法对其进行写入。Pod 中的其他容器可以将相同的存储卷挂载为读写。

在 Linux 上,只读挂载默认不是递归只读的。例如,考虑一个将主机 /mnt 挂载为 hostPath 存储卷的 Pod。如果 /mnt/<SUBMOUNT> 上有另一个以读写方式挂载的文件系统(例如 tmpfs、NFS 或 USB 存储),即使挂载本身被指定为只读,挂载到容器中的存储卷也将拥有一个可写的 /mnt/<SUBMOUNT>

递归只读挂载

特性状态: Kubernetes v1.33 [稳定](默认启用)

可以通过为 kubelet 和 kube-apiserver 设置 RecursiveReadOnlyMounts 功能门 (feature gate),并为 Pod 设置 .spec.containers[*].volumeMounts[*].recursiveReadOnly 字段来启用递归只读挂载。

允许的值为:

  • Disabled(默认):无效果。

  • Enabled:使挂载递归只读。需要满足以下所有要求:

    • readOnly 设置为 true
    • mountPropagation 未设置,或设置为 None
    • 主机运行的是 Linux 内核 v5.12 或更高版本
    • CRI 级别 的容器运行时支持递归只读挂载
    • OCI 级别容器运行时支持递归只读挂载。

    如果其中任何一个不为真,则会失败。

  • IfPossible:尝试应用 Enabled,如果内核或运行时类不支持该功能,则回退到 Disabled

示例

storage/rro.yaml
apiVersion: v1
kind: Pod
metadata:
  name: rro
spec:
  volumes:
    - name: mnt
      hostPath:
        # tmpfs is mounted on /mnt/tmpfs
        path: /mnt
  containers:
    - name: busybox
      image: busybox
      args: ["sleep", "infinity"]
      volumeMounts:
        # /mnt-rro/tmpfs is not writable
        - name: mnt
          mountPath: /mnt-rro
          readOnly: true
          mountPropagation: None
          recursiveReadOnly: Enabled
        # /mnt-ro/tmpfs is writable
        - name: mnt
          mountPath: /mnt-ro
          readOnly: true
        # /mnt-rw/tmpfs is writable
        - name: mnt
          mountPath: /mnt-rw

当 kubelet 和 kube-apiserver 识别到此属性时,.status.containerStatuses[*].volumeMounts[*].recursiveReadOnly 字段将被设置为 EnabledDisabled

实现

注意: 本节链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不对这些项目负责,项目按字母顺序排列。要将项目添加到此列表,请在提交更改之前阅读 内容指南更多信息。

已知以下容器运行时支持递归只读挂载:

CRI 级别

OCI 级别

  • runc,自 v1.1 起
  • crun,自 v1.8.6 起

接下来

请参阅 部署 WordPress 和 MySQL 并使用持久卷 的示例。


最后修改时间:2026 年 4 月 14 日凌晨 1:15(太平洋标准时间):fix(links): update kubernetes/community links from master to main (03c191bcc4)

此页面上的项目涉及提供 Kubernetes 所需功能的第三方产品或项目。Kubernetes 项目作者不对这些第三方产品或项目负责。有关详细信息,请参阅 CNCF 网站指南

在提出添加额外的第三方链接的更改之前,您应该阅读 内容指南