环境说明:
操作系统:ubuntu~18.04
Docker版本:19.03.3
PyCharm版本:2019.2.2 (Professional Edition)
ps. 本文操作指南在其他环境未必适用,欢迎参考。
转载请注明出处(https://www.jianshu.com/p/4f78bbaaf844) 如果觉得有用麻烦点个赞噢~
从github上下载别人的开源代码,往往发现工程里有一个文件叫Dockerfile
或者requirements.txt
,就算这两个文件都没有,但一般也会在某处说明一下工程适用的运行环境。Docker是一个不错的工具,用来在同一个电脑上轻松搭建不同的环境来支撑对不同工程的运行。
现在希望在一台具备独立显卡的电脑上运行docker,并让其他电脑可以远程使用Docker里的python作为PyCharm的解析器(Interpreter)。这样的好处是,我只需要在一台充当服务器的电脑上运行docker,根据工程所要求的环境配置独立的docker容器,然后在其他电脑上共享这些容器,用于调试或运行工程。下面我将运行docker服务的电脑称为服务器
,而远程访问docker容器的电脑称为客户端
。
这里有一些前提准备,本文不涉及:
- 服务器已经安装好了Docker CE
- 客户端已经安装好了PyCharm,并且是专业(付费)版的,据说社区免费版没有配置远程Interpreter的功能。
- 服务器具有公网可访问的IP
- 拥有一个动态域名,可解析到上一点所提的IP。
- 如果跟我一样,服务器就是家里的一个台式电脑,由于家庭网络通常都是动态IP,那么就需要一个动态域名(花生壳不错)。另外如果发现公网访问不进来,一般是因为宽带运营商没给你开通公网访问,打客服电话申请以下即可。最后还要配置路由,做好端口映射,才能将外部的访问转发到这个台式机上来。
一. 配置Docker Server
仅仅开放远程访问Docker API,这个还不够的,因为会有安全问题。关于这点,Docker有相关的安全机制,参考官方文档Protect the Docker daemon socket,大致就是:生成证书,用来达到验证客户端身份的目的。下面是操作步骤:
以下所有命令里的
$HOST
都替换成你给服务器的域名(DNS name)
* 找个合适的目录存储将要生成的文件
$ mkdir -pv /etc/docker/certs
$ cd /etc/docker/certs
* (服务器上执行)生成CA公私玥。
$ openssl genrsa -aes256 -out ca-key.pem 4096
Generating RSA private key, 4096 bit long modulus
......................++
........++
e is 65537 (0x10001)
Enter pass phrase for ca-key.pem:
Verifying - Enter pass phrase for ca-key.pem:
$ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
Enter pass phrase for ca-key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:Queensland
Locality Name (eg, city) []:Brisbane
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Docker Inc
Organizational Unit Name (eg, section) []:Sales
Common Name (e.g. server FQDN or YOUR name) []:$HOST
Email Address []:Sven@home.org.au
* (服务器上执行)生成server key
和CSR
$ openssl genrsa -out server-key.pem 4096
Generating RSA private key, 4096 bit long modulus
...............++
...............................++
e is 65537 (0x10001)
$ openssl req -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr
* (服务器上执行)用CA
签署一个公钥,由于TLS连接可以通过DNS域名也可以通过IP地址,这里需要指定这些信息(对于IP地址,建议设置两个,一个是127.0.0.1供本地访问,另一个是内网IP供局域网访问)
$ echo subjectAltName = DNS:$HOST,IP:10.10.10.20,IP:127.0.0.1 >> extfile.cnf
* (服务器上执行)设置这些key仅用于服务器鉴权
$ echo extendedKeyUsage = serverAuth >> extfile.cnf
* (服务器上执行)生成签名证书
$ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out server-cert.pem -extfile extfile.cnf
Signature ok
subject=/CN=your.host.com
Getting CA Private Key
Enter pass phrase for ca-key.pem:
* (服务器上执行)生成client key
和CSR
,供客户端发起远程访问时使用
$ openssl genrsa -out key.pem 4096
Generating RSA private key, 4096 bit long modulus
.........................................................++
................++
e is 65537 (0x10001)
$ openssl req -subj '/CN=client' -new -key key.pem -out client.csr
* (服务器上执行)配置这些key是供客户端鉴权使用的
$ echo extendedKeyUsage = clientAuth > extfile-client.cnf
* (服务器上执行)生成签名证书
$ openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out cert.pem -extfile extfile-client.cnf
Signature ok
subject=/CN=client
Getting CA Private Key
Enter pass phrase for ca-key.pem:
* (服务器上执行)确定已经生成好cert.pem
和server-cert.pem
之后,可以删除那几个CSR和cnf文件
$ rm -v client.csr server.csr extfile.cnf extfile-client.cnf
* (服务器上执行)为防止私钥文件被更改以及被其他用户查看,修改其权限
$ chmod -v 0400 ca-key.pem key.pem server-key.pem
* (服务器上执行)为防止公钥文件被更改,修改其权限
$ chmod -v 0444 ca.pem server-cert.pem cert.pem
* 测试是否已生效
# 将客户端的证书文件拷贝到客户端,包括:ca.pem,cert.pem,key.pem
# 假设放到 ~/.docker/certs/目录里:mkdir -pv ~/.docker/certs/; cd ~/.docker/certs/; scp ??? ./
# 在服务器上启动docker服务器
$ dockerd --tlsverify --tlscacert=/etc/docker/certs/ca.pem \
--tlscert=/etc/docker/certs/server-cert.pem \
--tlskey=/etc/docker/certs/server-key.pem \
-H=0.0.0.0:2376
# 在客户端执行
$ docker --tlsverify \
--tlscacert=/Users/howard/.docker/certs/ca.pem \
--tlscert=/Users/howard/.docker/certs/cert.pem \
--tlskey=/Users/howard/.docker/certs/key.pem \
-H=$HOST:2376 version
# 看到输出docker信息就说明通了
# 通过curl来访问docker api
$ curl https://$HOST:2376/images/json \
--cert ~/.docker/certs/cert.pem \
--key ~/.docker/certs/key.pem \
--cacert ~/.docker/certs/ca.pem
* (服务器上执行)配置Docker Server默认以此方式启动
# 拷贝安装包单元文件到/etc,这样就不会因为docker升级而被覆盖
$ cp /lib/systemd/system/docker.service /etc/systemd/system/docker.service
# 更新文件/etc/systemd/system/docker.service里的ExecStart设置为:
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376 \
--tlsverify --tlscacert=/etc/docker/certs/ca.pem \
--tlskey=/etc/docker/certs/server-key.pem \
--tlscert=/etc/docker/certs/server-cert.pem
# 重载systemd配置
$ systemctl daemon-reload
# 重启docker服务
$ systemctl restart docker
* (客户端上执行)配置默认远程调用服务器docker服务
# 配置~/.zshrc(或者~/.bashrc,根据你的客户端环境而定),在末尾添加以下几行
export DOCKER_HOST=tcp://$HOST:2376 DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=~/.docker/certs/
# 然后让加载到当前会话
$ source .zshrc
# 测试以下
$ docker ps
* 务必非常小心保管这些key,它们就跟服务器root密码一样重要,如果被坏人获取,等同于将服务器交出去了
二. 配置PyCharm
以下所有命令里的
$HOST
都替换成你给服务器的域名(DNS name)
* 打开配置(Preferences)窗口
* 选中Build,Execution,Deployment下的Docker,并点击添加一个Docker服务
* 选择TCP socket,Engine API URL
栏输入:https://$HOST:2376
,Certificates folder
栏输入那几个证书所在的目录
* 点击其他任意地方或者点击Apply按钮,如果成功连接,会看到提示:Connection successful
* 选择Project下的Project Interpreter,点击新增Project Interpreter
* 这里可以看见有一个叫Docker,一个叫Docker Compose。我们应该选择Docker Compose。
我们先明确一点,当我们在PyCharm中运行/调试代码时,远程调用服务器上的docker容器其实是临时创建的新容器,本次运行/调试结束后会自动被关闭。
而我们会在PyCharm中修改代码,我们需要能自动将变动同步到服务器的代码中,而服务器上的代码要能被临时启动的docker容器访问到。所以,临时启动的docker容器需要挂载相应的工程代码。
这个就是选择Docker Compose的原因所在,它可以读取预先配置好的docker-compose.yml文件,以我们预设的方式启动容器。我们可以在工程下新增一个文件命名为:docker-compose.yml
,内容类似于:
version: '3'
services:
interpreter:
image: detectron2:v0
volumes:
- /home/howard/Nutstore Files/fish:/fish
working_dir: /fish/detectron2
我们需要先写好这个文件后才能继续下面的步骤。
* 选择Docker Compose,然后Server栏选择前面创建的Docker server,在Configuration file栏选择上一步创建的yml文件,这个时候如果yml文件编写正确,可以在Service栏看到可选的服务,最后在Python interpreter path栏输入镜像里的python解析器路径(如果在环境变量里,那么也可以在直接写一个命令名python3)
* 回到上一级窗口,还要设置一下Path mappings,将本地工程目录跟docker容器里的路径关联起来
* 但本地的代码修改后,怎么同步到服务器上呢?这里只给思路,具体方法请自行搜索。一种同步方案是,通过类似坚果云之类的云盘服务,自动同步代码变动到服务器;另一种方案是,通过PyCharm自带的Deployment功能手动/自动同步到服务器。
三. Docker部署支持GPU的深度学习环境
这个主要依靠NVIDIA提供的nvidia-docker工具包来实现,参见其github:nvidia-docker。其github上已经有非常详细的安装和测试说明了。下面拷贝ubuntu环境的指南过来。
Make sure you have installed the NVIDIA driver and Docker 19.03 for your Linux distribution Note that you do not need to install the CUDA toolkit on the host, but the driver needs to be installed
* Ubuntu 16.04/18.04, Debian Jessie/Stretch/Buster
# Add the package repositories
$ 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-container-toolkit
$ sudo systemctl restart docker
* Usage
#### Test nvidia-smi with the latest official CUDA image
$ docker run --gpus all nvidia/cuda:9.0-base nvidia-smi
# Start a GPU enabled container on two GPUs
$ docker run --gpus 2 nvidia/cuda:9.0-base nvidia-smi
# Starting a GPU enabled container on specific GPUs
$ docker run --gpus '"device=1,2"' nvidia/cuda:9.0-base nvidia-smi
$ docker run --gpus '"device=UUID-ABCDEF,1"' nvidia/cuda:9.0-base nvidia-smi
# Specifying a capability (graphics, compute, ...) for my container
# Note this is rarely if ever used this way
$ docker run --gpus all,capabilities=utility nvidia/cuda:9.0-base nvidia-smi
* 如何通过docker-compose部署GPU容器?
- 安装nvidia-docker2
$ sudo apt install nvidia-docker2
- 修改/etc/docker/daemon.json,加入一下内容:
{
"default-runtime": "nvidia",
"runtimes": {
"nvidia": {
"path": "/usr/bin/nvidia-container-runtime",
"runtimeArgs": []
}
}
}
- 测试是否成功
# 编辑docker-compose.yml文件,内容如下
version: '3'
services:
cuda:
image: nvidia/cuda:9.0-base
# 然后执行下面命令
$ docker-compose run cuda nvidia-smi
# 如果能输出显卡信息,那么说明成功