利用LXD搭建多人共用GPU服务器
本文参考 训哥 的博客 使用 LXD 搭建多人使用的 GPU 服务器 实现,大部分内容没变(没错,就是懒的写),因为训哥的环境和主机跟我的不一样,中途出现很多错误,这里主要记录一下其中遇到的问题和解决思路。
本文中需要注意的地方我都已用红色 一级标题 给出。
环境:
显卡: GTX 1080ti (数量:1)
CPU: Intel i7-9700K (数量:1)
主板: 技嘉Z390-D
系统版本: Ubuntu 20.04.2 LTS
首先说一个问题:
一开始想先在旧系统(系统版本同上,已经用了小半年,各种环境变量、配置均已更改,未安装CUDA,安装源为阿里源)上安装试一下,但是执行如下安装命令:
sudo snap install lxd
# 或
sudo apt install lxd
一直报错如下:
error: unable to contact snap store
于是乎,只能重装系统,重装系统后再次执行安装命令,成功安装LXD。
至于原因,因为国内访问snap store一直都是个问题,个人猜测是使用了国内源的问题。废话不多说,正文开始。
1. 安装LXD及其组件
# 安装LXD
sudo snap install lxd
# 安装zfs及bridge-utils
sudo apt install zfsutils-linux bridge-utils
我们需要安装LXD实现虚拟容器,ZFS作为LXD的存储管理工具,bridge-utils用于搭建网桥。由于apt安装的LXD不是最新版本,这里使用snap安装工具安装LXD。
2.初始化 LXD
sudo lxd init
在初始化过程中,不要创建新的网桥,ZFS设置大小要尽量大,其他设置默认即可。详情如下:
Would you like to use LXD clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]: lxd
Name of the storage backend to use (ceph, btrfs, dir, lvm, zfs) [default=zfs]:
Create a new ZFS pool? (yes/no) [default=yes]:
Would you like to use an existing empty block device (e.g. a disk or partition)? (yes/no) [default=no]:
Size in GB of the new loop device (1GB minimum) [default=30GB]: 800
Would you like to connect to a MAAS server? (yes/no) [default=no]:
Would you like to create a new local network bridge? (yes/no) [default=yes]: no
Would you like to configure LXD to use an existing bridge or host interface? (yes/no) [default=no]: yes
Name of the existing bridge or host interface: br0
Would you like the LXD server to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
需要注意的是:
Name of the existing bridge or host interface: br0
中的 br0 有的主机不一定有网桥,但前面说了“在初始化过程中不要创建新的网桥”,因此这里我们可以用本机已有的网卡名称暂时进行代替。
使用如下命令查看本机的网卡名称:
ifconfig
我这里显示如下:
wuladuizhang@wuladuizhang:~$ ifconfig
enp4s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.120 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::1172:fdf3:78cf:18e6 prefixlen 64 scopeid 0x20<link>
ether 18:c0:4d:61:39:43 txqueuelen 1000 (Ethernet)
RX packets 56005 bytes 79580943 (79.5 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 14295 bytes 1389545 (1.3 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 407 bytes 36567 (36.5 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 407 bytes 36567 (36.5 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
即第一个网卡信息enp4s0
。至于网络配置可以见第4节。
3. 新建容器
如果网速允许可以尝试:
sudo lxc launch ubuntu:xenial yourContainerName
如果网速不行可以添加清华大学的镜像:
sudo lxc remote add tuna-images https://mirrors.tuna.tsinghua.edu.cn/lxc-images/ --protocol=simplestreams –public
提示:
Ubuntu 18.04使用上述命令添加清华源,但是在Ubuntu 20.04上报错:
To start your first instance, try: lxc launch ubuntu:18.04
Description:
Add new remote servers
URL for remote resources must be HTTPS (https://).
Basic authentication can be used when combined with the "simplestreams" protocol:
lxc remote add some-name https://LOGIN:PASSWORD@example.com/some/path --protocol=simplestreams
Usage:
lxc remote add [<remote>] <IP|FQDN|URL> [flags]
Flags:
--accept-certificate Accept certificate
--auth-type Server authentication type (tls or candid)
--domain Candid domain to use
--password Remote admin password
--protocol Server protocol (lxd or simplestreams)
--public Public image server
Global Flags:
--debug Show all debug messages
--force-local Force using the local unix socket
-h, --help Print help
--project Override the source project
-q, --quiet Don't show progress information
-v, --verbose Show all information messages
--version Print version number
Error: Invalid number of arguments
查看清华源官网
因此, Ubuntu 20.04需要使用如下命令:
sudo lxc remote add tuna-images https://mirrors.tuna.tsinghua.edu.cn/lxc-images/ --protocol=simplestreams --public
使用如下命令查看清华镜像的所有系统:
sudo lxc image list tuna-images:
列表输出会很长,前面的都看不到了,可以使用下面命令将列表内容输出到文本文件,然后在记事本中打开查看
sudo lxc image list tuna-images: > tuna-image-list.txt
文本部分内容如下:
我们发现没有 Ubuntu 20.04 版本的容器,所以本着向下兼容的原则,我们选择 Ubuntu 18.04 (如上图红框所示),记录第二列的 FINGERPRINT, 比如我选择的 容器 FINGERPRINT为 511d4b774713
然后使用如下命令创建新容器:
sudo lxc launch tuna-images:511d4b774713 yourContainerName
容器名字只能包含字母数字和连字符,_ 和 . 是不行的。
4. 配置网络环境
在实验室的其他电脑访问服务器中的 LXD 容器有两种方法。
给每个用户分配一个端口,利用 iptables 把这个端口转发到对应 LXD 容器的 22 端口;
使用桥接模式,每个容器的 IP 与实验室的 IP 域相同,物理外部、局域网内部的机器可以直接 ssh 容器的 IP,不需要端口号就可以登陆。
很明显,第 2 种方法比第 1 种方法好,不仅登录方便,而且每个容器的所有端口都是开放的,可以拿其他端口做一些事情,如搭建网站、开启服务等。
第 2 种方法的详细步骤可以参考 训哥的另一篇博客: LXD 使用 Netplan 实现在同一网络访问
这里我一并放在下面:
4.1 宿主机网络配置
Ubuntu 17.10 以后默认使用 Netplan 管理网络。进入 /etc/netplan/ 目录有一个 yaml 配置文件,下面的命令需要根据自己的 yaml 文件名称自行修改
备份配置文件:
# ubuntu 18.04中的配置文件名称是01-netcfg.yaml, 20.04的文件名称是01-network-manager-all.yaml
sudo cp /etc/netplan/01-network-manager-all.yaml /etc/netplan/01-network-manager-all.yaml.bak
编辑 yaml 配置文件:
sudo vim /etc/netplan/01-network-manager-all.yaml
yaml文件原始内容如下
# Let NetworkManager manage all devices on this system
network:
version: 2
renderer: NetworkManager
我们修改为如下:
# Let NetworkManager manage all devices on this system
network:
version: 2
renderer: NetworkManager
ethernets:
enp4s0:
dhcp4: no
dhcp6: no
bridges:
br0:
dhcp4: no
dhcp6: no
interfaces:
- enp4s0
addresses: [ 192.168.1.120/24 ]
gateway4: 192.168.1.1
nameservers:
addresses:
- 8.8.8.8
- 114.114.114.114
parameters:
stp: false
forward-delay: 0
- addresses: [ 192.168.1.2/24 ] 为任意网络无人占用的 IP 即可。
- gateway4 为网关地址。
- enp4s0 为网卡名称,可以使用 ip a 或 ifconfig 命令查看。
应用网络配置:
sudo netplan --debug apply
重启后确认网络可用:
sudo reboot
ip a
ping -c 4 baidu.com
4.2 容器网络配置
进入容器:
sudo lxc exec <container> bash
编辑 yaml 配置文件:
sudo vim /etc/netplan/10-lxc.yaml
10-lxc.yaml原始内容如下:
network:
version: 2
ethernets:
eth0:
dhcp4: true
dhcp-identifier: mac
我们可以使用ifconfig或ip a
查看一下现在的ip地址,确保可以使用,然后固定容器的IP地址,编辑10-lxc.yaml如下:
network:
version: 2
ethernets:
eth0:
dhcp4: no
dhcp6: no
addresses:
- 192.168.1.100/24
gateway4: 192.168.1.1
nameservers:
addresses:
- 114.114.114.114
- 8.8.8.8
应用网络配置:
netplan --debug apply
同样重启后检查网络连接:
sudo reboot
ip a
ping -c 4 baidu.com
5. 进入容器
sudo lxc exec ubuntu-18-04-deeplearning bash
我们登录的是root用户,在这个容器中已经存在了一个叫ubuntu的用户
修改容器的用户名密码
由于容器默认的用户名为 ubuntu,我们需要在安装其他程序之前更改用户名即密码,以避免路径冲突。
修改root密码:
passwd root
修改用户ubuntu密码:
passwd ubuntu
容器中安装 ssh 服务
sudo apt install openssh-server
6. 安装显卡驱动
在宿主机中首先需要安装显卡驱动,使用如下命令安装推荐的显卡驱动:
sudo ubuntu-drivers autoinstall
但是这个方法安装的驱动往往不是最新的,可以去 NVIDIA 官网 下载最新驱动并安装。
注意:这个命令请慎用!!!我之前用这条命令安装驱动,曾造成系统崩溃,进不去命令行终端。该命令本身应该是没什么问题,但是可能会与你的某些环境配置发生冲突,所以建议手动下载显卡驱动安装
手动安装显卡驱动方法可以参考 本人的另一篇博文:Ubuntu20.04安装NVIDIA显卡驱动+cuda+cudnn配置深度学习环境
如果是Ubuntu 16.04 或 18.04,参考:
超详细! Ubuntu 18.04 安装 NVIDIA 显卡驱动
使用 nvidia-smi 查看驱动版本,并在官网下载对应的驱动,以便于在容器中安装和宿主机相同版本的NVIDIA驱动
为容器添加所有GPU:
sudo lxc config device add <container> gpu gpu
添加指定GPU:
sudo lxc config device add <container> gpu0 gpu id=0
将下载好的显卡驱动传递到容器中
lxc file push <source> <container>/<path>
在容器中安装显卡驱动:
sudo sh ./NVIDIA-Linux-x86_64-xxx.xx.run --no-kernel-module
因为在容器中显卡驱动不需要安装内核文件,所以后面加上 --no-kernel-module。
在容器中安装好显卡驱动重启后输入 nvidia-smi 检查驱动是否安装成功.
如果没显示不要着急,检查一下是否给容器分配了GPU。
7. 宿主机安装 lxdui 管理容器
因为 LXD 相当于 LXC 增加了 RESTful API,所以可以通过 WEB 界面管理容器。lxdui 为不错的管理界面。具体安装方法详见 lxdui 的 github
目前 lxdui 版本为 2.1.2,我使用有一些 bug,例如 snapshot、clone 等操作只能在容器表格中的 Actions 进行操作,进入容器后顶部的 snapshot、clone 等按钮是无效的。所以 lxdui 主要还是方便查看和管理,具体的操作还是用命令吧。
8. 容器快照管理
以上所有对容器的操作基本完成,现在需要建立快照,以备将来需要恢复快照,或者可以从快照新建一个容器,这样可以避免上面的重复劳动。
创建快照:
sudo lxc snapshot <container> <snapshot name>
恢复快照:
sudo lxc restore <container> <snapshot name>
从快照新建一个容器 (新旧容器 MAC 地址不同):
sudo lxc copy <source container>/<snapshot name> <destination container>
这也是比较好的创建容器的方法。如果直接 clone 容器的话,MAC 地址等关键信息也会同样被复制。
9. 容器和宿主机间复制文件
在宿主机输入以下命令
sudo lxc file push <source path> <container>/<path> # 表示从容器中复制文件到宿主机
sudo lxc file pull <container>/<path> <target path> # 表示将宿主机的文件复制到容器
复制文件夹需要在最后加 -r
10. 共享文件夹
创建共享文件夹:
sudo lxc config set <container> security.privileged true
sudo lxc config device add <container> <device-name> disk path=/home/xxx/share source=/home/xxx/share
其中 path 为容器路径,source 为宿主机路径。device-name 随意取名字即可。
移除共享文件夹:
sudo lxc config device remove <container> <device-name>
11. CUDA 与 cuDNN
CUDA 与 cuDNN 的安装建议使用 Anaconda 安装,因为使用 Anaconda 安装 TensorFlow 或 PyTorch 都会自带 CUDA 与 cuDNN,并且据说有一定的优化。具体命令为:
conda create -n tf_gpu1.9 tensorflow-gpu=1.9
conda create -n pytorch pytorch=0.4
只要 CUDA 版本与 NVIDIA 驱动版本相对应即可,可以在 Release Notes :: CUDA Toolkit Documentation 查找。
(2021.12.29更新)
之前使用过程中一直没有问题,今天新创建了一个容器,必须要手动安装从官网下载的cuda工具包,安装完成后进行测试,结果发现测试未通过,且不能调用GPU。虽然在容器内执行 nvidia-smi
能够查看显卡信息,但是在使用cuda时仍旧报错。
网上一顿搜索后发现:出现这个问题的原因是因为宿主机里面缺少 /dev/nvidia-uvm
设备
解决方法:
缺少设备就给他挂载上不就完了吗
首先在宿主机上执行如下命令:
# 给宿主机增加nvidia-uvm设备
sudo /sbin/modprobe nvidia-uvm
D=`grep nvidia-uvm /proc/devices | awk '{print $1}'`
sudo mknod -m 666 /dev/nvidia-uvm c $D 0
只给宿主机挂载的话,宿主机是可以用GPU了,那容器呢?
将设备挂载到容器就可以了。
同样在宿主机上执行如下命令将nvidia-uvm
分配给指定的容器即可:
# 给容器分配nvidia-uvm设备
lxc config device add yourContainerName nvidia-uvm unix-char path=/dev/nvidia-uvm
如果执行完上述操作,还是不能调用GPU,那就使出 终极必杀技---->重启(容器)
12. 安装远程桌面
为了安装远程桌面,我曾尝试了网上各种实现方法,有一部分确实可以实现远程桌面控制,但是在使用过程中有各种问题(当然也不排除是因为自己菜,解决不了问题),这里提供三种可行的实现方法,并简单说一下他们各自的优缺点。
方法一:
- 优点:
安装简单方便 - 缺点:
安装不了搜狗输入法,界面有点丑
方法二:
- 优点:
借助软件实现,安装简单,兼容性好,兼容各种主流系统平台;在低带宽下也可以操作流畅 - 缺点:
如果在同一个局域网内存在多个主机安装了Nomachine服务器的话,那么Nomachine客户端将自动搜索并列出所有安装了服务端的主机及其信息,造成主机信息及状态暴露
方法三:
- 优点:
使用脚本一键安装,完全自动配置,不需要动手,方便多用户管理和登录 - 缺点:
对带宽要求稍高,在低带宽条件下,可能延迟较高,操作卡顿
----------------------------------- 方法一 -----------------------------------------------
参考文档:How to Connect to a Ubuntu 18.04 Server via Remote Desktop Connection using xRDP
12.1 安装 xRDP
xRDP 是一款非常不错的远程桌面软件,且全平台支持。安装 xRDP 最好在干净的系统上安装。
Windows 可以直接使用微软的远程桌面连接。
Mac 可以使用 Microsoft Remote Desktop mac 版,App Store 搜不到,这里放上 下载链接。
Linux 可使用 FreeRDP、Rdesktop 等开源软件。
sudo apt-get update
sudo apt-get install xrdp
12.2 安装桌面环境
Ubuntu 有很多桌面环境,例如 XFCE, Lubuntu, Xubuntu 和 MATE 等。这里使用 XFCE 为例:
sudo apt-get install xfce4
现在就可以使用远程桌面软件,通过 IP 即可访问。
----------------------------------- 方法二 -----------------------------------------------
----------------------------------- 方法三 -----------------------------------------------
13. 在 LXD / LXC 中使用 Docker 容器
在 LXD 环境中依然可以使用 Docker,但是需要更改一些配置,否则在 pull 镜像时会有 permission denied
错误,如下:
OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:430: container init caused \"rootfs_linux.go:58: mounting \\\"proc\\\" to rootfs \\\"/var/lib/docker/vfs/dir/c5cedb213621362913c6d950eec507ba91e04f2a933cd6d309f1c74a92c346ec\\\" at \\\"/proc\\\" caused \\\"permission denied\\\"\"": unknown
我们只需要在宿主机对容器进行以下配置即可:
sudo lxc config set <container> security.nesting true
sudo lxc config set <container> security.privileged true
14. RuntimeError: cuda runtime error (30)
重启宿主机后,再使用容器里的环境时会找不到 CUDA,报下面的错误:
RuntimeError: cuda runtime error (30) : unknown error at /tmp/pip-req-build-58y_cjjl/aten/src/THC/THCGeneral.cpp:50
或者 torch.cuda.is_available()
是 false
网上其他人报这个错误可能是因为 CUDA 没有安装,但我使用的是 Anaconda 自动安装的 CUDA 和 cuDNN,容器和宿主机都可以运行 nvidia-smi
命令查看显卡驱动版本,并且在重启前是没有错误的,为什么重启后就报错了呢?为此我甚至重装系统,但这个问题依然存在。。。
目前临时的解决办法是:
重启宿主机后,需要使用宿主机的 Python 环境运行一次使用 CUDA 的程序;
例如,如果是pytorch环境,可以运行下面的代码:
python -c 'import torch; print(torch.cuda.is_available())'
重启所有容器。
这样容器里的 CUDA 就可以找到了。
15. 宿主机重启后找不到显卡驱动
重启宿主机后,显卡驱动掉了,所有用到显卡的容器都无法启动,输入 nvidia-smi
报错:
NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running
原因可能是宿主机运行了sudo apt upgrade命令并更新了系统内核,导致安装显卡驱动时的内核与现有内核版本不一致,解决办法参考此解决办法
sudo apt-get install dkms # DKMS,Dynamic Kernel Module Support,可以帮我们维护内核外的这些驱动程序,在内核版本变动之后可以自动重新生成新的模块。
sudo dkms install -m nvidia -v 450.80
DKMS全称是Dynamic Kernel Module Support,它可以帮我们维护内核外的这些驱动程序,在内核版本变动之后可以自动重新生成新的模块。
450.80是安装驱动的版本,可进入/usr/src查看nvidia-450.80的文件夹获取版本号
重启后输入nvidia-smi就应该正常输出了。如果还是不好使,可以重启后重新运行上述命令再重启。
如果仍不好使,说明该内核下无法编译显卡驱动,解决方法是内核降级为原来版本,具体方法参考 如何降级/切换 Ubuntu 系统 Linux 内核启动版本
- 查看系统可用内核
grep menuentry /boot/grub/grub.cfg
可以看到子选项:
Ubuntu, with Linux 5.3.0-62-generic
- 修改内核启动版本
使用vim编辑grub文件:
sudo vim /etc/default/grub
GRUB_DEFAULT=0 // 0表示系统当前启动的内核序号
- 修改为想要启动的内核版本对应子选项:
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 5.3.0-62-generic"
注意,>两侧没有空格,这个原博客有错误。
- 更新 Grub
sudo update-grub
- 查看系统当前运行内核信息
uname -r(或-a)
16. 容器硬盘ZFS扩容
LXD 初始化的时候会对 ZFS 进行空间分配,但是随着时间的推移仍有扩容的需求。当容器变得很卡的时候,有可能就是 ZFS 分配的空间已满。
输入下面的命令可以对 ZFS 进行扩容,以扩容512GB为例:
sudo truncate -s +512G /var/snap/lxd/common/lxd/disks/lxd.img
sudo zpool set autoexpand=on lxd
sudo zpool online -e lxd /var/snap/lxd/common/lxd/disks/lxd.img
sudo zpool set autoexpand=off lxd
其中,lxd.img
为初始化时定义的容器名称,不知道容器名称的可以通过下述命令查看 sudo ls /var/snap/lxd/common/lxd/disks/
总结
至此,多人使用的 GPU 服务器就搭建完成了,当需要新建容器时,只需要完成以下几步:
从快照中新建容器;
lxc exec <container> bash
进入容器;
更改容器的 IP;
更改容器的用户名、密码;
新增共享目录;
这在以后可以编写脚本,使得新建操作更容易。
LXD常用命令
容器的启动
lxc start <container>
容器的停止
lxc stop <container>
如果容器失去控制(比如不响应SIGPWR),可强制执行:
lxc stop <container> --force
容器的重启
lxc restart <container>
也可强制执行:
lxc restart <container> --force
容器的暂停
在这种模式下,所有容器的任务都将收到一个等同于SIGSTOP的指令,也就是说这些任务仍可见和继续使用内存,但无法从调度器那得到CPU的时间片。
这很适合如下场景:有一个很吃CPU的容器,需要蛮久才能启动,但又不经常被使用。你可以先将它启动,然后暂停,当需要的时候再启动。
lxc pause <container>
容器的删除
最后,要干掉这个容器,你只要删掉它就好:
lxc delete <container>
注:同样可以用“-force”强制删除。
容器的重命名
lxc move <old name> <new name>
这里有一个要求,容器必须是停止的状态,那么容器里面的一切都会原样保存,包括冲突的信息(MAC地址等等)。
往容器中传递一个文件
lxc file push <source> <container>/<path>
评论区