Dockerfile实践

最近在玩OpenCV,顺手build了一个OpenCV 3.2.0的Docker image。这个image是基于Ubuntu 16.04和OpenCV 3.2.0的source code build的,顺带也build进了Python3的绑定。这个image比较适合用来作为开发和测试基于OpenCV的服务端程序环境的base image。由于包含了几乎全部的OpenCV组件,build的过程还是比较费时的,image的尺寸也比较
大,所以我将它push到了Docker Hub里。需要的话,可以用

[code lang=”bash”]docker pull chunliu/docker-opencv[/code]

把它拉下来使用。如果要精简组件,或build别的版本的OpenCV,可以修改Dockerfile,重新build。

实际上我以前并没有怎么用过Docker,只在虚机中安装过,顺着Docker官方的tutorial做过,并简单看过官方的几篇doc,仅此而已。大概明白Dockerfile是怎么回事,但没有写过很完整复杂的Dockerfile。事实证明,事非经过不知难,写这个Dockerfile还是有一些坑的。

首先,要写好这个Dockerfile,只靠记事本比较困难,使用辅助工具会容易一些。我用的是VS Code + Docker support,它能提供关键字着色和IntelliSense,也仅此而已。如果有工具能做语法检查就更好了,比如检查行尾是否少了一个续行符之类的。我开始几次都是跑build失败才发现,是某一行少了一个续行符。

另外,我没发现有什么好的方法,来debug和测试Dockfile。最开始,我是修改了Dockfile之后,就跑build,失败再找原因。但是这个build比较费时,这样不是很有效率。后来,我开始在一个container里,逐条跑Dockerfile里的命令,保证每条命令都没问题,再跑build。这样做的问题是,所有命令在一个bash session里跑成功了,并不能保证它们用RUN组织到Dockfile以后,build还能成功。

这就牵扯到Docker是怎么执行这个RUN的问题了。Docker的文档说,每一个RUN会是一个新的layer。我起初不太明白layer的含义,做过之后发现,所谓layer,就是一个中间状态的container。RUN后面的代码是在这个container里跑,跑完之后这个container被commit到image里,然后这个container被删除。后面的RUN,会基于新commit的image,起一个新的container。

所以,如果两段代码需要在一个bash session里跑的话,就需要在一个RUN里面才行。一个例子,比如build OpenCV的时候,会用下面的方式来make:

[code lang=”bash”]
mkdir build
cd build
cmake ……
make ……
[/code]

如果将cd,cmake和make分开到不同的RUN中,那cmake和make就有问题了,因为工作路径不对。实际上,RUN的工作目录是由WORKDIR设定的,每个RUN开始时都会使用它上面最靠近它的WORKDIR作为工作目录。所以如果非要将上面的代码分开到不同的RUN,也可以在RUN之间插入WORKDIR来指定路径,不过路径跳来跳去的,比较混乱。

细读Docker的两篇官方文档,对规避Dockerfile里的一些坑,是很有帮助的。

Reference