我们都知道 Container
中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用 程序带来一些问题。
Pod
中运行多个容器如何共享文件Kubernetes 卷(Volume)这一抽象概念能够解决这两个问题。
Docker 也有 卷(Volume)的概念,但对它只有少量且松散的管理。 Docker 卷是磁盘上或者另外一个容器内的一个目录。 Docker 提供卷驱动程序,但是其功能非常有限。
Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁 持久卷。对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。
卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放 的内容。
使用卷时, 在 .spec.volumes
字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。 容器中的进程看到的是由它们的 Docker 镜像和卷组成的文件系统视图。 Docker 镜像 位于文件系统层次结构的根部。各个卷则挂载在镜像内的指定路径上。 卷不能挂载到其他卷之上,也不能与其他卷有硬链接。Pod 配置中的每个容器必须独立指定各个卷的挂载位置。
当 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_dir
和 docker run -v /consumer_dir
。通过 docker inspect
查看容器的详细配置信息,我们发现两个容器都 mount 了同一个目录:
$ docker inspect 52305cbb0dec
$ docker inspect c946762ccc8c
这里Source
指定的路径就是 emptyDir 在 Host 上的真正路径。
emptyDir 是 Host 上创建的临时目录,其优点是能够方便地为 Pod 中的容器提供共享存储,不需要额外的配置。但它不具备持久性,如果 Pod 不存在了,emptyDir 也就没有了。根据这个特性,emptyDir 特别适合 Pod 中的容器需要临时共享存储空间的场景,比如前面的生产者消费者用例。
警告:
HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。 当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。
hostPath
卷能将主机节点文件系统上的文件或目录挂载到你的 Pod 中。 虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃生舱。
例如,hostPath
的一些用法有:
hostPath
挂载 /var/lib/docker
路径。hostPath
在运行 Pod 之前是否应该存在,是否应该创建以及应该以什么方式存在。除了必需的 path
属性之外,用户可以选择性地为 hostPath
卷指定 type
。
支持的 type
值如下:
取值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate |
如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。 |
Directory |
在给定路径上必须存在的目录。 |
FileOrCreate |
如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。 |
File |
在给定路径上必须存在的文件。 |
Socket |
在给定路径上必须存在的 UNIX 套接字。 |
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
配置所示。
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
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 类型的持久卷。
先创建一个共享资源的 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
,所以实现了挂载的隔离。
首先我们run一个普通的nginx作为前后的对照
apiVersion: v1 kind: Pod metadata: name: helloworld spec: containers: - image: nginx name: helloworld imagePullPolicy: IfNotPresent
演示步骤如下:
可以看到 正常的nginx配置文件有很多
接下来 我们通过ConfigMap
方式挂载一下nginx.conf
文件
创建一个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
创建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
演示步骤如下
这时就发现直接通过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
演示步骤如下: