一、基本结构
- Dockerfile 由一行行命令语句组成, 并且支持以#开头的注释行。
- 一般而言, Dockerfile 主体内容分为四部分:基础镜像信息、 维护者信息、 镜像操作指令和容器启动时执行指令。
- 下面给出一个简单的示例
# escape=\ (backslash)
# This dockerfile uses the ubuntu:xeniel image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command]
# Base image to use, this must be set as the first line
FROM ubuntu:xeniel
# Maintainer: docker user <docker user at email.com> @docker user )
LABEL maintainer docker user<docker user@email.com>
# Commands to update the image
RUN echo "deb http://archive.ubuntu.com/ubuntu/ xeniel main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\n daemon off;" >> /etc/nginx/nginx.conf
# Commands when creating a new container
CMD /usr/sbin/nginx
二、指令说明
2.1 概述
- Dockerfile 中指令的一般格式为 INSTRUCTION arguments, 包括 “配置指令" (配置镜像信息)和 “操作指令" (具体执行操作)。参见下表:
分类 | 指令 | 说明 |
---|---|---|
配置指令 | ARG | 定义创建镜像过程中使用的变量(镜像创建完之后,ARG变量将不再存在) |
配置指令 | FROM | 指定所创建镜像的基础镜像 |
配置指令 | LABEL | 为生成的镜像添加元数据标签信息(镜像描述信息) |
配置指令 | EXPOSE | 声明容器内服务监听的端口(仅声明,不会直接暴露端口) |
配置指令 | ENV | 指定镜像中的环境变量(镜像创建完之后,在镜像启动的容器中也会存在) |
配置指令 | ENTRYPOINT | 指定镜像的默认入口命令(与CMD类似,主要用于指定启动的父进程,PID=1) |
配置指令 | VOLUME | 创建挂载点, 将宿主机目录挂载到容器中 |
配置指令 | USER | 指定运行容器时的用户名或UID |
配置指令 | WORKDIR | 配置工作目录 |
配置指令 | ONBUILD | 创建子镜像时指定自动执行的操作指令 |
配置指令 | STOPSIGNAL | 指定容器退出时发送给PID=1进程的信号值 |
配置指令 | HEALTHCHECK | 配置所启动的容器如何进行健康检查 |
配置指令 | SHELL | 指定默认shell类型 |
操作指令 | RUN | 运行指定命令 |
操作指令 | CMD | 启动容器时指定默认执行的命令 |
操作指令 | ADD | 从构建宿主机复制文件到镜像中(tar文件会被解压) |
操作指令 | COPY | 从构建主机复制文件到镜像中 |
2.2 配置指令
2.2.1 ARG
- 格式: ARG name[=default value]
- 作用: 定义创建镜像过程中使用的变量。
- 特别说明
- 在执行 docker build 时,可以通过 --build-arg [key=value] 来为变量赋值,通过 --build-arg 选项为变量赋值会覆盖 dockerfile 中对应变量原来的值 。当镜像编译成功后, ARG 指定的变量将不再存在 (ENV指令指定的变量则会在镜像中保留)。
- Docker 内置了一些镜像创建变量, 用户可以直接使用而无须声明, 包括(不区分大小写) HTTP_PROXY、HTTPS_PROXY、FTP_PROXY、NO_PROXY。
- 样例
# 定义ARG KEY ARG VERSION=3.0.1 # 使用ARG VALUE LABEL VERSION_ID=${VERSION} #构建容器时重新赋值ARG docker build -t mynginx:v3.0.2 --build-arg VERSION=v3.0.2 .
2.2.2 FROM
- 格式
- FROM image [AS name]
- FROM image:tag [AS name]
- FROM image@digest [AS name]
- 作用: 指定所创建镜像的基础镜像
- 特别说明
- 任何Dockerfile中第一条指令必须为FROM 指令。 并且,如果在同一个Dockerfile 中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。
- 为了保证镜像精简, 可以选用体积较小的镜像如Alpine或Debian 作为基础镜像。
- 样例
ARG VERSION=9.3 FROM debian:${VERSION}
2.2.3 LABEL
- 格式:LABEL key1=value1 key2=value2 …
- 作用:LABEL 指令可以为生成的镜像添加元数据标签信息
- 特别说明
- 可以通过label信息辅助过滤出特定镜像。注意:标签信息key和value均区分大小写。
- MAINTAINER指令指定Dockerfile的作者/维护者已弃用,推荐使用LABEL指令来指定维护者信息。
- 样例
# 添加标签信息 LABEL version="l.0.0-rc3" LABEL author="yeasy@github" date="2023-12-01" LABEL description="This text illustrates\ that label-values can span multiple lines." # 通过标签信息过滤镜像(区分大小写) docker images --filter="label=author=service"
2.2.4 ENV
-
格式
- ENV key1=value1 [key2=value2 key3=value3 …]
- ENV key1 value1 key2 value2 key3 value3
-
作用
- 指定环境变量,在镜像生成过程中会被后续RUN指令使用,在镜像启动的容器中也会继续存在。
-
特别说明
- 当一条 ENV 指令中同时为多个环境变量赋值并且值也是从环境变量读取时,会在变量都赋值后再更新。如对于指令“ENV key1=value2”、“ENV key1=value1 key2;${key1}”,最终结果为 key1=value2 key2=value2。
- 指令指定的环境变量在运行时可以被覆盖掉, 如 docker run --env =
build_image。
-
样例
# 定义ENV 变量 ENV APP VERS工ON=l.0.0 ENV APP_HOME=/usr/local/app ENV PATH $PATH:/usr/local/bin # 容器运行时覆盖ENV变量 docker run -itd --name test2 --env key1=run_value1 mynginx:v3.0.2
2.2.5 EXPOSE
- 格式
- EXPOSE port [port/protocol… ]
- 作用
- 声明镜像内服务监听的端口。
- 特别说明
- 该指令只是起到声明作用, 并不会自动完成端口映射。如果要映射端口出来, 在启动容器时可以使用 -P 参数 (Docker 主机会自动分配一个宿主机的临时端口)或 -p HOST_PORT:CONTAINER_PORT 参数(具体指定所映射的本地端口)。
- 声明监听端口时,可以指定协议,如果不指定协议默认是tcp协议。
- 样例
- EXPOSE 22 80 8443/udp
2.2.6 VOLUME
- 格式
VOLUME ["/data"] # [“/data”]可以是一个JsonArray ,也可以是多个值 VOLUME /var/log VOLUME /var/log /opt
- 作用
- 创建一个数据卷挂载点。运行容器时可以从本地主机或其他容器挂载数据卷, 一般用来存放数据库和需要保持的数据等。
- 特别说明
- VOLUME类似于docker run -v /host_data /container_data。一般不需要在Dockerfile中写明,且在Kubernetes场景几乎没用
- 样例
2.2.7 USER
- 格式
- USER username/uid
- 作用
- 指定运行容器时的用户名或uid, 后续的RUN等指令也会使用指定的用户身份。
- 特别说明
- 当服务不需要管理员权限时, 可以通过该命令指定运行用户, 并且可以在 Dockerfile中创建所需要的用户。例如:RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
- 要临时获取管理员权限可以使用 gosu 命令
- 样例
2.2.8 WORKDIR
- 格式
- WORKDIR /path/to/workdir
- 作用
- 类似于cd命令,为了改变当前所在的工作目录,此后RUN、CMD、ENTRYPOINT、COPY、ADD等命令都在此目录下作为当前工作目录。
- 特别说明
- 可以使用多个 WORKDIR 指令, 后续命令如果参数是相对路径, 会基于之前命令指定的路径。
- 为了避免出错,推荐 WORKDIR 指令中只使用绝对路径。
- 样例
WORKDIR /a WORKDIR b WORKDIR c RUN pwd # 最终路径为 /a/b/c
2.2.9 ONBUILD
-
格式
- ONBUILD [INSTRUCTION[
-
作用
- 为镜像添加一个触发器,其参数可以是任意一个Dockerfile指令。该指令,对于使用该Dockerfile构建的镜像并不会生效,只有当其他Dockerfile以当前镜像作为基础镜像时被触发。例如:Dockfile A 构建了镜像A,Dockfile B中设置FROM A,此时构建镜像B是会运行ONBUILD设置的指令。
-
特别说明
- 由于 ONBUILD 指令是隐式执行的,推荐在使用它的镜像标签中进行标注。
- ONBUILD 指令在创建专门用于自动编译、检查等操作的基础镜像时,十分有用。
-
样例
#imageA Dockerfile [...] ONBUILD ADD . / app/src ONBUILD RUN /usr/local/bin/python build --dir / app/src [...] #imageB Dockerfile #等价于在imageA的Dockerfile添加如下命令 ## . / app/src ## RUN /usr/ ocal/ bin python-bu ld --dir /app/src FROM imageA
2.2.10 STOPSIGNAL
- 格式
- STOPSIGNAL signal
- 作用
- 设置停止时要发送给PID=1进程的信号;
- 主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s。
- 特别说明
- 默认的停止信号为:SIGTERM,也可以通过docker run -s指定
- 样例
2.2.11 HEALTHCHECK
- 格式
#格式1:根据所执行命令返回值是否为0来判断(0:成功; 1:失败; 2:保留) HEALTHCHECK [OPTIONS] CMD command #格式2: 禁止基础镜像中的健康检查 HEALTHCHECK NONE # OPTIONS选项 ## --interval=30:两次健康检查的间隔,默认为 30 秒; ## --timeout=30:健康检查命令运行的超时时间,超过视为失败,默认30秒; ## --retries=3:指定失败多少次视为unhealth,默认3次
- 作用
- 配置所启动容器如 进行健康检查(如 判断健康与否),自 Docker 1.12 开始支持。
- 特别说明
- 根据所执行命令返回值是否为0来判断;0:成功; 1:失败; 2:保留;
- 样例
HEALTHCHECK --interval=5s --timeout=3s \ CMD curl -fs http://localhost/ || exit 1 # 如果执行不成功返回1 #状态查看 [root@localhost nginx]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6720519218d9 mynginx:v3.0.4 "/docker-entrypoint.…" 14 seconds ago Up 12 seconds (healthy) 80/tcp test1
2.2.12 SHELL
- 格式
- SHELL [” executable ”,”parameters”]
- 作用
- SHELL 指令可以指定 RUN、ENTRYPOINT、CMD 指令的 shell。Linux 中默认为[“/bin/sh”, “-c”] ,Windows默认[“CMD”,“/S”,“/C”]。
- 特别说明
- 对于 windows 系统,shell路径中使用了“\”作为分隔符,建议在 Dockerfile 开头添加 #escape=` 来指定转义符。
- 样例
SHELL ["/bin/bash","-c"] SHELL ["powershell", "-command"] # 示例,比如在Windows时,默认shell是["CMD","/S","/C"] # docker调用的是cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt" RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt" # exec格式这样写,虽然没有调用cmd.exe 但写起来比较麻烦。 # 所以可以通过SHELL ["powershell", "-command"] 这样省去前边的powershell -command RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
2.2.13 ENTRYPOINT
- 格式
#语法1:exec 调用执行 ENTRYPOINT ["executable", "paraml ", "param2"] #语法2:shell 中执行 ENTRYPOINT executable param1 param2
- 作用
- 指定镜像的默认入口命令, 该入口命令会在启动容器时作为根命令执行, 所有传入值作为该命令的参数。
- 特别说明
- ENTRYPOINT与CMD类似,但存在区别,ENTRYPOINT主要用于指定启动的父进程(PID=1)。
- 每个 Dockerfile 中只能有一个 ENTRYPOINT, 当指定多个时, 只有最后一个起效。
- 在运行时, 可以被 --entrypoint 参数覆盖掉, 如 docker run --entrypoint。
- 若使用语法1的格式设置ENTRYPOINT指令,CMD指令的指定值将作为根命令的参数。
- 样例
# Dockerfile CMD echo this is dockerfile cmd one CMD echo this is dockerfile cmd two CMD ["echo", "this is dockerfile cmd three"] ENTRYPOINT echo "this is dockerfile entrypoint shell" ENTRYPOINT ["echo", "this is dockerfile etnryponit exec"] # 构建镜像 docker build -t mynginx:v3.0.3 . # 运行新容器。根据输出结果可见(重点) ## 若使用exec的格式设置ENTRYPOINT指令,CMD指令的指定值将作为根命令的参数 ## 当指定多个ENTRYPOINT指令时, 只有最后一个起效 [root@localhost nginx]# docker run -it --name test1 mynginx:v3.0.3 this is dockerfile etnryponit exec echo this is dockerfile cmd three [root@localhost nginx]# ## 当CMD指令是shell格式,则ENTRYPOINT指令的参数会带上/bin/sh -c + CMD指令 [root@localhost nginx]# docker run -it --name test1 mynginx:v3.0.3 this is dockerfile etnryponit exec /bin/sh -c echo this is dockerfile cmd two [root@localhost nginx]# # 使用--entryponit 参数选项覆盖 ENTRYPOINT 指令 #注意: ## --entryponit后面只能接可执行命令, 并且不能带参数, 如果带参数会报错 ## --entrypoint的位置在镜像名称前面,如果在镜像名称后面会覆盖CMD指令内容 [root@localhost nginx]# docker run -it --name test2 --entrypoint=echo mynginx:v3.0.3 $$ 4207 [root@localhost nginx]# # 错误示范 ## --entryponit后面带命令参数 [root@localhost nginx]# docker run -it --name test3 --entrypoint="echo $$" mynginx:v3.0.3 docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "echo 4207": executable file not found in $PATH: unknown. [root@localhost nginx]# ## --entryponit 位置在镜像名称后面(覆盖的是CMD指令内容,而不是ENTRYPOINT指令内容) [root@localhost nginx]# docker run -it --name test4 mynginx:v3.0.3 --entrypoint="echo $$" this is dockerfile etnryponit exec --entrypoint=echo 4207 [root@localhost nginx]# ##ENTRYPOINT指定的进程pid=1 [root@localhost nginx]# docker run -it --name test5 --entrypoint=top ubuntu ..... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 7292 1928 1384 R 0.0 0.0 0:00.04 top
- 使用场景
- ENTRYPOINT 用于初始化配置工作,例如
- 启动容器就是启动主进程,但启动主进程前,可能需要一些准备工作,比如 mysql 可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决
- 还可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务
- 这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作,这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 )作为命令,在脚本最后执行
- redis官方镜像参考
# Dockerfile FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD [ "redis-server" ] # docker-entrypoint.sh (该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行) #!/bin/sh ... # allow the container to be started with `--user` if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then find . \! -user redis -exec chown redis '{}' + exec gosu redis "$0" "$@" fi exec "$@"
- ENTRYPOINT 用于初始化配置工作,例如
2.3 操作指令
2.3.1 RUN
- 格式
- shell 形式: RUN command1 && command2
- exec 形式: RUN [“executable”,“param1”,“[aram2]”]
- 作用
- 指定镜像构建时执行的命令
- 特别说明
- RUN 在下一次建构期间,会优先查找本地缓存,若不想使用缓存可以通过–no-cache解除。例如:docker build --no-cache mynginx:v3.0.4 .
- RUN 指令指定的命令是否可以执行取决于基础镜像;
- 每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像层。当命令较长时可以使用\来换行
- shell形式, 默认使用/bin/sh -c 执行后面的command, 可以使用 && 或 \ 连接多个命令;
- exec形式, exec形式被解析为JSON序列,这意味着必须使用双引号 “”; 与 shell 形式不同,exec 形式不会调用shell解析。但exec形式可以运行在不包含shell命令的基础镜像中。
- 例如:RUN [“echo”,“$HOME”] ; 这样的指令 $HOME并不会被解析,必须RUN [“/bin/sh”,“-c”,“echo $HOME”]
- 样例
RUN apt-get update \ && apt-get install -y libsnappy-dev zliblg-dev libbz2-dev \ && rm -rf /var/cache/apt \ && rm -rf /var/lib/apt/lists/*
2.3.2 CMD
- 格式
#语法1, shell形式, 默认 /bin/sh -c运行 ##1.1 此时运行为shell的子进程,能使用shell的操作符(if、环境变量、? *通配符等) ##1.2 注意:进程在容器中的 PID != 1,这意味着该进程并不能接受到外部传入的停止信号docker stop CMD command param1 param2 ... #语法2, exec形式 ##2.1 不会以/bin/sh -c运行(非shell子进程),因此不支持shell的操作符 ##2.2 若运行的命令依赖shell特性,可以手动启动CMD ["/bin/sh","-c","executable","param1"...] CMD ["executable","param1","param2"] #语法3, exec形式, 仅设置参数, 配合ENTRYPOINT使用, 作为 ENTRYPOINT的默认参数 CMD ["param1", "param2"]
- 作用
- 指定启动容器时默认执行的命令
- 特别说明
- CMD运行结束后容器将终止;
- CMD可以被docker run后面的命令覆盖;
- 一个Dockerfile只有顺序向下的最后一个CMD生效;
- 样例
# Dockerfile CMD echo this is dockerfile cmd one CMD echo this is dockerfile cmd two CMD echo this is dockerfile cmd three # docker image inspect输出(默认/bin/sh -c运行;只有最后一个CMD生效) "Cmd": [ "/bin/sh", "-c", "echo this is dockerfile cmd three" ] # docker run 覆盖CMD指令 [root@localhost nginx]# docker run -it --name test1 mynginx:v3.0.3 echo "this is docker run override CMD" this is docker run override CMD
- ENTRYPOINT和CMD的组合情况说明
具体情况比较多,如表格所示
No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”,“p1_entry”] | |
---|---|---|---|
No CMD | error,not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”,“p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”,“p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
CMD exec_cmd p1_cmd | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
- 分析以上表格,可总结以下结论
- shell格式的ENTRYPOINT指令,不会使用CMD指令的内容作为参数
- exec格式的ENTRYPOINT指令,会使用CMD指令的内容作为参数;并且,如果是shell格式的CMD指令,还会把默认带上的“/bin/sh -c”作为ENTRYPOINT指令参数中的一部分。
2.3.3 COPY
- 格式
- COPY src dest
- COPY [“src”, “src”, … “dest”]
- 作用
- 从构建主机复制文件到镜像中
- 特别说明
- 复制构建主机的 src 必须在 Dockerfile 所在路径或子路径下,不能是其父目录;当目标路径不存在时,会自动创建。
- 当 src 是目录。其内部的文件和子目录都会递归复制,但 src 目录本身不会被复制;
- 如果指定了多个 src 或使用了通配符,这 dest 必须是一个目录,且必须以 / 结尾;否则 dest 会被作为一个普通文件,最后一个 src 的内容将被写入到 dest。
- 样例
# 错误案例 ## 1. COPY的src不在Dockerfile所在目录或子目录下 COPY /home/hyp/workspace/demo/test.c src ## ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 8584a5bb-281d-45d1-85da-b525e319a629::l67260bvxak2j3ikf8lez82tu: failed to walk /var/lib/docker/tmp/buildkit-mount160530273/home/hyp/workspace/demo: lstat /var/lib/docker/tmp/buildkit-mount160530273/home/hyp/workspace/demo: no such file or directory ## 2. 指定多个src,但是dest没有以 / 结尾 COPY demo/test.py demo/test.c target ## 因为dest没有以 / 结尾,被当作普通文件处理。上述命令最终的效果是最后一个src被复制成target文件。 # 正确案例 COPY demo/test.py demo/test.c dest/
2.3.4 ADD
- 格式
- ADD src dest
- ADD [“src”, “src”, … “dest”]
- 作用
- 从构建宿主机复制文件到镜像中。类似于COPY指令,但ADD支持tar文件以及URL路径。
- 特别说明
- 如果 src 是一个压缩文件(tar),会被解压为一个目录;如果src是通过URL下载一个文件的tar,则不会被解压;
- ADD指令与COPY指令功能类似,当使用本地目录为源目录时,推荐使用 COPY。
- 样例
# demo.tar 将解压成demo目录到dest目录下 (即 dest/demo/) ADD demo.tar dest/
2.4 查看镜像/容器的相关信息
2.4.1 查看镜像的指令信息
- 命令语法
- docker image inspect [OPTIONS] IMAGE [IMAGE…]
2.4.2 查看容器的指令信息
- 命令语法
- docker inspect [OPTIONS] NAME|ID [NAME|ID…]
OPTIONS说明: -f :指定返回值的模板文件。 -s :显示总的文件大小。 --type :为指定类型返回JSON。
- docker inspect [OPTIONS] NAME|ID [NAME|ID…]
- 示例
[root@localhost nginx]# docker inspect test1 [ { "Id": "a87510fecc9a1987de5f75521a1322b417efe684499df88bc2886be39cf38193", "Created": "2024-03-06T07:21:07.908834446Z", "Path": "/docker-entrypoint.sh", "Args": [ "nginx", "-g", "daemon off;" ], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 12805, "ExitCode": 0, "Error": "", "StartedAt": "2024-03-06T07:21:08.406050451Z", "FinishedAt": "0001-01-01T00:00:00Z", "Health": { "Status": "healthy", "FailingStreak": 0, "Log": [ { "Start": "2024-03-06T16:33:25.6583247+08:00", "End": "2024-03-06T16:33:25.922296207+08:00", "ExitCode": 0, "Output": "1024\n" } .... ] } }, "Image": "sha256:3d8a72e2e67e99b1aea5bcec45eb5e83f4bfe31d36db9011a22919647d364b8e", "ResolvConfPath": "/var/lib/docker/containers/a87510fecc9a1987de5f75521a1322b417efe684499df88bc2886be39cf38193/resolv.conf", "HostnamePath": "/var/lib/docker/containers/a87510fecc9a1987de5f75521a1322b417efe684499df88bc2886be39cf38193/hostname", "HostsPath": "/var/lib/docker/containers/a87510fecc9a1987de5f75521a1322b417efe684499df88bc2886be39cf38193/hosts", "LogPath": "/var/lib/docker/containers/a87510fecc9a1987de5f75521a1322b417efe684499df88bc2886be39cf38193/a87510fecc9a1987de5f75521a1322b417efe684499df88bc2886be39cf38193-json.log", "Name": "/test1", "RestartCount": 0, "Driver": "overlay2", "Platform": "linux", "MountLabel": "", "ProcessLabel": "", "AppArmorProfile": "", "ExecIDs": null, .... "Config": { "Hostname": "a87510fecc9a", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "80/tcp": {} }, "Tty": true, "OpenStdin": true, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "NGINX_VERSION=1.25.4", "NJS_VERSION=0.8.3", "PKG_RELEASE=1~bookworm", "key1=value1", "key2=value2 key3 value3" ], "Cmd": [ "nginx", "-g", "daemon off;" ], "Healthcheck": { "Test": [ "CMD-SHELL", "echo 1024" ], "Interval": 5000000000, "Timeout": 3000000000 }, "Image": "mynginx:v3.0.4", "Volumes": null, "WorkingDir": "", "Entrypoint": [ "/docker-entrypoint.sh" ], "OnBuild": null, "Labels": { "AUTHOR": "hyp@dgzq.com.cn", "DATE": "20231206", "VERSION_ID": "3.0.3", "maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>" }, "StopSignal": "SIGQUIT" }, ... } ]
2.4.3 本地镜像查找
2.4.3.1 images 语法
- docker images [OPTIONS] [REPOSITORY[:TAG]]
2.4.3.2 OPTIONS的常用值
- -a / --all: 显示所有镜像,包含中间映像(默认情况下中间映像是隐藏的);
- -q / --quiet: 只显示镜像id;
- –no-trunc:显示完整的镜像id。默认情况下,镜像的id只显示前12位,–no-truncno-trunc参数会将镜像id完整的显示出来;
- –digests:显示镜像的摘要信息;
- –filter / -f: 表示根据条件过滤要显示的镜像,语法为:docker images -f KEY=VALUE [REPOSITORY[:TAG]] ,key的可选值如下:
- dangling boolean: 过滤悬挂的镜像,如:dangling=true表示只显示悬挂的镜像;
- label string: 根据标签过滤,如:label=version表示显示有version标签的镜像,label=version=1.0表示显示version=1.0的镜像;
- before image: 显示在某个镜像之前创建的镜像,如:before=centos:5.8表示显示在centos:5.8这个镜像之前创建的镜像;
- since image: 显示在某个存在之后创建的镜像,如:since=centos:5.8表示显示在centos:5.8这个镜像存在之后的镜像;
- reference string:模糊匹配,如:reference=cent*:5*, 显示名称已cent开头版本号已5开头的镜像;
- –format: 指定镜像显示的格式,格式为:docker images --format “[table/json] {{COLUMN}}[{{COLUMN}}…]” [REPOSITORY[:TAG]]。table表示使用表格的方式输出显示,支持\t格式;json表示使用json格式输出显示。COLUMN的可选值如下:
Placeholder | Description |
---|---|
.ID | Image ID |
.Repository | Image repository |
.Tag | Image tag |
.Digest | Image digest |
.CreatedSince | Elapsed time since the image was created |
.CreatedAt | Time when the image was created |
.Size | Image disk size |
-
示例
$ docker images --format "{{.ID}}: {{.Repository}}" 77af4d6b9913: <none> b6fa739cedf5: committ 78a85c484f71: <none> $ docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}" IMAGE ID REPOSITORY TAG 77af4d6b9913 <none> <none> b6fa739cedf5 committ latest 78a85c484f71 <none> <none> $ docker images --format json {"Containers":"N/A","CreatedAt":"2021-03-04 03:24:42 +0100 CET","CreatedSince":"5 days ago","Digest":"\u003cnone\u003e","ID":"4dd97cefde62","Repository":"ubuntu","SharedSize":"N/A","Size":"72.9MB","Tag":"latest","UniqueSize":"N/A","VirtualSize":"72.9MB"} {"Containers":"N/A","CreatedAt":"2021-02-17 22:19:54 +0100 CET","CreatedSince":"2 weeks ago","Digest":"\u003cnone\u003e","ID":"28f6e2705743","Repository":"alpine","SharedSize":"N/A","Size":"5.61MB","Tag":"latest","UniqueSize":"N/A","VirtualSize":"5.613MB"}
2.4.4 悬挂镜像
- 构建镜像时,当新镜像和原来的镜像同名的时候,原镜像则会将名字取消掉,取而代之的则是新的镜像,原来的镜像就会如下显示,一般来说,悬挂镜像已经失去了存在的价值,是可以随意删除的。
REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> 3d8a72e2e67e 2 days ago 187MB <none> <none> ba9c646b273b 2 days ago 187MB <none> <none> 68a539de5f3a 2 days ago 187MB
- 查看悬挂镜像
- docker images -f dangling=true
- 删除所有悬挂镜像
- docker image prune
2.4.5 查看镜像历史
- 一个镜像文件可以由多个层组成, 如果需要知道各个层的内容具体是什么,可以使用 history 子命令,该命令将列出各层的创建信息。
[root@localhost demo]# docker history mynginx:v3.0.4 IMAGE CREATED CREATED BY SIZE COMMENT 1439d3105e94 2 days ago ADD demo.tar dest/ # buildkit 901B buildkit.dockerfile.v0 <missing> 2 weeks ago HEALTHCHECK &{["CMD-SHELL" "echo 1024"] "5s"… 0B buildkit.dockerfile.v0 <missing> 2 weeks ago RUN |1 VERSION=3.0.3 /bin/sh -c echo "this i… 29B buildkit.dockerfile.v0 <missing> 2 weeks ago RUN |1 VERSION=3.0.3 /bin/sh -c echo "${VERS… 6B buildkit.dockerfile.v0 <missing> 2 weeks ago ENV key1=value1 key2=value2 key3 value3 0B buildkit.dockerfile.v0 <missing> 2 weeks ago ENV key1=value2 key3 value3 0B buildkit.dockerfile.v0 <missing> 2 weeks ago LABEL AUTHOR=hyp@dgzq.com.cn DATE=20231206 0B buildkit.dockerfile.v0 <missing> 2 weeks ago LABEL VERSION_ID=3.0.3 0B buildkit.dockerfile.v0 <missing> 2 weeks ago ARG VERSION=3.0.3 0B buildkit.dockerfile.v0 <missing> 3 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B buildkit.dockerfile.v0 <missing> 3 weeks ago STOPSIGNAL SIGQUIT 0B buildkit.dockerfile.v0 <missing> 3 weeks ago EXPOSE map[80/tcp:{}] 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B buildkit.dockerfile.v0 <missing> 3 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB buildkit.dockerfile.v0 <missing> 3 weeks ago COPY 20-envsubst-on-templates.sh /docker-ent… 3.02kB buildkit.dockerfile.v0 <missing> 3 weeks ago COPY 15-local-resolvers.envsh /docker-entryp… 336B buildkit.dockerfile.v0 <missing> 3 weeks ago COPY 10-listen-on-ipv6-by-default.sh /docker… 2.12kB buildkit.dockerfile.v0 <missing> 3 weeks ago COPY docker-entrypoint.sh / # buildkit 1.62kB buildkit.dockerfile.v0 <missing> 3 weeks ago RUN /bin/sh -c set -x && groupadd --syst… 112MB buildkit.dockerfile.v0 <missing> 3 weeks ago ENV PKG_RELEASE=1~bookworm 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV NJS_VERSION=0.8.3 0B buildkit.dockerfile.v0 <missing> 3 weeks ago ENV NGINX_VERSION=1.25.4 0B buildkit.dockerfile.v0 <missing> 3 weeks ago LABEL maintainer=NGINX Docker Maintainers <d… 0B buildkit.dockerfile.v0 <missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:eb6a3def1f69e7665… 74.8MB
三、创建镜像
3.1 docker build 命令
- 编写完成 Dockerfile 之后,可以通过 docker [image] build 命令来创建镜像。
- 基本的格式为 docker image build [OPTIONS] PATH | URL | -
- 该命令将读取指定路径下(包括子目录)的 Dockerfile ,并将该路径下所有数据作为上下文( Context )发送给 Docker 服务端。Docker 服务端在校验 Dockerfile 格式通过后,逐条执行其中定义的指令,碰到 ADD、COPY、RUN 指令会生成一层新的镜像 。最终如果创建镜像成功,会返回最终镜像的ID。
- 如果上下文过大, 会导致发送大量数据给服务端,延缓创建过程。除非是生成镜像所必需的文件,不然不要放到上下文路径。如果使用非上下文路径下的 Dockerfile,可以通过 -f 选项来指定其路径。
- 要指定生成镜像的标签信息,可以通过 t 选项。该选项可以重复使用多次为镜像一次添加多个名称。例如,上下文路径为 /tmp/docker_builder/,并且希望生成镜像标签为 builder/first_image: 1.0.0, 可以使用下面的命令:
docker build -t builder/first_image:1.0.0 /tmp/docker_builder/
3.2 docker build命令选项
Option | Default | Description |
---|---|---|
–add-host | Add a custom host-to-IP mapping (host:ip) | |
–build-arg | Set build-time variables | |
–cache-from | Images to consider as cache sources | |
–cgroup-parent | Set the parent cgroup for the instructions during buildRUN | |
–compress | Compress the build context using gzip | |
–cpu-period | Limit the CPU CFS (Completely Fair Scheduler) period | |
–cpu-quota | Limit the CPU CFS (Completely Fair Scheduler) quota | |
-c, --cpu-shares | CPU shares (relative weight) | |
–cpuset-cpus | CPUs in which to allow execution (0-3, 0,1) | |
–cpuset-mems | MEMs in which to allow execution (0-3, 0,1) | |
–disable-content-trust | true | Skip image verification |
-f, --file | Name of the Dockerfile (Default is PATH/Dockerfile) | |
–force-rm | Always remove intermediate containers | |
–iidfile | Write the image ID to the file | |
–isolation | Container isolation technology | |
–label | Set metadata for an image | |
-m, --memory | Memory limit | |
–memory-swap | Swap limit equal to memory plus swap: -1 to enable unlimited swap | |
–network | API 1.25+ Set the networking mode for the RUN instructions during build | |
–no-cache | Do not use cache when building the image | |
–platform | API 1.38+ Set platform if server is multi-platform capable | |
–pull | Always attempt to pull a newer version of the image | |
-q, --quiet | Suppress the build output and print image ID on success | |
–rm | true | Remove intermediate containers after a successful build |
–security-opt | Security options | |
–shm-size | Size of /dev/shm | |
–squash | API 1.25+ experimental (daemon) Squash newly built layers into a single new layer | |
-t, --tag | Name and optionally a tag in the formatname:tag | |
–target | Set the target build stage to build. | |
–ulimit | Ulimit options |
3.3 构建基础镜像
- 基础镜像比较特殊,其Dockerfile中往往不存在FROM指令,或者基于 scratch 镜像(FROM scratch),这意味着其在整个镜像树中处于根的位置。
- 是否基于其他镜像制作还是自己制作,就取决于第一行的FROM是否包含scratch关键字(特殊的字段,表示一个空镜像)。
- FROM scratch:表示使用一个空镜像(FROM 子句是告诉Docker引擎构建镜像上下文的开始,必须存在);
- 如果自己制作基础镜像,那么必要的二进制文件及目录是必要的。
- 示例
# 构建一个运行在一个我自己剪裁过的Centos7基础系统二进制包上的 Java8 镜像
FROM scratch
ADD centos-7-x86_64-docker.tar.xz /
RUN yum install -y wget && \
yum clean all && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
ENV JAVA_VERSION jdk1.8.0_271
ENV JAVA_HOME /usr/lib/${JAVA_VERSION}
ENV PATH ${JAVA_HOME}/bin:$PATH
# 涉及改变镜像大小的指令,尽量放到同一行,这样构建过程中的删除指令对减小体积才能生效
RUN wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" \
http://download.oracle.com/otn-pub/java/jdk/8u271-b09/61ae65e088624f5aaa0b1d2d801acb16/jdk-8u271-linux-x64.tar.gz -O /usr/lib/jdk-8u271-linux-x64.tar.gz && \
tar -xf /usr/lib/jdk-8u271-linux-x64.tar.gz -C /usr/lib/ && \
rm -rf /usr/lib/jdk-8u271-linux-x64.tar.gz && \
rm -rf ${JAVA_HOME}/src.zip \
${JAVA_HOME}/lib/visualvm \
${JAVA_HOME}/jre/lib/plugin.jar \
${JAVA_HOME}/jre/bin/javaws \
${JAVA_HOME}/jre/lib/desktop \
${JAVA_HOME}/jre/plugin \
${JAVA_HOME}/jre/lib/deploy* \
${JAVA_HOME}/jre/lib/amd64/libglass.so \
${JAVA_HOME}/jre/lib/amd64/libgstreamer-lite.so \
${JAVA_HOME}/jre/lib/amd64/libjavafx*.so \
${JAVA_HOME}/jre/lib/amd64/libjfx*.so
CMD ['/bin/bash']
3.3 .dockerignore 文件
- 可以通过 .dockerignore 文件(每一行添 一条匹配模式)来让 Docker 忽略略匹配路径或文件,在创建镜像时候不将无关数据发送到服务端。
- 例如下面的例子中包括了6个忽略的模式(第一行为注释)
# dockerignore 文件中可以定义忽略模式
*/temp*
*/*/temp*
tmp?
-*
Dockerfile
!README.md
- .dockerignore 文件中模式语法支持 Golang 风格的路径正则格式:
- “*”: 表示任意多个字符;
- “?”: 代表单个字符;
- “!”: 表示不匹配(即不忽略指定的路径或文件)
3.4 多步骤创建
- 自17.05 版本开始, Docker 支持多步骤镜像创建(Multi-stage build) 特性,可以精简最终生成的镜像大小。
- 对于需要编译的应用(如 Go Java 语言等)来说,通常情况下至少需要准备两个环境的 Docker 镜像:
- 编译环境镜像:包括完整的编译引擎、依赖库等,往往比较庞大。作用是编译应用为二进制文件;
- 运行环境镜像::利用编译好的二进制文件,运行应用,由于不需要编译环境,体积比较小。
- 使用多步骤创建,可以在保证最终生成的运行环境镜像保持精筒的情况下,使用单一的Dockerfile ,降低维护复杂度。
- 示例
# 1. main.go
# Go 语言应用为例 创建干净目录,进入到目录中,创建 main.go文件,内容为:
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, Docker")
}
# 2. Dockerfile
# 创建 Dockerfile ,使用 go ang :l .9 镜像编译应用二进制文件为 ,使用精简的镜像 alpine:latest 作为运行环境。 Dockerfile完整内容为:
FROM golang:l.9 as builder # define stage name as builder
RUN mkdir -p /go/src/test
WORKDIR /go/src/test
COPY main.go .
RUN CGO_ENABLED=O GOOS=linux go build -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/test/app . # copy file from the builder stage
CMD ["./app"]
# 执行如下命令创建镜像,并运行应用:
docker build -t test-multistage:latest .
# 查看生成的最终镜像(最终大小仅6.55MB)
docker images | grep test-multistage
四、生产实践
4.1 选⽤最⼩化基础镜像
编写 Dockerfile 时,通常使⽤⼀个通⽤的容器镜像作为基础镜像,例如: eclipse-temurin:8-alpine ,选⽤最⼩化基础镜像,即只包含项⽬确实需要的系统⼯具和库的镜像,较⼩的基础镜像可以确保在⼀个新节点上拉起容器时有更快的启动时间(节省了从镜像仓库拉取镜像的⽹络请求时间),并且能够最⼩化系统的攻击⾯,确保所⽤的操作系统是安全的,⼀般推荐采⽤以 alpine 系统为基础的基础镜像。
4.2 避免不需要的包
为了降低复杂性、减少依赖、减少安全⻛险、减⼩镜像⽂件⼤⼩、节约构建时间,应该避免构建镜像过程中安装任何不必要的包,例如:不需要在应⽤镜像内包含⽂本编辑器(vim)等。
4.3 每个容器只运⾏⼀个进程或应⽤
尽管单个容器确实可以运⾏多个应⽤程序,但出于以下原因,需要尽可能考虑遵循“每个容器⼀个应⽤程序“(以下简称单进程容器)的最佳实践:
- 单进程容器架构更简单,⽔平伸缩更加容易,⽐如,在⼀个容器内同时包含 tomcat 和 mysql 等,容器的⽔ 平伸缩就会变得⾮常复杂甚⾄⽆法进⾏⽔平伸缩。
- 单进程容器的排障更简单,进⾏问题排查时,不必对容器内整个系统的各个部分进⾏排查,使应⽤更具有可移 植性和可预测性。
- 单进程容器使应⽤程序的⽣命周期管理更灵活,应⽤进程即容器主进程,应⽤异常退出容器即会⾃动销毁并重 新启动,使应⽤拥有故障⾃愈的能⼒。
- 从安全性和隔离性⻆度来看,单进程容器能够提供更安全的服务和应⽤程序间的隔离,以保持更稳定的安全状态。
4.4 构建时使⽤.dockerignore ⽂件
使⽤ Dockerfile 构建镜像时建议将 Dockerfile 放置⼀个新的空⽬录下,将构建所需要的⽂件添加到该⽬录中,为 了提⾼构建镜像的效率,可以在该⽬录下新建⼀个 .dockerignore ⽂件来指定要忽略的⽂件和目录。 .dockerignore ⽂件的排除模式语法和 git 的 .gitignore ⽂件相似。
4.5 最⼩化镜像层数
构建镜像时,Dockerfile 的每⼀条 RUN 指令都会在镜像上构建⼀层,为了减⼩镜像⽂件⼤⼩,建议在 Dockerfile可读性(也包括⻓期的可维护性)和减⼩层数之间做⼀个平衡,⽐如,将多个 RUN 指令进⾏合并执⾏(通过 &&符串连多条 shell 命令),可有效减少镜像的层数。
4.6 合理使⽤构建缓存
镜像的构建过程中,会顺序执⾏ Dockerfile 中的指令,在执⾏每条指令之前,会先从缓存中查找是否已经存在可重 ⽤的镜像,如果有就使⽤现存的镜像,不再重复创建,以加快镜像构建过程。如果不想在构建过程中使⽤缓存,可 在 docker build 命令中使⽤ --no-cache=true 选项,镜像缓存的基本规则包含:
- 从⼀个基础镜像开始(FROM 指令指定),下⼀条指令将和该基础镜像的所有⼦镜像进⾏匹配,检查这些⼦ 镜像被创建时使⽤的指令是否和被检查的指令完全⼀样。如果不是,则缓存失效。
- 在⼤多数情况下,只需要简单地对⽐ Dockerfile 中的指令和⼦镜像。然⽽,有些指令需要更多的检查和解 释。
- 对于 ADD 和 COPY 指令,镜像中对应⽂件的内容也会被检查,每个⽂件都会计算出⼀个校验和。⽂件的最后 修改时间和最后访问时间不会纳⼊校验。在缓存的查找过程中,会将这些校验和和已存在镜像中的⽂件校验和 进⾏对⽐。如果⽂件有任何改变,⽐如内容和元数据,则缓存失效。
- 除了 ADD 和 COPY 指令,缓存匹配过程不会查看临时容器中的⽂件来决定缓存是否匹配。例如,当执⾏完 RUN apt-get -y update 指令后,容器中⼀些⽂件被更新,但 Docker 不会检查这些⽂件。这种情况下,只有 指令字符串本身被⽤来匹配缓存。
- ⼀旦缓存失效,所有后续的 Dockerfile 指令都将产⽣新的镜像,缓存不会被使⽤。
4.7 让应⽤程序容器内进程ID=1
当⼀个容器被销毁(即停⽌运⾏)时,操作系统会发送 SIGTERM 给容器内的1号进程(pid=1,可通过 ps -ef 命令查看),1号进程接收到信号后可进⾏容器终⽌前的资源释放等处理操作。 例如:构建镜像时,可利⽤系统的 exec 命令特性,为应⽤启动编写独⽴的 shell 脚本⽂件,以 exec 形式启动相关进程。
4.8 合理利⽤环境变量
- 应⽤镜像应具有环境⽆关性,即需要确保可以在可预测的任何环境中都⽆需重新构建应⽤镜像即可直接运⾏容器,与环境相关的相关数据应尽可能的通过环境变量⽅式在镜像中引⽤,以确保容器的可移植性和可运维能⼒。例如:JVM启动参数 JAVA_OPTS( -Xmxn 、 -Xmsn 、 -Dproperty=value 等)、或 Spring Boot 的分区配置定义(spring.profiles.active指定的分区名)等。
- 出于安全考虑,应禁⽌将应⽤密钥(如:数据库密码、API访问凭据等)保存⾄镜像内。
4.9 设置健康检查
- 可以在K8S层面设置,也可以在Dockerfile层面设置。
4.10 正确使用版本号
- 使用明确的版本号信息,如 1.0 2.0 ,而非依赖于默认的 latest。通过版本号可以避免环境不一致导致的问题。
4.11 恰当使用多步骤创建 (17.05+ 版本支持)
- 通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个Dockerfile。