WSL下基于Docker的深度学习环境配置

为了兼顾学习和生活,之前在个人电脑上做了双系统,ubuntu用来跑模型,windows来玩游戏。这学期毕设实验做完之后,就没怎么开过ubuntu了。这两天邻近毕业准备做一点康复训练,但ubuntu又不太适合边跑小模型边摸鱼,所以打算在windows下用WSL做一个开发环境。另外这回不想用conda做环境隔离了,就干脆直接配了一套Docker的环境。(真希望所有开源代码实现都能搞个镜像让我直接pull下来啊)

WSL安装

windows侧的WSL安装基本按照官方文档:https://docs.microsoft.com/zh-cn/windows/wsl/install

· 首先安装适用于Linux的windows子系统。Linux发行版本可选,我选用了Ubuntu。

wsl --install -d Ubuntu

安装完成之后可以使用wsl -l -v查看:

状态状态

需要注意的是VERSION这一列的值,1/2表示WSL1和WSL2,只有WSL2支持GPU,所以如果是1的话需要手动拉到WSL2。使用wsl --set-version <distro name> 2进行升级。这边需要开启windows的虚拟机功能。

虚拟化虚拟化

这里应该还需要去bios里开启虚拟机,我的是微星主板,AMD 3700X,我的修改方式是进入BIOS页面后,找到“OC”——“SVM Mode” 选项,把“Disabled”修改为“Enabled”。完成设置之后,在任务管理器里可以看到

虚拟化开启虚拟化开启

到这一步后,可以在终端输入wsl尝试进入,我在这一步还会有一步报错:

请启用虚拟机平台 Windows 功能并确保在 BIOS 中启用虚拟化。

解决方案是bcdedit /set hypervisorlaunchtype auto。 输入后再次重启WSL就可以正常进入了。

Windows下CUDA安装

Windows环境下需要安装一下对应的显卡驱动,我忘了是我之前配的环境还是Nvidia Geforce Experience自己给我装的驱动了,我的机子上是已经有对应的驱动了,表现反应是命令行里nvidia-smi是有反馈输出的。

如果是从零开始配置的话可以根据官方文档进行配置:https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html

Docker 安装 & Nvidia Docker踩坑

最开始安装的是windows下的Docker Desktop,普通的镜像安装进入没有什么问题,但在按照Nvidia官方教程配置之后使用--gpus all这个指令的时候遇到报错

