Docker 笔记


Docker 笔记


容器是什么?镜像是什么?

容器:是一个轻量级、可独立运行的软件包。它是动态的可运行的。它是通过用镜像创建出来的一个具体的、正在运行的进程实例

镜像:是用来创建和运行 Docker 容器的。它包含了运行某个应用所需的一切:代码、运行时环境(如 Node.js 或 Java)、系统工具、系统库和设置。

分层 (Layer) 是什么?

一个 Docker 镜像并非一个单一的文件,而是由一组文件系统层 (Layers) 堆叠而成的。你可以把它想象成一个千层蛋糕。

  • 只读性: 镜像的每一层都是只读的 (read-only)
  • 来源: Dockerfile 中的每一条指令,通常都会创建一个新的镜像层。比如 FROM, RUN, COPY, ADD 等。
  • 共享与复用: 不同的镜像可以共享相同的层。如果它们都基于同一个基础镜像,那么这个基础镜像的层在主机上只需要存储一份。

工作原理:只读的镜像层 + 可写的容器层

当 Docker 使用一个镜像来启动一个容器时,它并不会改变这个镜像。而是在镜像的顶层添加了一个新的、可写的层,我们称之为 “容器层” (Container Layer)

  • 容器的生命周期内:你对容器做的所有更改,比如新建文件、修改文件、删除文件,都发生在这个可写的容器层里。
  • 写时复制 (Copy-on-Write): 如果容器需要修改一个底层镜像中已存在的文件,Docker 会先把这个文件从下面的只读层复制到最上层的可写容器层,然后再进行修改。原来的只读层中的文件保持不变。这就是所谓的“写时复制”机制。

分层架构的好处 (面试重点)

面试官通常会问,为什么 Docker 要采用这种分层设计?

  1. 高效的存储 (Efficient Storage):
    • 共享基础层: 假设你的主机上有三个Python服务,它们都基于python:3.12-slim。那么 python:3.12-slim 对应的所有层都只会在磁盘上存储一份。这极大地节省了磁盘空间。
      • 共享的基础镜像 (python:3.12-slim): 由 [基础层] 构成,大小为 100MB
      • 镜像 a (service-a):
        • 构成: [基础层] + [a层(5MB)]
        • docker images 显示的大小 ≈ 105MB
      • 镜像 b (service-b):
        • 构成: [基础层] + [b层(8MB)]
        • docker images 显示的大小 ≈ 108MB
      • 镜像 c (service-c):
        • 构成: [基础层] + [c层(2MB)]
        • docker images 显示的大小 ≈ 102MB
      • 总占用 = 100MB (基础) + 5MB (a) + 8MB (b) + 2MB (c) = 115MB
  2. 更快的构建 (Faster Builds):
    • 利用缓存: Docker 会缓存每一层的构建结果。当你修改 Dockerfile 并重新构建时,Docker 只会重新构建你修改的那一行指令以及它之后的所有层,之前的层会直接使用缓存。这极大地加快了开发和构建的速度。
      • 场景:开发一个简单的 Python Web 应用

        假设你的项目文件夹里有这两个文件:

        1. requirements.txt (记录 Python 依赖,比如 flask, requests)
        2. app.py (你的主程序代码)

        你的 Dockerfile 是这样精心设计的(这个顺序很关键):

        # --- 关卡 1: 选择基础镜像 ---
        FROM python:3.9-slim
        
        # --- 关卡 2: 设置工作目录 ---
        WORKDIR /app
        
        # --- 关卡 3: 复制依赖文件 ---
        COPY requirements.txt .
        
        # --- 关卡 4: 安装依赖 (这步通常很慢) ---
        RUN pip install -r requirements.txt
        
        # --- 关卡 5: 复制所有项目代码 ---
        COPY . .
        
        # --- 关卡 6: 设置启动命令 ---
        CMD ["python", "app.py"]
        

        第一次构建:从零开始

        你第一次运行 docker build -t my-app .

        • Docker 的动作:它会老老实实地从头到尾执行每一条指令。
          • 关卡1 (FROM): 执行… 完成!(创建存档1)
          • 关卡2 (WORKDIR): 执行… 完成!(创建存档2)
          • 关卡3 (COPY req...): 执行… 完成!(创建存档3)
          • 关卡4 (RUN pip...): 开始下载和安装库,可能需要几十秒甚至几分钟… 完成!(创建存档4)
          • 关卡5 (COPY . .): 执行… 完成!(创建存档5)
          • 关卡6 (CMD): 执行… 完成!(创建存档6)
        • 你的感受:有点慢,需要耐心等待 pip install 完成。

        第二次构建:只改了 Python 代码

        这是最常见的开发场景。你修改了 app.py 里面的逻辑,但没有动 requirements.txt。现在你再次运行 docker build -t my-app .

        • Docker 的动作
          • 关卡1 (FROM): 指令没变 -> 使用存档1 (USING CACHE)
          • 关卡2 (WORKDIR): 指令没变 -> 使用存档2 (USING CACHE)
          • 关卡3 (COPY req...): Docker 会检查 requirements.txt 文件的内容。发现文件没变 -> 使用存档3 (USING CACHE)
          • 关卡4 (RUN pip...): 因为上一层(关卡3)使用了缓存,并且本行指令也没变 -> 使用存档4 (USING CACHE)最耗时的安装步骤被瞬间跳过!
          • 关卡5 (COPY . .): Docker 检查要复制的所有文件,发现 app.py 变了! -> 存档5失效,重新执行!
          • 关卡6 (CMD): 重要规则:一旦中间某一层缓存失效,它之后的所有层都会重新执行 -> 重新执行!
        • 你的感受:构建速度极快!几乎是秒级完成,因为最慢的 pip install 被跳过了。

        第三次构建:修改了依赖文件

        这次,你往 requirements.txt 里增加了一个新的库,比如 numpy。然后再次运行 docker build -t my-app .

        • Docker 的动作
          • 关卡1 (FROM): 使用缓存。
          • 关卡2 (WORKDIR): 使用缓存。
          • 关卡3 (COPY req...): Docker 检查 requirements.txt,发现文件内容变了!-> 存档3失效,重新执行!
          • 关卡4 (RUN pip...): 因为上一层(关卡3)是重新执行的 -> 存档4失效,必须重新执行! Docker 会再次运行 pip install,这次会把 numpy 也装上。
          • 关-关6: 同样全部重新执行。
        • 你的感受:构建又变慢了。这是符合预期的,因为依赖确实发生了变化,需要重新安装。
  3. 高效的分发 (Efficient Distribution):
    • 按需传输: 当你从 Docker Hub 拉取 (docker pull) 一个镜像时,Docker 客户端会检查本地已经存在的层,只下载那些本地没有的层。同理,推送 (docker push) 镜像时也一样。这使得镜像的拉取和推送非常快。

