将 Lustre 客户端挂载到多个容器并实现并发读写

解读

国内金融、运营商、超算中心普遍用 Lustre 做并行文件系统,容器化后又要求“一次挂载、多容器复用”且保证 POSIX 语义一致、性能不降、故障域不扩大。面试官想确认你是否同时理解 Lustre 客户端内核态原理Docker 运行时隔离机制,并能在 国产化内核(麒麟、统信、Anolis OS) 下给出可落地的工程方案,而不是简单“-v 映射”。

知识点

  1. Lustre 客户端本质:内核模块 lustre、ldiskfs、ko2iblnd,依赖 /dev/ 下设备节点与 /proc/fs/lustre/ 状态接口,必须共享宿主机内核
  2. Docker 的 mount namespace 隔离:默认新容器有私有 mount ns,内核级文件系统挂载事件不会传播,导致“宿主机 mount 后容器不可见”。
  3. 特权容器与 CAP_SYS_ADMIN:挂载操作需要 CAP_SYS_ADMIN;国产化宿主机强制启用 SELinux 时,还需 CAP_DAC_READ_SEARCH
  4. 传播挂载(rshared/rslave):systemd 默认把 / 设为 shared,但 CentOS 7.6 之前为 slave,需手动 mount --make-rshared /
  5. 并发读写一致性:Lustre 本身提供 分布式锁(LDLM),但 缓存一致性依赖客户端的 lru_size 与 osc.*.max_dirty_mb 参数,需统一调优。
  6. 国产化坑点麒麟 V10 SP2 内核 4.19.90-23.6 缺 ko2iblnd 模块,需提前编译 kmod-lustre 并做 DKMS 签名,否则容器内 ls 直接挂死。
  7. DevOps 集成:镜像里不能带内核模块,CI 阶段只打包 lfs setstripe、lfs getstripe 等用户态工具;真正挂载由宿主机 Ansible playbook 统一完成,保证 “基础设施层统一、业务层无感知”

答案

步骤一:宿主机一次性准备

  1. 安装与内核严格匹配的 kmod-lustre-client(国产环境用 DKMS 签名)。
  2. 加载模块并挂载 MGS/MDT:
    modprobe lustre
    mount -t lustre 10.10.10.1@tcp0:/lustre /mnt/lustre
    
  3. 确认传播属性:
    mount --make-rshared /mnt/lustre
    findmnt -o TARGET,PROPAGATION /mnt/lustre   # 输出应为 shared
    

步骤二:容器侧声明 Dockerfile 里不装内核模块,只放用户态:

FROM registry.cn-hangzhou.aliyuncs.com/amd64/centos:7.9
RUN yum -y install lustre-client-dkms && yum clean all

构建后镜像仅 120 MB,不含 ko,避免合规风险

步骤三:运行多容器

docker run -d --name app1 \
  --cap-add SYS_ADMIN --cap-add DAC_READ_SEARCH \
  --security-opt label=disable \
  -v /mnt/lustre:/data:rw,rshared \
  my/lustre-app:1.0

docker run -d --name app2 \
  --cap-add SYS_ADMIN \
  -v /mnt/lustre:/data:rw,rshared \
  my/lustre-app:1.0

关键点

  • 必须加 rshared 标志,否则容器重启后挂载点消失;
  • 国产化 SELinux 强制模式下需 --security-opt label=disable 或打自定义策略,否则 touch 报 EACCES;
  • 非 root 用户运行时,容器内 uid/gid 要与 Lustre 服务端一致,提前在 LDAP/AD 统一。

步骤四:并发读写验证 容器 1 执行:

lfs setstripe -c 4 -S 4M /data/test.big
dd if=/dev/zero of=/data/test.big bs=1M count=1024 oflag=direct

容器 2 同时:

dd if=/data/test.big of=/dev/null bs=1M iflag=direct

观察 /proc/fs/lustre/osc/*/statsread_byteswrite_bytes 同时上涨,无 MD5 不一致即验收通过

步骤五:生产加固

  • 把挂载操作下沉到 DaemonSet,容器只负责 mount --bind 避免重复挂载;
  • 通过 Kubernetes CSI-lustre 插件(社区版已支持 ARM64)做动态卷,屏蔽 CAP_SYS_ADMIN
  • 统一调优:
    lctl set_param osc.*.max_dirty_mb=256
    lctl set_param llite.*.max_read_ahead_mb=128
    
    减少并发写抖动;
  • 监控:Prometheus + node_exporter 文本文件收集 /proc/fs/lustre/*/stats,告警项包括 ldlm_canceld 超时与 osc.*.ost_disconnect

拓展思考

  1. 多架构国产化:在鲲鹏 920 + 麒麟 V10 SP3 场景,内核 5.10 已内置 LNet self-test,但仍需关闭 ARM64 的 64K page 以兼容老 OST,如何在 Dockerfile 中探测 page size 并给出启动警告?
  2. 跨云混合挂载:公有云容器实例(如阿里云 ECI)无法加载自定义内核模块,是否考虑 用户态 FUSE-lustre?其性能损失 30%+,如何 通过 RDMA-FUSE 补丁降到 10% 以内
  3. 安全最小化:如果监管要求“容器不得携带 CAP_SYS_ADMIN”,能否利用 systemd-mount 的 setuid helper 在宿主机预挂载,然后容器通过 bind-mount + readonly 路径 实现“只读并发、写操作走 sidecar 代理”?请给出 sidecar 与主容器共享 POSIX 写锁的代码框架。