Dockerfile教程
简介
在使用镜像时,除了引用Docker hub上已有的、制作好的镜像,我们也可以基于现有镜像定制新的镜像。而定制镜像所需要的脚本文件就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction) ,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
有了Dockerfile,我们就可以在已有镜像的基础上,根据我们的需求,编写dockerfile,然后重新构建专属于我们自己使用的Docker镜像。
使用
Dockerfile文件
我们新建一个空白文件,命名为 dockerfile
,再文件中写入如下内容:
FROM redis:rc-alpine3.11
RUN mkdir redis
WORKDIR redis
COPY ./redis.conf /etc/
CMD ["redis-server", "/etc/redis.conf"]
我们依次解释上面每一行:
- FROM 就是指定 基础镜像 ,一个
Dockerfile
中FROM
是必备的指令,并且必须是第一条指令。如果不以任何镜像为基础,那应该用FROM scratch
作为起始指令。 - RUN 是Dockerfile的核心指令,用于执行一条命令,由于Dockerfile 每一条指令都会新建一层,所以应该尽量将执行的内容写在一行(多行内容可以通过在末尾加
\
以表示未结束),它有两种写法:- shell 格式:
RUN <命令>
,就像直接在命令行中输入的命令一样。 - exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
,这更像是函数调用中的格式。
- shell 格式:
- 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进行构建:
docker build -t redis_test:v0.1 .
# 会有类似如下输出:
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM redis
...
...
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c
docker build
的用法为:
docker build -t image:v1 .
- 参数详解
-t
-t 参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。.
当构建的时候,我们需要指定构建镜像上下文的路径,上下文路径不仅是指Dockerfile文件所在的路径,而且包括Dockerfile中COPY、ADD等指令中包含的文件路径。
在上面示例中,.
表示构建上下文路径为当前目录
运行镜像
[rancher@ros ~]$ docker run -it -d test_image:v1
90e92c32d9836b0dbedf41c6f6db7dee287e0d39547794ed4ec00b43779608e5
优化
开发测试镜像和生产镜像
区分开发测试镜像和生产镜像可以帮助我们最合理化的使用资源。
- 在开发测试镜像中,安装常用的vim,openssl等工具,设置root密码,默认启动ssh等。我们可以通过ssh连接容器,在容器内进行开发和测试。
- 在生产镜像中,只需要安装项目/服务所依赖的包,设置启动命令运行即可。
基础镜像优化
- 环境版本选择
在选择基础镜像时,我们有多个环境版本的镜像可以选择,eg: (alpine
,slim
)slim
风格的镜像是基于Debian
发行版制作的,而alpine
风格的镜像是基于体积更小的Alpine Linux
发行版制作的,非常轻量级。但并不是所有的语言都适合将alpine
作为基础镜像。Debian
使用的是GNU
项目所实现的C
语言标准库,而Alpine
使用的是Musl C
标准库,它被设计用来替代GNU C
标准库(glibc
)的替代品,用于嵌入式操作系统和移动设备。因此使用Alpine
在某些情况下会遇到兼容性问题。 基础镜像推荐
根据实际需要更换镜像版本号 | 语言 | 镜像 | | :—-: | :—-: | | python | python:3.9-rc-buster | | java | openjdk:15-ea-alpine3.11 | | node | node:alpine3.11 |指定标签
当基础镜像没有指定标签时,将默认使用latest
标签。因此,FROM ubuntu
指令等同于FROM ubuntu:latest
。
所以,当镜像更新时,latest
标签可能会指向不同的镜像,这时构建镜像有可能失败。所以最好指定确定的镜像标签。
微服务理念
容器只运行单个服务,不要一个镜像里包含多个进程
从技术角度讲,你可以在 Docker 容器中运行多个进程。你可以将数据库,前端,后端,ssh,supervisor 都运行在同一个 Docker 容器中。但是,这样会非常痛苦,eg:
- 构建时间较长
- 镜像体积较大
- 日志难以处理
- 横向扩展浪费资源
- ……
因此,建议大家要有微服务理念,为每个应用构建/使用单独的 Docker 镜像。
.dockerignore
.dockerignore
的作用和语法类似于.gitignore
,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少 Docker 镜像的大小。示例如下:
.git/
node_modules/
COPY和ADD
COPY
指令将从构建上下文目录中<源路径>
的文件/目录复制到新的一层的镜像内的<目标路径>
位置。
ADD
指令和COPY
的格式和性质基本一致。但是在COPY
基础上增加了一些功能。如果<源路径>
为一个压缩文件的话,压缩格式为gzip
、bzip2
/或者xz
的情况下,ADD
指令将会自动解压缩这个压缩文件到<目标路径>
去。
ADD rootfs.tar.xz /
COPY . /tmp/
COPY
和ADD
都有复制文件到镜像中的功能,但是我们还是要尽可能的使用COPY
,因为COPY
的语义很明确,就是复制文件而已,而ADD
则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是,ADD
指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
镜像缓存
在构建镜像时,我们经常会用到镜像缓存,不仅可以提升镜像构建的速度,并且可以节省空间。在使用镜像缓存时,我们需要注意以下:
- 当 Dockerfile 的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效
- 某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效。
对于类似
COPY
WORKDIR
ENV
LABEL
等命令,可以往后放,进行把变化频率小的往前放, 经常可能变化的命令往后放。 因为假设把经常变化的指令放在前面, 根据规定1, 缓存没有命中,则后面都要重新打镜像。
缩减镜像体积
- RUN优化
Docker镜像是分层的,Dockerfile 中的每个指令都会创建一个新的镜像层。
为了减少分层,减小镜像体积,我们可以将所有的RUN指令合并为一个指令。从而达到缩减镜像体积目的。
- RUN 指令清理,eg:
如果在 RUN 命令中执行apt
、apk
或者 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 参数指定,例如:
FROM gcc:9.3.0 AS mybuildstage
COPY hello.c .
RUN gcc -o hello hello.c
FROM ubuntu
COPY --from=mybuildstage hello .
CMD ["./hello"]
Dockerfile示例参考
- 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
2. python项目生产镜像
```dockerfile
# pyenv-test-[project]:3.6-buster
FROM python:3.6-buster
LABEL author="gpp" email="guopanpan@sinosoft.com.cn"
# 设置pip源为国内清华源,并安装requirements.txt中所有的包
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple &&\
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
ENV PYTHONPATH=/usr/src/app/neo4j-flask
WORKDIR /usr/src/app/neo4j-flask
COPY . .
# 项目启动
CMD [ "python", "src/app.py" ]
小教程alpine
Alpine Linux简介
Alpine Linux是一个3S(分别是Small,Simple,Secure)的Linux发行版本,基于musl libc和BusyBox构建,能够减小系统的体积与运行时资源消耗,同时它还提供了独有的强大的包管理工具apk,可以用来安装各种各样的软件包来扩展系统功能。
Alpine Linux优势和缺点
- 优势
- Alpine Linux的Docker镜像特点是轻巧(大小只有5M)且有完整的包管理工具(APK)。
- 问题
- Alpine Linux使用了musl,可能和其他Linux发行版使用的glibc实现会有些不同。
- musl实现的DNS服务不会使用resolv.conf文件中的search和domain两个配置,通过DNS来进行服务发现时需要注意。
Alpine Linux使用
- 修改apk安装源为国内清华源
sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
- 包管理工具(apk)语法 | 命令 | 备注 | | :—-: | :—-: | | apk update | 更新包列表 | | apk search [软件包名] | 搜索软件包 | | apk info [软件包名] | 获取软件包信息 | | apk add [软件包名] | 安装软件包 | | apk del [软件包名] | 卸载并删除PACKAGES | | apk upgrade | 升级所有软件 | | apk add —upgrade | 指定升级部分软件包 | | …… | …… |