搭建实验室公共GPU服务器

搭建需求,硬件配置,方案简介

搭建需求

实验室新进一台GPU服务器,带了两块Titan V,显存比单机多很多,公用的服务器总不能让同学一个一个轮流坐到电脑前操作,所以就有了多人同时使用的需求。

虽说Linux可以设置各种用户和权限、Anaconda可以建立各个虚拟环境,但难免遇到软件版本需求不同相互冲突、新手乱删文件等情况,最好还是给每个同学建立相互隔离的环境最舒服了,随便怎么折腾也不会干扰其他人。

具体的需求无非包括:

  • 独立:不同用户的环境相互独立,不能互相访问,且可以同时使用。(唯一通道是共享文件夹)
  • 隔离:用户是不可以访问宿主机的(唯一通道是共享文件夹)。
  • 自由:用户可以像使用一台自己的Linux机器一样,方便访问,拥有最大的权限,自有安装程序、自由访问网络等等。(事实上,这种操作方式与购买GPU云主机的体验基本等同)
  • GPU:最最重要的是每位同学能直接访问GPU。
  • 可控:虽说要自由,但必要时候(同时使用的人太多),还是要能够限制每位同学的资源的(CPU,内存,GPU这些)

硬件配置

指标 参数
CPU Intel至强 E5-2680 v4 *2
GPU NVIDIA TITAN V *2
Memory 128G
Disk 480G SSD + 8T HDD + (4T RAID HDD)

方案简介

鉴于之前搭建过一次GPU服务器的经历搭建公用GPU服务器过程记录 ,这次依旧采用LXD+ZFS的方案,即LXD做前端容器管理+ZFS做后端存储管理。虽然都说Docker这种方式是未来潮流,但我认为LXD/LXC并不差劲,且Docker的一应用一Docker的理念,可能与这种全权限访问容器主机的方式,是有本质上的区别的吧。

大致讲一下整个方案的流程:

  1. 宿主机安装Ubuntu18.04系统,安装GPU驱动。
  2. 安装LXD/ZFS软件并进行配置。
  3. 创建容器模板(Ubuntu16.04),包括:网络、GPU驱动、共享目录、SSH登录。
  4. 按需分配,克隆容器模板,并做个性化修改。
  5. 使用lxdui进行可视化容器管理。

第一步:安装系统、显卡驱动

系统

系统选择Ubuntu 18.04。

原因:Ubuntu 16.04下默认下载安装的是LXD2.0,LXD2.0不支持配置GPU到容器中,如果Ubuntu16.04需要配置GPU到容器,则需自行编译LXD3.0。而Ubuntu 18.04默认下载安装的则是LXD3.0的,安装方便。

安装完成后,注意将软件源配置为清华源,提高下载更新速度。具体步骤参考:Ubuntu 镜像使用帮助

GPU驱动

往常Linux下的GPU驱动安装是很复杂的,网上NVIDIA Driver教程百花绽放。经过实践发现,装完系统后,使用系统软件和更新中提供的NVIDIA驱动是可行的,非常方便。

GPU驱动

安装完成后,重启,输入nvidia-smi进行确认,注意显卡驱动版本:

显卡信息

第二步:LXD/ZFS安装及配置

LXD软件安装

  • LXD:用于创建和管理容器
  • ZFS:用于管理物理磁盘,支持LXD高级功能,负责容器存储。
  • Bridge-Utils:用于搭建网桥,负责容器上网。

运行命令进行安装:

1
sudo apt-get install lxd zfsutils-linux bridge-utils

配置

关于网络

需要注意的是,不同网络环境下,大家对于上网方式的诉求是不一样的。有些情况是希望用户在自己容器中登录校园网进行上网,有些情况则是上网免费,无所谓个人不个人。不同情况下,对于网络的配置是不同的。

