K8S引入动态挂载卷,实现工作负载运行时的存储灵活性(Part Ⅰ)

K8S引入动态挂载卷,实现工作负载运行时的存储灵活性(Part Ⅰ)

在 Kubernetes 生态中,工作负载一旦配置了持久卷声明(PVC),通常就无法在运行后更改已挂载的存储。这一限制对于追求高效和灵活性的用户来说,无疑是一个痛点。

想象一下,我们在处理海量数据的任务时,突发的存储需求增加,或者需要接入额外的第三方存储服务。传统的解决方案是需要停止实例,并重新配置存储选项,这种做法不仅耗时,而且会引起服务的暂停。

那么,有没有那么一种机制,能够在不中断业务的情况下,可以随时根据实际需求动态地添加或移除存储资源呢?这正是我们面临的一项重要挑战,也是本文接下来要深入探讨的问题。

本文将深入探讨动态卷供给 的核心机制,并揭示它如何赋予我们在容器运行时动态挂载存储卷 的能力,从而实现真正的应用灵活性。

容器存储挂载的探索

Kubernetes 项目它的本质是一个容器编排工具,所以我们不妨先来看下在容器里能否做到这一点?

挂载类型

Dcoker 它默认支持三种存储挂载类型:

  • bind(–mount type=bind)
  • volume(–mount type=volume)
  • tmpfs(–mount type=tmpfs),

如下图所示:
Volume Binding

其中 tmpfs mounts 类型它将数据存储在宿主机的内存中,而不是磁盘。这意味着数据的读写速度非常快,但是一旦容器停止,数据就会丢失,所以它不属于持久化的范畴,这里我们不需要关注。

而 volumes 类型是由 Docker 自身维护和管理,它是一种更加隔离的存储方式。即便容器被删除,通过 volumes 保存的数据也能得到保留,适用于需要持久化存储数据的应用,它是 Docker 里一个抽象的概念。

最后我们来看下 bind mounts 类型,这是最直接的存储方式,将宿主机上的某个目录或文件直接映射到容器中的指定路径,它是直接与原生文件系统做对接。

配置挂载传播

在 Docker 中,Bind propagation 定义了挂载点在宿主机和容器间的共享行为。这个高级功能适用于复杂的挂载场景,特别是在需要容器间共享挂载的情况下。

以下是 Docker 中挂载传播类型的简要说明:

Propagation Type Description
private 挂载点是私有的。宿主机上的挂载点不会传播到容器,同样容器中的挂载也不会传播到宿主机或其他容器。
rshared 挂载点是共享的。在宿主机上进行的挂载或卸载操作会传播到容器内,同时容器内的挂载或卸载操作也会传播到宿主机及所有使用相同挂载点的容器。
shared 类似于 rshared,但是限制更多。挂载点的传播仅限于在挂载点上明确创建的那些挂载。
slave 挂载点是从属的。宿主机上的挂载会传播到容器内,但容器内的挂载不会传播到宿主机或其他容器。
rslave 类似于 slave,但是传播是递归的,这意味着所有的挂载和卸载操作都会从宿主机传播到容器,但容器内的操作不会反向传播。

要设置挂载传播,我们可以在使用 docker run 命令时,通过 --mount 标志的 bind-propagation 选项来指定,例如:

1
2
3
4
5
docker run -d \
-it \
--name devtest \
--mount type=bind,source="$(pwd)"/target,target=/app,bind-propagation=rshared \
nginx:latest

这将创建一个名为 devtest 的容器,其中宿主机上的 "$(pwd)"/target 目录以 rshared 传播模式挂载到容器内的 /app 目录。这意味着任何在宿主机上对 "$(pwd)"/target 的挂载变更都会传播到容器内,反之亦然。

实践案例

环境准备

通过 MinIO 提供一个 S3 兼容的对象存储,容器通过 FUSE(s3fs)把对象存储的 bucket 挂载到本地目录 /data

💡 小贴士:可以通过下面的终端操作录屏,获取详细步骤的直观演示:

加载较慢,请耐心等待

  1. 快速启动MinIO实例

    1
    2
    3
    4
    5
    6
    7
    8
    docker run -d \
    --name pv-minio \
    -p 9000:9000 \ # API Endpoint
    -p 9001:9001 \ # Console Portal Endpoint
    -e "MINIO_ROOT_USER=admin" \ # Default Username
    -e "MINIO_ROOT_PASSWORD=password" \ # Default Password
    minio/minio server /data \ # 以服务器模式运行,并使用/data作为存储路径
    --console-address ":9001"
  2. 安装MinIO Client (MinIO取消了大部分的界面操作)

1
2
3
4
5
6
7
8
9
10
11
# 从MinIO发布页下载MinIO Client
wget https://dl.minio.org.cn/client/mc/release/linux-amd64/mc
chmod +x mc

