菜单
本页目录

Docker 镜像中的层(Layer)

在 Docker 中,层(Layer) 是 Docker 镜像和容器的核心概念之一。Docker 使用分层的文件系统(Union File System),每一层都是只读的。当你构建 Docker 镜像时,每一个指令(如 RUNCOPYADD 等)都会创建一层,这些层叠加在一起,形成一个完整的镜像。

  1. 分层结构:Docker 镜像是由多个只读层组成的,这些层构建时会依赖于彼此。每一层都是不可变的,每一层都表示镜像构建过程中产生的一个状态。比如,第一层可能是基础镜像(如 Ubuntu),第二层可能是你安装的依赖,第三层可能是你应用程序的代码。

  2. 镜像层(Image Layers):镜像的每一层是只读的。当 Docker 创建镜像时,镜像层不会发生改变。这些层之间是依赖关系,后续层依赖前面的层。每一层都可以被多个镜像共享。

  3. 容器层(Container Layer):当你运行一个容器时,Docker 会在镜像的顶层添加一个可写层。这一层允许容器对文件系统进行修改,而不影响原始的只读镜像层。容器停止或删除时,这一层会被丢弃,但镜像层保留。

每条 Dockerfile 指令产生一层

当 Docker 构建镜像时,Dockerfile 中的每一条指令(例如 RUNCOPYADD 等)都会创建一个新的层。Docker 使用这些层的缓存来加速镜像构建的过程。

示例:

FROM ubuntu:20.04            # 第一层: 基础镜像
RUN apt-get update           # 第二层: 更新包管理器缓存
RUN apt-get install -y nginx # 第三层: 安装 Nginx
COPY ./index.html /var/www/html/index.html # 第四层: 复制文件

每一条指令都会创建一层,它们叠加在一起,形成最终的镜像。

层的主要特点

  1. 可复用:镜像的每一层可以被其他镜像复用。例如,如果你在多个镜像中使用相同的基础镜像,Docker 只会下载和存储一次。

  2. 缓存机制:Docker 使用层的缓存机制来加速镜像的构建。如果某一层没有发生变化,Docker 会跳过这一步而复用已有的层。

  3. 分离可读和可写层:镜像的层是只读的,而容器在运行时,会有一个独立的可写层,所有写入操作(如文件修改、创建)都发生在容器层上,而不会影响到镜像。

层的优势

  • 存储效率:由于每一层是只读的,可以在多个镜像或容器之间共享,减少了存储空间。
  • 加速构建:通过层的缓存机制,Docker 只需要重新构建发生变化的层,未变的层可以复用,从而加速构建。
  • 镜像小巧:每一层只包含变化部分,避免了整个文件系统的重复存储,镜像更轻量化。

层的操作

  1. 查看镜像层: 你可以使用 docker history 命令来查看镜像的层。

    docker history <镜像名>
    

    输出会显示每一层对应的 Dockerfile 指令及其大小。

  2. 删除容器时清理可写层: 当你删除一个容器时,容器的可写层会被移除,但镜像的只读层不会受到影响。

Layer 工作原理示意图

+--------------------+
| 容器的可写层       |  <-- 运行时写操作在这里发生
+--------------------+
| 镜像的只读层 3     |  <-- COPY 指令产生
+--------------------+
| 镜像的只读层 2     |  <-- RUN 指令产生
+--------------------+
| 镜像的只读层 1     |  <-- FROM 指令产生
+--------------------+
| 基础镜像            |  <-- 例如 ubuntu:20.04
+--------------------+
  • 层(Layer) 是 Docker 镜像的核心组成部分,每个镜像由多个只读层叠加组成,每条 Dockerfile 指令创建一层。
  • 容器层 是可写的,当你运行容器时,Docker 在镜像层的顶部添加一个可写层,所有对文件系统的更改都会发生在这个层上。
  • Docker 的分层存储机制提高了镜像的效率,使得镜像和容器更加轻量,构建速度更快,并且减少了存储占用。

BaseImage

