16 Dockerfile
Docker 镜像原理
思考
- Docker 镜像本质是什么?
- 是一个分层文件系统
- Docker 中一个 centos 镜像为什么只有 200MB,而一个 centos 操作系统的 iso 文件要几个 G?
- Centos 的 iso 镜像文件包含 bootfs 和 rootfs,而 docker 的 centos 镜像复用操作系统的 bootfs,只有 rootfs 和其他镜像层
- Docker 中一个 tomcat 镜像为什么有 500MB,而一个 tomcat 安装包只有 70 多 MB?
- 由于 docker 中镜像是分层的,tomcat 虽然只有 70 多 MB,但他需要依赖于父镜像和基础镜像,所有整个对外暴露的 tomcat 镜像大小 500 多 MB
操作系统组成部分
- 进程管理子系统:
- 任务调度器(Scheduler): 控制在多任务环境下,哪个进程获得 CPU 时间。
- 进程调度和管理: 管理进程的创建、调度、终止等操作。
- 进程间通信(IPC): 提供进程之间进行通信的机制,如信号、消息队列、共享内存等。
- 内存管理子系统:
- 虚拟内存管理: 管理进程的虚拟内存空间,包括分页和分段。
- 物理内存管理: 控制物理内存的分配和释放。
- 页面置换算法: 当物理内存不足时,选择将哪些页面置换到磁盘。
- 文件系统子系统:
- 文件系统管理: 提供文件和目录的创建、读取、写入和删除等功能。
- 文件权限和所有权: 控制文件的访问权限和所有者。
- 磁盘空间管理: 管理文件系统中的磁盘空间,包括文件块的分配和释放。
- 网络子系统:
- TCP/IP 协议栈: 实现网络通信的基础协议。
- 设备驱动程序: 提供硬件设备与内核之间的接口,允许操作系统与硬件设备进行通信。
- 网络配置工具: 允许用户配置网络接口、IP 地址、路由表等网络参数。
- 设备驱动程序子系统:
- 设备驱动管理: 管理和加载硬件设备的驱动程序。
- 硬件抽象层(HAL): 提供一种标准接口,使得内核和设备驱动程序能够独立于底层硬件进行开发。
- 用户接口子系统:
- Shell: 提供用户与内核交互的命令行接口。
- 图形用户界面(GUI): 提供图形化的用户界面,包括窗口管理器和桌面环境。
- 系统调用接口:
- 系统调用: 提供用户空间程序与内核空间之间的接口,允许用户程序请求操作系统的服务。
- C 库(glibc): 提供与系统调用交互的标准函数库。
- 安全和权限管理子系统:
- 用户账户和组管理: 管理系统中的用户和用户组。
- 访问控制: 控制用户对文件和系统资源的访问权限。
- 安全策略: 实施安全策略,如 SELinux(Security-Enhanced Linux)。
Linux 文件系统
- 在 Linux 中,
bootfs和rootfs是两个关键的文件系统,它们在系统引导过程和系统运行时发挥着重要的作用。 - 不同的 linux 发行版,
bootfs基本一样,而rootfs不同,如 ubuntu,centos 等。 - 在系统启动时,
bootfs负责引导加载程序和内核的加载,而rootfs则负责提供运行时的根文件系统,使得系统能够正常启动和运行。 - 这两个文件系统的协同工作确保了 Linux 系统的顺利启动和正常运行。
bootfs
- 位置: 通常位于启动设备(如硬盘、SSD)的引导分区,它可以是一个专门的分区,也可以是 EFI 分区等。
- 作用:
bootfs包含了引导加载程序(bootloader)和内核映像(kernel image)。引导加载程序负责在计算机启动时加载操作系统内核,而内核映像则是操作系统核心的二进制文件。 - GRUB(GRand Unified Bootloader): 在许多 Linux 系统中,特别是使用传统 BIOS 的系统,GRUB 是一种常见的引导加载程序。GRUB 负责在启动时加载内核映像,并向内核传递必要的参数,例如内核的位置、启动参数等。
- UEFI(Unified Extensible Firmware Interface): 对于使用 UEFI 的系统,通常会有一个专用的 EFI 分区,其中包含了引导加载程序文件(通常是
grubx64.efi等)。UEFI 提供了更现代和灵活的引导过程。
rootfs
- 位置:
rootfs是一个虚拟文件系统,它在系统运行时被挂载为根文件系统。它可以是内存中的 tmpfs,也可以是硬盘上的实际文件系统,例如 ext4。 - 作用:
rootfs包含了系统运行时所需的基本文件和目录,包括/bin、/sbin、/lib、/etc等。这些文件和目录组成了用户空间的基本结构,支持系统的正常运行。 - 虚拟文件系统:
rootfs是一个虚拟文件系统,它可能是一个初始的根文件系统,而随着系统的运行,它可以被更换为其他实际的文件系统。这种灵活性允许系统在运行时切换根文件系统,例如在初始化阶段使用 initramfs 后,切换到实际的根文件系统。 - Initramfs(Initial RAM File System): 在一些系统中,特别是在启动时需要加载特定驱动程序或进行硬件检测的情况下,可能会使用
initramfs。initramfs是一个临时的文件系统,它会在引导过程中加载到内存中,并在引导完成后被卸载。
镜像原理
dockerfile
# 基础镜像
FROM ubuntu:20.04
# 在基础镜像上进行操作,构建新的镜像层
RUN apt-get update && apt-get install -y nginx
# 上面的 Dockerfile 中,`FROM ubuntu:20.04` 表示基础镜像是 Ubuntu 20.04 镜像
# `RUN apt-get update && apt-get install -y nginx` 表示在基础镜像上运行命令安装 Nginx,形成新的镜像层
# 这个 Dockerfile 就是一个简化的示例,实际上可能包含更多操作和配置
# 容器启动时,在最顶层加载一个读写文件系统作为容器
# 每个容器实例都有自己的顶层文件系统,可以在容器内进行读写操作,而不影响底层的镜像文件系统Docker 镜像构建原理
- Docker 镜像是通过特殊的文件系统叠加而成的。
文件系统层次结构
- 最底端是
bootfs,并使用宿主机的bootfs。 - 第二层是
root文件系统(rootfs),被称为基础镜像(base image)。 - 往上可以叠加其他的镜像文件,形成分层的结构。
Union File System 技术
- 使用统一文件系统(Union File System)技术,将不同层次的文件系统整合成一个。
- 提供了一个统一的视角,用户在使用时只看到一个文件系统,隐藏了底层多层的存在。
镜像的层次关系
- 一个镜像可以放在另一个镜像的上面。
- 位于下面的镜像称为父镜像,最底部的镜像称为基础镜像。
容器启动过程
- 当从一个镜像启动容器时,Docker 会在最顶层加载一个读写文件系统作为容器。
镜像制作
容器转为镜像
获取正在运行的容器的 ID.
bashdocker ps保存容器状态为镜像。
[container_id]为容器 ID[image_name]为新镜像的名字[tag]为标签
bashdocker commit [container_id] [image_name]:[tag]查看新创建的镜像。
bashdocker images推送到 Docker Hub(可选)。
bashdocker login docker push [image_name]:[tag]本地使用(可选)。
bash# 保存 docker save -o [image_name].tar.gz [image_name]:[tag] # 加载 docker load -i [image_name].tar.gz
dockerfile
创建 Dockerfile
- 创建一个文本文件,通常命名为
Dockerfile。 - 这个文件包含了一系列指令,按照顺序描述了构建 Docker 镜像的步骤。
- 创建一个文本文件,通常命名为
选择基础镜像
dockerfileFROM image[:tag] [AS name]- 在 Dockerfile 中使用
FROM指令选择一个基础镜像作为构建的起点。常见的选择包括 alpine、debian、ubuntu、centos、node、python、nginx 等。 - 基础镜像包含了操作系统和一些基本的软件包,例如
FROM ubuntu:20.04表示基于 Ubuntu 20.04 镜像。scratch是一个特殊的基础镜像,它实际上是一个空白镜像,不包含任何文件系统内容。因此,使用scratch作为基础镜像意味着你从头开始构建你的镜像,而不是基于其他镜像。- Alpine Linux 以小巧、高效而著称,它的 基础镜像 只有几兆大小(目前小于 6 MB),同时仍是一个完整的 Linux 发行版。这使得构建和部署容器时能够更快地完成。
- 有时候,你可能需要自定义基础镜像,以满足特定的需求。可以在
FROM指令中使用AS name为基础镜像命名,然后在后续的指令中引用这个名字。 - 例如,
FROM ubuntu:20.04 AS builder,在后续的指令中可以使用FROM builder引用这个自定义的基础镜像。
- 在 Dockerfile 中使用
指定维护者的信息。
dockerfileMAINTAINER xxx yourname@example.com- 指定维护者的信息,但推荐使用
LABEL代替。
- 指定维护者的信息,但推荐使用
为镜像添加元数据。
dockerfileLABEL version="1.0" maintainer="yourname@example.com"- 用于为镜像添加元数据,通常用于提供关于镜像的信息,如版本、维护者等。
运行命令和安装软件
dockerfile# 安装应用程序依赖 RUN apt-get update && apt-get install -y python3- 使用
RUN指令运行命令,在基础镜像上进行操作,例如安装软件、更新软件包等。 - 例如,
RUN apt-get update && apt-get install -y nginx表示在基础镜像上更新软件包并安装 Nginx。
- 使用
复制文件
dockerfile# 复制应用程序文件到容器中 COPY . /app # 复制并解压 ADD archive.tar.gz /app/archive- 使用
COPY或ADD指令将本地文件或目录复制到镜像中。 - 例如,
COPY ./myapp /app表示将本地的myapp目录复制到镜像的/app目录。
- 使用
设置环境变量
dockerfileENV APP_HOME /app ENV PORT 8080 # 使用环境变量在后续指令中定义路径 ENV APP_HOME /app WORKDIR $APP_HOME COPY . $APP_HOME # 在容器运行时使用环境变量 ENV PORT=8080 EXPOSE $PORT CMD ["python3", "-m", "http.server", "$PORT"]- 使用
ENV指令设置环境变量,例如ENV APP_HOME /app。 - 这可以为后续的指令提供一个可重用的变量。
- 使用
设置工作目录
dockerfile# 设置工作目录 WORKDIR /app- 使用
WORKDIR指令设置工作目录,例如WORKDIR /app。 - 这会影响后续的指令,使其相对于该目录执行。
- 使用
声明容器监听的端口
dockerfile# 暴露应用程序的端口 EXPOSE 80- 使用
EXPOSE指令声明容器运行时监听的端口,例如EXPOSE 80。
- 使用
定义启动命令
dockerfile# 定义容器启动时要执行的默认命令,可以被 Dockerfile 中的任何 CMD 或 docker run 命令覆盖 CMD ["python3", "app.py"] # 定义容器启动时要执行的命令,不可被 Dockerfile 中的 CMD 或 docker run 命令覆盖 ENTRYPOINT ["python3", "app.py"]使用
CMD或ENTRYPOINT指令定义容器启动时要运行的命令。例如,
CMD ["nginx", "-g", "daemon off;"]表示容器启动时运行 Nginx 以非守护进程方式。
构建镜像
bashdocker build -t myapp:latest .- 运行
docker build -t image_name:tag .命令来构建镜像。 image_name是镜像名,tag是版本标签,.表示当前目录。
- 运行
推送到 Docker Hub(可选)
bashdocker images docker login docker push myapp:latest- 如果你希望分享你的镜像,可以将其推送到 Docker Hub 或其他容器注册表。
- 使用
docker push your_image_name:tag命令推送镜像。
保存到本地(可选)
bash# 保存 docker save -o myapp.tar.gz myapp:latest # 加载 docker load -i myapp.tar.gz运行容器(可选)
bashdocker run -p 8080:80 myapp:latest
Dockerfile 概念及作用
- Dockerfile 是一个文本文件: Dockerfile 是一个文本文件,包含了一系列构建 Docker 镜像的指令。
- 每一条指令构建一层: 每一条 Dockerfile 中的指令都会构建一层,这些层是基于基础镜像的,最终形成一个完整的新镜像。
- 基于基础镜像构建新镜像: Dockerfile 指定了构建新镜像所需的基础镜像,并通过一系列指令对其进行定制,形成新的镜像。
- 为开发人员提供一致的开发环境: Dockerfile 的使用可以为开发人员提供一个一致的开发环境,确保在不同环境中的工作一致性。
- 为测试人员提供工作环境: 测试人员可以直接使用开发人员构建的镜像,或者通过 Dockerfile 文件构建一个新的镜像,以便开始工作。
- 在部署时实现应用的无缝移植: 运维人员在部署时可以使用 Dockerfile 构建镜像,实现应用的无缝移植,确保在不同环境中的一致性。
Dockerfile 关键字
| 关键字 | 作用 | 备注 |
|---|---|---|
FROM | 指定父镜像 | 指定 dockerfile 基于那个 image 构建。 |
MAINTAINER | 作者信息 | 用来标明这个 dockerfile 的作者。 |
LABEL | 标签 | 用来标明 dockerfile 的标签,可以使用 Label 代替 Maintainer,最终都是在 docker image 基本信息中可以查看。 |
RUN | 执行命令 | 执行一段命令,默认是 /bin/sh。 格式: RUN command 或者 RUN ["command" , "param1","param2"] |
CMD | 容器启动命令 | 提供启动容器时候的默认命令 和 ENTRYPOINT 配合使用。格式 CMD command param1 param2 或者 CMD ["command" , "param1","param2"] |
ENTRYPOINT | 入口 | 一般在制作一些执行就关闭的容器中会使用。 |
COPY | 复制文件 | build 的时候复制文件到 image 中。 |
ADD | 添加文件 | build 的时候添加文件到 image 中,不仅仅局限于当前 build 上下文,可以来源于远程服务。 |
ENV | 环境变量 | 指定 build 时候的环境变量,可以在启动的容器的时候通过 -e 覆盖。格式 ENV name=value |
ARG | 构建参数 | 构建参数。只在构建的时候使用的参数。如果有 ENV ,那么 ENV 的相同名字的值始终覆盖 arg 的参数。 |
VOLUME | 定义外部可以挂载的数据卷 | 指定 build 的 image 那些目录可以启动的时候挂载到文件系统中。启动容器的时候使用 -v 绑定。格式 VOLUME ["目录"] |
EXPOSE | 暴露端口 | 定义容器运行的时候监听的端口。启动容器的使用 -p 来绑定暴露端口。格式: EXPOSE 8080 或者 EXPOSE 8080/udp |
WORKDIR | 工作目录 | 指定容器内部的工作目录。如果没有创建则自动创建。 如果指定 / 使用的是绝对地址。如果不是 / 开头那么是在上一条 workdir 的路径的相对路径。 |
USER | 指定执行用户 | 指定 build 或者启动的时候,用户在 RUN CMD ENTRYPONT 执行的时候的用户。 |
HEALTHCHECK | 健康检查 | 指定监测当前容器的健康监测的命令,基本上没用。因为很多时候应用本身有健康监测机制。 |
ONBUILD | 触发器 | 当存在 ONBUILD 关键字的镜像作为基础镜像的时候,当执行 FROM 完成之后,会执行 ONBUILD 的命令。但是不影响当前镜像,用处也不怎么大。 |
STOPSIGNAL | 发送信号量到宿主机 | 该 STOPSIGNAL 指令设置将发送到容器的系统调用信号以退出。 |
SHELL | 指定执行脚本的 shell | 指定 RUN CMD ENTRYPOINT 执行命令的时候 使用的 shell。 |
案例
自定义 centos7 镜像
要求
- 默认登录路径为
/usr - 可以使用 vim
实现步骤
dockerfile
# 使用 centos:7 作为父镜像
FROM centos:7
# 定义作者信息
# MAINTAINER itheima <itheima@itcast.cn>
LABEL version="1.0" maintainer="yourname@example.com"
# 执行安装 vim 命令
RUN yum install -y vim
# 定义默认的工作目录
WORKDIR /usr
# 定义容器启动执行的命令
CMD ["/bin/bash"]通过 dockerfile 构建镜像:
bash
docker build -f Dockerfile -t centos-vim:1.0 .自定义 springboot 镜像
需求
- 定义 dockerfile,发布 springboot。
实现步骤
dockerfile
# 使用 java:8 作为父镜像
FROM java:8
# 定义作者信息
# MAINTAINER itheima <itheima@itcast.cn>
LABEL version="1.0" maintainer="yourname@example.com"
# 将 jar 包添加到容器
ADD springboot.jar app.jar
# 定义容器启动执行的命令
CMD ["java", "-jar", "app.jar"]通过 dockerfile 构建镜像:
bash
docker build -f Dockerfile -t java-springboot:1.0 .