K8s 里如何优雅地使用 /dev/shm 实现容器间共享内存

目录

  • [1. 从 docker run 的 --shm-size 参数聊起](#1. 从 docker run 的 --shm-size 参数聊起)
  • [2. Linux 里的 /dev/shm](#2. Linux 里的 /dev/shm)
  • [3. Docker 对共享内存的支持](#3. Docker 对共享内存的支持)
  • [4. K8s 里如何设置 /dev/shm 大小](#4. K8s 里如何设置 /dev/shm 大小)
    • [4.1 方案一:修改节点的 Docker 配置](#4.1 方案一:修改节点的 Docker 配置)
    • [4.2 方案二:通过 Volume 挂载一个大目录到 Pod 的 /dev/shm](#4.2 方案二:通过 Volume 挂载一个大目录到 Pod 的 /dev/shm)
    • [4.3 方案三:用 K8s 的方式共享内存](#4.3 方案三:用 K8s 的方式共享内存)
  • [5. 总结](#5. 总结)

1. 从 docker run 的 --shm-size 参数聊起

今天无意见看到算法同学写的一个 docker run 命令用到了 --shm-size 参数。没错,接着他想将这些容器跑到 K8s 里,于是我就得开始思考 --shm-size 在 K8s 里对应的特性是什么了。

我猜 K8s 里会有优雅的方式来实现一样的目的,最终我也找到了那个优雅的方法。不过这个过程还是稍稍坎坷,我在在网上看一圈,找到的前2种方案都有点"丑陋",但是给人一种"只能这样"的错觉。最后看到第三条路的时候,我不禁拍了一下大腿:"我X!太帅了!"。于是乎,我想让你们也拍一下大腿,然后就有了此文。行,且往下看。

2. Linux 里的 /dev/shm

Linux 里有一个 /dev/shm 目录,shm 代表共享内存(Shared Memory)。如果这是你第一次听说 shm,可能会有点疑惑:"这东西到底是内存还是磁盘"。其实 /dev/shm 是一个临时文件系统,让你用操作磁盘中的文件一样的方式来将读写内存。换言之,你可以用文件读写的语法来操作"/dev/shm/a.txt",但是这个文件本质是存在内存里的,所以读写速度会非常快。

有了 /dev/shm,你就可以让一个进程非常高效地存取一些临时文件,或者让多个进程非常高效的"共享内存"。这种"进程间快速共享内存"的能力对于很多机器学习框架来说非常有用。

顺口提一下,可能大伙平时接触最多的进程间通信(IPC)方式是 Socket 和 RPC,这类走网络通信的方式可以实现跨主机的 IPC,应用场景更广。不过上帝给你开了一扇门,就不会多给你开一堆窗,Socket 和 RPC 的通信效率肯定就没有"共享内存"来得快了。(可能你心里在想 http 协议,没错,http 用的更多,但是一般 http 不被归入进程间通信方式。)

3. Docker 对共享内存的支持

在 Linux 系统中,/dev/shm 的大小一般为物理内存的一半。Docker 在启动容器的时候,默认给容器设置的 /dev/shm 大小是 64M。(你品,你细品,是不是自定义容器内 shm 大小的需求就产生了?)

Docker 在 shm 大小的配置上提供了两种支持:

  1. docker run 支持通过 --shm-size=1g 这种方式指定一个容器的 shm 大小;
  2. Docker 配置 daemon.json 中可以通过 "shared-memory-size": "1G" 配置指定容器启动的默认 shm 大小。

不难猜到,--shm-size 参数的优先级会比 shared-memory-size 配置高。

4. K8s 里如何设置 /dev/shm 大小

K8s 里没有原生提供啥直观的 shm 配置方式。但是为了让应用能够尝到"共享内存"的滋味,YAML 工程师还是得给出方案。

4.1 方案一:修改节点的 Docker 配置

Sao 操作来了,因为 Docker 支持 shared-memory-size 配置,所以直接修改节点的 daemon.json 配置,那不就让所有 Pod 都能够用起来更大的 share memory 了?

我就不演示了。反正基本满足需求,但是冥冥之中又让你觉得哪里不太对劲,总之吧,不优雅。

4.2 方案二:通过 Volume 挂载一个大目录到 Pod 的 /dev/shm

不就是让 Pod 内的 /dev/shm 大一点嘛,那就挂卷呀:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: shared-memory-pod
spec:
  containers:
    - name: mycontainer
      image: your_image_name
      volumeMounts:
        - name: shared-memory-volume
          mountPath: /dev/shm
  volumes:
    - name: shared-memory-volume
      emptyDir: {}

如果用 PVC 的方式,后端存储系统支持限定卷大小的话,这里就能精准分配 shm 的大小了。总之,业务能跑起来。(你是不是已经忍不住要开喷了?忍忍。我知道,这种方式通过磁盘上的文件来实现"共享内存",功能走通了,性能不达标。)

到这一步,我以为"方案一"挺合理的,改改 Docker 的启动参数,似乎影响不大,没啥大坏处,妥。

不过更进一步,K8s 真的不支持配置 shm?这个需求听起来合理呀,为什么没有被实现呢?继续搜一搜,我在 stackoverflow 上看到了第三种玩法。

4.3 方案三:用 K8s 的方式共享内存

且看这段配置:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: shared-memory-pod
spec:
  volumes:
    - name: dshm
      emptyDir:
        medium: Memory
        sizeLimit: "1Gi"
  containers:
    - name: mycontainer
      image: your_image_name
      volumeMounts:
        - name: dshm
          mountPath: /dev/shm

行吧,破案了。之前我也不知道 emptyDir 可以配置 medium: Memory

5. 总结

散了吧,总结啥。大周五的,早点回家。