本文发自 http://www.binss.me/blog/learn-docker-with-me-about-building-compiler-environment/,转载请注明出处。

在上一篇中,我们学习了基本的命令,接下来我们就要真正地使用Docker来干活啦。

本篇介绍如何使用Docker来搭建C++编译/测试环境。

为什么要使用Docker来搭建C++编译/测试环境呢?其实主要是个人原因:

  • 最近在写一个http server,打算使用高大上的epoll,然而MAC下是木有epoll的

  • 在没有split的日子里无比蛋疼,忍无可忍准备把boost也装上,但是又不想把一堆东西装到系统上

  • 嗯,为了写这篇文章

开始干活

  1. 使用ubuntu镜像新建一个容器gpp:

    $ docker run -it --name="gpp" ubuntu /bin/bash
  2. 建立容器后,首先安装编译需要的g++。

    $ apt-get install g++

    报错了

    Some packages could not be installed. This may mean that you have
    requested an impossible situation or if you are using the unstable
    distribution that some required packages have not yet been created
    or been moved out of Incoming.

    目测是索引太老了,更新下apt-get再安装:

    $ apt-get update
    $ apt-get install g++

    安装完后,我们可以先提交修改,成为新的个人镜像:

    $ docker commit -a binss -m "install g++" gpp binss/ubuntu:v1

    然后退出容器。删除之,然后重新使用镜像binss/ubuntu:v1生成容器gpp:

    $ docker rm gpp
    $ docker run -it --name="gpp" binss/ubuntu:v1 /bin/bash
  3. 安装make

    ......
    $ apt-get install make

    提交......

    $ docker commit -a binss -m "install make" gpp binss/ubuntu:v2

    删除容器......

  4. 同理安装boost:

    ......
    $ apt-get install libboost-all-dev

    提交......

    $ docker commit -a binss -m "install boost" gpp binss/ubuntu:v3

    删除容器......

    此时查看我们好不容易打造出来的镜像:

    $ docker history binss/ubuntu:v3
    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    72f51f7e1264        56 seconds ago      /bin/bash                                       302.1 MB            install boost
    b8198a56e379        10 minutes ago      /bin/bash                                       1.228 MB            install make
    be8103068a5e        35 minutes ago      /bin/bash                                       127.9 MB            install g++
    91e54dfb1179        3 weeks ago         /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B
    d74508fb6632        3 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kB
    c22013c84729        3 weeks ago         /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic   194.5 kB
    d3a1f33e8a5a        3 weeks ago         /bin/sh -c #(nop) ADD file:5a3f9e9ab88e725d60   188.2 MB

泪流满面,历时40分钟。这时肯定有童鞋要问了:为什么不直接在原来的容器里commit三次?

我做了相关尝试,最后发现history永远只会显示最后一次提交的信息。也就是说,如果直接在原来的容器commit三次,那么只会有一条记录,COMMENT为install boost,SIZE为三次提交的文件之和。这是为什么呢?