BaseImage(基础镜像) 是 Docker 镜像构建的起点。它是创建自定义 Docker 镜像的底层镜像,为后续的指令提供操作系统环境和必要的依赖库。所有 Docker 镜像都是从一个基础镜像开始构建的,除非你使用 FROM scratch,这是一个特殊的空白基础镜像。

BaseImage 的主要作用:

  1. 提供基础环境:BaseImage 提供了一个操作系统的基本环境,通常包括文件系统、内核和一些基础的工具包。你可以在此基础上安装其他应用程序和依赖库。

  2. 构建镜像的起点:当你编写 Dockerfile 时,第一个指令通常是 FROM <BaseImage>,这就是告诉 Docker 使用哪个基础镜像作为构建的起点。

  3. 优化构建:使用合适的基础镜像可以减小自定义镜像的体积,并加快构建过程。基础镜像可以根据需要选择不同的操作系统、版本或配置。

常见的基础镜像类型

  1. 官方基础镜像: Docker Hub 上有许多官方维护的基础镜像,如 ubuntualpinedebiancentos 等,它们提供了各种操作系统的精简版。

    • Ubuntu:提供 Ubuntu 操作系统的基础环境。
    • Alpine:一个非常轻量级的 Linux 发行版,通常用于构建小巧的镜像。
    • Debian:基于 Debian 发行版的镜像,适用于需要稳定、全面的包管理支持的场景。
    • CentOS:提供 Red Hat 企业 Linux 兼容环境的镜像。

    示例

    FROM ubuntu:20.04
    

    这将基于 Ubuntu 20.04 作为基础镜像。

  2. 轻量级基础镜像: 为了构建更加精简的容器镜像,可以使用 alpine 等轻量级基础镜像。它只有几兆大小,非常适合需要快速启动且占用空间少的应用场景。

    示例

    FROM alpine:latest
    
  3. scratch:这是 Docker 提供的一个特殊基础镜像,它实际上是一个空镜像。使用 scratch 基础镜像通常用于构建非常精简的镜像,比如仅包含编译后的二进制文件的 Go 程序。

    示例

    FROM scratch
    COPY hello /hello
    CMD ["/hello"]
    

    这里的 FROM scratch 表示从一个完全空白的基础环境开始,没有任何预装的软件或操作系统。

BaseImage 的选择

选择合适的基础镜像对构建的效率和容器的大小有很大影响。以下是选择基础镜像时的一些考虑因素:

  1. 镜像大小:如果你关心镜像的大小,alpine 是一个非常好的选择,它的体积非常小,大约 5MB 左右。相比之下,ubuntudebian 基础镜像会更大一些。

  2. 操作系统依赖:如果你的应用程序依赖于特定的操作系统或包管理工具(如 aptyum),选择与之对应的基础镜像会更合适。

  3. 兼容性和稳定性:如果你构建的是生产环境的镜像,通常会选择一些稳定版本的基础镜像,如 ubuntu:20.04debian:buster

  4. 轻量 vs 完整:对于简单的应用,可以选择轻量级的基础镜像(如 alpine);而如果应用依赖于较多的库或工具,则可能需要一个更完整的基础镜像(如 ubuntudebian)。

BaseImage 的例子

1. 使用 Ubuntu 作为基础镜像

FROM ubuntu:20.04

RUN apt-get update && apt-get install -y nginx

COPY ./index.html /var/www/html/

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

2. 使用 Alpine 作为基础镜像

FROM alpine:latest

RUN apk add --no-cache nginx

COPY ./index.html /var/lib/nginx/html/

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

3. 使用 scratch 作为基础镜像

FROM scratch

COPY hello /hello

CMD ["/hello"]

在这个例子中,scratch 是一个完全空白的基础镜像,hello 是一个独立的二进制文件。

  • BaseImage 是构建 Docker 镜像的起点,它为你的应用程序提供了操作系统和基础环境。
  • 常见的基础镜像包括 ubuntualpinedebian 等,选择合适的基础镜像可以优化镜像大小和构建效率。
  • 特殊的 scratch 镜像用于构建极简的容器,通常只包含应用的二进制文件。
  • 基础镜像的选择应根据项目需求、操作系统依赖、体积优化等进行权衡。