Banner

背景

遥感所买了两台深度学习服务器,各有4*2080Ti显卡,256G内存,希望各个实验室能够共享使用。共享使用最大难题就是用户之间的环境冲突问题,不同的人需要不同的软件、不同的版本,权限也难以管理,如果处理不好后续肯定需要各种折腾。

所以在安装之前我仔细调研了一下,发现主要有两种方案:每人开账号LXD容器化,两者各有优缺点:

  • **每人开账号:**主机配置好深度学习环境,为各个用户开账号,用户没有sudo权限。优点是可以统一设置防火墙,ssh端口,密码强度,密码尝试次数,够安全;用户可以开启jupyter端口;共享数据方便,配置简单。缺点是用户软件环境容易冲突,自己的数据容易被别人看到。
  • **LXD容器化:**使用LXD等容器技术,为每个用户分配单独的虚拟主机,虚拟主机可以共享GPU硬盘内存资源。优点是用户环境隔离,自己有最大权限,可以自由安装软件,数据安全。缺点是安全难以保证,因为用户有虚拟机内的权限,容易设置简单密码,开危险的服务。

最后权衡之后决定使用LXD容器化的方案,然后额外注意安全设置就可以,简单方案如下:

  1. 宿主机安装LXD,为不同的实验室分配不同的虚拟机,每个实验室有一个虚拟机管理员,可以在虚拟机内给实验室的同学开账号。
  2. 虚拟机设置能够共享GPU、内存、CPU,存储相互隔离,服务器的固态硬盘所有虚拟机共享。
  3. 虚拟机通过主机NAT上网。然后通过端转发的方式,将ssh端口转发到宿主机上,这样用户可以通过宿主机的ip进行ssh连接,同时为每个虚拟机预留几个端口(用于开jupyter等)转发到宿主机上。
  4. 虚拟机设置用户只能通过密钥登录,禁止密码登录,这样放置用户设危险的弱密码。
  5. 宿主机加防火墙,设置ipv4只允许校内访问,ipv6只允许主动访问,这样每个虚拟机也受此规则限制,防止被攻击。

宿主机安装配置

给机械硬盘做RAID10,宿主机系统安装在这上面,后续虚拟机也安装在这上面,这样能减小系统崩溃,数据丢失的可能性。宿主机安装Ubuntu 18.04.3 LTS,系统只分配500G的空间就可以了,其余的空间不分配,用于后续新建zfs pool。

开启iptables,编辑/etc/iptables/rules.v4(v6),使得ipv4只允许校内访问,ipv6只允许主机主动访问外网。随后apt换成清华源,安装sshguard

$ sudo apt install iptables-persistent netfilter-persistent sshguard
$ netfilter-persistent start

设置自动清理和自动进行安全更新,安装build-essential,安装显卡驱动,然后安装nvidia container runtime,这样到时候可以配置宿主机与虚拟机之间直接共享显卡驱动。

安装zfslxd,然后运行lxd init,在余下的硬盘上新建pool用来安装虚拟机,格式用zfs,建立NAT网络:

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]: hdd_pool
Name of the storage backend to use (btrfs, dir, lvm, zfs) [default=zfs]: 
Create a new ZFS pool? (yes/no) [default=yes]: 
Would you like to use an existing block device? (yes/no) [default=no]: yes
Path to the existing block device: /dev/sda6
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]: 
What should the new bridge be called? [default=lxdbr0]: 
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: 
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: 
Would you like LXD to be available over the network? (yes/no) [default=no]: yes
Address to bind LXD to (not including port) [default=all]: 
Port to bind LXD to [default=8443]: 
Trust password for new clients: 
Again: 
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]: no

然后sudo lxc profile edit default编辑默认的配置,主要配置硬盘,内存,显卡的共享:

config:
  nvidia.runtime: "true"
  security.nesting: "true"
description: Default LXD profile
devices:
  eth0:
    name: eth0
    nictype: bridged
    parent: lxdbr0
    type: nic
  gpu:
    type: gpu
  root:
    path: /
    pool: hdd_pool
    size: 3TB
    type: disk
  shared_ssd:
    optional: "true"
    path: /shared_ssd
    pool: ssd_pool
    source: shared_ssd
    type: disk
name: default
used_by:

这样宿主机基本就配置好了,每次新开虚拟机的时候都会默认应用这个default profile,就能共享GPU。宿主机配置后,开始制作镜像。

制作与启动镜像

镜像主要根据需求来制作,工作量不大。首先pull下来Ubuntu 18.04的镜像,开一个虚拟机:

$ sudo lxc remote add tuna-daily https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/daily/ --protocol=simplestreams --public
$ sudo lxc launch tuna-images:ubuntu/18.04 template

然后在虚拟机内测试nvidia-smi看是否列出显卡: 显卡测试

修改/etc/ssh/sshd_config,设置PasswordAuthentication no,这样就禁止使用密码登录虚拟机了。由于用户有虚拟机root权限,后续可以修改这个配置文件,如果不放心,可以通过lxd文件映射的方式,在宿主机上新建一个配置文件,然后映射至虚拟机内,这样虚拟机内就无法修改这个配置文件,例如在主机新建一个sshd_config文件,在default profile,的devices项加入:

# 配置文件映射,可选
  sshd_config_file:
    path: /etc/ssh/sshd_config
    source: /home/ubuntu/manage_tools/conf_for_users/sshd_config
    type: disk

安装sshguard,设置ssh只允许密钥登录,apt换清华源,设置自动安装和清理周期,自动安全更新,安装常用软件,然后在用户目录下放置一些常用的安装包如Conda,CUDA。同时也可以修改/etc/motd,自定义虚拟机ssh登录的时候的banner,提醒用户阅读注意事项等。

然后将虚拟机保存为镜像:

sudo lxc publish template template-image

最后编写脚本帮助添加与删除虚拟机,容器的内存、CPU、硬盘、显卡共享已经通过Profile设置好,主要是把虚拟机的登录端口映射到主机上,这样用户通过主机的不同的端口就登录到了不同的虚拟机了。

具体规则是在这样:虚拟机的8880-8889端口被映射到主机连续的10个端口上,其中8880是虚拟机的ssh端口,被映射到主机的起始端口上,用户开启jupyter等服务之后可以通过映射后的端⼝访问(例如,ssh登陆时的端口是20000,那么虚拟机的8880-8889端口映射为主机的20000-20009端口了,在虚拟机开jupyter为8888端口,通过主机的20008端口就能访问了)。操作上,使用下面命令添加端口映射:

$ sudo lxc config device add test test_proxy proxy listen=tcp:0.0.0.0:20222 connect=tcp:localhost:22

脚本编写完后,用户申请虚拟机时,先生成公钥私钥对,将公钥发到管理员这里来,管理员通过脚本开启新的虚拟机,然后将公钥写入虚拟机的账户下,这样用户就可以直接凭私钥登录服务器了。

这样,在管理员可以很简单地给每个实验室分配虚拟机,用户从宿主机上不同的端口登录自己的虚拟机,可以愉快地在自己的虚拟机内跑实验了:)

例子