缓存失效了,之前的缓存会自动删除吗

回答:不会,缓存失效后,旧的缓存层不会在构建时自动删除。

它们会变成一种特殊的状态,我们称之为 “悬空镜像” (dangling image) 或更广义的 “悬空层” (dangling layers)。

这是什么意思?我们用一个比喻来理解

把镜像的标签(比如 my-app:latest)想象成一个便利贴

  1. 第一次构建:
    • 你构建了一个镜像,我们叫它 镜像A
    • Docker 把 my-gzh:v1 这张便利贴粘在了 镜像A 上。
    • 此时,镜像A 是有主(有便利贴)的。
  2. 你修改了代码,再次构建:
    • 因为缓存失效,Docker 创建了一个全新的镜像,我们叫它 镜像B
    • Docker 会把 my-app:latest 这张便利贴从 镜像A 身上撕下来,然后粘到新的 镜像B 身上。
  3. 现在发生了什么?
    • 镜像B 成为了新的 my-app:latest
    • 镜像A 呢?它身上的便利贴被撕走了,它变成了一个没有名字、没有标签的“无主孤魂”

这个被撕掉标签的 镜像A,就是我们所说的 “悬空镜像” (dangling image)。它和它所包含的那些旧的、失效的缓存层,依然静静地躺在你的硬盘上,并不会自动消失。

为什么要这么设计?

Docker 这么做有它的道理,比如:

  • 安全回滚:万一你发现新的构建有问题,那个旧的、没有标签的镜像还在,理论上你还可以通过它的 ID 找回它,重新给它贴上标签来回滚。
  • 性能考虑:在构建过程中频繁地进行磁盘删除操作,可能会影响构建性能。所以 Docker 选择把“构建”和“清理”这两个动作分开。

那如何清理这些“无主孤魂”呢?

既然 Docker 不会自动清理,就需要我们手动当一下“清洁工”。Docker 提供了非常方便的命令。

  1. 查看有哪些悬空镜像
    你可以用这个命令,专门列出所有被撕掉标签的悬空镜像:

    docker images -f dangling=true
    

    你可能会看到一堆 <none>:<none> 的镜像,这些就是它们。

  2. 清理悬空镜像

    使用 prune (修剪) 命令可以一键清理。

    docker image prune
    

    执行后,Docker 会找到所有悬空镜像并询问你是否要删除它们,你输入 y 即可。这些镜像占用的磁盘空间就会被释放。

如何查看镜像的分层?

你可以使用 docker history 命令来查看一个镜像的构建历史和分层信息。

# 拉取一个镜像用于演示
docker pull python:3.9-slim

# 查看它的分层历史
docker history python:3.9-slim

你会看到类似下面的输出,每一行都代表一个层,以及创建该层的命令和它的大小:

IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
a1b2c3d4e5f6   2 weeks ago    /bin/sh -c #(nop)  CMD ["python3"]              0B
      2 weeks ago    /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B
      2 weeks ago    /bin/sh -c #(nop) COPY file:238737301c4475…     341B
      2 weeks ago    /bin/sh -c set -ex;   savedAptMark="$(apt-ma…   4.86MB
      2 weeks ago    /bin/sh -c #(nop)  ENV PYTHON_GET_PIP_URL=h…   0B
... (更多层) ...

总结一下:

镜像是静态的、只读的模板。它由多个只读层构成,每一层对应Dockerfile的一条指令。启动容器时,会在镜像之上增加一个可写层。这种分层结构带来了存储、构建和分发上的巨大效率优势。

Docker 的整体架构

Docker 采用的是经典的 C/S 架构 (Client/Server, 客户端/服务器架构)。它主要由三大部分组成:

  1. 客户端 (Docker Client)
  2. 主机 (Docker Host) / 守护进程 (Docker Daemon)
  3. 镜像仓库 (Docker Registry)

客户端 (Docker Client)

  • 是什么:就是你主要打交道的对象,最常见的就是 docker 命令行工具。
  • 做什么:当你输入一个命令,比如 docker run ...docker build ...,Client 会将这个命令通过 REST API 发送给 Docker Daemon。
  • 在哪:Client 可以和 Docker Daemon 运行在同一台机器上,也可以通过网络连接到远程的 Docker Daemon 上进行管理。

简单来说,客户端就是个“传话的”,负责把你的指令传递给真正的“干活的”。

主机 (Docker Host) & 守护进程 (Docker Daemon)

