Dockerfile教程

简介

在使用镜像时,除了引用Docker hub上已有的、制作好的镜像,我们也可以基于现有镜像定制新的镜像。而定制镜像所需要的脚本文件就是 Dockerfile。

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

有了Dockerfile,我们就可以在已有镜像的基础上,根据我们的需求,编写dockerfile,然后重新构建专属于我们自己使用的Docker镜像。

DockerFile.png

使用

Dockerfile文件

我们新建一个空白文件,命名为 dockerfile,再文件中写入如下内容:

  1. FROM redis:rc-alpine3.11
  2. RUN mkdir redis
  3. WORKDIR redis
  4. COPY ./redis.conf /etc/
  5. CMD ["redis-server", "/etc/redis.conf"]

我们依次解释上面每一行:

  • FROM 就是指定 基础镜像 ,一个 DockerfileFROM 是必备的指令,并且必须是第一条指令。如果不以任何镜像为基础,那应该用FROM scratch作为起始指令。
  • RUN 是Dockerfile的核心指令,用于执行一条命令,由于Dockerfile 每一条指令都会新建一层,所以应该尽量将执行的内容写在一行(多行内容可以通过在末尾加\以表示未结束),它有两种写法:
    • shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。
    • exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。
  • WORKDIR 表示指定当前工作目录,相当于cd命令。
  • COPY 即复制文件到容器中,在这里是把redis.conf文件复制到容器的/etc目录下。
  • CMD 是启动程序的命令,写法和RUN相同,一般推荐使用exec格式。

常用Docker指令列表如下:

指令 含义 用法
FROM 指定基础镜像 FROM <基础镜像>
RUN 执行指令 RUN ["可执行文件", "参数1", "参数2"]
COPY 复制文件 COPY ["<源路径1>",... "<目标路径>"]
ADD 更高级的复制文件 ADD "<压缩文件>"
CMD 容器启动命令 CMD ["可执行文件", "参数1", "参数2"...]
ENTRYPOINT 入口点 ENTRYPOINT ["可执行文件", "参数1", "参数2"]
ENV 设置环境变量 ENV <key1>=<value1> <key2>=<value2>...
ARG 构建参数 ARG <参数名>[=<默认值>]
VOLUME 定义匿名卷 VOLUME ["<路径1>", "<路径2>"...]
EXPOSE 暴露端口 EXPOSE <端口1> [<端口2>...]
WORKDIR 指定工作目录 WORKDIR <工作目录路径>
USER 指定当前用户 USER <用户名>
HEALTHCHECK 健康检查 `HEALTHCHECK NONE [选项] CMD <命令>`
ONBUILD 构建下级镜像 ONBUILD <其它指令>
MAINTAINER 指定作者 ONBUILD <作者>

更多指令及用法请参照官方文档

构建镜像

如上,我们完成了一个使用自己配置文件的redis镜像的准备工作,之后依据这个Dockerfile进行构建:

  1. docker build -t redis_test:v0.1 .
  2. # 会有类似如下输出:
  3. Sending build context to Docker daemon 2.048 kB
  4. Step 1 : FROM redis
  5. ...
  6. ...
  7. Removing intermediate container 9cdc27646c7b
  8. Successfully built 44aa4490ce2c

docker build的用法为:

  1. docker build -t image:v1 .
  • 参数详解
  1. -t
    -t 参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。
  2. .
    当构建的时候,我们需要指定构建镜像上下文的路径,上下文路径不仅是指Dockerfile文件所在的路径,而且包括Dockerfile中COPY、ADD等指令中包含的文件路径。
    在上面示例中,.表示构建上下文路径为当前目录

运行镜像

  1. [rancher@ros ~]$ docker run -it -d test_image:v1
  2. 90e92c32d9836b0dbedf41c6f6db7dee287e0d39547794ed4ec00b43779608e5

优化

开发测试镜像和生产镜像

区分开发测试镜像和生产镜像可以帮助我们最合理化的使用资源。

  1. 在开发测试镜像中,安装常用的vim,openssl等工具,设置root密码,默认启动ssh等。我们可以通过ssh连接容器,在容器内进行开发和测试。
  2. 在生产镜像中,只需要安装项目/服务所依赖的包,设置启动命令运行即可。

