容器

Dockerfile不会写?于是我花十分钟看了这篇文章

转载:Dockerfile不会写?于是我花十分钟看了这篇文章

It all starts with a Dockerfile. (docker万物始于此)

这是官网对Dockerfile的描述。

前言


Dockerfile 是一个文本文件,其中包含了若干个命令,用户可以调用这些命令来构建一个镜像。通过这个文件,开发者能够定义应用程序运行环境的所有细节,从基础操作系统的选择到需要安装的软件包,再到启动服务所需的配置。

Dockerfile 不仅是自动化构建的基础,也是实现持续集成和持续部署(CI/CD)流程的关键组成部分。它使得开发团队能够在一致的环境中开发、测试和部署应用,从而减少“在我机器上能跑”的问题。此外,Dockerfile 促进了微服务架构的发展,让每个服务都可以独立打包成容器,易于管理和扩展。

Dockerfile 案例


Dockerfile 有个和其他文件与众不同的点,就是他没有后缀,他全部的名字就叫做Dockerfile 而不是Dockerfile.txt 或者Dockerfile.yml

# 使用指定的基础镜像
FROM registry.gitee-ai.local/base/iluvatar-corex:3.2.0-bi100
# 设置工作目录
WORKDIR /app

# 添加 Java 环境
RUN apt-get update && \
    apt-get install -y openjdk-8-jdk && \
    apt-get clean

# 复制当前目录下的所有文件到容器内的/app目录下
COPY . /app

EXPOSE 7860

# 指定启动命令
ENTRYPOINT ["java","-jar","/app/mergevideo-0.0.1-SNAPSHOT.jar"]
  • FROM registry.gitee-ai.local/base/iluvatar-corex:3.2.0-bi100: 使用指定的基础镜像 iluvatar-corex:3.2.0-bi100 从私有仓库 registry.gitee-ai.local 中拉取。
  • WORKDIR /app: 设置工作目录为 /app
  • RUN apt-get update && apt-get install -y openjdk-8-jdk && apt-get clean: 更新包列表并安装 OpenJDK 8 JDK,然后清理缓存。
  • COPY . /app: 将当前目录下的所有文件复制到容器内的 /app 目录下。
  • EXPOSE 7860: 声明容器监听 7860 端口。
  • ENTRYPOINT [“java”,”-jar”,”/app/mergevideo-0.0.1-SNAPSHOT.jar”]: 指定容器启动时运行的命令,使用 Java 启动 /app/mergevideo-0.0.1-SNAPSHOT.jar 应用。

而作者写的这个dockerFile几乎覆盖了官方文档中说到了主要几个命令。这里介绍一下: docker build 命令。

docker build 命令可以从 Dockerfile 构建一个新的 Docker 镜像。

docker build


基本语法

docker build [OPTIONS] PATH | URL | -

常用选项

  • -t, –tag: 为生成的镜像指定标签(通常是 name:tag 格式)。
  • -f, –file: 指定 Dockerfile 的路径(默认是当前目录下的 Dockerfile)。
  • –build-arg: 设置构建时的变量。
  • –no-cache: 构建时不使用缓存。
  • –rm: 构建完成后删除中间容器(默认是启用的)。
  • –pull: 始终尝试从远程仓库拉取最新版本的基础镜像。
  • -q, –quiet: 只输出构建的镜像 ID。

示例

1. 基本构建

假设你的 Dockerfile 位于当前目录下,并且你想为生成的镜像打上标签 my-app:latest,可以使用以下命令:

docker build -t my-app:latest .

这里的 . 表示当前目录。

2. 指定 Dockerfile 路径

如果你的 Dockerfile 不在当前目录下,可以使用 -f 选项指定其路径:

docker build -t my-app:latest -f path/to/Dockerfile .

3. 设置构建时变量

你可以使用 --build-arg 选项传递构建时变量。假设你的 Dockerfile 中使用了 ARG 指令:

ARG BUILD_DATE
RUN echo "Build date: $BUILD_DATE"

你可以这样构建镜像:

docker build -t my-app:latest --build-arg BUILD_DATE=$(date) .

4. 不使用缓存

如果你希望构建时不使用缓存,可以使用 --no-cache 选项:

docker build -t my-app:latest --no-cache .

5. 删除中间容器

默认情况下,构建完成后会删除中间容器。如果你想明确指定这一点,可以使用 --rm 选项:

docker build -t my-app:latest --rm .

6. 拉取最新基础镜像

如果你希望在构建前始终从远程仓库拉取最新版本的基础镜像,可以使用 --pull 选项:

docker build -t my-app:latest --pull .

7. 静默输出

如果你只想输出最终的镜像 ID,可以使用 --quiet 或 -q 选项:

docker build -q -t my-app:latest .

完整示例

假设你有一个复杂的 Dockerfile,并且需要设置多个构建时变量,可以这样构建:

docker build -t my-app:latest \
    --build-arg BUILD_DATE=$(date) \
    --build-arg VERSION=1.0.0 \
    --no-cache \
    --pull \
    -f path/to/Dockerfile \
    .

