1、Docker底层技术
命名空间
PID 命名空间
- 作用:进程号隔离,每个容器内的进程都拥有独立的进程号,互不干扰。容器内的进程视图与主机系统或其他容器的进程隔离。
- 通俗例子:想象每个容器是一个独立的办公室,办公室内的员工(进程)只知道自己办公室的情况,而无法看到其他办公室的员工。
NET 命名空间
- 作用:提供独立的网络栈,每个容器都有自己的网络接口、IP 地址和路由设置。容器之间的网络隔离,除非特别配置,否则无法直接互访。
- 通俗例子:每个容器就像一个独立的房间,有自己的网络连接(电话线)。房间之间的电话线互不相通,除非专门连接。
IPC 命名空间
- 作用:提供独立的进程间通信环境,使得每个容器拥有自己的 IPC 资源,如消息队列、信号量等。
- 通俗例子:每个会议室都有自己的白板和投影仪,只有在这个会议室内的人员可以使用这些设备,其他会议室的人员无法使用。
MNT 命名空间
- 作用:为每个容器提供独立的文件系统挂载点,确保容器内部的文件和目录结构不会与其他容器或主机系统混淆。
- 通俗例子:每个容器就像一个独立的存储柜,你可以在柜子里自由存取文件,而其他柜子里的文件对你不可见。
UTS 命名空间
- 作用:提供独立的系统标识,如主机名和域名。每个容器拥有自己的主机名,避免与主机系统或其他容器的标识冲突。
- 通俗例子:每个容器相当于一个独立的办公室,拥有自己独立的名称牌,不会与其他办公室的名称牌冲突。
USER 命名空间
- 作用:提供独立的用户和组 ID 映射。容器内的用户 ID 和组 ID 可以与主机系统的用户和组 ID 不同,从而提高安全性。
- 通俗例子:每个办公室(容器)有自己独立的钥匙卡系统,团队 A 的钥匙卡与团队 B 的钥匙卡互不相通,确保只有对应的成员可以使用。
控制组(cgroup)
- 限制和隔离容器的资源使用,例如 CPU、内存、磁盘 I/O 等。
- 确保容器的资源使用不会影响主机系统或其他容器的资源。
联合文件系统
作用:
层级存储:联合文件系统通过层的方式来管理文件系统。这些层可以是只读的或者可写的,底层通常是只读的镜像层,而顶层是可写的容器层。
快速启动:通过复用已经存在的只读层,可以快速创建和启动新的容器,减少了对磁盘空间的消耗。
高效利用:当多个容器共享同一个镜像层时,可以节省磁盘空间,因为它们只需要一个副本即可。
通俗易懂的例子:
想象你在准备一个三明治,联合文件系统就像是在做一个多层的三明治,每一层都是独立的,但是最终你可以看到一个完整的三明治。
第一层(基础层):就像三明治的底层面包,这个层包含了操作系统和基本工具,是只读的。
第二层(应用层):像是三明治的肉或蔬菜,这一层包含了你需要的应用和依赖,也是只读的。
第三层(容器层):就像三明治的顶层面包,这一层是可写的,你可以在上面添加任何你想要的调料(例如,配置文件、日志等)。
当你需要一个新的三明治(新的容器)时,你不需要重新做整个三明治(创建所有的层),你只需要在现有的基础上添加新的顶层(容器层)。
在 Docker 容器中的具体应用:
- 镜像和容器:
- 镜像:Docker 镜像是只读的层的集合,包含了运行应用所需的所有内容。每个镜像由多个层组成,每一层都是前一层的增量修改。
- 容器:容器是在镜像的基础上添加了一个可写层,运行时的所有修改都记录在这个层上。
- 写时复制(Copy-on-Write):
- 当容器需要修改一个文件时,联合文件系统会将这个文件从只读层复制到可写层,并在可写层上进行修改。这样,原始的只读层保持不变。
- 镜像构建:
- Dockerfile 中的每一条指令(如
RUN
,COPY
,ADD
等)都会创建一个新的层。这些层叠加在一起形成最终的镜像。
具体例子:
- 共享基础镜像:
- 多个容器可以共享相同的基础镜像层。例如,所有基于
ubuntu
镜像的容器都共享ubuntu
的只读层,从而节省了存储空间。
- 快速构建和部署:
- 由于只需创建和添加变化的部分,构建新的镜像和启动容器变得非常快速。例如,基于一个应用的基础镜像,可以快速创建一个带有最新代码的容器用于测试。
- 节省存储空间:
- 通过共享层和写时复制机制,减少了重复数据的存储。例如,不同版本的应用容器可以共享大部分相同的库和依赖,只需存储变化的部分。
2、容器和VM的差异
容器和虚拟机(VM)都是用于隔离应用程序和运行环境的技术,但它们在架构、性能和资源利用等方面存在显著差异。
1. 架构
容器:
- 共享主机操作系统的内核。
- 每个容器包含应用程序及其依赖项,但不包括完整的操作系统。
- 通过 Docker 等容器引擎管理。
虚拟机:
- 包含完整的操作系统,包括内核。
- 每个虚拟机运行在虚拟机监控程序(Hypervisor)上,Hypervisor可以在宿主机硬件之上提供多个虚拟机的运行环境。
- 具有完全独立的操作系统实例。
2. 启动时间
容器:
- 启动速度非常快,通常在几秒钟内即可启动。
- 由于共享宿主机的操作系统内核,启动过程更加轻量级。
虚拟机:
- 启动时间较长,需要几分钟。
- 每个虚拟机需要引导自己的操作系统,这增加了启动时间。
3. 资源利用
容器:
- 资源利用率高,因为它们共享宿主机的操作系统内核,减少了内存和存储的开销。
- 可以在相同的硬件上运行更多的容器。
虚拟机:
- 资源利用率相对较低,每个虚拟机都需要分配操作系统所需的资源。
- 更高的内存和存储开销,导致在相同硬件上运行的虚拟机数量较少。
4. 隔离性
容器:
- 使用命名空间和控制组(cgroups)实现资源和进程的隔离。
- 虽然隔离性较好,但由于共享内核,安全性略逊于虚拟机。
虚拟机:
- 完全隔离的操作系统实例,隔离性更强。
- 提供更高的安全性,因为虚拟机之间没有共享内核。
5. 持久化和存储
容器:
- 默认情况下,容器的文件系统是临时的。
- 需要通过挂载卷(volumes)或绑定挂载(bind mounts)来持久化数据。
虚拟机:
- 每个虚拟机有自己的持久化存储,通常是虚拟磁盘文件。
- 数据在虚拟机重启或迁移时保持不变。
6. 使用场景
容器:
- 适合微服务架构、持续集成/持续部署(CI/CD)、开发和测试环境。
- 高效的资源利用和快速启动使其在大规模部署和动态扩展中表现优异。
虚拟机:
- 适合需要强隔离性和完全独立操作系统环境的应用,如多租户环境和运行不同操作系统的场景。
- 更适合传统的企业应用和需要高安全性的应用。
3、多阶段构建
多阶段构建(Multi-stage builds)是一种在 Dockerfile 中使用的构建技术,旨在优化 Docker 镜像的大小和构建效率。通过这种方法,开发者可以在一个 Dockerfile 中定义多个构建阶段,每个阶段可以使用不同的基础镜像和构建工具。最终,只有需要的部分会被复制到最终的镜像中,从而减少了镜像的体积和不必要的文件。
多阶段构建的工作原理
- 定义多个构建阶段:
在 Dockerfile 中,可以使用FROM
指令定义多个构建阶段。每个FROM
指令开始一个新的构建阶段,并且可以使用不同的基础镜像。 - 选择性复制文件:
在每个构建阶段中,可以进行代码编译、依赖安装等操作。最终阶段可以选择性地从之前的阶段复制需要的文件和目录,而忽略中间步骤产生的临时文件和工具。 - 优化最终镜像:
通过只将最终需要的文件和应用程序复制到最终镜像中,减少了最终镜像的体积,从而提高了部署效率和启动速度。
示例
假设你有一个 Node.js 应用程序,你希望在构建 Docker 镜像时,将构建依赖和工具隔离在不同的阶段,只将最终应用程序复制到生产镜像中。以下是一个使用多阶段构建的 Dockerfile 示例:
# 第一阶段:构建应用
FROM node:18 AS builder
# 创建工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json 文件
COPY package*.json ./
# 安装应用依赖
RUN npm install
# 复制应用源代码
COPY . .
# 构建应用
RUN npm run build
# 第二阶段:创建最终镜像
FROM node:18-slim
# 创建工作目录
WORKDIR /app
# 只复制构建产物和需要的运行时依赖
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
# 复制应用启动文件
COPY --from=builder /app/package.json ./
# 设置启动命令
CMD ["node", "dist/app.js"]
解释
构建阶段:
FROM node:18 AS builder
:定义一个名为builder
的构建阶段,使用 Node.js 官方镜像作为基础镜像。- 在这个阶段,进行应用的构建操作,包括依赖安装和应用构建。
最终镜像阶段:
FROM node:18-slim
:定义一个新的阶段,这里使用一个较小的镜像node:18-slim
,它不包含构建工具和其他开发依赖。COPY --from=builder
:从builder
阶段中复制构建产物到最终镜像中,只包含必要的文件和目录。
优点
- 减少镜像体积:多阶段构建通过只将必要的文件复制到最终镜像,显著减少了镜像的体积。
- 优化构建流程:通过在不同阶段处理不同任务,可以更好地管理和优化构建流程。
- 提高安全性:减少最终镜像中包含的工具和依赖,降低了安全风险。
4、构建镜像时COPY和ADD的区别
在 Dockerfile 中,COPY
和 ADD
是两个常用的指令,用于将文件和目录从宿主系统复制到 Docker 镜像中。虽然它们的基本功能相似,但在细节和用途上有所不同。
1. COPY 指令
基本功能:
- 仅用于将文件或目录从宿主系统复制到镜像的文件系统中。
- 语法:
COPY <源路径>... <目标路径>
特点:
COPY
是一个简单的复制命令,不具备其他功能。- 适合在不需要解压缩或从 URL 下载文件时使用。
- 示例:
COPY ./src /app/src
2. ADD 指令
基本功能:
- 除了具备
COPY
的基本功能外,还支持其他一些附加功能。 - 语法:
ADD <源路径>... <目标路径>
- 除了具备
特点:
- 可以解压缩从宿主系统复制的 tar 文件。
- 支持从 URL 下载文件并复制到镜像中。
- 由于功能更强大,相对
COPY
而言,更复杂和多功能。
- 示例:
ADD ./src /app/src
ADD https://example.com/file.tar.gz /app/file
3. 主要区别
解压功能:
COPY
:只复制文件,不进行解压缩。ADD
:如果源文件是一个压缩的 tar 文件,会自动解压缩。
URL 支持:
COPY
:不支持从 URL 下载文件。ADD
:支持从 URL 下载文件,并将其复制到镜像中。
推荐使用场景:
COPY
:建议在仅需要复制文件或目录的情况下使用,以保持 Dockerfile 的简洁明确。ADD
:仅在需要解压缩功能或从 URL 下载文件时使用。
4. 示例 Dockerfile
以下是一个示例 Dockerfile,演示了 COPY
和 ADD
的使用:
# 使用 Python 官方基础镜像
FROM python:3.9
# 设置工作目录
WORKDIR /app
# 使用 COPY 复制本地文件到容器中
COPY requirements.txt .
# 安装依赖
RUN pip install -r requirements.txt
# 使用 ADD 解压缩一个本地的 tar 文件
ADD my_app.tar.gz /app/
# 使用 ADD 从 URL 下载文件
ADD https://example.com/config.yaml /app/config.yaml
# 设置容器启动命令
CMD ["python", "app.py"]
5、在Dockerfile中复制基于alpine制作的nginx镜像里的resolv.conf
# 第一个阶段:从基于 Alpine 的 Nginx 镜像中提取 resolv.conf
FROM nginx:alpine AS resolver
# 复制 /etc/resolv.conf 文件到 /tmp 目录
COPY /etc/resolv.conf /tmp/resolv.conf
# 第二个阶段:创建最终的镜像(可以是基于任何镜像)
FROM alpine:latest
# 创建工作目录
WORKDIR /app
# 从第一个阶段复制 resolv.conf 文件到最终镜像中
COPY --from=resolver /tmp/resolv.conf /etc/resolv.conf
# 安装必要的包(如果需要)
RUN apk add --no-cache nginx
# 复制你的应用文件(如果有的话)
# COPY ./your-app /app/
# 设置 Nginx 的启动命令
CMD ["nginx", "-g", "daemon off;"]
解释
第一阶段:提取
resolv.conf
文件- 使用
nginx:alpine
作为基础镜像,创建一个名为resolver
的构建阶段。 - 使用
COPY
指令将/etc/resolv.conf
文件复制到/tmp
目录中。
- 使用
第二阶段:创建最终镜像
- 使用
alpine:latest
作为最终镜像的基础镜像。 - 使用
COPY --from=resolver
从第一个构建阶段中复制/tmp/resolv.conf
文件到最终镜像的/etc/resolv.conf
位置。 - 安装 Nginx 或其他必要的软件包。
- 设置 Nginx 的启动命令。
- 使用