基础镜像优化

  1. 环境版本选择
    在选择基础镜像时,我们有多个环境版本的镜像可以选择,eg: (alpineslim
    slim 风格的镜像是基于 Debian 发行版制作的,而 alpine 风格的镜像是基于体积更小的 Alpine Linux 发行版制作的,非常轻量级。但并不是所有的语言都适合将alpine作为基础镜像。
    Debian 使用的是 GNU 项目所实现的 C 语言标准库,而 Alpine 使用的是 Musl C 标准库,它被设计用来替代 GNU C 标准库(glibc)的替代品,用于嵌入式操作系统和移动设备。因此使用 Alpine 在某些情况下会遇到兼容性问题。
  2. 基础镜像推荐
    根据实际需要更换镜像版本号 | 语言 | 镜像 | | :—-: | :—-: | | python | python:3.9-rc-buster | | java | openjdk:15-ea-alpine3.11 | | node | node:alpine3.11 |

  3. 指定标签
    当基础镜像没有指定标签时,将默认使用latest标签。因此, FROM ubuntu指令等同于FROM ubuntu:latest
    所以,当镜像更新时,latest标签可能会指向不同的镜像,这时构建镜像有可能失败。所以最好指定确定的镜像标签。

微服务理念

容器只运行单个服务,不要一个镜像里包含多个进程

从技术角度讲,你可以在 Docker 容器中运行多个进程。你可以将数据库,前端,后端,ssh,supervisor 都运行在同一个 Docker 容器中。但是,这样会非常痛苦,eg:

  • 构建时间较长
  • 镜像体积较大
  • 日志难以处理
  • 横向扩展浪费资源
  • ……

因此,建议大家要有微服务理念,为每个应用构建/使用单独的 Docker 镜像。

.dockerignore

.dockerignore的作用和语法类似于.gitignore,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少 Docker 镜像的大小。示例如下:

  1. .git/
  2. node_modules/

COPY和ADD

COPY指令将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置。

ADD指令和COPY的格式和性质基本一致。但是在COPY基础上增加了一些功能。如果<源路径>为一个压缩文件的话,压缩格式为gzipbzip2/或者xz的情况下,ADD指令将会自动解压缩这个压缩文件到<目标路径>去。

  1. ADD rootfs.tar.xz /
  2. COPY . /tmp/

COPYADD都有复制文件到镜像中的功能,但是我们还是要尽可能的使用COPY,因为COPY的语义很明确,就是复制文件而已,而ADD则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。

另外需要注意的是,ADD指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

镜像缓存

在构建镜像时,我们经常会用到镜像缓存,不仅可以提升镜像构建的速度,并且可以节省空间。在使用镜像缓存时,我们需要注意以下:

  1. 当 Dockerfile 的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效
  2. 某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效。

Dockerfile_sy.png

对于类似COPY WORKDIR ENV LABEL 等命令,可以往后放,进行把变化频率小的往前放, 经常可能变化的命令往后放。 因为假设把经常变化的指令放在前面, 根据规定1, 缓存没有命中,则后面都要重新打镜像。

缩减镜像体积

  1. RUN优化

Docker镜像是分层的,Dockerfile 中的每个指令都会创建一个新的镜像层。

为了减少分层,减小镜像体积,我们可以将所有的RUN指令合并为一个指令。从而达到缩减镜像体积目的。

Dockerfile_run.png

  1. RUN 指令清理,eg:

如果在 RUN 命令中执行aptapk 或者 yum 类工具,可以借助这些工具提供的一些小技巧来减少镜像层数量及镜像大小。举几个例子:

  • 执行 apt install -y 时增加选项--no-install-recommends,可以不用安装建议性的依赖,也可以在执行 apk add 时添加选项--no-cache 达到同样效果;
  • 执行 yum/apt install -y 时候, 可以同时安装多个工具,比如 yum install -y gcc gcc-c++ make ...,将所有 install 任务放在一条 RUN 命令上执行,从而减少镜像层的数量;
  • 清理组件安装时遗留下来的缓存或垃圾 | 系统 | 命令 | | :—-: | :—- | | alpine | apk —update add php7 && rm -rf /var/cache/apk/ | | Ubuntu/Debian | rm -rf /var/lib/apt/lists/ | | CentOS | yum clean all |

多层构建

要想大幅度减少镜像的体积,多阶段构建是必不可少的。

多阶段构建的想法很简单:“我不想在最终的镜像中包含一堆 C 或 Go 编译器和整个编译工具链,我只要一个编译好的可执行文件!”

多阶段构建可以由多个 FROM 指令识别,每一个 FROM 语句表示一个新的构建阶段,阶段名称可以用 AS 参数指定,例如:

  1. FROM gcc:9.3.0 AS mybuildstage
  2. COPY hello.c .
  3. RUN gcc -o hello hello.c
  4. FROM ubuntu
  5. COPY --from=mybuildstage hello .
  6. CMD ["./hello"]

Dockerfile示例参考

  1. python开发测试镜像 ```dockerfile

    pyenv-code:3.6-buster

    FROM python:3.6-buster LABEL author=”gpp” email=”guopanpan@sinosoft.com.cn”

配置Debian软件源为国内清华源

RUN echo >/etc/apt/sources.list “\n\ deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free\n\ deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free\n\ deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free\n\ deb https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free\n\ “ && \

设置pip源为国内清华源

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple &&\

安装sudo、vim、ssh等,配置ssh设置root用户密码等使其能够正常使用

apt update && apt install -y sudo vim openssh-server openssh-client &&\ mkdir -p /var/run/sshd &&\ echo ‘root:asd123’ | chpasswd &&\ sed -i ‘s/#PermitRootLogin prohibit-password/PermitRootLogin yes/‘ /etc/ssh/sshd_config &&\ sed ‘s@session\srequired\spam_loginuid.so@session optional pam_loginuid.so@g’ -i /etc/pam.d/sshd &&\ echo “export VISIBLE=now” >> /etc/profile

EXPOSE 22

启动镜像生成容器时执行的命令

CMD /usr/sbin/sshd && /bin/bash

  1. 2. python项目生产镜像
  2. ```dockerfile
  3. # pyenv-test-[project]:3.6-buster
  4. FROM python:3.6-buster
  5. LABEL author="gpp" email="guopanpan@sinosoft.com.cn"
  6. # 设置pip源为国内清华源,并安装requirements.txt中所有的包
  7. RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple &&\
  8. pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
  9. ENV PYTHONPATH=/usr/src/app/neo4j-flask
  10. WORKDIR /usr/src/app/neo4j-flask
  11. COPY . .
  12. # 项目启动
  13. CMD [ "python", "src/app.py" ]

小教程alpine

Alpine Linux简介

Alpine Linux是一个3S(分别是Small,Simple,Secure)的Linux发行版本,基于musl libc和BusyBox构建,能够减小系统的体积与运行时资源消耗,同时它还提供了独有的强大的包管理工具apk,可以用来安装各种各样的软件包来扩展系统功能。

Alpine Linux优势和缺点

  • 优势
  1. Alpine Linux的Docker镜像特点是轻巧(大小只有5M)且有完整的包管理工具(APK)。
  • 问题
  1. Alpine Linux使用了musl,可能和其他Linux发行版使用的glibc实现会有些不同。
  2. musl实现的DNS服务不会使用resolv.conf文件中的search和domain两个配置,通过DNS来进行服务发现时需要注意。

Alpine Linux使用

  1. 修改apk安装源为国内清华源
  1. sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
  1. 包管理工具(apk)语法 | 命令 | 备注 | | :—-: | :—-: | | apk update | 更新包列表 | | apk search [软件包名] | 搜索软件包 | | apk info [软件包名] | 获取软件包信息 | | apk add [软件包名] | 安装软件包 | | apk del [软件包名] | 卸载并删除PACKAGES | | apk upgrade | 升级所有软件 | | apk add —upgrade | 指定升级部分软件包 | | …… | …… |