这是 Docker 架构的核心和大脑,真正的“实干家”。

  • 守护进程 (Docker Daemon, dockerd):
    • 它是一个常驻在后台的系统进程。
    • 它负责监听来自 Docker Client 的 API 请求。
    • 它拥有管理一切 Docker 对象的全部权力,包括:
      • 镜像 (Images): 管理本地镜像,如果本地没有,会从 Registry 拉取。
      • 容器 (Containers): 创建、运行、停止、删除容器。
      • 网络 (Networks): 创建和配置容器网络。
      • 数据卷 (Volumes): 管理数据的持久化。
  • Docker Host: 指安装了 Docker Daemon 的物理机或虚拟机。Daemon 就是运行在这个 Host 上的。

镜像仓库 (Docker Registry)

  • 是什么:一个集中存放和分发 Docker 镜像的服务。你可以把它理解为 Docker 世界的 “GitHub” 或 “Maven 中央仓库”。
  • 做什么
    • docker pull: Daemon 从 Registry 拉取镜像到本地。
    • docker push: Daemon 将本地构建的镜像推送到 Registry。
  • 分类
    • 公共仓库 (Public Registry): 比如官方的 Docker Hub,是默认的 Registry。
    • 私有仓库 (Private Registry): 出于安全或网络原因,公司通常会搭建自己的私有仓库,比如 Harbor,或者使用云厂商提供的私有仓库服务 (如阿里云 ACR)。

Dockerfile 最佳实践与深度理解

1. 保持镜像轻量 (Keep Images Small)

为什么重要?

  • 存储成本:更小的镜像占用更少的磁盘和仓库空间。
  • 分发速度:更小的镜像 pushpull 都更快,能极大地提升 CI/CD 效率和应用部署速度。

实践方法一:选择一个精简的基础镜像

FROMDockerfile 的第一行,也是决定镜像大小的起点。

  • ubuntu (或 centos): 包含完整的操作系统工具,体积最大,通常几百MB。适合需要大量系统工具的场景。
  • image:tag-slim (如 python:3.9-slim): 官方提供的“瘦身版”,移除了很多非必需的工具包,体积适中。通常是生产环境的首选。
  • alpine: 基于 Alpine Linux,一个极度轻量化的发行版。体积最小,通常只有几MB。但它使用 musl 代替 glibc 作为 C 标准库,偶尔可能遇到兼容性问题。

建议没有特殊理由,就从 slim 版本开始。如果对体积有极致要求,再考虑 alpine

实践方法二:使用多阶段构建 (Multi-Stage Builds)

这是减小镜像体积最有效的手段,尤其适用于需要编译的语言(如 Go, Java, Rust)或需要构建步骤的前端应用(如 Node.js, React)。

核心思想:用一个包含完整构建工具的“构建镜像”来完成编译/打包,然后把最终的产物(可执行文件、静态资源等)复制到一个干净的、极小的“生产镜像”中。

糟糕的例子 (单阶段):

# 把所有构建工具和中间产物都打进了最终镜像
FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build # 生成了 dist 目录

# 问题:最终镜像里包含了完整的Node.js、npm、devDependencies等,非常臃肿
CMD ["npm", "start"]

这个镜像可能有 1GB 大小。

优秀的例子 (多阶段):

# --- 构建阶段 (Builder Stage) ---
# 使用一个完整的 Node.js 镜像,并给它起个别名 `builder`
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# --- 生产阶段 (Production Stage) ---
# 使用一个极度轻量化的 Nginx 服务器作为最终镜像
FROM nginx:1.21-alpine
# 只从 `builder` 阶段复制出最终的静态文件产物
COPY --from=builder /app/dist /usr/share/nginx/html

# EXPOSE 80 (Nginx 默认)
# CMD ["nginx", "-g", "daemon off;"] (Nginx 默认)

这个最终镜像可能只有 20MB 大小。对比天差地别!

实践方法三:清理不必要的中间文件

RUN 指令中,应该把多个命令用 && 串联起来,并在结尾清理掉缓存和临时文件。因为每一条 RUN 都会创建一个新的层,如果你不一次性清理,垃圾就会被永久封存在上一层里。

糟糕的例子:

# 垃圾被留在了第一层
RUN apt-get update
# 安装了软件,但缓存还在
RUN apt-get install -y curl

优秀的例子:

RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*

把所有操作合并到一条 RUN 指令中,并在结尾删除了 apt 缓存,确保这一层是干净的。

2. 高效利用构建缓存

我们之前讨论过缓存的原理。这里的核心就是优化 Dockerfile 的指令顺序

核心原则:把变化最少、最稳定的层放在前面;把最频繁变化的层放在后面。

糟糕的例子:

FROM python:3.9-slim
WORKDIR /app
# 只要任何一个代码文件变动,下面整块都要重新执行
COPY . .
# 导致每次改代码都要重新安装所有依赖,非常慢
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

优秀的例子:

FROM python:3.9-slim
WORKDIR /app
# 1. 先只复制依赖文件。这个文件通常不怎么变。
COPY requirements.txt .
# 2. 安装依赖。只要 requirements.txt 不变,这一层就会被完美缓存。
RUN pip install -r requirements.txt
# 3. 最后再复制你的代码。这样你频繁修改代码时,上面耗时的步骤都能利用缓存。
COPY . .
CMD ["python", "app.py"]

Docker网络操作

Docker 主要有三种常见的网络模式:

  1. bridge (桥接模式):默认模式,单机环境下的“局域网”。Docker 会为这个网络分配一个私有的 IP 地址段(比如 172.17.0.0/16),每个接入的容器都会获得一个自己的内部 IP 地址。
  2. host (主机模式):直接使用主机的网络,性能最好但隔离性差。容器直接共享使用宿主机的网络协议栈。在容器内部看到的网卡、IP 地址、端口等,都和宿主机上完全一样。
  3. overlay (覆盖网络):用于多台主机之间的容器通信,是集群(Swarm)的基石。
    它可以在多台不同的物理或虚拟主机之上,创建一个统一的、虚拟的二层网络。这使得连接到同一个 overlay 网络的容器,即使它们分布在不同的主机上,也感觉像是在同一个局域网内,可以互相直接通信。

