Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖到一个可移植的镜像中,然后发布到Linux或Window系统中,也可以实现虚拟化。容器是完全使用沙箱机制,相互之前不会有任何接口。
- 1. Docker
- 2. 镜像&容器
- 3. 使用Docker运行程序
- 4. 在容器中运行jupyter notebook
- 5. 在容器中启动tensorboard
- 6. docker commit镜像
- 7. 构建自己的镜像(这个不是必须的)
- 8. 我的镜像
1. Docker
参考资料:
资料1
Docker就是一个运行在操作系统上的软件。这个软件上运行很多容器,这些容器相互独立,相互隔离。容器中可以安装很多应用程序。
我们平时要在Windows上安装Linux系统,都需要先安装一个VMWare,然后在上面安装Linux系统。原理就是虚拟出一套硬件资源,然后在上面运行一个完整的操作系统,再在操作系统上运行所需要的应用程序。在安装虚拟机时,需要先提前给虚拟机分配硬盘,内存等资源。一旦分配,这些资源就被虚拟机全部占用。Docker也可以实现虚拟化。但是Docker没有自己的内核,Docker容器内的应用程序是直接运行在宿主的内核,Docker比传统的虚拟机更轻便。Docker就是一个软件。如果以后在Windows上安装Linux系统,可以先在本地电脑上安装一个Windows版本的Docker。
2. 镜像&容器
2.1. 镜像
官方定义:Docker镜像是一个只读模板,可以用来创建Docker容器。镜像是一种轻量级的,可执行的独立软件包,软件和依赖的环境可以打包成一个镜像。这个镜像包含某个软件需要的所有内容,包括代码、库、环境变量、配置文件等。
比如我们开发的Web应用需要JDK,Tomcat,环境变量等。那我们就可以把这些都打包成一个镜像,包括代码+JDK+Tomcat+CentOS系统+各种配置文件等。打包后的镜像如果可以运行,那么这个镜像就可以在任何安装有Docker的电脑上运行。
任何镜像的创建会基于其他的父镜像,也就是说镜像是一层套一层的。比如一个Tomcat镜像需要运行在CentOS上面,那我们的Tomcat镜像就会基于CentOS镜像创建。
2.2. 容器
Docker的容器是用镜像创建的运行实例,Docker可以利用容器独立运行一个或一组应用。我们可以使用客户端或API控制容器的启动、开始、停止、删除。每个容器都是相互独立的。上一步创建的镜像是一个静态的文件,这个文件想要运行的话,就要先变成容器。我们可以把容器看做是一个简易版的Linux系统和运行在上面的程序。
镜像和容器的关系
类似于Java中的类和对象的关系。镜像可以看做一个类,容器是镜像的一个实例。可以根据一个类new很多个实例,new出来的实例就相当于一个个容器。镜像是静态的文件,容器是有生命的个体。
容器的状态
docker容器有几种状态,分别是created、up、exited、paused。
- 我们通过
run
命令运行容器时,其实是将容器从created到up状态的过程。 当通过
stop
命令停止容器时,容器进入exited状态。容器退出后,系统仍然保存该容器实例,即退出的容器仍然会占用系统的硬盘资源,需要使用rm删除该容器才能完全清除容器的资源占用。容器stop或Ctrl+D时,会保存当前容器的状态之后退出,下次start时会保存上次的关闭时更改,而且每次attach进去的界面是一样的,和第一次run启动一样。1
2[root@localhost ~]
e83cf32fbc22重新启动退出的容器
处于exited状态的容器,可以通过start命令重新启动。1
2[root@localhost ~]
e83cf32fbc22重启容器
1
2[root@localhost ~]
e83cf32fbc22
2.3. 镜像命令
docker images
查看本地主机上所有的镜像。注意是本地主机的!这里能看到镜像的名称、版本、id、大小等基本信息,注意这里的 imageID 是镜像的唯一标识!docker pull 镜像名:标签
从镜像官网docker hub,找到自己需要的镜像,并拉取镜像docker pull mxnet/python:1.4.1_gpu_cu90_mkl_py3
docker push 镜像名:标签
例如docker push lin-ai-27:5000/mxnet:1.41_cu100_py3
将镜像push到本地服务器中的仓库docker ps
查看容器详细信息docker ps
:显示当前正在运行的容器,在 PORTS 一列,如果暴露的端口是连续的,还会被合并在一起,例如一个容器暴露了3个 TCP 端口:100,101,102,则会显示为 100-102/tcp。docker ps -a
:显示所有的容器,
容器的状态共有 7 种:created、restarting、running、removing、paused、exited、dead。docker ps -n 3
:显示最后被创建的n个容器docker ps -q
:只显示正在运行容器的id,在清理容器时非常好用。docker ps -s
:显示容器文件大小,该命令很实用,可以获得 2 个数值:一个是容器真实增加的大小,一个是整个容器的虚拟大小。
docker run
:通过镜像创建一个容器
案例1:docker run -v $PWD:/root -d -ti --runtime=nvidia --name wbbmxnet -u 1042 mxnet/python:1.4.1_gpu_cu90_mkl_py3
案例2:
docker run -itd --runtime=nvidia -v $PWD:/workdir/ --name wbb -u 1042 songchao/tensorflow:1.9.0_py36_cu90_cudnn7 /bin/bash
下面介绍
docker run
中的一些参数-d
:启动容器,并且后台运行(Docker 容器后台运行,就必须要有一个前台进程,容器运行的命令如果不是一直挂起的命令,容器启动后就会自动退出)。使用-d不会进入docker的交互界面,只会返回一个长id。使用docker ps可以看到对应的短id,使用docker attach 短id,进入到docker的交互界面。-i
:以交互模式运行容器,通常与 -t 同时使用。-t
:为容器重新分配一个伪输入终端,通常与 -i 同时使用(容器启动后进入到容器内部的命令窗口)。-P
:随机端口映射,容器内部端口随机映射到主机的高端口。-p
:指定端口映射,格式为:主机(宿主)端口:容器端口。-v
:建立宿主机与容器目录的同步。--name="myTomcat"
:为容器指定一个名称(如果不指定,则有个随机的名字)。--rm
:表示程序运行完,这个容器就删掉了,这和参数不能和-d
同时使用-bash
表示打开一个命令行
docker attach 容器id或name
当容器在后台运行时,我们使用容器的id和name进入到容器,推荐使用容器名字,更好记。通过容器id进入时,我们需要先使用docker ps
先查看容器的id
,然后使用这个id进入docker tag source des
镜像重命名docker rmi
删除本地的镜像,如下图所示,可以加上 -f 参数进行强制删除。
这里的 rmi 命令跟 Linux 中的删除命令就很像啦,只是这里加了一个 i 代表 image!docker rm
删除容器。删除容器前首先通过stop
命令停止容器,容器的相关文件仍然存储在宿主主机中,为了释放这部分空间,需要删除这些容器。
(1)先将容器停止docker stop e83cf32fbc22
(2)docker rm id
可以删除已经停止的对应id的容器
(3)批量删除除了运行以外的程序docker rm $(docker ps -a -q)
(4)如果要批量删除指定状态的容器docker rm $(docker ps -a -q status=exited)
docker search
根据镜像名称搜索远程仓库中的镜像!
- Ctrl+P+Q把容器挂在后台
3. 使用Docker运行程序
登录上网
上网的目的是能够访问docker镜像官网,我们才可以从官网上拉取镜像links 10.1.61.1/a30.htm
登录之后按Ctrl+C
退出在dockerhub上查询镜像
如果你在服务上已经有镜像了,就不需要执行步骤2和3
浏览器进入docker hub,找到自己需要的镜像,gpu28号服务器Cuda的版本是9.0,下载的镜像需要和服务器上Cuda版本一致。
使用cat /usr/local/cuda/version.txt
查看服务器的Cuda版本拉取镜像
例如镜像的全称为:mxnet/python:1.4.1_gpu_cu90_mkl_py3
docker pull mxnet/python:1.4.1_gpu_cu90_mkl_py3
这条命令会把镜像下载到服务器上,如果本地已经存在该镜像,docker会使用本地的镜像已经有的镜像,不再下载,节省空间。将镜像push到个人仓库
在上一步从公共仓库拉取了一个镜像docker pull mxnet/python:1.1_gpu_cu100_mkl_py3
现在将镜像push到27号服务器上面的仓库,然后我们就可以在28/29/53等服务器上都可以访问到这个镜像了。
首先使用tag
对镜像重命名再push
。
在重命名的时候,注意lin-ai-27:5000/
表示push到27服务器上的仓库上,后面可以自定义自己的名字docker tag mxnet/python:1.1_gpu_cu100_mkl_py3 lin-ai-27:5000/mxnet:1.41_cu100_py3
向27号仓库中推送镜像docker push lin-ai-27:5000/mxnet:1.41_cu100_py3
在别的服务器上将刚才的镜像pull下来
将镜像push到27号服务器的仓库上,在其他服务器上就可以访问到这个镜像,比如我们要在53号服务器上跑程序,我们就可以在53号服务器上使用docker pull
命令把镜像拉取到53号服务器上docker pull lin-ai-27:5000/mxnet:1.41_cu100_py3
镜像重命名
在53号服务器拉取到镜像后,可以再给镜像重命名,也可以使用原先的名字docker tag 镜像id wangbeibei/mxnetx:latest
通过镜像启动容器
有了镜像之后,我们就可以使用了。通过镜像启动容器,然后在容器里运行自己的代码。在启动容器的时候,我们需要使用
-u
参数来表示这个容器是属于哪个用户的。我们在服务器上使用id
命令来查看,输出结果如下所示,则在这台服务器上我的id就是10421
uid=1042(WangBeibei) gid=1002(insis) groups=1002(insis),999(docker)
我们通过下面的命令来启动容器
docker run -v $PWD:/root -d -ti --runtime=nvidia --name wbbmxnet -u 1042 <镜像名:标签>
例如1
docker run -v $PWD:/root -d -ti --runtime=nvidia --name wbbmxnet -u 1042 mxnet/python:1.4.1_gpu_cu90_mkl_py3
docker run
:启动容器-v
:建立宿主机与容器目录的同步,例如这里我将当前的目录$PWD
和容器中的/root
进行同步,也就是当进入容器的/root
目录时,就可以看到服务器中$PWD
的全部内容。当前这里也可以把服务器中别的目录挂载到/root
中,例如-v /data/WangBeibei/test:/root
中-d
:使用run命令和-d
命令后,会把容器挂到后台运行。如果不指定这个参数,当使用上面的命令启动容器的时候就会直接进入容器。(如果要退出这个容器,使用exit
命令,当使用这个命令的时候,退出容器的同时,这个容器也没了)
推荐的方法:使用-d
命令让容器在后台运行。使用-d不会进入容器的交互界面,会把容器挂到后台运行,并返回一个长的container id。这里的容器id唯一表示一个容器。此时并没有进入容器,我们需要使用id来进入容器。具体方法为:- 我们首先在服务器上通过
docker ps
命令查看当前服务器都有哪些容器,这里输出的是容器id的前12位,下图所示,我们通过docker ps
命令就可以看到刚才启动的容器,可以看到容器的id和name。 - 之后使用
docker attach 容器id或name
(推荐使用name,因为更好记)进入到刚才的容器。 - 然后我们进入容器的
/root
目录,就可以看到该目录下的文件和服务器本地$PWD
下相同的目录。说明服务器本地的$PWD
目录已经挂在到容器上了。此时在容器/root
目录中的文件操作都会同步到服务器本地$PWD
目录下。例如我们运行程序的时候会保存一些内容,当我们即使退出容器的时候,也可以在服务器本地$PWD
下看到这些保存的内容。
- 我们首先在服务器上通过
-ti
:以交互模式运行容器--name xxx
:容器的名字,不同的容器的名字不能一样-u xxx
:标识哪位用户启动了这个容器。如果不指定用户,使用gpustat
就会看到当前运行的程序是属于root用户。容器所挂载的目录所属用户也是root用户。那么你在服务器中去操作这个目录就会提示permission denied。例如在使用docker run
命令创建一个容器时,将服务器的$PWD
这个目录挂在在容器的/root
目录下,那么容器中/root
目录下面的内容就是服务器中$PWD
这个目录下面的内容,在容器中/root
中使用-ls -l
查看文件或目录,所属的用户是root
用户,那么在服务器中的$PWD
目录下面,就没有权限操作当前目录,比如在服务器本地执行mkdir dira
,就会出现permission denied
。所以在启动容器的时候,一定要指定-u 1042
安装额外的包(可选)
如果你用的镜像有些包没有,那么你可以在启动这个容器的使用使用pip install xx
安装所需要的包。这种方式安装的话,这个包只存在这个容器中,让你使用exit
退出这个容器时,容器没了,这个包也就没了。下次再启动容器的时候,需要再次安装。
在容器中install GPU版本的第三方库时,一定要先看一下镜像cuda的版本。第三方库的cuda版本要和镜像的版本一致在容器中运行GPU程序
现在容器已经启动了,我们就可以在容器上运行程序了。
如果程序需要传入gpus的id,我们可以使用gpustat -i 2
查看当前空闲的GPU,使用空闲的GPU运行程序。1
2
3
4
5
6
7
8
9
10#启动容器
docker run -v $PWD:/root -d -ti --runtime=nvidia --name wbbmxnet -u 1042 mxnet/python:1.4.1_gpu_cu90_mkl_py3
#进入容器
docker attach wbbmxnet
#进入容器的/root目录
cd /root
#进入需要py文件所在的目录
cd xxxx
#运行程序
python3 xxx.py --config xx --gpus xx让容器在后台运行
当使用上面的命令在容器上跑上程序之后,我们按Ctrl+P+Q
让容器在后台运行
等到我们想再次进入容器的时候,使用docker attach 容器name
即可退出容器
如果你的程序全都运行完了,不想要这个容器了,直接使用exit
退出容器,并且这个容器也被删除了,再次使用docker ps
就看不到这个容器了。不过这没关系,下次可以再使用docker run
创建另一个容器即可。
4. 在容器中运行jupyter notebook
在服务器上查看用户id
在启动容器之前先查看自己在这台服务器上的id,在服务上直接输入id
就可以查看。在启动容器的时候需要加上-u 1042
表示这个容器是属于哪个用户的。如果不指定用户,使用
gpustat
就会看到当前运行的程序是属于root用户。容器所挂载的目录所属用户也是root用户。那么你在服务器中去操作这个目录就会提示permission denied。例如在使用docker run
命令创建一个容器时,将服务器的$PWD
这个目录挂在在容器的/root
目录下,那么容器中/root
目录下面的内容就是服务器中$PWD
这个目录下面的内容,在容器中/root
中使用-ls -l
查看文件或目录,所属的用户是root
用户,那么在服务器中的$PWD
目录下面,就没有权限操作当前目录,比如在服务器本地执行mkdir dira
,就会出现permission denied
。所以在启动容器的时候,一定要指定-u 1042
启动容器
docker run -it -p 7000:7000 -v $PWD:/root --runtime=nvidia -u 1042 --name wbbJupyter ufoym/deepo:all-jupyter-py36
前面的7000是服务器的端口,后面的7000是容器的端口。由于服务器是所有人共用的,需要找一个没有被占用的端口。容器的端口因为只有你一个人用,所有不会被占用。进入到容器中
通过docker attach wbbJupyter
进入到容器中
cd /root
会看到服务器本地的目录- 在容器中运行jupyter notebook
jupyter notebook --no-browser --ip 0.0.0.0 --port=7000 --allow-root
- 在putty中添加7000的端口号
这里的7000指的是服务器的端口,不是容器的端口
然后再浏览器中输入http://localhost:7000/tree
或者不用在putty中添加端口映射,直接在浏览器中输入http://gpu28:7000,这里的7000指的是服务器的端口。 - 如果想要jupyter notebook在后台运行,按Ctrl+P+Q,会退回到服务器
- 使用docker ps会看到你的容器正在后台运行
- 使用docker attach 容器的名字/id再次进入到容器中,按Ctrl+C会看到当前正在有jupyter notebook运行
5. 在容器中启动tensorboard
启动容器
想在网页中查看tensorboard。在上面那个容器中,因为使用了jupyter notebook,不能运行命令。所以需要使用上面那个镜像再开一个容器。tensorboard的端口默认是:6006,使用下面的命令1
docker run -it -p 6688:6006 -v $PWD:/root --runtime=nvidia -u 1042 --name wbbtensorboard lin-ai-29:5000/dlspree:latest bash
启动tensorboard
进入到容器之后,然后使用下面的命令启动tensorboard--logdir 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
3. 浏览器查看tensorboard
然后在浏览器上http://gpu28:6688,就可以看到在网页上看到
# 6. docker commit镜像
上面我们提到发现这个容器中有一个库没有安装,可以在容器中使用pip install在这个容器中安装使用,但是这个容器删除了之后里面安装的东西也没有了。
有2种方法可以解决这个问题:
- 使用commit从容器生成镜像,但是这种方式不推荐,但是省事
- 写Dockerfile重新build一个镜像
启动一个容器--》在容器中安装环境--》退出容器--》docker commit制作镜像
**在容器中install GPU版本的第三方库时,一定要先看一下镜像cuda的版本。第三方库的cuda版本要和镜像的版本一致**
先使用**root用户**启动一个容器,因为在构造镜像时,Anaconda是使用root用户操作的,即只有root用户才可以install和uninstall第三方库。使用root用户install方库后,然后使用commit命令导出为镜像,别的用户在用这个镜像启动容器。
```python
#root用户启动镜像
docker run -it -p 6688:6006 -v $PWD:/roo--runtime=nvidia --name wbbtensorboard ufoydeepo:all-jupyterpy36v
#在容器中安装需要的第三方库
pip install nni
#退出容器
Ctrl+P+Q
#查看刚刚启动容器的id
docker ps
#从容器中commit镜像
docker commit 容器id wangbeibei/mxnet_nni:latest(新镜像的名字)
#删除原来的镜像,镜像id可以通过docker images查看
docker rmi 镜像id
#将刚才新的镜像重命名
docker tag 镜像id wangbeibei/mxnet:latest
#将新的镜像push到别的服务器上
docker tag wangbeibei/mxnet:latest lin-ai-27:500wangbeibei/mxnet:latest
docker push lin-ai-27:5000/wangbeibei/mxnet:latest
#从其他服务器上pull镜像
docker pull lin-ai-27:5000/wangbeibei/mxnet:latest
Docker的cuda和服务器的cuda是不冲突的。
7. 构建自己的镜像(这个不是必须的)
7.1. 创建Dockerfile
通过Dockerfile构建自己的镜像。镜像构建时,会一层层的构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是该文件一直跟随镜像。因此在创建镜像的时候,需要小心,每一层尽量只添加该层需要的东西,任何额外的东西应该在该层构建结束前清理掉。
Docker能够从Dockerfile中读取指令自动的构建镜像。
在服务器上创建一个目录mkdir wbb_docker_gcn
cd wbb_docker_gcn
可以在这个目录下下载一些创建镜像需要的文件git clone https://github.com/NVIDIA/cuda-samples.git
然后编写用于构建镜像的Dockerfilevi Dockerfile
内容如下:
1 | #指定镜像要构建在哪个镜像之上 |
7.2. 构建
Dockerfile是执行是从上到下顺序执行的,每条执行都会创建一个新的镜像层,并对镜像进行提交。编写好Dockerfile文件后,就需要使用dockerbuild命令对镜像进行构建了。docker build [OPTIONS] PATH | URL | -
docker build -t chaosong/cuda-10.1-cudnn7-devel:with_cuda_samples .
-f:指定要使用的 Dockerfile 路径,如果不指定,则在当前工作目录寻找 Dockerfile 文件!
-t:镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。 注意后面的 . , 用于指定镜像构建过程中的上下文环境的目录。
7.3. 通过镜像启动容器
构建完镜像就可以启动这个容器了,启动完之后就可以运行py脚本docker run -itd --runtime=nvidia -v $PWD:/workdir/ --name wbb -u 1042 songchao/tensorflow:1.9.0_py36_cu90_cudnn7 /bin/bash
由于这个命令非常重要,所以下面列出几个比较重要的参数:
8. 我的镜像
8.1. mxnet
8.1.1. 无hdfs
lin-ai-27:5000/wangbeibei/mxnet:cu_100
8.1.2. 有hdfs
在OpenPai平台上运行程序,使用该镜像lin-ai-27:5000/wangbeibei/mxnet:cu100_hdfs
8.2. pytorch
8.2.1. 无hdfs
lin-ai-27:5000/wangbeibei/pytorch_nni:cu_100
8.2.2. 有hdfs
在OpenPai平台上运行程序,使用该镜像172.31.246.45:5000/dlspree:hdfs_pyg
lin-ai-27:5000/wangbeibei/pytorch:cu100_hdfs