Kubernetes 存储卷

我们知道默认情况下容器的数据都是非持久化的,在容器消亡以后数据也跟着丢失,所以 Docker 提供了 Volume 机制以便将数据持久化存储。类似的,Kubernetes 提供了更强大的 Volume 机制和丰富的插件,解决了容器数据持久化和容器间共享数据的问题。

与 Docker 不同,Kubernetes Volume 的生命周期与 Pod 绑定

  • 容器挂掉后 Kubelet 再次重启容器时,Volume 的数据依然还在
  • 而 Pod 删除时,Volume 才会清理。数据是否丢失取决于具体的 Volume 类型,比如 emptyDir 的数据会丢失,而 PV 的数据则不会丢

Volume 类型

目前,Kubernetes 支持以下 Volume 类型:

  • emptyDir
  • hostPath
  • gcePersistentDisk
  • awsElasticBlockStore
  • nfs
  • iscsi
  • flocker
  • glusterfs
  • rbd
  • cephfs
  • gitRepo
  • secret
  • persistentVolumeClaim
  • downwardAPI
  • azureFileVolume
  • azureDisk
  • vsphereVolume
  • Quobyte
  • PortworxVolume
  • ScaleIO
  • FlexVolume
  • StorageOS
  • local

注意,这些 volume 并非全部都是持久化的,比如 emptyDir、secret、gitRepo 等,这些 volume 会随着 Pod 的消亡而消失。

API 版本对照表

Kubernetes 版本 Core API 版本
v1.5+ core/v1

emptyDir

如果 Pod 设置了 emptyDir 类型 Volume, Pod 被分配到 Node 上时候,会创建 emptyDir,只要 Pod 运行在 Node 上,emptyDir 都会存在(容器挂掉不会导致 emptyDir 丢失数据),但是如果 Pod 从 Node 上被删除(Pod 被删除,或者 Pod 发生迁移),emptyDir 也会被删除,并且永久丢失。

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: test-pd
  5. spec:
  6. containers:
  7. - image: gcr.io/google_containers/test-webserver
  8. name: test-container
  9. volumeMounts:
  10. - mountPath: /cache
  11. name: cache-volume
  12. volumes:
  13. - name: cache-volume
  14. emptyDir: {}

hostPath

hostPath 允许挂载 Node 上的文件系统到 Pod 里面去。如果 Pod 需要使用 Node 上的文件,可以使用 hostPath。

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: test-pd
  5. spec:
  6. containers:
  7. - image: gcr.io/google_containers/test-webserver
  8. name: test-container
  9. volumeMounts:
  10. - mountPath: /test-pd
  11. name: test-volume
  12. volumes:
  13. - name: test-volume
  14. hostPath:
  15. path: /data

NFS

NFS 是 Network File System 的缩写,即网络文件系统。Kubernetes 中通过简单地配置就可以挂载 NFS 到 Pod 中,而 NFS 中的数据是可以永久保存的,同时 NFS 支持同时写操作。

  1. volumes:
  2. - name: nfs
  3. nfs:
  4. # FIXME: use the right hostname
  5. server: 10.254.234.223
  6. path: "/"

gcePersistentDisk

gcePersistentDisk 可以挂载 GCE 上的永久磁盘到容器,需要 Kubernetes 运行在 GCE 的 VM 中。

  1. volumes:
  2. - name: test-volume
  3. # This GCE PD must already exist.
  4. gcePersistentDisk:
  5. pdName: my-data-disk
  6. fsType: ext4

awsElasticBlockStore

awsElasticBlockStore 可以挂载 AWS 上的 EBS 盘到容器,需要 Kubernetes 运行在 AWS 的 EC2 上。

  1. volumes:
  2. - name: test-volume
  3. # This AWS EBS volume must already exist.
  4. awsElasticBlockStore:
  5. volumeID: <volume-id>
  6. fsType: ext4

gitRepo

gitRepo volume 将 git 代码下拉到指定的容器路径中

  1. volumes:
  2. - name: git-volume
  3. gitRepo:
  4. repository: "git@somewhere:me/my-git-repository.git"
  5. revision: "22f1d8406d464b0c0874075539c1f2e96c253775"

使用 subPath

Pod 的多个容器使用同一个 Volume 时,subPath 非常有用

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: my-lamp-site
  5. spec:
  6. containers:
  7. - name: mysql
  8. image: mysql
  9. volumeMounts:
  10. - mountPath: /var/lib/mysql
  11. name: site-data
  12. subPath: mysql
  13. - name: php
  14. image: php
  15. volumeMounts:
  16. - mountPath: /var/www/html
  17. name: site-data
  18. subPath: html
  19. volumes:
  20. - name: site-data
  21. persistentVolumeClaim:
  22. claimName: my-lamp-site-data

FlexVolume

如果内置的这些 Volume 不满足要求,则可以使用 FlexVolume 实现自己的 Volume 插件。注意要把 volume plugin 放到 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/<vendor~driver>/<driver>,plugin 要实现 init/attach/detach/mount/umount 等命令(可参考 lvm 的 示例)。

  1. - name: test
  2. flexVolume:
  3. driver: "kubernetes.io/lvm"
  4. fsType: "ext4"
  5. options:
  6. volumeID: "vol1"
  7. size: "1000m"
  8. volumegroup: "kube_vg"

Projected Volume