目前我们是这样打算的,宿主机使用我的个人账号登录,每个容器就直接通过宿主机NAT上网,也不需要每个同学登录了,反正费用最后都能报销。对于较大的几十个G的数据集,则建议用户自己下载下来后,通过物理拷贝或者局域网内传输的方式上传到容器。

这种情况的下网络配置是最简单的,因为LXD默认初始化的网络方式就是:宿主机通过DHCP的方式给每个容器分配IP。如果上网方式跟我的相同的话,可以参考下节的配置。如果不是的话,请留意网络/网桥的配置

初始化LXD

运行sudo lxd init 进行LXD初始化配置,选项如下图:

LXD初始化

  • LXD Clustering:不需要
  • new storage pool:需要创建一个存储池
  • Name of storage pool:给存储池命名
  • storage backend:存储后端,使用ZFS
  • Create a new ZFS pool:需要创建一个ZFS池
  • use an existing block device:Yes
    Path to block device:使用已有的磁盘分区用于ZFS的存储后端。细节可以参考Be aware
  • MAAS server?:不知道是啥,不需要
  • new local network bridge?:需要,我只需要使用LXD默认的网桥即可。
  • new bridge be called:给网桥命名
  • IPv4:默认auto
  • IPv6:默认auto
  • LXD available over the network?:默认no
  • stale cached?:默认yes
  • YAML printed?:打印信息,yes/no都行,原谅最后手抖的yno

测试

sudo zpool list lxd 查看ZFS的后端存储池。

sudo lxc info 查看LXD的配置信息。

sudo lxc profile show default 查看默认容器配置。

sudo lxc list 查看容器列表。

更多命令可以参考LXD Documentation

#第三步:创建容器模板

创建容器模板的意义在于:每次创建一个容器都需要重复大量工作(更新、配置GPU、共享目录、SSH登录等等),而创建容器模板以后,可以省去这些操作,利用LXD的克隆功能,复制一个容器稍作修改,就能成为用户独属的容器。

创建容器模板

LXC清华源

使用lxc清华源可以加速镜像的下载。LXC Images 镜像帮助

运行如下命令配置:

1
2
# 创建一个remote链接,指向TUNA镜像站。
sudo lxc remote add tuna-images https://mirrors.tuna.tsinghua.edu.cn/lxc-images/ --protocol=simplestreams --public
1
2
# 查看镜像列表,寻找合适的镜像的FINGERPRINT,用于下载
sudo lxc image list tuna-images:

下载创建

1
2
3
4
5
# FINGERPRINT是镜像的指纹,在上条命令下查找,ContainerTemplateName为容器模板名称,自己定义。
sudo lxc launch tuna-images:<FINGERPRINT> <ContainerTemplateName>

# 举例,创建一个名为xenialTemplate的容器。在lxc清华源中24be957c5e9f是Ubuntu16.04的fingerprint
sudo lxc launch tuna-images:24be957c5e9f xenialTemplate

运行 sudo lxc list 进行容器列表查看。

运行sudo lxc exec <ContainerTemplateName> bash可进入容器的root用户下 bash。

可以使用su ubuntusudo su 进行用户切换。

如果容器下出现 sudo: no tty present and no askpass program specified 的问题。

解决方式:

创建 /etc/sudoer.d/ubuntu 文件,编辑内容为

1
ubuntu ALL=(ALL) NOPASSWD:ALL

配置共享目录和GPU

共享目录设置

设置共享目录来实现宿主机与容器之间的文件传输。

1
2
3
4
5
# 设置键值
sudo lxc config set <ContainerTemplateName> security.privileged true
# 设置共享目录,其中shareName为虚拟的设备名称,lxd会虚拟出该设备并导通接通两者共享目录。
# path1为宿主机下共享目录路径,path2为容器下共享目录路径
sudo lxc config device add <ContainerTemplateName> <shareName> disk source=<path1> path=<path2>

GPU配置

添加GPU