# 连接当前MinIO服务,指定别名为local
./mc alias set local {MinIO API Endpoint} {Username} {Password}

# 为local添加新的API Key和API Secret
./mc admin user add local {API KEY} {API Secret}
# 为新API KEY指定权限策略
./mc admin policy attach local readwrite --user={API KEY}
  1. 用MC创建Bucket
1
./mc mb --ignore-existing local/datasets

可以通过9001端口访问MinIO的界面,进行简单操作。

  1. 构建带Geesefs的基础镜像
1
docker build -t ubuntu-with-geesefs:0.0.1 .

DockerFile内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
FROM ubuntu:20.04

# 设置时区和避免交互提示
ENV DEBIAN_FRONTEND=noninteractive

# 安装运行时依赖
RUN apt-get update && apt-get install -y \
fuse3 \
ca-certificates \
wget \
tzdata \
&& rm -rf /var/lib/apt/lists/*

# 下载并安装预编译的 geesefs 二进制文件
RUN wget https://github.com/yandex-cloud/geesefs/releases/latest/download/geesefs-linux-amd64 -O /usr/local/bin/geesefs \
&& chmod +x /usr/local/bin/geesefs

# 创建工作目录
RUN mkdir -p /data

# 设置挂载点
VOLUME /data

# 验证安装
RUN geesefs --version

# 设置入口点
CMD ["/bin/bash"]

基于事件传播的挂载点

通过辅助容器,可以将绑定事件传播到相同挂载点的容器

💡 小贴士:可以通过下面的终端操作录屏,获取详细步骤的直观演示:

加载较慢,请耐心等待

  1. 启动业务容器
    1
    2
    3
    4
    5
    6
    # 业务容器

    docker run -d -it \
    --name pv-user-container \
    -v "$(pwd)"/datasets:/datasets:slave \
    busybox-curl:latest

相信大家也明白,如果仅仅使用单纯的 Docker 卷挂载命令是无法达到动态管理的目的的。这是因为标准的 Docker 挂载通常是静态的,一旦创建,就不能再改变挂载点内容而不重启容器。

那么我们该怎么做呢?

为了实现这一需求,我们可以创建一个辅助容器:pv-assist-container,内部使用了 Geesefs 文件系统和 rshared 挂载传播类型。这里的关键点在于 rshared,它允许挂载点的变化在宿主机和所有共享该挂载点的容器之间传播。如下所示:

环境变量文件内容如下:

1
2
3
4
5
6
# env file
# MINIO 认证信息
AWS_ACCESS_KEY_ID=VoojEqEgWlZKzhwh6PcZ
AWS_SECRET_ACCESS_KEY=vjULzgWmjX0u5w4HOdemEOMxm2P72zu7OcJN0Jsn
# 设置API端点
MINIO_ENDPOINT=http://{your-minio-server}:9000 #替换为节点地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 辅助容器
# 启动geesefs基础容器
docker run -d -it \
--privileged \
--name pv-assist-container \
--env-file=.env \
-v "$(pwd)"/datasets:/data:rshared \
ubuntu-with-geesefs:0.0.1

# 进入容器中
docker exec -it pv-assist-container /bin/bash

# 通过Geesefs绑定外挂
geesefs \
--endpoint $MINIO_ENDPOINT \
-o allow_other \
--log-file /dev/stderr \
--setuid 65534 -setgid 65534 \
--memory-limit 1000 --dir-mode 0777 \
--file-mode 0666 datasets /data/foo

下面有两个要点我们需要特别的注意

  • 必须给与 --privileged 权限,确保了辅助容器有足够的权限执行 FUSE 文件系统的挂载操作,这对于 geesefs 来说是必需的。
  • 将宿主机的上的 "$(pwd)"/datasets 目录以 rshared 传播模式挂载到辅助容器的 /data 目录;
    看到这里,想必大家也明白了。没错,实际上我们将要利用辅助容器来做文章。

当 geesefs 在辅助容器的 /data 目录下挂载或卸载数据集时,由于挂载点的传播属性,这些变化也会同步到业务容器的 /datasets 目录中。

这种方法无需重启业务容器,就可以灵活地管理数据集,为动态数据管理提供了有效的解决方案。

小结

我们通过容器间的挂载传播和 FUSE 文件系统,不仅解决了数据动态挂载的问题,还创造了可扩展的存储模式,适应复杂场景。这体现了容器技术在应用部署和管理的灵活性和实力,特别是在云原生环境中的重要性。

K8S引入动态挂载卷,实现工作负载运行时的存储灵活性(Part Ⅰ)

https://minram.github.io/kubernetes/kubernetes-01-dynamicvolume-mounting-runtime-part-1/

作者

Yiyang Huang

发布于

2025-11-10

更新于

2025-11-11

Licensed under

评论