为了了解commit后镜像的生成规则,我做了相关尝试,并得出以下结论:

  • commit成功后必定会生成一个镜像,由IMAGE ID唯一标识。IMAGE ID会在commit成功后回显。

    $ docker commit -a binss -m "install boost" gpp binss/ubuntu:v3
    b4ad18b2aa0f911c060c4e5fb35d2fea613e29bff7dd649cf4e857680b9c804b

    是的,原来IMAGE ID有那么长,在docker images里面看到的只是前12位而已。

  • 如果commit时指定的REPOSITORY:TAG存在,那么images里会生成多一行:

    不存在时:

    REPOSITORY                    TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    binss/ubuntu                  v3                  72f51f7e1264        4 minutes ago       619.6 MB

    已存在时:

    REPOSITORY                    TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    binss/ubuntu                  v3                  72f51f7e1264        4 minutes ago       619.6 MB
    <none>                        <none>              ba1d64b0accb        19 minutes ago      619.6 MB

    ba1d64b0accb是你原来名为binss/ubuntu:v3 的老镜像。也就是说,虽然名字被覆盖了,但原来的镜像并不会被覆盖,由于名字被抢了,只能用: 来替代了。

  • commit后得到的镜像实际上为原镜像的增量。注意到我们看到的大小619.6 MB是VIRTUAL SIZE,实际上,通过history查看,实际大小为302.1 MB 。

    IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT
    8d6f032f8175        About a minute ago   /bin/bash                                       302.1 MB            install boost

    而由于我们commit的容器是基于镜像ubuntu:v2,ubuntu:v2的VIRTUAL SIZE为317.5 MB,317.5+302.1=619.6,嗯,bingo。

  • 如果不作任何修改就再次commit,Docker仍然会基于增量(即使是无)生成一个新的镜像(符合第一条)。我们可以通过以下命令进入到Docker的虚拟机里,然后cd到存放镜像的目录下:

    $ docker-machine ssh default
    $ cd /mnt/sda1/var/lib/docker/aufs/diff/

    可以看到,镜像的本质是一个个以IMAGE ID为名的文件夹。在文件夹中,存放着增量。

  • 为了能够更直观地查看镜像间的依赖关系,建议大家装个dockviz(官方坑爹地把docker images -t取消了,导致要使用第三方实现):

    https://github.com/justone/dockviz

    安装方法是在.bash_profile里加一行:

    alias dockviz="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz"

    然后使之生效并执行,第一次会自动下载镜像nate/dockviz:

    $ source .bash_profile
    $ dockviz

    装好后,可以通过以下命令查看镜像的依赖关系:

    $ dockviz images -t
    然后你会看到这种结构:
    ├─d3a1f33e8a5a Virtual Size: 188.2 MB
    │ └─c22013c84729 Virtual Size: 188.4 MB
    │   └─d74508fb6632 Virtual Size: 188.4 MB
    │     └─91e54dfb1179 Virtual Size: 188.4 MB Tags: ubuntu:latest
    │       └─be8103068a5e Virtual Size: 316.3 MB Tags: binss/ubuntu:v1
    │         └─b8198a56e379 Virtual Size: 317.5 MB Tags: binss/ubuntu:v2
    │           ├─33243ce05402 Virtual Size: 619.6 MB Tags: binss/ubuntu:v4
    │           └─72f51f7e1264 Virtual Size: 619.6 MB Tags: binss/ubuntu:v3

    对于重复commit同一容器的binss/ubuntu:v4,其实和binss/ubuntu:v3基于的是同一个镜像。虽然它们的增量完全一样(连续commit),但都另外占据空间,不共享增量。

综合以上5点,为什么history永远只会显示最后一次提交的信息的原因也就明了了:

基于某个镜像X生成的容器,commit时永远会生成新的镜像,且该镜像永远是基于该镜像X的增量镜像。也就是说当我们每次Commit的时候,都是针对容器的基础镜像进行的一次增量修改,而不是对前一次提交生成镜像的增量修改。只有使用前一次提交生成的镜像再生成容器,才能针对前一次提交进行增量修改。

好了,回归主题,在建立了我们想要的编译环境的镜像后,使用它来生成一个容器。为了方便我们的编译和调试,使用磁盘映射和端口映射。

$ docker run -it -v /Users/bin/Desktop/share:/home/binss -p 8888:8888 --name="gpp" binss/ubuntu:v3 /bin/bash
  • -v 做的是磁盘映射,这里做的是把/Users/bin/Desktop/share目录映射到容器中的/home/binss目录。目的是避免频繁地在两个系统间拷贝文件。

  • -p 做的是端口映射,这里做的是把本机的8888端口映射到容器的8888端口。目的是直接能在系统直接使用浏览器访问容器中跑起来的http server。

  • -it 老生常谈了,交互式进入容器

在/Users/bin/Desktop/share目录中新建文件夹HTTPServer,放入待编译的文件:

进入容器,cd进/home/binss/目录,就可以找到HTTPServer,然后cd进去,执行make编译:

很顺利地生成了使用了epoll的可执行文件HttpServer,run it:

./HttpServer

然后使用浏览器去访问http://192.168.99.100:8888/,这里只实现了header的回射:

访问成功。

怎么知道要访问的ip和地址是什么?

最简单的办法是打开Kitematic,找到正在运行的容器,在IP & PORT 可以找到:

通过以上步骤,我们很方便地搭建了自己的c++编译环境,额,好吧,其实是很不方便,麻烦主要集中在如何创建镜像部分:run->commit->rm无限循环真的反人类?有没有简便办法?

请看下集:跟我一起学Docker——Dockerfile篇