1
2
3
4
# 为容器添加所有GPU: 
sudo lxc config device add <ContainerTemplateName> gpu gpu
# 添加指定GPU: 
sudo lxc config device add <ContainerTemplateName> gpu0 gpu id=0

安装显卡驱动

还记得之前记录的宿主机显卡驱动的版本号码,根据版本号去官网下载驱动文件,通过共享目录传至容器中。

例如,宿主机中的NVIDIA Driver Version为390.77,则下载NVIDIA-Linux-x86_64-390.77.run

安装显卡驱动。

1
2
3
4
# 进入容器
sudo lxc exec <ContainerTemplateName> bash
# 安装驱动
sudo sh NVIDIA-Linux-x86_64-xxx.xx.run --no-kernel-module

运行nvidia-smi进行确认。

配置SSH免密码登录

SSH免密登录网上资料一大把,有不明白的地方可以网上找。

###安装SSH服务

1
2
3
4
5
6
7
8
# 装OpenSSH服务
sudo apt install openssh-server

# 启动SSH服务
sudo service ssh start

# 查看SSH服务状态
sudo service ssh status

生成RSA文件

1
2
3
4
5
6
7
8
9
10
11
# 进入SSH目录
cd ~/.ssh

# 生成RSA
ssh-keygen -t rsa

# 复制公钥内容到authorized_keys
cat id_rsa.pub >> authorized_keys

# 重启SSH服务
sudo service ssh restart

测试SSH

容器终端运行ifconfig ,确认容器IP和网段,这些是LXD自动分配的。如模板容器的IP为10.135.139.83。

宿主机终端运行ifconfig,查看宿主机在网桥lxdbr0下的IP。如宿主机在该网段下的IP为10.135.139.1。

通过共享目录将SSH秘钥 id_rsa文件拷贝到宿主机,宿主机运行如下命令登录容器:

1
2
3
4
5
# 给容器SSH秘钥文件合适权限
sudo chmod 400 id_rsa

# 命令行登录SSH
ssh -i id_rsa ubuntu@10.135.139.83

成功后,进行下一步frp的设置(端口转发)。

配置frp(内网穿透工具)

关于frp

根据上面的网络配置,可以看到,每个容器其实是处于宿主机构建出来的小型局域网内的,并不暴露在校园网内,也就是说用户从校园网是无法直接访问容器的。

一般来讲,要做的就是在宿主机上做端口转发了。我使用的是内网穿透工具frp,配置和使用都很简单。

架在宿主机上,我们可以通过校园网在校园内访问容器;架在公网服务器上,我们就可以在家访问容器。

FRP架构

使用方式

frp版本发布页面 下载linux的文件,如frp_0.21.0_linux_amd64.tar.gz。解压后文件如下:

  • frps:服务端执行文件
  • frps_full.ini:服务端参数参考
  • frps.ini:服务端参数文件
  • frpc:客户端执行文件
  • frpc_full.ini:客户端参数参考
  • frpc.ini:客户端参数文件

服务端配置

服务端文件放置在宿主机中,宿主机的frps.ini可供参考,更多参数请看官方文档。

1
2
3
4
5
[common]
bind_port = 7000

# 限制端口,要对端口做好管理
allow_ports = 2000-3000

宿主机执行./frps -c frps.ini 开启服务。

客户端配置

客户端文件放置在用户容器中,容器的frpc.ini可供参考,更多参数请看官方文档。

1
2
3
4
5
6
7
8
9
[common]
server_addr = 10.135.139.1 # 网段下宿主机的IP
server_port = 7000 # 服务端的bind_port

[ssh-template] # 一个转发实例,注意每个实例名称都不能相同,括号中的名称自定义
type = tcp
local_ip = 127.0.0.1
local_port = 22 # 容器本地SSH访问端口
remote_port = 2011 # 宿主机映射端口

容器执行./frpc -c frpc.ini 连接服务。

在校园网内进行测试

如上步骤,在已知宿主机校园网IP和容器SSH的映射端口之后,就可以在校园网内访问容器了。

