分布式 HPC 应用容器化解决方案

需要解决的问题

为什么使用容器

  • 可移植
  • 可扩展
  • 可重复使用
  • 轻量化
  • 一定的隔离性

主流 AI 集群架构

云部署集群(e.g. Kubernetes)

  • 几百/几千个小节点
  • 应用容器化
  • 节点开放网络

私有化 GPU 集群(HPC-like)

  • 10 - 100 个大节点
  • ”可信“用户
  • 容器/非容器混合
  • 具有 checkpoint 的多节点作业(深度学习)
  • 计算节点封闭

HPC 容器需要什么

  • 高性能
  • 支持 Docker 镜像
  • 支持 GPU 调度
  • 支持 cgroup 资源隔离
  • 支持多节点作业

Why not Docker

  • 资源传播缺陷:当在调度中指定资源(如 GPU 卡数)时,需要额外在 Docker 调用中重复配置参数,因为 Docker 守护进程并不关心 Slurm 守护进程的资源分配
  • 网络限制:一些网络配置的修改会破坏作业进程与 Slurm 控制器之间的通信
  • 安全性:必要的 subuid ( newuidmap ) 和 subgid ( newgidmap ) 设置可能会带来安全问题

Why not Singularity

https://github.com/NVIDIA/enroot/issues/25

Why (not) Enroot

优势:

  • 仅需要 2 个命名空间(mount & user)
    • 与宿主机共享网络减小开销
    • 共享的 PID 命名空间不易混淆
    • 共享 IPC 命名空间用于节点内快速通讯
  • 资源隔离(cgroups)由调度程序(Slurm)处理
  • 镜像格式(squashfs)简单通用
  • GPU(NVIDIA)支持
  • 有专门适配 Slurm 的插件
  • 轻量化,无守护进程
  • 开源、结构简单、遵循 KISS 原则

缺点:

  • 文档简单
  • 市场占有率不高
  • 容器作业兼容性待验证
  • 高级用法依赖容器相关先验知识

基本概念

Enroot

一个简单但功能强大的工具,可将传统容器/操作系统镜像(image)转变为非特权沙箱(sandbox)。

Enroot 容器镜像是标准的 squashfs 镜像。

Squashfs 是一套供Linux核心使用的GPL开源只读压缩文件系统。Squashfs能够为文件系统内的文件、inode及目录结构进行压缩,并支持最大1024千字节的块大小,以提供更大的压缩比。

Enroot 通过 libnvidia-container 提供 GPU 支持。

libnvidia-container 提供了一个库和一个简单的 CLI 实用程序,可以利用 NVIDIA 硬件自动配置 GNU/Linux 容器。

pyxis

pyxis 是一个 Slurm SPANK 插件,允许非特权集群用户通过 Slurm 运行容器化任务。

SPANKSlurm Plug-in Architecture for Node and job (K)control)插件可以在不修改 Slurm 源码的情况下构建,只需要根据spank.h头文件编译,添加到配置文件plugstack.conf中,就会在下一次作业启动运行时被加载。

如何使用

快速上手

通过 Enroot 使用 Docker 容器需要三个步骤

  1. 导入镜像
  2. 创建容器
  3. 启动容器

导入镜像

enroot import 从 dockerhub(默认镜像仓库)拉取最新的镜像,并转化为 ubuntu.sqsh 文件存储到当前目录

1
$ enroot import docker://ubuntu

-o 参数可以指定生成 squash 文件的文件名:

1
$ enroot import -o my-ubuntu docker://ubuntu

如果需要指定镜像仓库、版本和文件名,可以使用如下方式:

1
$ enroot import docker://nvcr.io#nvidia/pytorch:21.04-py3

import 命令官方文档:
https://github.com/NVIDIA/enroot/blob/master/doc/cmd/import.md

具体转化的源码实现可以参考:
https://github.com/NVIDIA/enroot/blob/master/src/docker.sh

创建容器

-n 参数用于指定生成容器的容器名

1
$ enroot create -n nvidia+pytorch+21.04-py3 nvidia+pytorch+21.04-py3.sqsh 

解压缩之后的容器存储在 $ENROOT_DATA_PATH 路径下,默认指向 $HOME/.local/share/enroot/

已生成的容器可以通过 enroot list 命令查看

1
2
3
$ enroot list
nvidia+cuda
nvidia+pytorch+21.04-py3

启动容器

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
29
30
31
32
33
34
35
$ enroot start --rw nvidia+pytorch+21.04-py3 bash

=============
== PyTorch ==
=============

NVIDIA Release 21.04 (build 22382700)
PyTorch Version 1.9.0a0+2ecb2c7

Container image Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.

Copyright (c) 2014-2021 Facebook Inc.
Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
Copyright (c) 2011-2013 NYU (Clement Farabet)
Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston)
Copyright (c) 2006 Idiap Research Institute (Samy Bengio)
Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz)
Copyright (c) 2015 Google Inc.
Copyright (c) 2015 Yangqing Jia
Copyright (c) 2013-2016 The Caffe contributors
All rights reserved.

NVIDIA Deep Learning Profiler (dlprof) Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.

Various files include modifications (c) NVIDIA CORPORATION. All rights reserved.

This container image and its contents are governed by the NVIDIA Deep Learning Container License.
By pulling and using the container, you accept the terms and conditions of this license:
https://developer.nvidia.com/ngc/nvidia-deep-learning-container-license

NOTE: MOFED driver was detected, but nv_peer_mem driver was not detected.
Multi-node communication performance may be reduced.

srun

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ srun --pty -p l --nodelist=l1 --container-image='/root/hyx/ubuntu.sqsh' bash
root@l1:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo

sbatch

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/bin/bash

#SBATCH --job-name=jupyter-container
#SBATCH --partition=t
#SBATCH -N 1
#SBATCH --cpus-per-task=4
#SBATCH --ntasks-per-node=1
#SBATCH --gpus-per-task=1
#SBATCH -o %j.out
#SBATCH -e %j.err

#export XDG_RUNTIME_DIR=/tmp

#生成随机端口
port=$((RANDOM % (65535 - 2048) + 1024))

# 检查端口是否被占用
while [[ $(ss -tuln | grep ":$port ") ]]; do
port=$((port + 1))
done


#查看启动节点
node=$(cat /etc/hosts |grep -m 1 `hostname -s` |awk '{print $1}')

relative_path=$(pwd | sed "s|^$HOME/||")

container_workdir="/workspace/$relative_path"

export XDG_RUNTIME_DIR=/tmp

srun \
--container-name=test \
--container-image="$HOME/nvidia+pytorch+21.04-py3.sqsh" \
--container-mounts="$HOME:/workspace" \
--container-workdir=$container_workdir \
--no-container-remap-root \
--container-writable \
jupyter lab \
--allow-root \
--no-browser \
--notebook-dir=/workspace \
--ip=$node \
--port=$port \
> out.log 2>&1 &

token=$(grep -oP 'token=\K[^ ]+' out.log | head -n 1)
while true
do
if [[ "$token" == "" ]];then
sleep 2
token=$(grep -oP 'token=\K[^ ]+' out.log | head -n 1)
else
break
fi
done
url="http://$node:$port/?token=$token"

## 生成ogsp所需信息
echo -e "{'hostname':'$node','port':'$port','webUrl':'$url'}" > ogsp_conn.json

sleep 100000

进阶用法

[[slurm-enroot-manual]]

参考资料