Projected volume 将多个 Volume 源映射到同一个目录中,支持 secret、downwardAPI 和 configMap。

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: volume-test
  5. spec:
  6. containers:
  7. - name: container-test
  8. image: busybox
  9. volumeMounts:
  10. - name: all-in-one
  11. mountPath: "/projected-volume"
  12. readOnly: true
  13. volumes:
  14. - name: all-in-one
  15. projected:
  16. sources:
  17. - secret:
  18. name: mysecret
  19. items:
  20. - key: username
  21. path: my-group/my-username
  22. - downwardAPI:
  23. items:
  24. - path: "labels"
  25. fieldRef:
  26. fieldPath: metadata.labels
  27. - path: "cpu_limit"
  28. resourceFieldRef:
  29. containerName: container-test
  30. resource: limits.cpu
  31. - configMap:
  32. name: myconfigmap
  33. items:
  34. - key: config
  35. path: my-group/my-config

本地存储限额

v1.7 + 支持对基于本地存储(如 hostPath, emptyDir, gitRepo 等)的容量进行调度限额,可以通过 --feature-gates=LocalStorageCapacityIsolation=true 来开启这个特性。

为了支持这个特性,Kubernetes 将本地存储分为两类

  • storage.kubernetes.io/overlay,即 /var/lib/docker 的大小
  • storage.kubernetes.io/scratch,即 /var/lib/kubelet 的大小

Kubernetes 根据 storage.kubernetes.io/scratch 的大小来调度本地存储空间,而根据 storage.kubernetes.io/overlay 来调度容器的存储。比如

为容器请求 64MB 的可写层存储空间

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: ls1
  5. spec:
  6. restartPolicy: Never
  7. containers:
  8. - name: hello
  9. image: busybox
  10. command: ["df"]
  11. resources:
  12. requests:
  13. storage.kubernetes.io/overlay: 64Mi

为 empty 请求 64MB 的存储空间

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: ls1
  5. spec:
  6. restartPolicy: Never
  7. containers:
  8. - name: hello
  9. image: busybox
  10. command: ["df"]
  11. volumeMounts:
  12. - name: data
  13. mountPath: /data
  14. volumes:
  15. - name: data
  16. emptyDir:
  17. sizeLimit: 64Mi

Mount 传递

在 Kubernetes 中,Volume Mount 默认是 私有的,但从 v1.8 开始,Kubernetes 支持配置 Mount 传递(mountPropagation)。它支持两种选项

  • HostToContainer:这是开启 MountPropagation=true 时的默认模式,等效于 rslave 模式,即容器可以看到 Host 上面在该 volume 内的任何新 Mount 操作
  • Bidirectional:等效于 rshared 模式,即 Host 和容器都可以看到对方在该 Volume 内的任何新 Mount 操作。该模式要求容器必须运行在特权模式(即 securityContext.privileged=true

注意:

  • 使用 Mount 传递需要开启 --feature-gates=MountPropagation=true
  • rslavershared 的说明可以参考 内核文档

Volume 快照

v1.8 新增了 pre-alpha 版本的 Volume 快照,但还只是一个雏形,并且其实现不在 Kubernetes 核心代码中,而是存放在 kubernetes-incubator/external-storage 中。

TODO: 补充 Volume 快照的设计原理和示例。

Windows Volume

Windows 容器暂时只支持 local、emptyDir、hostPath、AzureDisk、AzureFile 以及 flexvolume。注意 Volume 的路径格式需要为 mountPath: "C:\\etc\\foo" 或者 mountPath: "C:/etc/foo"

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: hostpath-pod
  5. spec:
  6. containers:
  7. - name: hostpath-nano
  8. image: microsoft/nanoserver:1709
  9. stdin: true
  10. tty: true
  11. volumeMounts:
  12. - name: blah
  13. mountPath: "C:\\etc\\foo"
  14. readOnly: true
  15. nodeSelector:
  16. beta.kubernetes.io/os: windows
  17. volumes:
  18. - name: blah
  19. hostPath:
  20. path: "C:\\AzureData"
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: empty-dir-pod
  5. spec:
  6. containers:
  7. - image: microsoft/nanoserver:1709
  8. name: empty-dir-nano
  9. stdin: true
  10. tty: true
  11. volumeMounts:
  12. - mountPath: /cache
  13. name: cache-volume
  14. - mountPath: C:/scratch
  15. name: scratch-volume
  16. volumes:
  17. - name: cache-volume
  18. emptyDir: {}
  19. - name: scratch-volume
  20. emptyDir: {}
  21. nodeSelector:
  22. beta.kubernetes.io/os: windows

挂载传播

挂载传播(MountPropagation)是 v1.9 引入的新功能,并在 v1.10 中升级为 Beta 版本。挂载传播用来解决同一个 Volume 在不同的容器甚至是 Pod 之间挂载的问题。通过设置 `Container.volumeMounts.mountPropagation),可以为该存储卷设置不同的传播类型。

支持三种选项:

  • None:即私有挂载(private)
  • HostToContainer:即 Host 内在该目录中的新挂载都可以在容器中看到,等价于 Linux 内核的 rslave。
  • Bidirectional:即 Host 内在该目录中的新挂载都可以在容器中看到,同样容器内在该目录中的任何新挂载也都可以在 Host 中看到,等价于 Linux 内核的 rshared。仅特权容器(privileged)可以使用 Bidirectional 类型。

注意:

  • 使用前需要开启 MountPropagation 特性
  • 如未设置,则 v1.9 和 v1.10 中默认为私有挂载(None),而 v1.11 中默认为 HostToContainer
  • Docker 服务的 systemd 配置文件中需要设置 MountFlags=shared

其他的 Volume 参考示例

https://github.com/kubernetes/examples/tree/master/staging/volumes/iscsi