数据持久化:Volume vs. Bind Mount

1. 数据卷 (Volumes):Docker 的“专业行李箱”

这是 Docker官方最推荐的数据持久化方式。

  • 是什么:Volume 是一个由 Docker 自己创建和管理的特殊目录。它独立于容器的生命周期,专门用来存放持久化数据。在 Linux 系统上,它通常位于 /var/lib/docker/volumes/ 目录下,但我们不应该直接去操作这个目录。
  • 工作原理:你可以把它想象成一个 Docker 帮你保管的、可插拔的“U盘”。你创建一个 Volume (U盘),然后把它“插”到容器的指定目录上。即使容器这个“电脑”坏了、被扔掉了,这个“U盘”和里面的数据都还在,可以再插到新的容器上继续使用。
  • 特点
    • Docker 管理:创建、删除、查看等都通过 Docker 命令进行,与宿主机的具体目录结构解耦。
    • 独立生命周期:删除容器时,Volume 不会被删除(除非你显式删除它)。
    • 高性能:在 Linux 上,Volume 的读写性能接近原生磁盘。
    • 跨平台兼容性好:无论你在 Windows, macOS 还是 Linux 上,Volume 的行为都是一致的。
    • 更安全:容器只能访问到挂载的 Volume,无法随意访问宿主机的其他文件。
  • 适用场景
    • 数据库数据:比如 MySQL, PostgreSQL 的数据目录。
    • 应用生成的内容:比如用户上传的图片、附件等。
    • 需要在多个容器之间共享数据的场景。
    • 所有需要持久化的生产环境数据

示例

# 1. 创建一个 Volume
docker volume create my-db-data

# 2. 运行 MySQL 容器,并将这个 Volume 挂载到容器内的数据目录
# -v my-db-data:/var/lib/mysql
#  (Volume名称):(容器内路径)
docker run -d --name mysql-db \
  -v my-db-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:5.7

2. 绑定挂载 (Bind Mounts):宿主机的“任意门”

这种方式是直接将宿主机上的一个已存在的目录或文件挂载到容器中。

  • 是什么:Bind Mount 就是在容器和宿主机之间开了一扇“任意门”。门的一边是宿主机的某个指定路径,另一边是容器内的某个路径。两边的内容是实时同步、完全一致的。
  • 工作原理:容器内的进程对挂载点的读写,实际上就是直接对宿主机文件系统的读写。容器内的 uid/gid 权限也可能会影响到宿主机上的文件权限。
  • 特点
    • 直接映射:非常直观,所见即所得。
    • 依赖宿主机:强依赖于宿主机的特定文件路径。如果换一台机器,这个路径可能不存在,导致无法启动。
    • 潜在安全风险:如果挂载了像 //etc 这样的系统目录,容器内的进程(尤其是 root 用户)就可能修改甚至破坏宿主机系统。
    • 性能:在 Linux 上性能也很好,但在 macOS 和 Windows 上,因为有虚拟化文件系统的介入,性能可能会比 Volume 差一些。
  • 适用场景
    • 开发环境:这是 Bind Mount 最核心、最完美的应用场景。你可以把本地的源代码目录挂载到容器里,这样你在本地 IDE 里修改代码,容器内的应用可以立刻感知到变化(比如用 nodemon 实现热更新),无需重新构建镜像。
    • 需要将主机的某些配置文件(如 nginx.conf)共享给容器使用的场景。

示例

# 把宿主机当前目录下的 `my-app` 文件夹,挂载到容器内的 `/app` 目录
# -v $(pwd)/my-app:/app
#  (宿主机绝对路径):(容器内路径)
docker run -d --name dev-container \
  -v $(pwd)/my-app:/app \
  my-dev-image

如何分析Docker镜像的大小组成

方法一:基础体检 - docker history

这是 Docker 自带的最基础的工具,可以快速查看镜像的“履历”,也就是它的分层历史和每一层的大小。

  • 如何使用

    docker history your-image:tag
    
  • 能看到什么
    它会列出构成这个镜像的每一层,以及创建该层的 Dockerfile 指令(CREATED BY 列)和该层自身的大小(SIZE 列)。

    IMAGE          CREATED         CREATED BY                                      SIZE
    f1a3a4b5c6d7   5 minutes ago   /bin/sh -c #(nop) CMD ["./my-app"]              0B
    a2b3c4d5e6f7   5 minutes ago   /bin/sh -c #(nop) COPY file:123... in /app/     15MB
    c3d4e5f6a7b8   10 minutes ago  /bin/sh -c pip install -r requirements.txt      120MB  <-- 尺寸可疑!
    d4e5f6a7b8c9   2 hours ago     /bin/sh -c #(nop) COPY file:456... in /app/     1KB
    ...
    
  • 优点

    • 无需安装任何额外工具,是 Docker 内置功能。
    • 可以快速定位到是哪一条 RUNCOPY 命令导致了镜像体积的剧增。
  • 缺点

    • 只能看到层的大小,看不到层里面具体是哪个文件大。比如上面那个 120MB 的层,我们只知道是 pip install 产生的,但不知道是 numpy 占了 80MB 还是 pandas 占了 100MB。

方法三:专业分析工具 - dive (强烈推荐)