我们的build命令都会根据DockerFile 里面的内容来一步一步构建我们的镜像,那么介绍了build命令,再来认识一下build之后构建的镜像如何运行

docker run


运行一个 Docker 容器:

docker run -d -p 8999:8999 aijava:1.0
  • docker run:这是运行 Docker 容器的命令。
  • -d:表示在后台运行容器。
  • -p 8999:8999:将宿主机的 8999 端口映射到容器的 8999 端口。
  • aijava:1.0:这是要运行的镜像名称和标签。

最后,我们就来认识一下DockerFile里面的命令所代表的意思了

DockerFile 入门


Docker 镜像由层组成。每个层都是构建的结果 指令。层按顺序堆叠,每个层都是 表示应用于上一层的更改的增量。

syntax指定构造器

# syntax=docker/dockerfile:1

# 使用指定的基础镜像
FROM registry.gitee-ai.local/base/iluvatar-corex:3.2.0-bi100

这个是DockerFile中一个特殊的注释

# syntax=docker/dockerfile:1 注释告诉 Docker 使用 BuildKit 构建器来解析和构建 Dockerfile。BuildKit 是 Docker 的下一代构建工具,旨在提高 Docker 镜像构建的性能和可靠性。它引入了许多新的特性和改进,使其成为比传统 Docker 构建器更强大的工具。

写了这个注释可以让我们利用 BuildKit 的高级特性,还可以提高构建性能和灵活性。如果不写这个注释,Docker 将使用传统的构建器构建镜像

FROM基础镜像

# 使用指定的基础镜像
FROM registry.gitee-ai.local/base/iluvatar-corex:3.2.0-bi100

这块就是告诉docker以 这个镜像为基础构建一个新镜像,大家可以这么理解,需要装修得要毛坯房吧,而这个基础的镜像就是毛坯房,一般我们使用 FROM Linux系统 这样的写法,因为我们自己构建的镜像其实本质也是运行在一个系统中的,就像我们上面的案例:

# 使用指定的基础镜像
FROM registry.gitee-ai.local/base/iluvatar-corex:3.2.0-bi100
# 设置工作目录
WORKDIR /app

# 添加 Java 环境
RUN apt-get update && \
    apt-get install -y openjdk-8-jdk && \
    apt-get clean

在ubuntu的镜像基础上,安装Java 环境,而这里的结构图就像这样:

这样是不是大家就很好理解这句话了:层按顺序堆叠,每个层都是 表示应用于上一层的更改的增量(Docker images consist of layers. Each layer is the result of a build instruction in the Dockerfile. Layers are stacked sequentially, and each one is a delta representing the changes applied to the previous layer.)。

RUN命令

# 添加 Java 环境
RUN apt-get update && \
    apt-get install -y openjdk-8-jdk && \
    apt-get clean

这个命令会在上面的镜像的基础上运行一些命令,例如作者这里写的就是安装jdk环境,因为再官方提供的那个镜像是没有jdk环境的,所以运行了这个命令之后,在这个镜像中就有了Java应用所需要的jdk环境了

#注释

这个就对应开发者来说太熟悉了,就是注释,例如上面的 # 添加 Java 环境 写了这个dockerfile在构建的过程中就会跳过这个注释,很好理解

COPY复制

这个语法是会将你与dockerfile同路径的文件复制到一个新的目录下面,而新的目录你可以理解为linux中的目录,例如 /就是可以理解为新镜像中的根目录,例如上面作者的案例就是把所有文件复制到 /app目录下

# 复制当前目录下的所有文件到容器内的/app目录下
COPY . /app

如果你要指定某个文件的话,例如jar包可以这样写:

COPY ./masiyi.jar /app
# 或者这样
COPY masiyi.jar /app

ENV设置环境变量

这个命令设置环境变量,如果熟悉shell命令的同学有福了,就类似那种的设置

VARIABLE_NAME=value

而dockerfile使用 ENV作为标识,例如在生产过程中你可以这样写:

ENV MAX_HEAP 2048m
ENV MIN_HEAP 1024m

CMD java -jar  -Xms${MIN_HEAP} -Xmx${MAX_HEAP}  masiyi.jar 

这样就可以使用变量来控制jvm的大小了

EXPOSE暴露端口

这块也很好理解,如果你是web服务,需要一个端口,这里只需要和你的web服务保持一样就好了

CMD启动应用程序

这里是容器启动时所调用的命令,例如上面的

CMD java -jar  -Xms${MIN_HEAP} -Xmx${MAX_HEAP}  masiyi.jar 

最后你在启动这个镜像的时候就会构建一个容器,从而执行这个命令,这样一个jar包就被启动起来了。但是很多同学容易把CMD和上面的RUN搞混淆,我们来了解一下他们有什么区别

CMD 和 RUN 是 Dockerfile 中用于指定命令的两个不同指令,它们在 Docker 镜像构建和容器运行过程中扮演着不同的角色。

CMD 和 RUN的区别

