Docker

    Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖到一个可移植的镜像中,然后发布到Linux或Window系统中,也可以实现虚拟化。容器是完全使用沙箱机制,相互之前不会有任何接口。

1. Docker

参考资料:
资料1

资料2

    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 ~]# docker stop e83cf32fbc22
    e83cf32fbc22

    重新启动退出的容器
    处于exited状态的容器,可以通过start命令重新启动。

    1
    2
    [root@localhost ~]# docker start e83cf32fbc22
    e83cf32fbc22

    重启容器

    1
    2
    [root@localhost ~]# docker restart e83cf32fbc22
    e83cf32fbc22

2.3. 镜像命令

  1. docker images
    查看本地主机上所有的镜像。注意是本地主机的!这里能看到镜像的名称、版本、id、大小等基本信息,注意这里的 imageID 是镜像的唯一标识!

  2. docker pull 镜像名:标签
    从镜像官网docker hub,找到自己需要的镜像,并拉取镜像
    docker pull mxnet/python:1.4.1_gpu_cu90_mkl_py3

  3. docker push 镜像名:标签
    例如docker push lin-ai-27:5000/mxnet:1.41_cu100_py3
    将镜像push到本地服务器中的仓库

  4. 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 个数值:一个是容器真实增加的大小,一个是整个容器的虚拟大小。
  1. 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表示打开一个命令行
  2. docker attach 容器id或name
    当容器在后台运行时,我们使用容器的id和name进入到容器,推荐使用容器名字,更好记。通过容器id进入时,我们需要先使用docker ps先查看容器的id,然后使用这个id进入

  3. docker tag source des
    镜像重命名

  4. docker rmi
    删除本地的镜像,如下图所示,可以加上 -f 参数进行强制删除。
    这里的 rmi 命令跟 Linux 中的删除命令就很像啦,只是这里加了一个 i 代表 image!

  5. 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)

  6. docker search
    根据镜像名称搜索远程仓库中的镜像!

  1. Ctrl+P+Q把容器挂在后台

3. 使用Docker运行程序

  1. 登录上网
    上网的目的是能够访问docker镜像官网,我们才可以从官网上拉取镜像
    links 10.1.61.1/a30.htm
    登录之后按Ctrl+C退出

  2. 在dockerhub上查询镜像
    如果你在服务上已经有镜像了,就不需要执行步骤2和3
    浏览器进入docker hub,找到自己需要的镜像,gpu28号服务器Cuda的版本是9.0,下载的镜像需要和服务器上Cuda版本一致
    使用cat /usr/local/cuda/version.txt查看服务器的Cuda版本

  3. 拉取镜像
    例如镜像的全称为:
    mxnet/python:1.4.1_gpu_cu90_mkl_py3
    docker pull mxnet/python:1.4.1_gpu_cu90_mkl_py3
    这条命令会把镜像下载到服务器上,如果本地已经存在该镜像,docker会使用本地的镜像已经有的镜像,不再下载,节省空间。

  4. 将镜像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

  5. 在别的服务器上将刚才的镜像pull下来
    将镜像push到27号服务器的仓库上,在其他服务器上就可以访问到这个镜像,比如我们要在53号服务器上跑程序,我们就可以在53号服务器上使用docker pull命令把镜像拉取到53号服务器上
    docker pull lin-ai-27:5000/mxnet:1.41_cu100_py3

  6. 镜像重命名
    在53号服务器拉取到镜像后,可以再给镜像重命名,也可以使用原先的名字
    docker tag 镜像id wangbeibei/mxnetx:latest

  7. 通过镜像启动容器
    有了镜像之后,我们就可以使用了。通过镜像启动容器,然后在容器里运行自己的代码。

    在启动容器的时候,我们需要使用-u参数来表示这个容器是属于哪个用户的。我们在服务器上使用id命令来查看,输出结果如下所示,则在这台服务器上我的id就是1042

    1
    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
  8. 安装额外的包(可选)
    如果你用的镜像有些包没有,那么你可以在启动这个容器的使用使用pip install xx安装所需要的包。这种方式安装的话,这个包只存在这个容器中,让你使用exit退出这个容器时,容器没了,这个包也就没了。下次再启动容器的时候,需要再次安装。
    在容器中install GPU版本的第三方库时,一定要先看一下镜像cuda的版本。第三方库的cuda版本要和镜像的版本一致

  9. 在容器中运行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
  10. 让容器在后台运行
    当使用上面的命令在容器上跑上程序之后,我们按Ctrl+P+Q让容器在后台运行
    等到我们想再次进入容器的时候,使用docker attach 容器name即可

  11. 退出容器
    如果你的程序全都运行完了,不想要这个容器了,直接使用exit退出容器,并且这个容器也被删除了,再次使用docker ps就看不到这个容器了。不过这没关系,下次可以再使用docker run创建另一个容器即可。

