DockerFile 定制镜像

什么是DockerFile

镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

FROM 指定基础镜像

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。

在 Docker Store 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如nginx 、 redis 、 mongo 、mysql 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 node 、 openjdk 、 python 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如ubuntu 、 debian 、 centos 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

FROM scratch
...

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

RUN 执行命令

  RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

  • shell 格式: RUN <命令> ,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。

    RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec 格式: RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。
      既然 RUN 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢?比如这样:

FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

  之前说过,Dockerfile 中每一个指令都会建立一层, RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。

  而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误(我也不能原谅自己ε=(´ο`*)))唉)。

  Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过127 层。

  上面的 Dockerfile 正确的写法应该是这样:

FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps

并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。

  此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,之前有说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

常见错误

执行docker build 准备上下文失败

unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /var/docker/Dockerfile: no such file or directory

这是因为没有指定DockerFile的路径导致找不到。

解决上面的错误有两种方法,任选其一即可。

  • 重命名dockerfile文件名,把DockerFile改为Dockerfile
  • 指定dockerfile,使用-f ,比如:docker build -t "test/run_nginx" -f DockerFile .

报错bash:$’\r’: command not found!

出现这个问题的原因是在win下编辑,然后放linux里面跑,而win的回车其实是”\r\n”,而放linux里面换行只有”\n”,所以需要将文件的格式从dos转换为unix。

这里提供三种解决方法:

a:使用dos2unix命令

dos2unix   xxx.sh

b: 使用vim或者vi命令

进入编辑页面之后

输入:set ff

可以看到当前的文件格式是dos,现在我们把它转unix。

:set ff=unix或者:set fileformat=unix

c:使用sed命令去掉”\r”

sed -i ‘s/\r$//’ <filename>

报错No such file or directory The command ‘/bin/sh’

原因有二。

a:路径原因。

你的sh文件不在docker的/bin/sh里面。

b:同bash:$’\r’: command not found!报错原因

在win下编辑放linux里会报该错误。解决方法同上。

Docker build时 Sending build context to Docker daemon 过大的问题

mark

Docker Client会默认发送Dockerfile同级目录下的所有文件到Dockerdaemon中。

解决办法有两种:

1.使用.dockerignore文件,设置黑名单,该文件包含的目录不会被发送到Docker daemon中

2.将Dockerfile迁移后其他目录中执行。

参考文档

  1. 官方DockerFile-Reference

  2. 官方Best practices for writing Dockerfiles

  3. 使用 Dockerfile 定制镜像

  4. 郁师傅、踩坑笔记0rz

文章作者: V0WKeep3r
文章链接: http://v0w.top/2018/09/22/DockerFile定制镜像/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 V0W's Blog
支付宝打赏
微信打赏