dive 是一个开源的第三方工具,是专门为分析 Docker 镜像而生的“神器”。它可以让你像透视一样看清镜像的每一层。

  • 如何使用
    你需要先安装 dive (在 macOS 上可以用 brew install dive,其他系统请参考其 GitHub 页面)。然后执行:

    dive your-image:tag
    
  • 能看到什么
    dive 会打开一个交互式的界面,主要分为两个窗格:

    • 左侧窗格:显示了镜像的所有分层,和 docker history 的结果类似。你可以用上下键在不同层之间切换。
    • 右侧窗格:实时显示当前选中层的文件系统树。你可以像在文件管理器里一样,展开目录,查看文件。

Docker Compose - 单机环境下的“轻量级”编排工具

version: '3' # 文件格式版本
services: # “食材”列表,定义了应用包含的所有服务
  
  web: # 第一个服务,名为 "web"
    image: nginx:latest # 使用哪个镜像
    ports: # 端口映射
      - "80:80"
    volumes: # 数据挂载
      - ./html:/usr/share/nginx/html
    networks: # 连接到哪个网络
      - webnet
     
  db: # 第二个服务,名为 "db"
    image: mysql:5.7
    environment: # 环境变量
      MYSQL_ROOT_PASSWORD: password
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - webnet

volumes: # 定义这个应用会用到的数据卷
  db_data:

networks: # 定义这个应用会用到的网络
  webnet:

核心优势

  1. 一键管理,简单高效
    • docker-compose up -d:一键启动并后台运行整个应用。
    • docker-compose down:一键停止并删除所有相关的容器、网络。
    • docker-compose logs -f:一键查看所有服务的日志。
      你不再需要关心单个容器,而是从整个应用的层面来管理服务。
  2. 自动化的服务发现与网络
    当你运行 docker-compose up 时,Compose 会自动创建一个专用的桥接网络(在上面的例子里就是 webnet)。
    • 处在这个网络里的所有服务(webdb),可以直接通过服务名作为主机名进行通信
    • 例如,web 服务可以直接连接 mysql://db:3306 来访问数据库,而不需要关心 db 容器的 IP 地址是什么。这个过程是全自动的。
  3. 配置即代码,易于分享和版本控制docker-compose.yml 是一个纯文本文件,你可以像对待代码一样,把它提交到 Git 仓库里。
    • 团队协作:新同事加入项目时,他只需要 git pull 拉取代码,然后运行 docker-compose up,一个完全一致的开发环境瞬间就搭建好了。
    • 环境一致性:确保了开发、测试、甚至简单生产环境的配置都是完全一致的,避免了“在我电脑上是好的”这种经典问题。

Docker Swarm

定义:Docker 官方内置的、原生的集群管理和编排工具
Docker Swarm 则是把多台安装了 Docker 的主机(物理机或虚拟机)聚合在一起,组成一个“虚拟的单一主机”,也就是一个 Docker 集群。

核心概念与角色

  • Swarm (集群): 指的是由多台 Docker 主机组成的整个集群。
  • Node (节点): 集群中的每一台 Docker 主机就是一个节点。节点分为两种角色:
    • Manager Node (管理节点): 集群的“大脑”和“指挥官”
      • 职责:接收用户的指令(比如 docker service create)、维护集群的期望状态、决定将容器(任务)调度到哪个节点上运行、管理整个集群的状态。
      • 高可用:为了防止“指挥官”单点故障,生产环境通常会设置 3 个或 5 个 Manager 节点。它们之间通过 Raft 一致性算法同步数据,即使一个 Manager 宕机,其他 Manager 也能接管工作。
    • Worker Node (工作节点): 集群的“工兵”和“劳动力”
      • 职责:不参与决策,唯一的任务就是接收来自 Manager 节点的指令,然后运行或停止容器(任务)。它们是真正干活的。
  • Service (服务): 这是在 Swarm 模式下部署应用的核心抽象
    • 它不再是单个的容器,而是一个服务定义。你向 Manager 声明:“我想要一个名为 webapp 的服务,它使用 nginx 镜像,需要一直保持 3 个副本在运行,并对外暴露 80 端口”。
    • Swarm 会持续监控,确保任何时候都有 3 个符合你定义的容器在运行。
  • Task (任务): 一个服务运行的具体实例,也就是一个容器。如果一个服务有 3 个副本(replicas),那么它就对应着 3 个正在运行的任务(容器)。任务是 Swarm 中最小的调度单位。

工作流程

第一步:初始化集群

在一台准备当 Manager 的机器上执行:

docker swarm init --advertise-addr 

这个命令会把这台机器变成 Swarm 集群的第一个(也是目前唯一一个)Manager 节点。并且它会生成一个“入群口令”(Token)。

第二步:节点加入集群
在其他机器上,使用第一步生成的口令,让它们作为 Worker 节点加入集群:

docker swarm join --token  :2377

第三步:部署服务 (下发乐谱)

现在,在Manager 节点上告诉“Manager ”要部署什么服务:

docker service create --name web --replicas 3 -p 8080:80 nginx

docker swarm启动服务的命令是:docker service create

  • 我想要一个名为 web服务
  • 它的期望状态是永远保持 3 个副本-replicas 3)。
  • 它使用 nginx 镜像。
  • 将集群的 8080 端口映射到这个服务所有副本的 80 端口。