这一步就不多讲了。

第四步:给用户分配容器

创建了容器模板之后,就可以按需给用户分配容器了。

克隆容器

1
2
3
4
5
6
7
8
# 克隆容器 参数一为模板容器名称,参数二为目标容器名称
sudo lxc copy <ContainerTemplateName> <newContainerName>

# 运行新容器
sudo lxc start <newContainerName>

# 进入新容器bash
sudo lxc exec <newContainerName> bash

修改hostname

克隆的容器还保留着模板的hostname,看起来令人不悦。快速修改hostname的步骤如下:

1
2
3
4
5
6
7
8
9
10
11
# 进入新容器bash
sudo lxc exec <newContainerName> bash

# 将旧的hostname 改成新的
sudo vim /etc/hostname

# 将旧的hostname改成新的,在127.0.0.1后面
sudo vim /etc/hosts

# 重启生效
sudo reboot

修改SSH秘钥文件

克隆的容器还保留着模板的SSH id_rsa文件,要是大家都用一份id_rsa文件访问不同的容器,就太扯了。所以要生成新的id_rsa文件。快速修改id_rsa文件步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 进入新容器bash
sudo lxc exec <newContainerName> bash

# 注意切换成ubuntu用户
su ubuntu

# 进入SSH目录
cd ~/.ssh/

# 生成新的id_rsa文件
ssh-keygen -t rsa

# 复制公钥内容到authorized_keys
cat id_rsa.pub >> authorized_keys

# 重启SSH服务
sudo service ssh restart

# 记得将新生成的id_rsa文件拷贝到外部,共SSH访问使用

修改frp映射端口

道理很简单,要能访问不同的容器,就要将不同容器的SSH 22端口映射到宿主机不同的端口上。

直接修改容器中的frpc.ini,将转发实例中的括号名和remote_port。

然后容器执行./frpc -c frpc.ini 连接服务即可。

交付容器

把容器的SSH秘钥文件、SSH访问IP和SSH访问端口交付给同学即可。

Tips 记得交付之前,调用LXD的快照(snapshot)进行初始版本的备份,免得后面弄砸了,又要重新配置。

执行sudo lxc snapshot <ContainerName>进行快照。

第五步:拓展

LXDUI 可视化管理界面

LXDUI是一个LXD/LXC的Web UI工具,支持LXD/LXC的一些基本操作。

具体使用请参考:lxdui GitRepo

效果如下:

lxdui

lxdui

##资源限制问题

目前是公平的给大家所有的硬件访问权限,后面如果涉及到资源拥挤的话,不可避免需要对每个人的资源进行限制,资源限制请参考:LXD 2.0 系列(四):资源控制

图形界面

目前我还没有实现图形界面,比较担心的是大家都用图形界面的话,带宽会不会被占用太多。

NAS资源访问

NAS后期更新。

驱动的更新

宿主机的软件一般不用更新,毕竟能够维持容器运行即可,所有的操作反正都在容器中进行。

但偶尔难免会出现打开终端双手不受控制的输入update和upgrade的情况,一般情况都还行,但是万一更新显卡驱动,就有糟心事了。

但是别慌,如果由于宿主机更新显卡驱动,导致容器显卡没法用了,就按照之前的安装容器显卡驱动一样,下载和宿主机显卡驱动版本相同的驱动文件,拷到容器内,无内核安装即可。

2019.1.9 更新:关于镜像自动备份

使用固然方便,如果能替大家实现定时备份容器的镜像就更好了。可以弄崩溃了恢复原来的备份即可。

解决方案参考:

LXD Automatic snapshotting

lxdsnap

需要注意的地方就是,关注一下代码和定时设置:定时间隔?哪些要保存?哪些过期删除?千万不要误删了重要的备份节点。

参考链接

这次的安装记录整理的实在太累了。下次都上带个相机,直接录下来就好了。