Docker基础
镜像与容器
Docker 镜像和容器的区别是什么?
一句话原理 Docker 镜像是静态的只读模板(相当于“类”或“安装包”),包含了运行应用所需的代码、库和环境;而容器是镜像的动态运行实例(相当于“对象”或“进程”),拥有独立的文件系统和资源隔离环境。
一句话源码
在操作层面,通过 docker build 或 docker pull 生成镜像,通过 docker run 将镜像实例化为容器,容器在镜像层之上叠加了一个可写的容器层。
一句话项目/场景 在 CI/CD 流水线中,开发人员构建出一个版本固定的 镜像,测试人员将该镜像部署到测试环境运行成 容器,运维人员再将同一镜像部署到生产环境,实现“一次构建,到处运行”。
详细区别对比
| 维度 | Docker 镜像 | Docker 容器 |
|---|---|---|
| 生命周期 | 静态,永久存储(除非手动删除) | 动态,可创建、启动、停止、销毁 |
| 面向对象类比 | 类 | 对象 |
| 物理文件类比 | 安装包 / 光盘 | 运行中的软件 / 正在播放的电影 |
| 读写权限 | 只读,不可修改 | 可读写,修改发生在最上层 |
| 关系 | 是容器的基础模板 | 是镜像的运行实例 |
| 存储结构 | 分层存储,多层共享,节省空间 | 在镜像层之上增加“容器层” |
如何构建一个 Docker 镜像?Dockerfile 中常见的指令有哪些(FROM、RUN、CMD、ENTRYPOINT、COPY、ADD 等)?
一句话原理 构建 Docker 镜像本质上是编写 Dockerfile 指令并调用 Docker 引擎逐层构建,Dockerfile 定义了从操作系统环境到应用代码的一系列文件系统变更。
一句话源码
执行 docker build -t my-app:v1 . 命令,解析当前目录下的 Dockerfile,依次执行 FROM 拉取基础镜像、COPY 导入代码、RUN 编译安装依赖,最后通过 CMD 或 ENTRYPOINT 定义启动命令。
一句话项目/场景 在“Java Spring Boot 应用打包”场景中,通常使用多阶段构建:第一阶段用 Maven 镜像编译出 Jar 包,第二阶段用 JRE 镜像仅运行该 Jar 包,最终生成一个仅包含运行环境的精简镜像。
Dockerfile 常见指令详解
1. 基础指令
- FROM:指定基础镜像,必须是第一条指令。
- 例如:
FROM openjdk:17或FROM ubuntu:20.04。
- 例如:
- LABEL / MAINTAINER:添加元数据,如作者信息。
2. 文件操作指令
- COPY:将本地文件复制到镜像中。
- 例如:
COPY target/app.jar /app/app.jar。
- 例如:
- ADD:功能类似 COPY,但额外支持自动解压 tar 包和支持 URL 下载。
- 注意:官方推荐优先使用 COPY,因为 ADD 行为不确定。
3. 执行命令指令
- RUN:在构建阶段执行命令,并提交结果(如安装软件),每个 RUN 都会创建一个新的镜像层。
- 例如:
RUN apt-get update && apt-get install -y vim。
- 例如:
- CMD:容器启动时默认执行的命令。
- 特点:如果
docker run后面跟了其他参数,CMD 会被覆盖。 - 例如:
CMD ["java", "-jar", "app.jar"]。
- 特点:如果
- ENTRYPOINT:配置容器为可执行程序。
- 特点:无法被
docker run的参数覆盖,docker run的参数会传给 ENTRYPOINT 作为参数。 - 例如:
ENTRYPOINT ["java", "-jar", "app.jar"]。
- 特点:无法被
4. 其他指令
- WORKDIR:设置工作目录,后续指令都在此目录下执行。
- 例如:
WORKDIR /app。
- 例如:
- EXPOSE:声明容器运行时的监听端口。
- 例如:
EXPOSE 8080(仅作文档声明,不实际映射)。
- 例如:
- ENV:设置环境变量。
- 例如:
ENV TZ=Asia/Shanghai。
- 例如:
CMD 和 ENTRYPOINT 的区别?如何组合使用?
一句话原理
CMD 是“默认参数”,可被 docker run 后的命令行参数直接覆盖;ENTRYPOINT 是“入口主命令”,会将 docker run 后的参数追加在后面执行。
一句话源码
在 Dockerfile 中,ENTRYPOINT 指定执行器(如 ["java"]),CMD 指定默认参数(如 ["-jar", "app.jar"]);运行时执行 docker run myimage -Xms512m,最终效果为 java -jar app.jar -Xms512m。
一句话项目/场景
在“构建通用 Python 脚本执行镜像”时,定义 ENTRYPOINT ["python"] 和 CMD ["script.py"],默认执行 python script.py;用户运行时可直接覆盖参数 docker run myimage other_script.py 来运行不同脚本,无需重新构建镜像。
详细区别与组合用法
1. 核心区别
| 指令 | 行为特征 | 是否容易被覆盖 | 典型用途 |
|---|---|---|---|
| CMD | 设置启动命令及默认参数 | 是,docker run 后的指令会完全替换 CMD | 设置默认行为,允许用户灵活修改 |
| ENTRYPOINT | 设置固定的主命令 | 否,docker run 后的指令会追加为参数 | 将容器作为可执行程序,固定入口 |
2. 两种指令的格式(Exec vs Shell)
- Exec 格式(推荐):JSON 数组形式
["executable", "param1"]。- 信号传递:能正确接收
SIGTERM信号(Ctrl+C),利于优雅停机。
- 信号传递:能正确接收
- Shell 格式:直接写命令
command param1。- 实际执行为
/bin/sh -c "command param1",可能屏蔽信号。
- 实际执行为
3. 组合使用的最佳实践
场景:制作一个启动 Java 应用的通用镜像
我们希望容器像一个可执行程序一样,用户可以动态传入 JVM 参数。
Dockerfile 编写:
运行效果演示:
默认运行:
docker run myapp- 执行结果:
java -jar app.jar(ENTRYPOINT + CMD)
- 执行结果:
传递参数:
docker run myapp app.jar --server.port=8081- 执行结果:
java -jar app.jar --server.port=8081(ENTRYPOINT + run参数) - 解释:run 参数追加到了 ENTRYPOINT 后面。
- 执行结果:
动态调试:
docker run --entrypoint bash myapp -c "ls /app"- 执行结果:进入容器内部查看文件。
- 解释:使用
--entrypoint标志可以强制覆盖 ENTRYPOINT。
4. 总结
- 如果希望容器作为一个固定的命令行工具(如
docker run myimage --help),使用 ENTRYPOINT。 - 如果希望容器仅运行默认任务,且允许用户轻松换成其他命令,使用 CMD。
- 生产环境推荐:
ENTRYPOINT+CMD组合,既固定了执行环境,又提供了默认配置灵活性。
如何减少 Docker 镜像的大小?(多阶段构建、使用轻量级基础镜像、清理缓存)
一句话原理 减小 Docker 镜像大小的核心在于“做减法”:选择最小化基础环境、利用多阶段构建丢弃编译依赖、清理中间产物、并减少文件系统层数。
一句话源码
在 Dockerfile 中,使用 FROM alpine 替代 ubuntu,通过 COPY --from=builder 复制仅需要的二进制文件,并利用 && 连接命令清理 /var/cache 等临时文件。
一句话项目/场景
在“Go 语言应用构建”中,第一阶段使用 golang:1.20 镜像(约 1GB)编译出二进制文件,第二阶段使用 alpine 镜像(约 5MB)运行该文件,最终镜像大小从 1GB 骤降至 10MB 左右。
详细优化策略
使用轻量级基础镜像
这是最直接的优化方式。
- Alpine Linux:基于 musl libc 和 busybox,镜像通常小于 5MB。
- 注意:Alpine 使用 musl libc,而主流 Linux 使用 glibc,可能导致某些依赖 glibc 的应用(如某些 Python 包或 Java 应用)出现兼容性问题。Java 推荐使用
openjdk:alpine或eclipse-temurin:17-jre-alpine。
- 注意:Alpine 使用 musl libc,而主流 Linux 使用 glibc,可能导致某些依赖 glibc 的应用(如某些 Python 包或 Java 应用)出现兼容性问题。Java 推荐使用
- Distroless:Google 出品,仅包含应用运行时必需的库,不包含 Shell、包管理器等,安全性极高,容量极小。
- 注意:无法使用
docker exec -it <container> sh进入容器调试。
- 注意:无法使用
- Scratch:空镜像,大小为 0 字节。适合静态编译的语言(如 Go、C++),直接将编译好的二进制文件放入 Scratch 镜像中。
多阶段构建—— 最推荐方案
将构建环境和运行环境分离。
- 原理:在第一阶段使用完整的 SDK(如 Maven, Golang)编译代码,在第二阶段仅将编译产物拷贝到精简的运行环境中。
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13# 第一阶段:构建环境 FROM maven:3.8.6-openjdk-18 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # 第二阶段:运行环境 FROM openjdk:17-jdk-alpine WORKDIR /app # 仅从第一阶段拷贝 jar 包 COPY --from=builder /app/target/app.jar . CMD ["java", "-jar", "app.jar"]
合并指令与清理缓存
Docker 使用分层存储,每一个指令都会创建一层。
- 合并 RUN 指令:将多个
RUN命令合并为一个,减少层数。 - 清理缓存:在安装软件包的同一层中删除缓存文件,防止缓存被“封存”在镜像层中。
- 错误示例:
- 正确示例:
利用 .dockerignore 排除无关文件
类似于 .gitignore,在构建镜像时忽略不需要的文件,防止这些文件被 COPY 或 ADD 指令意外打包进上下文。
- 配置示例:
其他技巧
- 最小化安装:只安装必要的依赖包,避免安装推荐包(如
apt-get install --no-install-recommends)。 - 层级优化:将变化最频繁的指令(如
COPY . .)放在 Dockerfile 的最后面,利用构建缓存加速构建并减少层变动。
容器生命周期
容器的生命周期有哪些状态?
一句话原理 Docker 容器的生命周期从创建到销毁,核心状态流转为:Created(已创建) -> Running(运行中) -> Paused(暂停) -> Stopped(已停止) -> Deleted(已删除)。
一句话源码
通过 API 或 CLI 触发状态流转:docker create 创建、docker start 启动、docker pause 暂停、docker stop 停止、docker rm 销毁。
一句话项目/场景 在“Kubernetes Pod 重启”场景中,当容器内的应用进程异常退出时,状态由 Running 变为 Exited,Kubelet 监测到后触发重启策略,将状态重置回 Running,实现服务自愈。
详细状态解析与流转图
核心状态详解
| 状态 | 英文名称 | 说明 | 触发命令 |
|---|---|---|---|
| 已创建 | Created | 文件系统已准备,进程未启动。 | docker create / docker run (第一阶段) |
| 运行中 | Running | 容器内至少有一个进程在运行。 | docker start / docker run |
| 已暂停 | Paused | 暂停容器内的所有进程(冻结 CPU)。 | docker pause |
| 已停止 | Stopped / Exited | 进程终止,文件系统保留。 | docker stop / docker kill / 进程自然退出 |
| 已删除 | Deleted | 容器从 Docker 宿主机中彻底移除。 | docker rm |
状态流转全景图
容易混淆的细节
Created vs Stopped:
Created是新建状态,从未运行过。Stopped是运行过后的终止状态。- 两者都可以通过
docker start变为 Running。
Pause vs Stop:
- Pause:冻结进程,内存数据保留,恢复极快,不释放内存。
- Stop:发送 SIGTERM 信号,进程退出,释放内存,恢复需重新启动进程。
Kill vs Stop:
docker stop:优雅停止,先发 SIGTERM,给进程清理资源的时间,超时再发 SIGKILL。docker kill:强制停止,直接发送 SIGKILL,立即终止进程。
docker run 和 docker start 的区别?
一句话原理
docker run 是**“从无到有”的创建并启动流程(组合命令),而 docker start 是“起死回生”**的启动流程,针对已存在但停止的容器。
一句话源码
docker run 等价于 docker create + docker start,每次执行都会生成一个新的容器 ID;docker start 仅修改已有容器的状态字段,不改变容器 ID。
一句话项目/场景
在“部署新版本应用”时使用 docker run 启动新实例;在“服务器重启后恢复服务”或“排查问题临时停止后恢复”时,使用 docker start 重启旧容器。
详细区别对比
| 维度 | docker run | docker start |
|---|---|---|
| 作用对象 | 镜像 | 已存在的容器 |
| 核心动作 | 创建新容器 + 启动 | 启动已停止的容器 |
| 执行次数 | 可多次执行,每次产生新容器 | 针对同一容器,通常只执行一次 |
| 参数支持 | 支持大量参数(-p, -v, -e, –name 等) | 参数极少,通常只跟容器 ID/名称 |
| 数据持久化 | 如果不挂载卷,新容器是全新的 | 容器内的文件系统数据保持上次停止时的状态 |
底层逻辑拆解
1. docker run 的本质
它是“复合操作”,幕后执行了两个步骤:
- 创建容器:根据镜像构建文件系统层,生成容器 ID。
- 对应命令:
docker create --name my-nginx nginx:latest
- 对应命令:
- 启动容器:启动容器内的进程。
- 对应命令:
docker start my-nginx
- 对应命令:
2. docker start 的本质
它是“状态流转操作”:
- 检查容器状态是否为
Exited或Created。 - 调用底层 containerd 启动容器进程。
- 状态变更为
Running。
常见误区与最佳实践
- 误区:想重启服务,却反复执行
docker run。- 后果:导致服务器上堆积大量同名(或自动命名)的
Exited容器,端口冲突报错port is already allocated。
- 后果:导致服务器上堆积大量同名(或自动命名)的
- 正确姿势:
- 首次部署:
docker run -d --name myapp ... - 后续重启:
docker start myapp - 常用重启命令:
docker restart myapp(相当于 stop + start)。
- 首次部署:
如何进入运行中的容器?docker exec 和 docker attach 的区别?
一句话原理
docker exec 是在容器内新开一个进程(常用),适合运维调试;docker attach 是连接到容器的主进程(标准输入输出),适合查看日志,但退出时可能导致容器停止。
一句话源码
执行 docker exec -it <ID> /bin/bash 启动新的 Shell 会话;执行 docker attach <ID> 将当前终端输入流绑定到容器 PID 1 的 stdin。
一句话项目/场景
在“排查线上 Java 应用 CPU 飙高”问题时,使用 docker exec 进入容器执行 top 或 jstack 命令;在“调试启动报错”时,使用 docker attach 查看容器主进程实时打印的标准输出日志。
详细区别对比
| 维度 | docker exec | docker attach |
|---|---|---|
| 进程模型 | 新建进程:在容器内启动一个新的 Bash 或 Shell。 | 连接主进程:直接接入容器当前的 PID 1 进程。 |
| 多窗口支持 | 支持:多个终端 exec 进入互不影响。 | 不支持:多个终端 attach 看到的是同一个屏幕,操作会同步显示。 |
| 退出影响 | 安全:退出当前 Shell 不影响容器主进程。 | 危险:如果输入 exit 或 Ctrl+C,会直接终止容器主进程,导致容器停止。 |
| 典型用途 | 进入容器修改配置、查看文件、执行命令。 | 实时查看容器标准输出日志(仅读场景)。 |
实操指南
1. 进入容器(推荐方式)
通常使用 docker exec 进入容器,参数 -it 表示开启交互式终端:
2. attach 的正确用法与避坑
如果不小心使用了 docker attach,想要退出但不停止容器:
- 错误操作:
exit或Ctrl+C-> 容器停止。 - 正确操作:按
Ctrl + P,然后按Ctrl + Q-> 分离终端,容器继续运行。
3. 为什么要小心 attach?
Docker 容器的生命周期绑定在主进程(PID 1)上。attach 将你的终端输入直接传给了主进程。如果你发出了中断信号(如 Ctrl+C),主进程会捕获该信号并终止,从而导致容器停止。
容器退出后,数据会丢失吗?如何持久化容器数据?
一句话原理 容器退出后,只要容器未被删除,写在容器内的数据就不会丢失;但一旦容器被销毁,数据即消失。持久化的核心是将容器内的目录与宿主机目录进行挂载绑定。
一句话源码
通过 -v 或 --mount 参数将宿主机路径映射到容器内路径:docker run -v /host/data:/container/data ...,此时数据实际存储在宿主机文件系统中。
一句话项目/场景
在“MySQL 数据库容器化”场景中,必须挂载 /var/lib/mysql 目录到宿主机,否则容器重建后数据库表结构和数据将全部清空。
详细解析
1. 容器退出 vs 容器删除
很多初学者会混淆这两个概念:
- 容器退出:数据还在容器的可读写层中,重新启动容器,数据依然存在。
- 容器删除:Docker 容器的设计理念是“ ephemeral ”(临时的),删除容器会清理其文件系统,数据会永久丢失。
2. 数据持久化方案
主要有三种方式实现数据持久化:
| 方案 | 命令格式 | 特点 | 适用场景 |
|---|---|---|---|
| Bind Mount (绑定挂载) | -v /宿主机路径:/容器路径 | 直接映射宿主机目录,最常用,性能最好。 | 生产环境、配置文件、日志存储。 |
| Volume (数据卷) | -v 卷名:/容器路径 | Docker 管理的存储区域(通常在 /var/lib/docker/volumes),与宿主机路径解耦。 | 多容器共享数据、数据迁移、备份。 |
| tmpfs Mount (临时挂载) | --tmpfs /容器路径 | 数据存储在内存中,容器停止即消失。 | 敏感数据、缓存、高性能临时存储。 |
3. 实战命令示例
场景:启动 Nginx 并持久化网页目录
方式一:指定宿主机路径(推荐开发使用)
- 效果:宿主机
/usr/local/nginx/html目录下的文件会同步到容器内,修改即时生效。
- 效果:宿主机
方式二:使用 Docker Volume(推荐生产使用)
- 效果:数据存储在 Docker 管理的区域,即使删除容器,数据卷
mydata依然存在。
- 效果:数据存储在 Docker 管理的区域,即使删除容器,数据卷
4. 进阶技巧:数据卷容器
如果多个容器需要共享同一份数据,可以使用“数据卷容器”模式:
- 创建一个专门用于挂载数据的容器(甚至不需要运行)。
- 其他容器通过
--volumes-from参数共享其挂载点。
| |
网络与存储
Docker 的网络模式有哪些?(bridge、host、none、container)
一句话原理 Docker 网络模式决定了容器如何与外界通信:Bridge(默认虚拟网桥,隔离性好)、Host(共享宿主机网络,性能最高)、None(无网络,完全隔离)、Container(共享其他容器网络,进程间通信)。
一句话源码
通过 docker run --network <mode> 指定模式,默认为 bridge;host 模式下容器不再拥有独立 IP,直接绑定宿主机端口。
一句话项目/场景 在“部署 Nginx 反向代理”时使用 Bridge 模式进行端口映射;在“高性能 Web 服务”或“网络监控工具”中使用 Host 模式避免 NAT 性能损耗;在“微服务 Sidecar 代理”架构中使用 Container 模式实现本地回环通信。
详细模式解析
1. Bridge 模式(默认模式)
- 原理:
Docker 启动时会在宿主机创建一个虚拟网桥
docker0,容器启动时像“插网线”一样连接到这个网桥上。容器拥有独立的 IP(如 172.17.0.x)。 - 特点:
- 隔离性强,容器间可以通过虚拟 IP 互访。
- 外部无法直接访问容器,必须通过
-p参数做端口映射。
- 适用场景: 大多数常规应用,如 Web 服务、数据库等。
2. Host 模式
- 原理: 容器与宿主机共享网络命名空间。容器没有独立的 IP 和网卡,直接使用宿主机的 IP 和端口。
- 特点:
- 性能最好:没有 NAT 转换带来的性能损耗。
- 端口冲突风险:如果容器监听了 80 端口,宿主机的 80 端口也被占用。
- 适用场景: 对网络性能要求极高的应用,或需要直接操作宿主机网络的工具(如监控 Agent)。
3. None 模式
- 原理: 容器拥有独立的网络命名空间,但不配置任何网络设备(只有 lo 回环网卡)。
- 特点:
- 完全封闭,无法联网。
- 适用场景: 安全性要求极高的数据处理,或需要自定义网络配置的特殊场景。
4. Container 模式
- 原理: 新创建的容器共享已存在容器的网络命名空间(IP、端口、路由表)。
- 特点:
- 两个容器就像在同一台机器上运行,可以通过
localhost互相访问。
- 两个容器就像在同一台机器上运行,可以通过
- 适用场景: Kubernetes Pod 的实现原理。Pod 内的所有容器共享同一个 Pause 容器的网络,实现共享 IP 和通信。
网络模式对比表
| 模式 | 是否有独立 IP | 网络性能 | 端口冲突 | 典型用途 |
|---|---|---|---|---|
| Bridge | 是 | 中 (有 NAT) | 无 | 默认通用场景 |
| Host | 否 (共享宿主机) | 高 | 有 | 高并发、监控工具 |
| None | 是 | - | 无 | 安全隔离、冷数据 |
| Container | 否 (共享其他容器) | 高 | 有 | Pod 内通信、Sidecar |
如何实现容器之间的通信?
一句话原理 容器间通信主要依赖 Docker 的虚拟网络设备,通过 默认 Bridge 网桥(基于 IP 通信)或 自定义 Bridge 网络(基于容器名通信,推荐)实现数据链路层的互联互通。
一句话源码
创建自定义网络 docker network create my-net,启动容器时指定 --network my-net,容器 A 即可通过 ping 容器B名称 直接访问容器 B。
一句话项目/场景
在“微服务架构”中,将订单服务和数据库服务加入同一个自定义网络,订单服务只需配置数据库地址为 mysql-container:3306,无需关心具体的容器 IP,实现服务解耦。
详细通信方式解析
1. 自定义 Bridge 网络(最推荐)
这是生产环境最常用的方式。
- 原理: 用户创建一个独立的虚拟网桥,连接在该网桥上的容器可以自动解析彼此的容器名称(DNS 解析)。
- 优势:
- 容器名通信:直接用容器名访问,不用记 IP。
- 隔离性:不同自定义网络之间的容器默认隔离,安全性高。
- 操作步骤:
2. 默认 Bridge 网络
Docker 安装后默认创建的 docker0 网桥。
- 原理:
所有未指定
--network的容器都会挂载到这里,容器间可以通过内网 IP 互访。 - 缺点:
- 不支持容器名解析:只能通过 IP 访问(如
ping 172.17.0.3),IP 变化会导致服务中断。 - 缺乏隔离:所有容器都在同一个网络,容易产生端口冲突或安全问题。
- 不支持容器名解析:只能通过 IP 访问(如
- 旧方案:
早期使用
--link参数来打通容器名映射,但该参数已被官方废弃,不推荐使用。
3. Host 模式通信
- 原理:
多个容器都使用
--network host模式,它们共享宿主机的网络栈。 - 通信方式:
就像在同一台物理机上运行多个进程,通过
localhost或127.0.0.1直接访问彼此监听的端口。
4. Container 模式通信
- 原理: 新容器共享已存在容器的网络命名空间。
- 通信方式:
两者共享网卡和 IP,通过
localhost通信。 - 场景: 适用于 Sidecar 模式(如一个容器跑业务,另一个容器做日志收集代理)。
5. Joined Network(多网络连接)
一个容器可以同时加入多个网络。
- 场景:数据库容器连接在业务网络(供业务访问)和管理网络(供运维工具备份)。
总结对比
| 方式 | 访问方式 | 隔离性 | 推荐指数 | 适用场景 |
|---|---|---|---|---|
| 自定义 Bridge | 容器名 (DNS 自动解析) | 高 | ⭐⭐⭐⭐⭐ | 生产环境、微服务集群 |
| 默认 Bridge | 容器 IP | 无 | ⭐ | 简单测试、单机开发 |
| Host | localhost | 无 | ⭐⭐⭐ | 高性能网络、网络监控工具 |
| Container | localhost | 弱 | ⭐⭐ | Sidecar 模式 |
什么是数据卷(Volume)?和绑定挂载(bind mount)的区别?
一句话原理 数据卷 是 Docker 管理的存储区域,与宿主机文件系统解耦;绑定挂载 是直接将宿主机的特定目录映射到容器中,强依赖宿主机路径。
一句话源码
数据卷通过 docker volume create 创建,挂载时使用 -v volume_name:/path;绑定挂载直接使用 -v /host/path:/container/path。
一句话项目/场景 在“开发环境代码热更新”场景中,使用 绑定挂载 将本地源码目录挂载进容器,修改即时生效;在“生产环境数据库存储”场景中,使用 数据卷 确保数据存储位置标准化且易于备份迁移。
详细区别对比
| 维度 | 数据卷 | 绑定挂载 |
|---|---|---|
| 管理权 | Docker 管理 | 用户管理 |
| 存储位置 | 默认在 /var/lib/docker/volumes/ 下,路径固定。 | 可以是宿主机任意路径(如 /home/user/data)。 |
| 路径依赖 | 解耦:不依赖宿主机特定目录,移植性强。 | 耦合:Dockerfile 或 Compose 文件中写死宿主机路径,移植困难。 |
| 初始化行为 | 若容器挂载空卷,Docker 会自动将容器内目录数据复制到卷中。 | 若挂载空目录,容器内目录原有数据会被覆盖或保持空(不会自动复制)。 |
| I/O 性能 | 极高(尤其在 Mac/Windows 的 Docker Desktop 中优化明显)。 | 较高,但在 Mac/Windows 上跨文件系统性能开销较大。 |
| 安全性 | 只能由 Docker 容器或 Docker CLI 访问,较安全。 | 任何宿主机进程都能访问修改,灵活但风险稍高。 |
| 适用场景 | 生产环境数据、持久化数据库、共享数据、备份迁移。 | 开发环境代码同步、配置文件挂载。 |
实战命令对比
1. 数据卷操作
2. 绑定挂载操作
深度解析:初始化行为的坑
这是面试和实操中经常遇到的坑:
- 场景:镜像内
/app/config.ini已经存在配置文件,你想挂载一个卷来覆盖或修改它。 - Bind Mount:如果宿主机目录是空的,挂载后容器内的
/app/config.ini会消失(被空的宿主机目录覆盖)。这叫“覆盖挂载”。 - Volume:如果数据卷是空的,Docker 会将容器内
/app/config.ini复制到数据卷中,容器内依然能看到文件。这叫“初始化填充”。
最佳实践总结
- 生产环境:优先使用 Volumes。便于备份、迁移,且不依赖宿主机文件结构。
- 开发环境:优先使用 Bind Mounts。方便 IDE 直接修改代码,实现容器内应用热加载。
- 配置文件:通常使用 Bind Mounts 或 Configs(Swarm 模式),方便运维人员直接在宿主机修改配置。
如何备份、恢复或迁移数据卷?
一句话原理
数据卷的备份与恢复本质上是利用一个临时容器,挂载目标数据卷,并通过 tar 命令将数据在“卷”与“宿主机归档文件”之间进行打包或解包操作。
一句话源码
备份:docker run --rm -v 数据卷名:/source -v $(pwd):/backup busybox tar cvf /backup/backup.tar /source;
恢复:docker run --rm -v 数据卷名:/target -v $(pwd):/backup busybox tar xvf /backup/backup.tar -C /target。
一句话项目/场景
在“MySQL 容器迁移”场景中,先停止数据库容器防止数据写入,启动临时容器将 /var/lib/mysql 卷打包成 db_backup.tar 下载到本地,随后在新的服务器上创建新卷并解包恢复。
详细操作步骤
1. 备份数据卷
假设我们有一个名为 my-data 的数据卷,需要备份为当前目录下的 backup.tar。
操作命令:
- 解析:
--rm:容器执行完命令后自动删除,保持环境干净。busybox:包含基础 Linux 命令(如 tar)的最小镜像。- 执行后,当前目录下会生成
backup.tar文件。
2. 恢复/迁移数据卷
假设要在新的服务器或新的数据卷 new-data 中恢复数据。
第一步:创建目标数据卷(如果不存在)
| |
第二步:执行恢复命令
- 关键参数解析:
-C /target:指定解压目录为容器内的挂载点。--strip-components=1:重要。备份时 tar 打包的是/source目录本身,解压时会多出一层目录结构(如/target/source/...),此参数用于去掉第一层目录(source),直接将内容解压到/target根下。
3. 迁移至另一台服务器
结合 scp 或 rsync 命令即可完成跨主机迁移:
- 源主机:执行备份操作生成
backup.tar。 - 传输:
scp backup.tar user@remote-host:/home/user/。 - 目标主机:执行恢复操作将数据注入新容器或新数据卷。
总结
数据卷位于 Docker 的特定管理目录下(通常 /var/lib/docker/volumes/),直接操作文件系统风险较高且路径难找。“临时容器 + tar 命令” 是官方推荐的标准通用方案,既安全又具备跨平台兼容性。