第四步:Swarm 的魔法:声明式服务与自愈

  1. 调度:Manager 节点收到命令后,会查看所有 Worker 节点的负载情况,然后决定在哪几个 Worker 上启动这 3 个 Nginx 容器(任务)。
  2. 路由网格 (Routing Mesh)-p 8080:80 这个端口映射非常强大。无论你访问集群中任何一个节点8080 端口,Swarm 都会自动把请求负载均衡到这 3 个 Nginx 容器中的一个,不管那个容器具体在哪台机器上。
  3. 自愈 (Self-healing):这是编排平台的核心魅力。假设一个运行着 Nginx 容器的 Worker 节点突然宕机了。
    • Manager 会立刻发现:“警告!web 服务的当前状态(2个副本)不等于期望状态(3个副本)!”
    • 为了恢复到期望状态,Manager 会马上在另一个健康的 Worker 节点上,自动启动一个新的 Nginx 容器
    • 整个过程无需人工干预,服务会自动恢复。你只需要声明你的“期望”,Swarm 会搞定一切。

第五步:扩缩容与更新

  • 扩容:业务高峰来了,你想把 3 个 Nginx 增加到 10 个。

    docker service scale web=10
    

    Manager 会立刻调度,启动 7 个新的 Nginx 容器。

  • 更新:你想把 Nginx 的版本升级到 1.21

    docker service update --image nginx:1.21 web
    

    Swarm 会自动进行“滚动更新”,一个一个地用新版本容器替换旧版本容器,保证服务在更新过程中不中断。

路由网格 (Routing Mesh)

docker service create --name my-nginx-service --replicas 3 -p 8080:80 nginx

这里的 -p 8080:80 已经不是“把主机端口 8080 映射到单个容器的 80 端口”这个简单的意思了。它的含义是:

“在整个集群的所有节点上,都发布 8080 端口,并让它成为 my-nginx-service 这个服务的统一入口。”

  • 发布端口:当你创建服务并发布端口后,Swarm 会在集群中的每一个节点上都监听 8080 端口,准备接收外部流量。
  • 智能路由:无论你的请求到达哪一个节点的 8080 端口(可以是 Manager,也可以是 Worker,即使这个节点上并没有运行 Nginx 容器),路由网格都会接收这个请求。
  • 负载均衡:路由网格知道 my-nginx-service 背后有 3 个正在运行的容器(任务),也知道它们各自的内部 IP 地址。它会自动将接收到的请求,以轮询的方式负载均衡到这 3 个容器中的一个去处理。

一、Docker 基础配置

  1. 安装后配置

    # 将当前用户添加到docker组(避免每次使用sudo)
    sudo usermod -aG docker $USER
    
    # 配置Docker开机自启
    sudo systemctl enable docker
    sudo systemctl start docker
    
    # 查看Docker版本
    docker version
    docker info
    
  2. 配置镜像加速器

    # 编辑Docker配置文件
    sudo vim /etc/docker/daemon.json
    
    # 添加镜像加速器配置
    {
      "registry-mirrors": [
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        ""
      ]
    }
    
    # 重启Docker服务
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    

二、镜像管理

  1. 搜索和拉取镜像

    docker search nginx                 # 搜索镜像
    docker pull nginx                   # 拉取最新版本
    docker pull nginx:1.21              # 拉取指定版本
    docker pull ubuntu:20.04            # 拉取Ubuntu 20.04
    
  2. 查看本地镜像

    docker images                       # 列出本地镜像
    docker images -a                    # 显示所有镜像(包括中间层)
    docker images nginx                 # 查看特定镜像
    docker images --format "{{.ID}}: {{.Repository}}"  # 自定义格式输出
    
  3. 镜像详细信息

    docker inspect nginx                # 查看镜像详细信息
    docker history nginx                # 查看镜像历史
    docker image prune                  # 清理未使用的镜像
    docker rmi nginx:1.21               # 删除指定镜像
    docker rmi $(docker images -q)      # 删除所有镜像
    
  4. 保存和加载镜像

    docker save -o nginx.tar nginx:latest     # 保存镜像到tar文件
    docker load -i nginx.tar                  # 从tar文件加载镜像
    docker save nginx:latest | gzip > nginx.tar.gz  # 压缩保存
    
  5. 构建镜像

    docker build -t myapp:1.0 .              # 从Dockerfile构建
    docker build -t myapp:1.0 -f Dockerfile.dev .  # 指定Dockerfile
    docker build --no-cache -t myapp:1.0 .   # 不使用缓存构建
    

三、容器管理

  1. 运行容器

    docker run nginx                    # 运行nginx容器
    docker run -d nginx                 # 后台运行
    docker run -p 8080:80 nginx         # 端口映射
    docker run --name mynginx nginx     # 指定容器名称
    docker run -it ubuntu bash          # 交互式运行
    docker run -e MYSQL_ROOT_PASSWORD=123456 mysql  # 设置环境变量
    docker run -v /host/data:/container/data nginx  # 挂载数据卷
    docker run --rm nginx               # 容器退出后自动删除
    
  2. 查看容器

    docker ps                           # 查看运行中的容器
    docker ps -a                        # 查看所有容器
    docker ps -q                        # 只显示容器ID
    docker ps --format "table {{.ID}}\\t{{.Names}}\\t{{.Status}}"
    
  3. 容器操作

    docker start container_id           # 启动容器
    docker stop container_id            # 停止容器
    docker restart container_id         # 重启容器
    docker pause container_id           # 暂停容器
    docker unpause container_id         # 恢复容器
    docker rm container_id              # 删除容器
    docker rm -f container_id           # 强制删除运行中的容器
    docker container prune              # 清理停止的容器
    
  4. 容器交互

    docker exec -it container_id bash   # 进入运行中的容器
    docker exec container_id ls /app    # 在容器中执行命令
    docker attach container_id          # 附加到容器
    docker cp file.txt container_id:/app/  # 复制文件到容器
    docker cp container_id:/app/file.txt ./  # 从容器复制文件
    
  5. 查看容器信息

    docker logs container_id            # 查看容器日志
    docker logs -f container_id         # 实时查看日志
    docker logs --tail 100 container_id # 查看最后100行
    docker top container_id             # 查看容器进程
    docker stats                        # 查看容器资源使用
    docker inspect container_id         # 查看容器详细信息
    

