目录
镜像制作及原因
快照方式制作镜像
Dockerfile 制作镜像
为什么需要 Dockerfile
Dockerfile 指令
常见问题
镜像制作及原因
镜像制作是因为某种需求,官方的镜像无法满足需求,需要我们通过一定手段来自定义镜像来满足要求。
制作镜像往往因为以下原因
1. 编写的代码如何打包到镜像中直接跟随镜像发布
2. 第三方制作的内容安全性未知,如含有安全漏洞
3. 特定的需求或者功能无法满足,如需要给数据库添加审计功能
4. 公司内部要求基于公司内部的系统制作镜像,如公司内部要求使用自己的操作系统作为基础镜像
Docker 镜像制作方式
制作容器镜像,主要有两种方法:
制作快照方式获得镜像(偶尔制作的镜像):在基础镜像上(比如 Ubuntu),先登录容器中,然后安装镜像需要的所有软件,最后整体制作快照。
Dockerfile 方式构建镜像(经常更新的镜像):将软件安装的流程写成 Dockerfile,使用 docker build 构建成容器镜像。
快照方式制作镜像
制作命令
docker commit
功能
从容器创建一个新的镜像。
语法
Shell
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
参数
○ -a : 提交的镜像作者;
○ -c : 使用 Dockerfile 指令来创建镜像;可以修改启动指令
○ -m : 提交时的说明文字;
○ -p : 在 commit 时,将容器暂停。
如:
docker commit c3f279d17e0a aaa/mynginx:v01
Dockerfile 制作镜像
Dockerfile 是什么
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像 ,这个脚本就是 Dockerfile 。
Dockerfile 是一个文本文件,其内 包含了一条条的指令 (Instruction) , 每一条指令 构建一层,因此每一条指令的内容,就是描述该层应当如何构建 。
Dockerfile 格式
# Comment
INSTRUCTION arguments
该指令不区分大小写。然而,约定是它们是大写的,以便更容易地将它们与参数区分开来。
Docker 按顺序运行指令 Dockerfile
Docker 将以 开头 的行视为 # 注释,行中其他任何地方的标记 #都被视为参数。这允许像这样的语句:
Shell
# Comment
RUN echo 'we are running some # of cool things'
生活案例
Dockerfile 就像我们盖房子的时候的施工图纸,地基多深有多少层,每层是什么,以及多高。
为什么需要 Dockerfile
可以按照需求自定义镜像
○ 和 docker commit 一样能够自定义镜像,官方的镜像可以说很少能直接满足我们应用的,都需要我们自己打包自己的代码进去然后做成对应的应用镜像对外使用。
很方便的自动化构建,重复执行
○ 通过 dockerfile 可以自动化的完成镜像构建,而不是像 docker commit 一样,手动一个命令一个命令执行,而且可以重复执行, docker commit 的话很容易忘记执行了哪个命令,哪个命令没有执行。
维护修改方便,不再是黑箱操作
○ 使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜 更加标准化,体积可以做的更小
○ docker 容器启动后,系统运行会生成很多运行时的文件,如果使用 commit 会导致这些文件也存储到镜像里面,而且 commit 的时候安装了很多的依赖文件,没有有效的清理机制的话会导致镜像非常的臃肿。使用 Dockerfile 则会更加标准化,而且提供多级构建,将编译和构建分开,不会有运行时的多余文件,更加的标准化,dockerfile 很容易二次开发。
Dockerfile 指令
FROM | 构建镜像基于哪个镜像,也就是基础镜像 |
LABEL | 为镜像添加元数据 |
COPY | 拷贝文件或目录到镜像中,跟 ADD 类似,但不具备自动下载或解压的功能 |
ADD | 拷贝文件或目录到镜像中,如果是 URL 或 压缩包便会自动下载或自动解压 |
WORKDIR | 指定工作目录 |
VOLUME | 指定容器挂载点 |
RUN | 指定 docker build 过程中运行的程序 |
EXPOSE | 声明容器的服务端口(仅仅是声明) |
ENV | 设置环境变量 |
CMD | 运行容器时执行的命令 |
ENTRYPOINT | 运行容器时程序入口 |
ARG | 指定构建时的参数 |
SHELL | 指定采用哪个 shell |
USER | 指定当前用户 |
HEALTHCHECK | 健康检测指令 |
ONBUILD | 在当前镜像构建时并不会被执行。只有当以 当前镜像为基础镜像,去构建下一级镜像的 时候才会被执行 |
STOPSIGNAL 允许您覆盖发送到容器的默认信号.
FROM
功能
○ FROM 指令用于 为镜像文件构建过程指定基础镜像,后续的指令运行于此基础镜像所提供的运行
环境;
注意事项
○ FROM 指令必须是 Dockerfile 中 非注释行或者 ARG 之后的第一个指令 ;
○ 实践中,基准镜像可以是任何可用镜像文件,默认情况下, docker build 会在 docker 主机上查找指定的镜像文件 ,在其不存在时,则 会自动从 Docker 的公共 库 pull 镜像下来。如果找不到指定的镜像文件, docker build 会返回一个错误信息;
○ FROM 可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像 , 或将一个构建阶段作为另一个的依赖。
○ 如果 FROM 语句没有指定镜像标签,则 默认使用 latest 标签 。
语法
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
参数
○ <platform>: 构建的 cpu 架构,如 linux/amd64 , linux/arm64, windows/amd64
○ <image> :指定作为 base image 的名称;
○ <tag> : base image 的标签,省略时默认 latest ;
○ <digest> :是镜像的哈希码;
○ AS <name>: 指定构建步骤的名称,配合 COPY --from=<name>可以完成多级构建
样例
FROM ubuntu:22.04 as buildbase
docker build -t web1:v0.1 .
这个指令表示:用./目录下的Dockfile构建镜像,将镜像命名为web1:v0.1
LABEL
功能
○ 为镜像添加元数据,元数据是 kv 对形式
•
语法
Shell
LABEL <key>=<value> <key>=<value> <key>=<value> ...
•
样例
Shell
LABEL company="com.bit" app="nginx"
COPY
•
功能
○ 用于从 docker 主机复制新文件或者目录至创建的新镜像指定路径中 。
•
语法
Shell
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
参数
○ <src> :要复制的源文件或目录, 支持使用通配符 ;
○ <dest> :目标路径,即正在创建的 image 的文件系统路径; 建议 <dest> 使用 绝对路径 ,否则, COPY 指定以 WORKDIR 为当前路径
在路径中有空白字符时,通常使用第 2 种格式;
○ --chown :修改用户和组
○ --from <name> 可选项 :
▪
可以从之前构建的步骤中拷贝内容,结合 FROM .. AS <name>往往用作多级构建,后续我们有实战课专门完成多级构建
•
注意事项
○ <src> 必须是 build 上下文中的路径, 不能是其父目录中的文件 ;
○ 如果 <src> 是目录 ,则其 内部文件或子目录会被递归复制 ,但 <src> 目录自身 不会被复制 ;
○ 如果指定了多个 <src> ,或在 <src> 中使用了通配符,则 <dest>必须是一个目录,且 必须以 / 结尾 ;
○ 如果 <dest> 事先不存在,它将 会被自动创建 ,这包括父目录路径。
•
样例
Shell
COPY index.html /data/web/html/ # 要确保 dockerfile 同级路径下有index.html 文件
ENV
•
功能
○ 用于为镜像定义所需的 环境变量 ,并可被 Dockerfile 文件中 位于其后的其它指 ( 如 ENV 、 ADD 、 COPY 等 ) 所调用
○ 调用格式为 $variable_name 或 ${variable_name}
•
语法
Shell
ENV <key>=<value> ...
•
样例
Shell
ENV MY_NAME="John Doe"
•
实战
1. 目录后面可能复用,我们将目录的位置提取为变量,通过 ENV 来设置,我们再次编辑 Dockerfile
Shell
# 我的 web 站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou maxhou@bit.com"
LABEL company="com.bit" app="nginx"
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
WORKDIR
•
功能
○ 为 Dockerfile 中所有的 RUN 、 CMD 、 ENTRYPOINT 、 COPY 和 ADD 指定 设 定工作目录
•
语法
Shell
WORKDIR /path/to/workdir
•
注意事项
○ 默认的工作目录是 /
○ 如果提供了相对路径,它将相对于前一条 WORKDIR 指令的路径。
○ WORKDIR 指令可以解析先前使用设置的环境变量 ENV
样例
Shell
WORKDIR /usr/local
ADD
•
功能
○ ADD 指令类似于 COPY 指令, ADD 支持使用 TAR 文件和 URL 路径 ,会自动完成解压和下载
•
语法
Shell
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
•
参数
○ <src> :要复制的源文件或目录, 支持使用通配符 ;
○ <dest> :目标路径,即正在创建的 image 的文件系统路径; 建议 <dest> 使用 绝对路径 ,否则, ADD 指定以 WORKDIR 为其实路径;在路径中有空白字符时,通常使用第 2 种格式;
○ --chown :修改用户和组
RUN
•
功能
○ 用于指定 docker build 过程中运行的程序,其可以是任何命令( 也就是说在build之前,会先执行dockerfile中的run。常常用来完成build之前的环境搭建、目录构建) •
语法
Shell
#shell form
RUN <command>
#exec form
RUN ["executable", "param1", "param2"]
•
参数
○ 第一种格式中, <command> 通常是一个 shell 命令 , 且 以 “/bin/sh -c” 来运 行 它 ,Windows 默认为 cmd /S /C 。如果一个脚本 test.sh 不能自己执行,必须要 /bin/sh -c test.sh 的方式来执行,那么,如果使用 RUN 的 shell 形式 ,最后得到的命令相当于:
Bash
/bin/sh -c "/bin/sh -c 'test.sh'"
○ 第二种语法格式中的参数是一个 JSON 格式的数组 ,其中 <executable>为要运行的命令,后面的 <paramN>为传递给命令的选项或参数;然而,此种格式指定的命令 不会以 “/bin/sh -c” 来发起 ,因此 常见的 shell 操作如变量替换以及通配符 (?,* 等 ) 替换 将不会进行 ;不过,如果要运行的命令依赖于此 shell 特性的话,可以将其替换为类似下面的格式。 RUN ["/bin/bash", "-c", "<executable>",
"<param1>"]
样例
ENV WEB_SERVER_PACKAGE nginx-1.21.1.tar.gz
RUN cd ./src && tar -xf ${WEB_SERVER_PACKAGE}
CMD
•
功能
○ 类似于 RUN 指令 , CMD 指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同
○ RUN 指令运行于映像文件 构建 过程 中,而 CMD 指令运行于基于 Dockerfile 构建出的新映像文件 启动 一个容器时
CMD 指令的首要 目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过, CMD 指定的命令其可以被 docker run 的命令行选项所覆盖
○ 在 Dockerfile 中可以存在 多个 CMD 指令,但仅最后一个会生效
语法
CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
第二种则用于为 ENTRYPOINT 指令提供默认参数
json 数组中,要使用 双引号 ,单引号会出错
CMD ["/usr/bin/wc","--help"]
ENTRYPOINT
功能
○ 用于指定容器的启动入口
•
语法
Shell
#exec from
ENTRYPOINT ["executable", "param1", "param2"]
# shell form
ENTRYPOINT command param1 param2
•
参数
○ json 数组中,要使用 双引号 ,单引号会出错
•
样例
Shell
ENTRYPOINT ["nginx","-g","daemon off;"]
ARG
功能
○ ARG 指令类似 ENV ,定义了一个变量;区别于 ENV:用户可以在构建时docker build --build-arg <varname> = <value> 进行对变量的修改; ENV 不可以;
○ 如果用户指定了未在 Dockerfile 中定义的构建参数,那么构建输出警告。
•
语法
ARG <name>[=<default value>]
•
注意事项
○ Dockerfile 可以包含一个或多个 ARG 指令
○ ARG 支持指定默认值
○ 使用范围:定义之后才能使用,定义之前为空,如下面的案例,执行命令
docker build --build-arg username=what_user . 第二行计算结果为
some_user ,不是我们指定的 build-arg 中的参数值 what_user
Shell
FROM busybox
USER ${username:-some_user}
ARG username
USER $username
# ...
○ ENV 和 ARG 同时存在, ENV 会覆盖 ARG
Shell
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER
执行下面指令输出 v1.0.0
Plaintext
docker build --build-arg CONT_IMG_VER=v2.0.1 .
我们可以优化写法为
Shell
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
○ 系统内置了一些 ARG 变量
▪
HTTP_PROXY
▪
http_proxy
HTTPS_PROXY
▪
https_proxy
▪
FTP_PROXY
▪
ftp_proxy
▪
NO_PROXY
▪
no_proxy
▪
ALL_PROXY
▪
all_proxy
•
样例
Shell
FROM busybox
ARG user1=someuser
ARG buildno=1
arg常用于镜像版本的设计
env的变量一般用于容器运行阶段,而arg变量一般用于镜像搭建阶段
HEALTHCHECK
功能
○ HEALTHCHECK 指令告诉 Docker 如何测试容器以检查它是否仍在工作。
○ 即使服务器进程仍在运行,这也可以检测出陷入无限循环且无法处理新连接的 Web 服务器等情况。
语法
Shell
HEALTHCHECK [OPTIONS] CMD command (check container health by running a command inside the container)
HEALTHCHECK NONE (disable any healthcheck inherited from the base image)
OPTIONS 选项有:
▪
--interval=DURATION (default: 30s) :每隔多长时间探测一次,默认 30秒
▪
-- timeout= DURATION (default: 30s) :服务响应超时时长,默认 30 秒
▪
--start-period= DURATION (default: 0s):服务启动多久后开始探测,默认 0 秒
▪
--retries=N (default: 3) :认为检测失败几次为宕机,默认 3 次
○ 返回值
▪
0 :容器成功是健康的,随时可以使用
▪
1 :不健康的容器无法正常工作
▪
2 :保留不使用此退出代码
•
样例
Shell
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
docker build
docker build 命令用于使用 Dockerfile 创建镜像。
•
语法
Shell
docker build [OPTIONS] PATH | URL | -
•
关键参数
○ --build-arg=[] : 设置镜像创建时的变量;
○ -f : 指定要使用的 Dockerfile 路径;
--label=[] : 设置镜像使用的元数据;
○ --no-cache : 创建镜像的过程不使用缓存;
○ --pull : 尝试去更新镜像的新版本;
○ --quiet, -q : 安静模式,成功后只输出镜像 ID ;
○ --tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构
建中为一个镜像设置多个标签。
○ --network: 默认 default 。在构建期间设置 RUN 指令的网络模式
•
样例
Shell
docker build -t mynginx:v1 .
常见问题
镜像的多阶段构建
通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,用户也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个 Dockerfile 。
多个 From 指令如何使用
多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条FROM 为准,之前的 FROM 会被抛弃,那么之前的 FROM 又有什么意义呢?
每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。
最大的使用场景是将编译环境和运行环境分离
# 第一个构建阶段:使用编译环境
FROM node:14 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build# 第二个构建阶段:使用运行环境
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
编译阶段的镜像并不会最终生成,但是会执行编译阶段,目的是生产最终的运行镜像版本。
分层构建在 Docker 中不仅可以减少 Dockerfile 的维护成本,还可以帮助减少运行容器的体积。
以下是分层构建的一些好处:
-
缓存复用:Docker 在构建镜像时会缓存每一步的中间结果。如果你在 Dockerfile 中合理地安排指令,那么在后续的构建过程中,只有那些依赖的改变会被重新构建,而未改变的部分可以直接使用缓存,这样可以大大加快构建速度。
-
减少镜像大小:通过将编译和安装步骤与最终运行环境分开,你可以创建一个只包含运行时所需文件的最小镜像。例如,你可以在一个包含编译工具的镜像中进行编译,然后将编译好的二进制文件复制到一个更小的、不包含编译工具的基础镜像中。
-
安全性:分层构建可以帮助你创建更安全的镜像,因为你可以确保最终镜像中只包含运行应用所必需的文件和依赖项,而不包含任何不必要的工具或库,从而减少了潜在的安全风险。
-
可维护性:通过将 Dockerfile 分成多个阶段,你可以更清晰地组织构建过程,每个阶段负责特定的任务,这使得 Dockerfile 更易于理解和维护。
-
灵活性:分层构建允许你在不同的阶段使用不同的基础镜像,这可以根据需要在不同的构建阶段优化镜像大小和性能。
# 编译阶段
FROM gcc:latest as builder
WORKDIR /build
COPY . .
RUN mkdir build && cd build && cmake .. && make# 运行阶段
FROM alpine:latest
COPY --from=builder /build/myapp /myapp
CMD ["./myapp"]
CMD ["./myapp"]
在这个例子中,编译阶段使用了一个包含 GCC 编译器的镜像,而运行阶段则使用了一个更小的 Alpine 镜像,并且只复制了编译好的二进制文件,从而减小了最终镜像的大小。
这可以省去运行镜像一些不必要的软件,比如gcc等,可以减少体积;否则我们需要ADD将上下文中的gcc加入到运行镜像,使其变得臃肿
什么是空悬镜像(dangling )
仓库名、标签均为 <none> 的镜像被称为虚悬镜像,一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的。
造成虚悬镜像的原因:
原因一:
原本有镜像名和标签的镜像,发布了新版本后,重新 docker pull *** 时,旧的镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消;
原因二:
docker build 同样可以导致这种现象。比如用 dockerfile1 构建了个镜像 tnone1:v1,又用另外一个 Dockerfile2 构建了一个镜像 tnone1:v1,这样之前的那个镜像就会变成空悬镜像。
中间层镜像是什么?
为了加速镜像构建、重复利用资源, Docker 会利用 中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。默认的 docker image ls 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。
这是 Docker 层级存储(Layered Storage)的核心优势。无论中间镜像有多少层,只要这些层在最终镜像中也被使用,它们就不会在磁盘上重复存储。
你可以随意删除 这些中间镜像。它们的存在与否 完全不影响 最终镜像的功能和运行。删除它们通常被认为是清理 Docker 系统空间的一种方式。你可以使用 docker image rm