1. 执行时机

  • RUN:
    • 执行时机: 在 构建镜像时执行。
    • 作用: 用于在构建过程中执行命令,通常用于安装软件、配置环境等。
    • 结果: 执行的结果会被保存到镜像的层中,成为镜像的一部分。
  • CMD:
    • 执行时机: 在 启动容器时执行。
    • 作用: 用于指定容器启动时默认执行的命令或参数。
    • 结果: 只在容器启动时生效,不会影响镜像的构建过程。

2. 命令执行方式

  • RUN:
    • 每次执行 RUN 指令时,Docker 会创建一个新的中间层,执行该命令,并将结果保存到这个层中。多个 RUN 指令会导致多个层的创建。
    • 例如,RUN apt-get update && apt-get install -y curl 会在构建镜像时安装 curl,并且这个安装结果会被保存到镜像中。
  • CMD:
    • CMD 指定的命令只在容器启动时执行,不会在构建镜像时执行。
    • 如果你在运行容器时指定了其他命令(例如通过 docker run 的命令行参数),CMD 中的命令会被覆盖。
    • 例如,CMD ["java", "-jar", "app.jar"] 会在容器启动时运行 java -jar app.jar,但如果你在 docker run 时指定了其他命令,CMD 中的命令将不会执行。

3. 可覆盖性

  • RUN:
    • 不可覆盖RUN 指令在构建镜像时执行,生成的层是镜像的一部分,无法在运行容器时被覆盖或修改。
  • CMD:
    • 可覆盖CMD 指令可以在运行容器时被覆盖。你可以通过 docker run 命令行参数指定不同的命令来替代 CMD 中的默认命令。
    • 例如,如果你有一个 Dockerfile 如下:
CMD ["echo", "Hello, World!"]

你可以通过以下命令覆盖 CMD

docker run my-image echo "Hello, Docker!"

4. 多条指令的行为

  • RUN:
    • 你可以有多个 RUN 指令,每个 RUN 指令都会创建一个新的层。为了减少镜像的层数,建议将多个相关的命令合并为一个 RUN 指令,使用 && 连接多个命令。
    • 例如:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
  • CMD:
    • 你只能有一个 CMD 指令。如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 会生效。
    • 例如:
CMD ["echo", "First command"]
CMD ["echo", "Second command"]  # 只有这一条会生效

示例

使用 RUN 安装软件

# 安装 curl
RUN apt-get update && apt-get install -y curl

使用 CMD 启动应用程序

# 启动 Java 应用
CMD ["java", "-jar", "app.jar"]

ENTRYPOINT最终启动命令

不知道大家注意到一个细节没有,那就是dockerfile中的命令基本都是大写,那么最后一个要介绍一个命令就是ENTRYPOINT

ENTRYPOINT 是 Dockerfile 中的一个重要指令,用于定义容器启动时执行的主命令。与 CMD 不同,ENTRYPOINT 提供了一种更灵活的方式来指定容器的默认行为,并且可以与 CMD 结合使用以提供更多的灵活性。

例如作者上面的案例:

# 指定启动命令
ENTRYPOINT ["java","-jar","/app/mergevideo-0.0.1-SNAPSHOT.jar"]

这个就会在容器启动的时候执行 java -jar /app/mergevideo-0.0.1-SNAPSHOT.jar

这是他的一个最基础的用法,但是ENTRYPOINT 和 CMD 可以一起使用,CMD 提供的参数会被传递给 ENTRYPOINT 指定的命令。这种组合非常有用,尤其是当你希望容器在启动时执行一个特定的命令,但允许用户通过 docker run 提供额外的参数时。

# 使用 ENTRYPOINT 和 CMD 启动 Java 应用
ENTRYPOINT ["java", "-jar"]
CMD ["/app/mergevideo-0.0.1-SNAPSHOT.jar"]

解释:

  • ENTRYPOINT ["java", "-jar"] 指定了容器启动时的主命令是 java -jar
  • CMD ["/app/mergevideo-0.0.1-SNAPSHOT.jar"] 提供了默认的参数 /app/mergevideo-0.0.1-SNAPSHOT.jar
  • 当你运行 docker run my-image 时,容器会执行 java -jar /app/mergevideo-0.0.1-SNAPSHOT.jar

覆盖 CMD:

  • 如果你在 docker run 时提供了其他参数,CMD 中的参数会被覆盖。
  • 例如:
docker run my-image another-app.jar

这将执行

java -jar another-app.jar

ENTRYPOINT 与 CMD 的区别

特性ENTRYPOINTCMD
执行时机容器启动时执行容器启动时执行,默认参数可以被覆盖
是否可覆盖不可覆盖,除非使用 --entrypoint 选项可以通过 docker run 命令行参数覆盖
用途定义容器的主命令,适合创建可执行容器提供默认参数或命令,适合提供默认行为
与 docker run 的关系docker run 参数会作为 ENTRYPOINT 的参数传递docker run 参数会覆盖 CMD 中的默认参数

总结


至此,我们已经学到了dockerfile中最常用的命令,至于其他的都是作为特殊需求或者更定制化的dockerfile脚本去使用,而这篇文章中的命令基本可以作为企业中的dockerfile使用,当然,dockerfile的语法远远不值这么简单,那么更多的信息可以参考官方文档:https://docs.docker.com/reference/dockerfile/[6]