四、网络管理

  1. 网络操作

    docker network ls                   # 列出网络
    docker network create mynet         # 创建网络
    docker network inspect bridge       # 查看网络详情
    docker network rm mynet             # 删除网络
    
  2. 容器网络

    docker run --network=mynet nginx    # 指定网络运行容器
    docker network connect mynet container_id    # 连接容器到网络
    docker network disconnect mynet container_id  # 断开连接
    
  3. 端口映射

    docker run -p 8080:80 nginx         # 映射端口8080到容器80
    docker run -p 127.0.0.1:8080:80 nginx  # 只绑定本地地址
    docker run -P nginx                 # 随机映射端口
    docker port container_id            # 查看端口映射
    

五、数据卷管理

  1. 创建和管理数据卷

    docker volume create mydata         # 创建数据卷
    docker volume ls                    # 列出数据卷
    docker volume inspect mydata        # 查看数据卷详情
    docker volume rm mydata             # 删除数据卷
    docker volume prune                 # 清理未使用的数据卷
    
  2. 使用数据卷

    docker run -v mydata:/app nginx     # 挂载命名数据卷
    docker run -v /host/path:/container/path nginx  # 挂载主机目录
    docker run --mount source=mydata,target=/app nginx  # 使用--mount
    docker run -v $(pwd):/app nginx     # 挂载当前目录
    

六、Docker Compose

  1. 基本命令

    docker-compose up                   # 启动服务
    docker-compose up -d                # 后台启动
    docker-compose down                 # 停止并删除容器
    docker-compose ps                   # 查看服务状态
    docker-compose logs                 # 查看日志
    docker-compose logs -f service_name # 查看特定服务日志
    
  2. 服务管理

    docker-compose start                # 启动服务
    docker-compose stop                 # 停止服务
    docker-compose restart              # 重启服务
    docker-compose build                # 构建服务
    docker-compose pull                 # 拉取服务镜像
    
  3. docker-compose.yml示例

    version: '3'
    services:
      web:
        image: nginx:latest
        ports:
          - "80:80"
        volumes:
          - ./html:/usr/share/nginx/html
        networks:
          - webnet
    
      db:
        image: mysql:5.7
        environment:
          MYSQL_ROOT_PASSWORD: password
        volumes:
          - db_data:/var/lib/mysql
        networks:
          - webnet
    
    volumes:
      db_data:
    
    networks:
      webnet:
    

