首页 > 数据库 > 数据库开发技术 > 五分钟学K8S系列-深入浅出Dockerfile
Dockerfile 是 Docker 镜像构建的核心,它通过一系列指令自动化地定义了镜像的构建过程。下面我们将详细介绍 Dockerfile 的制作流程,并通过案例展示其应用。
在讨论 Dockerfile 的制作流程之前,我们先来探讨为什么要使用 Dockerfile 进行自动构建。
高效利用存储空间:Dockerfile 利用缓存机制,避免重复下载和处理相同的文件,减少了镜像的体积。
节省带宽:由于镜像分层存储,只有变化的层会被传输,这大大减少了传输数据的大小。
简化修改过程:Dockerfile 中的指令清晰记录了构建步骤,修改和更新更加直观和方便。
提高构建效率:Dockerfile 允许重用已有的镜像层,避免重复构建,加快了构建速度。
Dockerfile 是构建 Docker 镜像的核心脚本,它包含了一系列的指令,这些指令定义了镜像的构建过程。每个指令都会创建一个新的层,这些层最终组合成一个完整的镜像。Dockerfile 的设计哲学与 Makefile 类似,都用于自动化构建过程,但 Dockerfile 更专注于容器镜像的构建。

一个 Dockerfile 通常包含以下四个部分:
基础镜像信息:指定从哪个现有的镜像开始构建。这是通过 FROM 指令实现的,它是 Dockerfile 中的第一条指令。
维护者信息:虽然这不是强制性的,但 MAINTAINER 或 LABEL 指令可以用来指定镜像的作者或维护者信息,这有助于镜像的管理和归属。
镜像操作指令:这些指令定义了镜像的内容和结构,包括安装软件包、复制文件、设置环境变量、创建用户和工作目录等。常见的指令有 RUN、COPY、ADD、ENV、WORKDIR 等。
容器启动时执行指令:定义容器启动时应该运行的命令。这通常由 CMD 或 ENTRYPOINT 指令指定,两者可以一起使用以提供默认行为和可覆盖的入口点。
手动构建镜像就像是直接烹饪一道菜,而 Dockerfile 则像是这道菜的食谱。使用 Dockerfile,你只需按照食谱上的步骤操作,就可以复现相同的菜式。
手动制作 Docker 镜像:首先手动创建一个 Docker 镜像,并记录下所有使用的命令。
编写 Dockerfile 文件:根据记录的命令,编写 Dockerfile,将命令转换为 Docker 可以理解的指令。
使用 docker build 构建镜像:运行 docker build 命令,Docker 会根据 Dockerfile 中的指令构建镜像。
测试镜像功能:构建完成后,运行并测试镜像以确保其按预期工作。
FROM 指令用于指定基础镜像,是 Dockerfile 中的第一条指令。
有两种格式
FROM<image>指定基础image为该image的最后修改的版本。
或者:
FROM<image>:<tag>指定基础image为该image的一个tag版本
示例:
FROM ubuntu:18.041. Shell 格式:
RUN <command> (the command is run in a shell - `/bin/sh -c`)这种格式在 shell 环境中执行命令,允许使用 shell 的特性,如变量替换、管道、通配符等。
2. Exec 格式:
RUN ["executable", "param1", "param2", ... ] (exec form)这种格式直接执行命令,不通过 shell。这有助于避免 shell 带来的潜在安全风险,并提供更清晰的执行环境。
RUN apt-get update \&& apt-get install -y \curl \vim
格式:
ADD <src> <dest>说明:
<src> 可以是 Dockerfile 所在目录的相对路径,也可以是一个 URL,或者是一个 tar 文件(在这种情况下,它将被自动解压)。
<dest> 是容器中的绝对路径,或者是相对于 WORKDIR 指令设置的路径。
特点:
ADD 会保留文件的权限,但所有文件和文件夹的权限会被设置为 0755,uid 和 gid 被设置为 0。
如果 <src> 是一个目录,那么只有目录内的内容会被复制,不包括目录本身。
如果 <src> 是一个可识别的压缩格式,Docker 会自动解压缩它。
如果 <src> 是一个文件,并且 <dest> 以斜杠 / 结尾,那么 <src> 将被拷贝到 <dest> 目录下。
如果 <src> 是一个文件,并且 <dest> 没有以斜杠 / 结尾,那么 <src> 的内容将被写入 <dest>。
格式:
COPY <src> <dest>说明:
COPY 只能访问 Dockerfile 所在目录(构建上下文)中的文件,不能访问 URL 或 tar 文件。
<dest> 的工作方式与 ADD 相同。
特点:
COPY 不会自动解压缩 tar 文件,它仅仅是复制文件或目录。
COPY 在权限和所有权方面比 ADD 更透明,它保留了文件原有的权限和所有权。
使用 ADD 复制本地文件:
ADD local-file /dest-path使用 ADD 从 URL 下载文件:
ADD http://example.com/remote-file /dest-path使用 COPY 复制本地文件:
COPY local-file /dest-path安全性: ADD 可以下载文件,因此如果使用 URL 作为 <src>,需要注意安全性和信任问题。
透明度: COPY 在大多数情况下更推荐使用,因为它的行为更可预测,更透明。
解压缩: 如果需要复制并解压缩 tar 文件,确保使用 ADD 指令。
单个变量:
ENV <key> <value>
多个变量:
ENV <key1>=<value1> <key2>=<value2> ...
FROM ubuntuENV APP_HOME /appENV PATH=$APP_HOME:$PATH
APP_HOME 被设置为 /app。
PATH 被修改为在原有的 PATH 基础上添加了 APP_HOME 的值。
环境变量的覆盖: 如果在构建过程中多次设置了相同的环境变量,只有最后设置的值会被保留。
环境变量的继承: 环境变量会从基础镜像继承,并且可以被当前镜像中的 ENV 指令修改。
安全性: 避免在 ENV 指令中设置敏感信息,如密码或密钥。
VOLUME ["<mountpoint>"]
<mountpoint> 是容器内部的绝对路径,它指定了挂载点的位置。
FROM baseVOLUME ["/tmp/data"]
docker run -d --name my_container -v /tmp/data my_image
docker run -t -i -rm --volumes-from my_container -name another_container my_image bash在这个例子中,another_container可以访问my_container的/tmp/data数据卷。
数据卷的生命周期:数据卷的生命周期独立于容器,容器删除后,数据卷中的数据仍然存在。
数据卷的权限:数据卷的权限可能需要根据运行容器的用户权限进行适当配置。
数据卷的备份:虽然数据卷提供了数据持久化,但仍建议定期备份重要数据。
EXPOSE <port> [<port>...]
映射单个端口:
EXPOSE 80
映射多个端口:
EXPOSE 80 443
随机映射宿主机端口:
docker run -p 80 image这将容器的 80 端口映射到宿主机的一个随机端口上。
指定宿主机端口:
docker run -p 8080:80 image这将容器的 80 端口映射到宿主机的 8080 端口上。
映射多个端口:
docker run -p 8080:80 -p 8443:443 image这将容器的 80 端口映射到宿主机的 8080 端口,同时将容器的 443 端口映射到宿主机的 8443 端口。
端口映射的安全性: 将容器端口映射到宿主机时,需要考虑安全性,确保不会暴露敏感服务。
端口冲突: 确保宿主机上没有其他服务使用相同的端口,否则会导致映射失败。
查看端口映射: 使用 docker ps 可以查看容器的端口映射情况,或者使用 docker port <container_id_or_name> <port> 来查看特定端口在宿主机上的映射。
CMD ["executable", "param1", "param2"]
CMD command param1 param2
CMD ["param1", "param2"]
Exec 格式:
CMD ["sh", "-c", "echo Hello World"]Shell 格式:
CMD echo Hello World与 ENTRYPOINT 配合使用:
ENTRYPOINT ["/usr/bin/my_app"]CMD ["--arg1", "value1"]
单一性: 每个 Dockerfile 中只能有一条 CMD 命令。如果有多条,只有最后一条会被执行。
覆盖: 用户在启动容器时指定的命令将覆盖 CMD 指定的命令。
与 ENTRYPOINT 的关系: 如果 CMD 用于给 ENTRYPOINT 提供参数,它必须使用 JSON 数组格式。
Exec 格式:
ENTRYPOINT ["executable", "param1", "param2"]这种格式使用 JSON 数组直接指定可执行文件及其参数。
Shell 格式:
ENTRYPOINT command param1 param2这种格式在 shell 中执行命令。注意,这种格式在 Dockerfile 中不太常用,因为它可能受到 shell 环境的影响,导致跨平台问题。
独立使用: 当 ENTRYPOINT 独立使用时,它指定的命令将在容器启动时执行,并且不会被 docker run 提供的任何参数覆盖。
FROM ubuntuENTRYPOINT ["top", "-b"]
与 CMD 配合使用: 当 ENTRYPOINT 与 CMD 配合使用时,CMD 指定的参数将传递给 ENTRYPOINT 指定的命令。在这种情况下,CMD 不是一个完整的命令,而是参数。
FROM ubuntuCMD ["-l"]ENTRYPOINT ["/usr/bin/ls"]
在这个例子中,容器启动时将执行 /usr/bin/ls -l。
单一性: 每个 Dockerfile 中只能有一个 ENTRYPOINT 指令。如果有多个,只有最后一个会生效。
参数传递: 当 ENTRYPOINT 与 CMD 配合使用时,CMD 提供的参数将作为 ENTRYPOINT 命令的参数。
覆盖问题: 如果在 ENTRYPOINT 之后还使用了 CMD,并且 CMD 是一个完整的命令,那么 ENTRYPOINT 将被覆盖。
USER <用户名或UID>
USER <用户名>:<用户组或GID>
指定运行容器的用户:
USER daemon创建用户并指定运行容器的用户:
RUN groupadd -r postgres && useradd -r -g postgres postgresUSER postgres
与 ENTRYPOINT 结合使用: 如果服务的可执行文件接受用户参数,可以直接在 ENTRYPOINT 中指定:
ENTRYPOINT ["memcached", "-u", "daemon"]或者,如果服务的可执行文件不接受用户参数,可以在 USER 指令中指定:
ENTRYPOINT ["memcached"]USER daemon
权限问题: 如果以非 root 用户运行,确保该用户具有执行所需操作的权限。
层的顺序: USER 指令应该在需要以特定用户身份执行的命令之前。例如,任何 RUN 指令,如果需要特定用户权限,都应该在 USER 指令之后。
用户存在: 在指定用户之前,确保该用户已经存在。可以使用 useradd 命令在镜像构建过程中创建用户。
WORKDIR /path/to/workdir
/path/to/workdir 是容器内部的绝对路径,或者是相对于之前 WORKDIR 指令的相对路径。
# 设置工作目录为 /appWORKDIR /app# 等同于 WORKDIR /appRUN mkdir app# 将工作目录切换到上一步创建的 app 目录WORKDIR app# 此时执行 vim a.txt,是在 /app/app 目录下执行RUN vim a.txt
相对路径:WORKDIR 可以接受相对路径,它相对于上一个 WORKDIR 指定的路径。
路径叠加:连续使用 WORKDIR 指令可以叠加路径,Docker 会创建所有中间目录。
环境变量:WORKDIR 也可以使用环境变量,例如 WORKDIR $USER/home。
ONBUILD <Dockerfile 指令>
HEALTHCHECK [OPTIONS] CMD command (容器必须返回的状态码)HEALTHCHECK [OPTIONS] NONE
--interval=<duration>:两次健康检查之间的时间间隔。
--timeout=<duration>:健康检查命令的超时时间。
--start-period=<duration>:在容器启动后,多久开始健康检查。
--retries=<num-retries>:健康检查失败后,容器重启前尝试的次数。
HEALTHCHECK--interval=5m--timeout=3s--start-period=1m--retries=3\CMD curl -f http://localhost:80 || exit 1
--interval=5m:每 5 分钟执行一次健康检查。
--timeout=3s:如果健康检查命令在 3 秒内没有返回,它将被视为失败。
--start-period=1m:容器启动 1 分钟后开始健康检查。
--retries=3:如果健康检查连续失败 3 次,Docker 将认为容器不健康,并可能采取行动,如重启容器。
必须返回的状态码:健康检查命令必须返回 0(成功)或 1(失败)。
容器内命令:CMD 后面跟的命令必须在容器内部运行,并且能够检测容器的健康状态。
下面是一个使用上述指令的 Dockerfile 示例,构建一个运行 Nginx 服务的镜像:
# 使用官方的 Ubuntu 基础镜像FROM ubuntu:18.04# 设置环境变量,指定时区ENV TZ=UTC \DEBIAN_FRONTEND=noninteractive# 更新包索引并安装 NginxRUN apt-get update && apt-get install -y nginx# 将本地文件复制到容器中的 /app 目录COPY . /app# 设置工作目录为 /appWORKDIR /app# 监听的端口EXPOSE 80# 设置容器启动时执行的命令CMD ["nginx", "-g", "daemon off;"]# 设置健康检查HEALTHCHECK --retries=3 CMD [ "curl", "-f", "http://localhost" ]
构建镜像
docker build -t dockerfile-sre-nginx -f demo_2.dockerfile .[+] Building 69.6s (9/9) FINISHED docker:default=> [internal] load build definition from demo_2.dockerfile 0.0s=> => transferring dockerfile: 514B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:18.04 5.5s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [1/4] FROM docker.io/library/ubuntu:18.04@sha256:152dc042452c496007f07ca9127571cb9 8.3s=> => resolve docker.io/library/ubuntu:18.04@sha256:152dc042452c496007f07ca9127571cb9 0.0s=> => sha256:7c457f213c7634afb95a0fb2410a74b7b5bc0ba527033362c240c7 25.69MB / 25.69MB 6.4s=> => sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2b 1.33kB / 1.33kB 0.0s=> => sha256:dca176c9663a7ba4c1f0e710986f5a25e672842963d95b960191e2d9f718 424B / 424B 0.0s=> => sha256:f9a80a55f492e823bf5d51f1bd5f87ea3eed1cb31788686aa99a2fb6 2.30kB / 2.30kB 0.0s=> => extracting sha256:7c457f213c7634afb95a0fb2410a74b7b5bc0ba527033362c240c7a11bef4 1.7s=> [internal] load build context 2.9s=> => transferring context: 187.38MB 2.9s=> [2/4] RUN apt-get update && apt-get install -y nginx 54.4s=> [3/4] COPY . /app 1.0s=> [4/4] WORKDIR /app 0.0s=> exporting to image 0.3s=> => exporting layers 0.3s=> => writing image sha256:d72611e47ccde8d858882cdc2a23bf166c8a96fcdf2973e273495e5ca6 0.0s=> => naming to docker.io/library/dockerfile-sre-nginx
启动容器
#构建好的镜像docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEdockerfile-sre-nginx latest d72611e47ccd 8 minutes ago 356MB#启动容器docker run -it -d dockerfile-sre-nginx3a1bdfa88872ee2797181710b16189ec087ab2e67e13762c5eeacfa4a7e0163e
使用这个 Dockerfile,我们可以通过 Docker 构建一个镜像,该镜像启动后会运行 Nginx 服务,并且可以通过健康检查来验证服务是否正常运行。
在本文中,我们探讨了 Dockerfile 的重要性以及如何有效利用它来自动化 Docker 镜像的构建过程。Dockerfile 提供了一种声明式的方法来定义镜像内容,使得镜像的构建变得简洁、高效且易于维护。
原文作者:五分钟学SRE;如有侵权,请联系管理员删除。