exit status 1, stdout: , stderr: nvidia-container-cli: initialization error: driver error: failed to process request\\n\""": unknown

开始是按照11.7.0的CUDA文档进行的安装:https://docs.nvidia.com/cuda/archive/11.7.0/wsl-user-guide/index.html#installing-nvidia-drivers

wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin
sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda-repo-wsl-ubuntu-11-7-local_11.7.0-1_amd64.deb
sudo dpkg -i cuda-repo-wsl-ubuntu-11-7-local_11.7.0-1_amd64.deb
sudo apt-get update
sudo apt-get -y install cuda

最后是上面那个报错,这个方案没有走通,但中间碰到了一个小问题也记录一下,主要问题就是在解压deb包的时候由于没有公钥,无法验证签名

W: GPG error: file:/var/cuda-repo-wsl-ubuntu-11-7-local InRelease: NO_PUBKEY FAF69C646FF368B7

当时忙着解决问题,忘记截图了,大概是这么个报错,就是PUBKEY会不一样,解决方案是去请求一个公钥回来,用sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys FAF69C646FF368B7 。对于不同的系统keyserver也不一样。

对比了一下deb包和电脑上的驱动的版本,发现我自己的是11.6.1的CUDA版本,觉得有可能是版本不同的问题,于是去找了11.6.1版本的文档:

https://docs.nvidia.com/cuda/archive/11.6.1/wsl-user-guide/index.html#installing-nvidia-drivers

命令如下:

curl https://get.docker.com | sh   
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
sudo apt-get install -y nvidia-docker2      

这版本的安装和11.7.0略微不太一样,需要安装一个nvidia-docker2,跟着安装之后,在service docker stop/start一步遇到了问题:

docker: unrecognized service

找了很多回答都是建议删除docker重新安装的,之后我尝试了不使用Docker Desktop而在WSL里直接用apt-get install docker.io的,但报错信息一样。之后又找啊找啊找,找到了另一个在WSL下安装linux原生docker的方法,命令如下:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo service docker start

这种方法安装后可以通过service docker stop/start 启停docker服务, 之后再根据11.6.1的文档安装nvidia-docker2。用

docker run --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark

进行测试。可以调用GPU。

VS Code 开发环境配置

由于没有使用Docker Desktop,所以在VS Code里的远程资源管理器里无法找到WSL里启动的容器,为了使开发的过程更加遍历,选择使用ssh进行连接。

这里我拉了一个tensorflow1.4.1+py2.7的镜像,后续都以这个镜像为例。

docker run -it -d --name CAN --net host --gpus all -v /mnt/d/wsl_workspcae:/workspace can:gpu_v3 tail -f /dev/null

-d 使容器在后台运行,tail -f /dev/null使容器有个事做,免得没事被kill掉。--net host把里面的端口全都映射出来,这里其实只要映射ssh的22端口即可,我偷懒而且也不跑什么网络相关的项目,就全映射出来了。--gpus all 使容器内能调用GPU。这样就只需要在容器内部开一个ssh服务就可以在windows下的VS Code中进行开发了。

ssh服务配置

和纯Ubuntu环境下一样,只需要安装openssh-server即可

sudo apt update                         #  更新源
sudo apt install openssh-server         # 安装ssh服务
sudo service ssh start/stop/restart

这里需要配置一下/etc/sshd_config 中的PasswordAuthentication,默认应该是no,改成yes,否则外面使用密码连接会有报错。

为了方便外面连接,还做了一个免密登录,主要是通过ssh-keygen生成公钥和密钥,在Docker的~/.ssh文件夹下新建一个authorized_keys,把id_rsa.pub粘到里面,在windows下的C:\Users{username}\.ssh文件夹下放id_rsa文件,之后在对容器内的文件授权,chmod 600 authorized_keys,chmod 700 .(当前目录为~/.ssh)。

在/etc/sshd_config中的相关配置如下:

RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile      %h/.ssh/authorized_keys

至此就可以不用密码进行ssh连接了。

啊,还有一个是wsl的地址,需要用到:https://www.ohyee.cc/post/note_wsl2_net文章里提到的方法进行获取和自动写入。

ip addr show eth0 | grep 'inet ' | cut -f 6 -d ' ' | cut -f 1 -d '/')是用来获取容器地址的方法。每次网络变化生成的ip地址都不同,所以需要自动获取并写入,方法如下:

需要修改的文件为hosts(C:\Windows\System32\drivers\etc\hosts),在文件最后写入

123.123.123		winip
123.123.123		wslip

这边开始写入的两个地址没有关系,因为后续会覆写掉。同时需要Windows 设置可写入,右键 host 文件,属性-安全-高级,把 Windows 登录用户给予“完全控制”权限即可。

之后在wsl的 ~/.local.bashrc文件中增加

# WSL 
export WINIP=$(cat /etc/resolv.conf | grep 'nameserver' | cut -f 2 -d ' ')
export WSLIP=$(ip addr show eth0 | grep 'inet ' | cut -f 6 -d ' ' | cut -f 1 -d '/')

vim "+:%s/^.*winip/$WINIP\t\twinip/g" "+:%s/^.*wslip/$WSLIP\t\twslip/g" '+:wq' -E /mnt/c/Windows/System32/drivers/etc/hosts

即可完成自动配置。

进入WSL自动启动容器并启动ssh服务

每次想要连接进容器都要先启动wsl,再进入容器开启ssh,这样会显得挺麻烦的。为了方便,在容器内的bashrc和wsl的bashrc分别配置了一些脚本,这样每次进入加载~/.bashrc的时候都会自动进行连接。

WSL侧:

function can_env(){
    service docker start
    sleep 3
    docker start CAN
    sleep 3
    docker exec -itd CAN /bin/bash
}
can_env

加入如下脚本,每次开启wsl会以非交互的形式进入CAN(配置的环境)

容器侧:

function check_ssh(){
    SERVICE="ssh"
    netstat -anp | grep $SERVICE &> /dev/null
    if [ $? -eq 0 ]
    then
        echo "already exists"
    else
        echo "starting"
        service  $SERVICE  start
    fi
}
check_ssh

加入一个服务检测,检测是否启动了ssh服务,若无,启动。

配置完成之后,当启动wsl并加载bashrc之后,系统会进入容器并加载容器内的检测启动脚本,从而实现自动化。