七、Dockerfile编写

  1. 基础指令

    # 基础镜像
    FROM node:14-alpine
    
    # 维护者信息
    LABEL maintainer="name@example.com"
    
    # 设置工作目录
    WORKDIR /app
    
    # 复制文件
    COPY package*.json ./
    COPY . .
    
    # 运行命令
    RUN npm install
    
    # 环境变量
    ENV NODE_ENV=production
    ENV PORT=3000
    
    # 暴露端口
    EXPOSE 3000
    
    # 启动命令
    CMD ["npm", "start"]
    
  2. 多阶段构建

    # 构建阶段
    FROM node:14 AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm install
    COPY . .
    RUN npm run build
    
    # 生产阶段
    FROM nginx:alpine
    COPY --from=builder /app/dist /usr/share/nginx/html
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    
  3. 最佳实践

    # 使用特定版本的基础镜像
    FROM node:14.17.0-alpine
    
    # 使用非root用户
    RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    USER appuser
    
    # 利用构建缓存
    COPY package*.json ./
    RUN npm install
    COPY . .
    
    # 减少层数
    RUN apt-get update && apt-get install -y \\
        package1 \\
        package2 \\
        && rm -rf /var/lib/apt/lists/*
    

八、镜像仓库操作

  1. 登录和推送

    docker login                        # 登录Docker Hub
    docker login registry.example.com   # 登录私有仓库
    docker tag myapp:1.0 username/myapp:1.0  # 标记镜像
    docker push username/myapp:1.0      # 推送镜像
    
  2. 私有仓库

    # 运行私有仓库
    docker run -d -p 5000:5000 --name registry registry:2
    
    # 标记并推送到私有仓库
    docker tag myapp:1.0 localhost:5000/myapp:1.0
    docker push localhost:5000/myapp:1.0
    
    # 从私有仓库拉取
    docker pull localhost:5000/myapp:1.0
    

九、容器资源限制

  1. CPU限制

    docker run --cpus=2 nginx           # 限制使用2个CPU
    docker run --cpu-shares=512 nginx   # CPU共享权重
    docker run --cpuset-cpus="0,1" nginx  # 指定CPU核心
    
  2. 内存限制

    docker run -m 512m nginx            # 限制内存512MB
    docker run --memory-swap=1g nginx   # 限制总内存(含swap)
    docker run --oom-kill-disable nginx # 禁用OOM killer
    
  3. 其他资源限制

    docker run --device-read-bps /dev/sda:1mb nginx  # 限制磁盘读取速度
    docker run --device-write-bps /dev/sda:1mb nginx # 限制磁盘写入速度
    docker run --pids-limit 100 nginx   # 限制进程数
    

十、Docker健康检查

  1. Dockerfile中定义

    HEALTHCHECK --interval=30s --timeout=3s \\
      CMD curl -f <http://localhost/> || exit 1
    
  2. 运行时指定

    docker run -d --health-cmd="curl -f  || exit 1" \\
               --health-interval=30s \\
               --health-timeout=3s \\
               --health-retries=3 \\
               nginx
    
  3. 查看健康状态

    docker inspect --format='{{.State.Health.Status}}' container_id
    docker ps --filter health=healthy
    

十一、日志管理

  1. 日志驱动配置

    # 配置JSON文件日志驱动
    docker run --log-driver=json-file \\
               --log-opt max-size=10m \\
               --log-opt max-file=3 \\
               nginx
    
    # 配置syslog日志驱动
    docker run --log-driver=syslog \\
               --log-opt syslog-address=tcp://192.168.0.42:123 \\
               nginx
    
  2. 查看日志

    docker logs container_id            # 查看容器日志
    docker logs -f --tail 100 container_id  # 实时查看最后100行
    docker logs --since 30m container_id    # 查看最近30分钟的日志
    

十二、清理和维护

  1. 系统清理

    docker system df                    # 查看Docker磁盘使用
    docker system prune                 # 清理未使用的数据
    docker system prune -a              # 清理所有未使用的数据
    docker system prune --volumes       # 同时清理数据卷
    
  2. 资源清理

    docker container prune              # 清理停止的容器
    docker image prune                  # 清理未使用的镜像
    docker volume prune                 # 清理未使用的数据卷
    docker network prune                # 清理未使用的网络
    

十三、故障排查

  1. 容器调试

    docker logs container_id            # 查看容器日志
    docker inspect container_id         # 查看容器详细信息
    docker exec -it container_id sh     # 进入容器shell
    docker diff container_id            # 查看容器文件变化
    docker events                       # 查看Docker事件
    
  2. 网络调试

    docker network inspect bridge       # 查看网络详情
    docker exec container_id ping other_container  # 测试容器间连通性
    docker exec container_id nslookup other_container  # DNS解析测试
    
  3. 性能分析

    docker stats                        # 实时查看资源使用
    docker top container_id             # 查看容器进程
    docker inspect -f '{{.State.Pid}}' container_id  # 获取容器PID
    

十四、安全最佳实践

  1. 运行时安全

    # 以只读模式运行容器
    docker run --read-only nginx
    
    # 限制容器capabilities
    docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
    
    # 使用非root用户运行
    docker run --user=1000:1000 nginx
    
  2. 镜像安全

    # 扫描镜像漏洞
    docker scan nginx:latest
    
    # 使用官方镜像或可信来源
    docker pull docker.io/library/nginx:latest
    
    # 签名验证
    docker trust inspect nginx:latest
    

十五、常用技巧和别名

  1. 实用别名

    # 添加到~/.bashrc或~/.zshrc
    alias dps='docker ps'
    alias dpsa='docker ps -a'
    alias di='docker images'
    alias drm='docker rm $(docker ps -aq)'
    alias drmi='docker rmi $(docker images -q)'
    alias dex='docker exec -it'
    alias dlog='docker logs -f'
    
  2. 常用组合命令

    # 停止所有容器
    docker stop $(docker ps -aq)
    
    # 删除所有停止的容器
    docker rm $(docker ps -aq -f status=exited)
    
    # 删除所有未打标签的镜像
    docker rmi $(docker images -f "dangling=true" -q)
    
    # 查看容器IP地址
    docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_id
    
    # 导出容器文件系统
    docker export container_id > container.tar
    

十六、Docker Swarm集群管理

  1. 初始化和加入集群

    # 初始化Swarm集群
    docker swarm init --advertise-addr 192.168.1.100
    
    # 获取加入token
    docker swarm join-token worker
    docker swarm join-token manager
    
    # 加入集群
    docker swarm join --token SWMTKN-1-xxx 192.168.1.100:2377
    
  2. 服务管理

    # 创建服务
    docker service create --name web --replicas 3 -p 80:80 nginx
    
    # 扩展服务
    docker service scale web=5
    
    # 更新服务
    docker service update --image nginx:1.21 web
    
    # 查看服务
    docker service ls
    docker service ps web
    

十七、实际应用示例

  1. Web应用部署

    # 创建网络
    docker network create webapp-net
    
    # 运行数据库
    docker run -d \\
      --name db \\
      --network webapp-net \\
      -e MYSQL_ROOT_PASSWORD=secret \\
      -e MYSQL_DATABASE=webapp \\
      -v mysql-data:/var/lib/mysql \\
      mysql:5.7
    
    # 运行Web应用
    docker run -d \\
      --name webapp \\
      --network webapp-net \\
      -p 8080:80 \\
      -e DB_HOST=db \\
      -e DB_USER=root \\
      -e DB_PASSWORD=secret \\
      -e DB_NAME=webapp \\
      webapp:latest
    
  2. 开发环境搭建

    # docker-compose.yml
    version: '3'
    services:
      frontend:
        build: ./frontend
        volumes:
          - ./frontend:/app
          - /app/node_modules
        ports:
          - "3000:3000"
        command: npm start
    
      backend:
        build: ./backend
        volumes:
          - ./backend:/app
        ports:
          - "5000:5000"
        environment:
          - NODE_ENV=development
        depends_on:
          - db
    
      db:
        image: postgres:13
        volumes:
          - pgdata:/var/lib/postgresql/data
        environment:
          - POSTGRES_PASSWORD=secret
          - POSTGRES_DB=myapp
    
    volumes:
      pgdata:
    
  3. CI/CD集成

    # Jenkinsfile示例
    pipeline {
      agent { docker { image 'node:14' } }
      stages {
        stage('Build') {
          steps {
            sh 'npm install'
            sh 'npm run build'
          }
        }
        stage('Test') {
          steps {
            sh 'npm test'
          }
        }
        stage('Docker Build') {
          steps {
            sh 'docker build -t myapp:${BUILD_NUMBER} .'
          }
        }
        stage('Deploy') {
          steps {
            sh 'docker push myapp:${BUILD_NUMBER}'
          }
        }
      }
    }
    

  目录