4. 在容器中运行jupyter notebook

  1. 在服务器上查看用户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

  2. 启动容器
    docker run -it -p 7000:7000 -v $PWD:/root --runtime=nvidia -u 1042 --name wbbJupyter ufoym/deepo:all-jupyter-py36
    前面的7000是服务器的端口,后面的7000是容器的端口。由于服务器是所有人共用的,需要找一个没有被占用的端口。容器的端口因为只有你一个人用,所有不会被占用。

  3. 进入到容器中
    通过docker attach wbbJupyter进入到容器中
    cd /root会看到服务器本地的目录

  4. 在容器中运行jupyter notebook
    jupyter notebook --no-browser --ip 0.0.0.0 --port=7000 --allow-root
  5. 在putty中添加7000的端口号
    这里的7000指的是服务器的端口,不是容器的端口
    然后再浏览器中输入http://localhost:7000/tree
    或者不用在putty中添加端口映射,直接在浏览器中输入http://gpu28:7000,这里的7000指的是服务器的端口。
  6. 如果想要jupyter notebook在后台运行,按Ctrl+P+Q,会退回到服务器
  7. 使用docker ps会看到你的容器正在后台运行
  8. 使用docker attach 容器的名字/id再次进入到容器中,按Ctrl+C会看到当前正在有jupyter notebook运行

5. 在容器中启动tensorboard

  1. 启动容器
    想在网页中查看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
  2. 启动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
然后编写用于构建镜像的Dockerfile
vi Dockerfile
内容如下:

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
#指定镜像要构建在哪个镜像之上  
#如果程序需要用到GPU,那就一定要构建在nvidia/cuda这个镜像上
FROM nvidia/cuda:9.0-cudnn7-devel

#给镜像添加元数据,指定作者邮箱等信息
LABEL WangBeibei 18120408@bjtu.edu.cn

#RUN会在当前镜像的最上面创建一个新层,并且能执行任何的命令,
#然后对执行的结果进行提交,提交后的结果镜像在Dockerfile的后续步骤中使用

#更新Ubuntu的索引
RUN apt-get update

#安装gcc工具
RUN apt-get install -y wget python3-dev gcc git vim && \
wget https://bootstrap.pypa.io/get-pip.py && \
python3 get-pip.py

#在容器上创建一个目录,镜像就安装在该目录下
RUN mkdir /root/docker_gcn

#关于COPY命令,如果要复制目录的话,COPY命令会把目录里面所有文件赋值到另一个目录下,
#而不是把这个目录直接复制过去,所以上面先在容器中创建了一个docker_gcn的目录,然后COPY命令将服务器本地的wbb_docker_gcn里面的文件都复制到了/root/docker_gcn/里面
#将服务器本地的文件拷贝到容器中的目录上
COPY wbb_docker_gcn /root/docker_gcn/

#切换到那个目录,如果该目不存在,则创建。WORKDIR是切换当前工作路径,下面的RUN命令都会在这个WORKDIR下面执行
WORKDIR /root/docker_gcn

#下载

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

打赏
0%