docker镜像制作
docker 镜像生命周期
制作镜像方法:
- 手工制作(基于容器)
- 自动制作(基于 dockerfile),企业通常都是基于 dockerfile 制作镜像
手动构建镜像 commit
将现有容器通过 docker commit
或 docker container commit
手动构建镜像
根据容器的更改创建新镜像:
1 | # docker container commit --help |
说明:
- 制作镜像和容器的状态无关,停止状态也可以制作
- 如果没有指定[REPOSITORY[:TAG]],REPOSITORY 和 TAG 都为<none>
- 提交的时候标记 TAG,后期可以根据 TAG 标记创建不同版本的镜像以及创建不同版本的容器
具体步骤
- 下载一个官方的基础镜像,例如:centos、ubuntu、alpine
- 基于基础镜像启动一个容器,并进入
- 在容器里面进行安装服务、修改配置等操作
- 提交一个新镜像
docker container commit
- 基于自己的镜像创建容器并访问、
案例:基于 alpine 基础镜像制作 nginx 镜像
下载最新版 alpine 基础镜像
1
root@Z510:~# docker pull alpine
启动 alpine 并进入
1
2root@Z510:~# docker container run -it alpine
/ #另开一个终端,将 shell 脚本拷贝到 alpine 镜像
1
2
3
4
5lujinkai@Z510:~/www/script$ sudo docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b8de1bdb6c88 alpine "/bin/sh" About a minute ago Up About a minute funny_chatelet
lujinkai@Z510:~/www/script$ sudo docker container cp -a ./alpine-docker/ b8de:/root回到容器,运行 shell 脚本
1
2
3
4
5
6
7
8
9
10root@Z510:~# docker container run -it alpine
/ # cd
~ # ls
alpine-docker
~ # cd alpine-docker/
~/alpine-docker # ./install.sh
....
make[1]: Leaving directory '/root/alpine-docker/src/nginx-1.18.0'
Nginx installed successfully!
~/alpine-docker # exit提交镜像
1
2
3
4
5
6
7
8
9
10lujinkai@Z510:~$ sudo docker container commit \
-a 'lujinkai<root@lujinkai.com>' \
-c 'EXPOSE 80' \
-c 'CMD ["/usr/local/nginx/sbin/nginx"]' \
b8de1bdb6c88 nginx-alpine:v1
lujinkai@Z510:~$ sudo !!
sudo docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-alpine v1 de56d80c5c8c 14 seconds ago 340MB
alpine latest d6e46aa2470d 4 weeks ago 5.57MB启动基于新制作镜像的容器
1
2
3
4root@Z510:~# docker container run -d -p 80:80 nginx-alpine:v1
ebbe778a7fe303e6a5cb840203d95a7b5e2a07208365b71f784ddc8357a664d7
root@Z510:~# curl 127.0.0.1:81
hello nginxiptables
1
2
3
4
5
6root@Z510:~# iptables -t nat -nL
...
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- anywhere anywhere
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:81 to:172.17.0.2:80ss
1
2
3
4
5root@Z510:~# ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
...
LISTEN 0 4096 *:81 *:*
...有需求的话,可以导出镜像:
docker image save [OPTIONS] IMAGE
自动构建镜像
docker builder build
从 DockerFile 文件中构建镜像
docker builder build
docker builder build
或 docker build
命令用来基于 dockerfile 构建镜像
1 | docker builder build [OPTIONS] PATH | URL | - |
1 | docker build . |
Dockerfile 介绍
DockerFile 是一种被 Docker 程序解释执行的脚本,由一条条的命令组成的,每条命令对应 linux 下面的一条命令,Docker 程序将这些 DockerFile 指令翻译成真正的 linux 命令,其有自己的书写方式和支持的命令,Docker 程序读取 DockerFile 并根据指令生成 Docker 镜像,相比手动制作镜像的方式,DockerFile 更能直观的展示镜像是怎么产生的,有了 DockerFile,当后期有额外的需求时,只要在之前的 DockerFile 添加或者修改响应的命令即可重新生成新的 Docker 镜像,避免了重复手动制作镜像的麻烦,类似与 shell 脚本一样,可以方便高效的制作镜像。
Docker 守护程序 Dockerfile 逐一运行指令,如有必要,将每个指令的结果提交到新镜像,然后最终输出新镜像的 ID。Docker 守护程序将自动清理之前发送的上下文。
请注意,每条指令都是独立运行的,并会导致创建新镜像,比如 RUN cd /tmp
对下一条指令不会有任何影响。
Docker 将尽可能重用中间镜像层(缓存),以显著加速 docker builder build
命令的执行过程,这由 Using cache 控制台输出中的消息指示。
Dockerfile 镜像制作和使用流程
Dockerfile 文件的制作镜像的分层结构
1 | # 按照业务类型或系统类型等方式划分创建目录环境,方便后期镜像比较多的时候进行分类 |
build 构建过程
- 从基础镜像运行一个容器
- 顺序执行一条指令对容器做出修改
- 执行类似
docker commit
的操作提交一个新的镜像层(可以利用中间层镜像创建容器进行调试和排错) - docker 基于刚才提交的镜像运行一个新的容器
- 执行 Dockerfile 中的下一条指令,直至所有指令执行完毕
Dockerfile 文件格式
Dockerfile 是一个有特定语法格式的文本文件
- 每一行以 Dockerfile 的指令开头,指令不区分大小写,但是惯例使用大写
- 使用 # 开始作为注释
- 每一行只支持一条指令,每条指令可以携带多个参数
- 指令按文件的顺序从上至下进行执行
- 每个指令的执行会生成一个新的镜像层,为了减少分层和镜像大小,尽可能将多条指令合并成一条指令
- 制作镜像一般可能需要反复多次,每次执行 Dockerfile 都按顺序执行,从头开始,已经执行过的指令已经缓存,不需要再执行,如果后续有一行新的指令没执行过,其往后的指令将会重新执行,所以为加速镜像制作,将最常变化的内容放下 Dockerfile 的文件的后面
Dockerfile 相关指令
docker builder build
命令基于 dockerfile 构建镜像,docker container run
命令基于镜像启动容器
所以 dockerfile 的指令也分为三类:
- 在
docker builder build
阶段运行 - 在
docker container run
阶段运行 - 在以上两个阶段都运行,这种命令都是“设置”相关的,例如
USER
设置用户、ENV
设置环境变量、WORKDIR
设置工作目录,它们的设置在 build 和 run 两个阶段都生效
FROM
FORM
指定基础镜像,此指令通常必需放在 Dockerfile 文件第一个非注释行。后续的指令都是运行于此基准镜像所提供的运行环境
1 | FROM [--platform=<platform>] <image>[:<tag>] |
关于 scratch 镜像:
该镜像是一个空的镜像,可以用于构建 busybox 等超小镜像,可以说是真正的从零开始构建属于自己的镜像该镜像在构建基础镜像(例如 debian 和 busybox)或超最小镜像(仅包含一个二进制文件及其所需内容,例如:hello-world)的上下文中最有用
LABEL
LABEL
指定镜像元数据,如 镜像作者等
1 | LABEL <key>=<value> <key>=<value> <key>=<value> ... |
示例:
1 | LABEL maintainer="lujinkai <root@lujinkai.com>" \ |
注意:MAINTAINER 指令已过时,使用 LABEL 代替
RUN
RUN
指令用来在 build 阶段需要执行 shell 命令,一定要是继承的镜像所支持的 shell 命令
注意: RUN
可以写多个,每一个 RUN
指令都会建立一个镜像层,所以尽可能使用 && 将多条指令合并为一条
相邻的 RUN
命令相互独立,示例:
1 | RUN cd /app |
ENV
ENV
定义环境变量和值,会被后续指令通过$KEY或${KEY}进行引用,并在容器运行时保持
1 | ENV <key1>=<value1> \ |
docker run
运行容器,如果需要修改环境变量,使用 -e
重新定义即可覆盖
COPY
复制指令,从上下文目录中复制文件或者目录到容器里指定路径
1 | COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] # 如果路径中有空白字符,加双引号 |
--chown=<user>:<group>
:改变复制到容器内文件的属主和属组,默认保留所有元数据"<src>"
- 支持通配符,通配符规则满足 Go 的 filepath.Match 规则
- 如果是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制
"<dest>"
- 支持绝对路径 或 WORKDIR 指定的相对路径
- 如果不存在,会自动创建,递归创建目录
- 如果
"<src>"
是多个文件,则"<dest>"
必须是目录,且以/
结尾
范例:
1 | COPY hom* /mydir/ |
ADD
增强版 COPY
,支持自动解压缩,如果只是复制,尽量使用 COPY
支持解压缩(identity、gzip、bzip2、xz),支持解包(tar),支持像解压缩+解包,例如 .tar.gz
注意:ADD 识别压缩文件,取决于文件内容,不取决于文件后缀,如果一个文本文件直接修改后缀为 .tar.gz
,该文件将被简单地复制到目标文件
1 | ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] |
"<src>"
:支持 URL,下载后的文件权限自动设置为 600,如果下载的是 tar 文件将不会自动展开
示例:
1 | ADD test relativeDir/ # adds "test" to `WORKDIR`/relativeDir/ |
CMD
CMD
指定启动容器时默认执行的一个命令
- 命令运行结束后容器也会停止,所以一般指定持续运行且为前台的命令
- 每个 Dockerfile 只能有一条
CMD
命令。如指定了多条,只有最后一条被执行 - 如果用户启动容器时用 docker run xxx 指定运行的命令,则会覆盖
CMD
指定的命令
1 | # 使用 exec 执行,推荐方式,第一个参数必须是命令的全路径,此种形式不支持环境变量 |
示例:
1 | CMD ["nginx", "-g", "daemon off;"] |
ENTRYPOINT
入口点,功能类似于CMD
,配置容器启动后执行的命令及参数
ENTRYPOINT
不能被docker run
提供的参数覆盖,而是以追加的形式作为ENTRYPOINT
的参数- 每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效
1 | # 使用 exec 执行 |
CMD
和 ENTRYPOINT
都定义了容器运行时的执行命令。如下是它们的一些使用规则:
CMD
和ENTRYPOINT
在 Dockerfiles 中应该至少应该有一个被定义- 当构建可执行容器时,应该定义
ENTRYPOINT
指令 CMD
要么用于给ENTRYPOINT
提供默认参数,要么用于在容器中执行一个特定命令CMD
可以通过容器启动命令docker run
的参数来替换它
ARG
ARG
在 build 阶段指定变量,和 ENV
不同的是,容器运行时不会存在这些环境变量
1 | ARG <name>[=<default value>] |
如果和
ENV
同名,ENV
覆盖ARG
变量可以用
docker build --build-arg <参数名>=<值>
来覆盖在
FROM
之前声明的ARG
在构建阶段之外,所以它不能在FROM
之后的任何指令中使用。 要使用在第一个FROM
之前声明的ARG
的默认值,请在构建阶段内使用没有值的ARG
指令1
2
3
4
5# 示例:
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
VOLUME
匿名卷,将宿主机上的目录挂载至 VOLUME
指定的容器目录。即使容器后期被删除,此宿主机的目录仍会保留,从而实现容器数据的持久保存
1 | VOLUME <容器内路径> |
VOLUME
实现的是匿名卷,无法指定宿主机路径和容器目录的挂载关系,宿主机目录位于:1
/var/lib/docker/volumes/
通过
docker rm -fv <容器ID>
可以删除容器的同时删除 VOLUME 指定的卷如果要指定宿主机目录,使用
docker run
的 -v 参数,参考:docker 数据管理
示例:
1 | # 1. dockerfile,生成两个匿名卷 |
EXPOSE
WORKDIR
指定工作目录,即后续 RUN
、CMD
、ENTRYPOINT
指令的相对路径,如该目录不存在,WORKDIR
会自行创建
1 | WORKDIR /path/to/workdir |
示例:
1 | # 两次RUN独立运行,不在同一个目录, |
ONBUILD
ONBUILD
指令在当前镜像 build 阶段不会执行,会在子镜像 build 阶段执行,即:延迟执行
使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild
USER
指定执行后续指令的用户和用户组,后续的 RUN 也会使用指定用户,默认是 root 身份执行
这个用户必须是事先建立好的,否则无法切换
1 | USER <user>[:<group>] |
HEALTHCHECK
检查容器的健康性
1 | HEALTHCHECK [选项] CMD <命令> # 设置检查容器健康状况的命令 |
示例:
1 | FROM nginx |
STOPSIGNAL
退出容器的信号
STOPSIGNAL
设置将被发送到容器退出的系统调用信号。该信号可以是与内核 syscall 表中的位置匹配的有效无符号数字(例如 9),也可以是 SIGNAME 格式的信号名称(例如 SIGKILL)
1 | STOPSIGNAL signal |
SHELL
指定 shell,SHELL
指令可以出现多次。 每个SHELL
指令将覆盖所有先前的SHELL
指令,并影响所有后续的指令
1 | SHELL ["executable", "parameters"] |
示例:
1 | SHELL ["powershell", "-command"] |
.dockerignore 文件
与.gitignore 文件类似,生成构建上下文时 Docker 客户端应忽略的文件和文件夹指定模式
示例:
1 | test/* #排除 test 目录下的所有文件 |