Docker 高频面试题
Docker 高频面试题
简介
Docker 是目前最主流的容器化技术,通过将应用及其依赖打包到轻量级容器中,实现了"一次构建,到处运行"的目标。本文整理了 Docker 面试中高频出现的问题,涵盖镜像管理、容器操作、Dockerfile 编写、Docker Compose 编排、网络模型和数据卷等核心知识点,帮助开发者全面准备 Docker 相关面试。这些问题不仅考察基础概念,更关注在真实项目中的实践经验和对容器化架构的理解深度。
特点
镜像相关
1. 什么是 Docker 镜像?镜像的分层结构是怎样的?
Docker 镜像是一个只读的模板,包含了创建容器所需的文件系统层和配置信息。镜像采用联合文件系统(UnionFS)的分层存储结构,每一层代表一个文件系统变更指令(如添加文件、安装软件包等)。
分层结构的优势在于:不同镜像可以共享相同的层,大幅节省磁盘空间和传输带宽。例如 ubuntu:20.04 和 ubuntu:22.04 共享相同的基础层,只有差异部分需要额外存储。
镜像的最底层是基础镜像(Base Image),上层通过增量的方式叠加修改。每一层都有一个唯一的 SHA256 哈希值,Docker 通过这些哈希值来管理和复用层。
理解分层结构对优化 Dockerfile 至关重要:合理利用缓存可以大幅加快构建速度;合并指令可以减少层数;多阶段构建可以减小最终镜像体积。
2. Docker 镜像和容器的关系是什么?
镜像是静态的只读模板,容器是镜像的可运行实例。可以通过一个镜像创建多个容器,每个容器拥有独立的可写层(Container Layer)。
容器运行时,文件修改发生在可写层,删除容器后可写层也被清除。如果需要持久化容器中的修改,可以使用 docker commit 命令将容器的可写层固化为一层新的镜像。但生产环境中更推荐使用 Dockerfile 来构建镜像,因为 Dockerfile 提供了可重复、可审计的构建流程。
容器 = 镜像 + 可写层 + 运行配置(环境变量、端口映射、挂载卷等)。
3. 如何减小 Docker 镜像的体积?
减小镜像体积是 Docker 实践中的核心优化点,直接影响到镜像传输速度和部署效率。
常用方法包括:
选择更小的基础镜像:alpine(约 5MB)替代 ubuntu(约 77MB);scratch(空镜像,约 0MB)适用于 Go/Rust 等静态编译语言。
使用多阶段构建(multi-stage build):只在最终镜像中保留运行时所需的文件,编译工具和中间产物留在构建阶段。
# 构建阶段
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /publish
# 运行阶段 — 只包含运行时和编译产物
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime
WORKDIR /app
COPY --from=build /publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]合并 RUN 指令并清理缓存:每条 RUN 指令都会创建一个新的层。合并多条指令并清理不需要的文件可以减少层数和体积。
RUN apt-get update && apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean使用 .dockerignore:排除不必要的文件(如 .git、node_modules、build artifacts)。
避免安装调试工具:生产镜像中不需要 vim、curl 等调试工具。
4. Docker 镜像的构建缓存机制是怎样的?
Docker 构建 Dockerfile 时会逐条执行指令,每条指令生成一个镜像层。如果某条指令及其之前的所有指令都没有变化,Docker 会使用缓存而不重新构建。
一旦某条指令发生变化(如 COPY 的文件内容改变),该指令及后续所有指令的缓存都将失效。
因此建议将变化频率低的指令放在 Dockerfile 前面(如安装系统包),变化频率高的指令放在后面(如拷贝源代码),以最大化利用缓存。
# 好:先安装依赖(变化少),再拷贝源码(变化多)
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# 差:先拷贝所有文件,每次代码变动都会使 npm install 缓存失效
COPY . .
RUN npm install容器相关
5. Docker 容器的生命周期是怎样的?
Docker 容器有以下几种状态:
Created:已创建但未启动,可以通过 docker create 创建后手动 docker start。
Running:运行中,容器内的主进程正在执行。
Paused:暂停状态,使用 cgroups 的 freezer 冻结容器内所有进程。进程不会继续执行但不会终止,内存状态保持。通过 docker pause/unpause 切换。
Stopped:已停止,容器内的主进程已退出。通过 docker stop 优雅停止(发送 SIGTERM,等待 10 秒后发送 SIGKILL)或 docker kill 强制停止(直接发送 SIGKILL)。
Deleted:已删除,通过 docker rm 移除已停止的容器。
关键操作:
docker stop发送 SIGTERM 信号,给容器内的应用优雅关闭的机会(如完成正在处理的请求、关闭数据库连接)docker kill直接发送 SIGKILL,立即终止,可能导致数据不一致docker restart重启容器,配置了RestartPolicy的容器可以自动重启
6. 容器退出后数据会丢失吗?如何持久化数据?
默认情况下容器退出后,其可写层的数据会随容器删除而丢失。持久化数据的方式主要有三种:
Docker Volume(docker volume create):由 Docker 引擎管理存储在 /var/lib/docker/volumes/ 下。优势:Docker 统一管理,支持多种存储驱动,跨平台兼容性好。生产环境推荐使用。
Bind Mount(-v /host/path:/container/path):直接将宿主机目录挂载到容器中。优势:开发环境方便(修改宿主机文件直接反映到容器中),可以访问宿主机上的任意目录。缺点:依赖宿主机路径,可移植性差。
tmpfs Mount:数据存储在内存中,适用于临时敏感数据(如密码、密钥)。容器停止后数据丢失。
# Docker Volume
docker volume create mydata
docker run -v mydata:/data myimage
# Bind Mount
docker run -v /home/user/data:/data myimage
# tmpfs
docker run --tmpfs /tmp:rw,size=100m myimage7. 如何查看容器的日志和资源使用情况?
查看容器日志使用 docker logs 命令:
-f:实时跟踪日志输出--tail N:显示最后 N 行--since:显示指定时间之后的日志--timestamps:显示时间戳--until:显示指定时间之前的日志
# 实时查看最后 100 行日志
docker logs -f --tail 100 mycontainer
# 查看最近 1 小时的日志
docker logs --since 1h mycontainer查看容器资源使用情况使用 docker stats 命令,可以实时显示 CPU、内存、网络 I/O 和磁盘 I/O 的使用数据。添加 --no-stream 参数只显示一次快照。
还可以通过 docker top 查看容器内的进程列表,docker inspect 获取容器的详细配置信息(JSON 格式),docker port 查看端口映射。
8. 如何进入正在运行的容器进行调试?
主要有三种方式:
docker exec -it container_id /bin/bash:最常用的方式,在容器内启动一个新的 bash 进程。退出后不影响容器运行(因为它是新进程,不是容器的主进程)。如果容器中没有 bash(如 alpine 镜像),使用 /bin/sh。
docker attach container_id:连接到容器的主进程(PID 1),共享标准输入输出。使用 Ctrl+P, Ctrl+Q 可以退出而不停止容器。注意:attach 时如果按 Ctrl+C 会直接终止容器。
docker cp:在容器和宿主机之间复制文件,无需进入容器。适合快速查看配置文件或提取日志。
# 方式1:exec(推荐)
docker exec -it mycontainer /bin/bash
# 方式2:attach
docker attach mycontainer
# 退出不停止: Ctrl+P, Ctrl+Q
# 方式3:复制文件
docker cp mycontainer:/etc/nginx/nginx.conf ./nginx.confDockerfile 相关
9. Dockerfile 中 CMD 和 ENTRYPOINT 的区别是什么?
CMD 指定容器启动时的默认执行命令,可以被 docker run 的命令行参数完全覆盖。一个 Dockerfile 中只有最后一条 CMD 生效。
ENTRYPOINT 指定容器的入口程序,不会被 docker run 的命令行参数覆盖,而是将命令行参数追加到 ENTRYPOINT 之后。
两者搭配使用时,ENTRYPOINT 定义可执行程序,CMD 提供默认参数:
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# docker run myimage -> nginx -g daemon off;
# docker run myimage -t -> nginx -t (CMD 被覆盖,但 ENTRYPOINT 不变)使用 docker run --entrypoint 可以覆盖 ENTRYPOINT,但一般不建议这样做。
选择建议:如果镜像作为可执行程序(如 nginx、python),使用 ENTRYPOINT;如果镜像作为运行环境(如 ubuntu、python 基础镜像),使用 CMD。
10. Dockerfile 中 COPY 和 ADD 指令有什么区别?
COPY 是最基本的文件复制指令,将宿主机文件或目录复制到镜像中,语义清晰明确。只支持从构建上下文中复制文件。
ADD 在 COPY 的基础上增加了两个功能:
- 如果源文件是 tar 压缩包(gzip、bzip2、xz),会自动解压到目标目录
- 源路径支持 URL,可以从远程下载文件
由于 ADD 的隐式行为可能带来不可预期的结果(如远程文件内容变化导致构建不可复现),Docker 官方推荐优先使用 COPY,仅在需要自动解压 tar 包时使用 ADD。
# 推荐:使用 COPY
COPY app.js /app/
# 需要 ADD 的场景:自动解压
ADD archive.tar.gz /app/
# 不推荐:ADD 从 URL 下载(应该用 curl/wget + RUN)
ADD http://example.com/file.txt /app/11. 什么是多阶段构建(Multi-stage Build)?有什么优势?
多阶段构建允许在一个 Dockerfile 中使用多个 FROM 指令,每个 FROM 开始一个新的构建阶段。通过 COPY --from=阶段名 可以将前一阶段的产物复制到后一阶段。
核心优势:
- 构建阶段可以使用完整的编译环境(如包含 SDK、编译器、构建工具)
- 最终镜像只需要包含运行时环境和编译产物
- 大幅减小镜像体积(从数百 MB 减少到几十 MB 甚至几 MB)
例如一个 .NET 应用可以在构建阶段使用 mcr.microsoft.com/dotnet/sdk 镜像(约 700MB)编译,运行阶段使用 mcr.microsoft.com/dotnet/aspnet:8.0-alpine 镜像(约 90MB),最终镜像体积减少约 85%。
多阶段构建也适用于前端项目(Node.js 编译 -> Nginx 运行)、Go/Rust 项目(编译 -> 静态二进制 -> scratch 空镜像)。
Docker Compose 相关
12. Docker Compose 的作用是什么?
Docker Compose 是用于定义和运行多容器 Docker 应用的工具。通过一个 docker-compose.yml 文件声明式地配置应用所需的所有服务(容器)、网络和卷。
使用 docker-compose up -d 一条命令即可按照依赖顺序启动所有服务。
Compose 支持的功能:服务编排(多容器协作)、环境变量管理(.env 文件)、健康检查(healthcheck)、资源限制(deploy.resources)、依赖关系(depends_on)、网络配置(自定义网络)、数据卷管理、日志配置等。
Compose 是开发和测试环境中最常用的容器编排工具,但在生产环境中通常使用 Kubernetes 或 Docker Swarm。
13. docker-compose.yml 中 depends_on 和 links 的区别?
depends_on 表示服务间的启动依赖关系,控制容器启动顺序,并支持条件检查:
condition: service_started(默认,服务已启动)condition: service_healthy(服务健康检查通过)condition: service_completed_successfully(服务成功执行完成)
links 是旧版功能,除了建立启动依赖外还会在 /etc/hosts 中添加主机名映射,并设置环境变量。
Docker 官方已不推荐使用 links,建议使用自定义网络(networks)实现服务发现(通过服务名直接访问其他容器),使用 depends_on 管理启动顺序。
14. 如何在 Docker Compose 中实现服务的滚动更新?
可以通过 docker-compose up -d --no-deps --build service_name 实现单个服务的更新而不影响其他服务。
对于生产环境的滚动更新,建议使用 Docker Swarm 或 Kubernetes。在 Docker Compose 中可以配合 deploy.update_config 配置滚动更新策略(仅在使用 docker stack deploy 时生效),包括:
parallelism(并行更新数量)delay(更新间隔时间)failure_action(失败处理策略)monitor(更新后监控时间)
网络相关
15. Docker 有哪些网络模式?
bridge(默认模式):容器通过虚拟网桥(docker0)通信,通过 NAT 访问外部网络。同一 bridge 网络下的容器可以通过容器名互相访问。自定义 bridge 网络比默认 bridge 提供更好的隔离性和 DNS 解析。
host:容器直接使用宿主机网络栈,无网络隔离,性能最好。但端口冲突风险高(容器内的端口直接暴露在宿主机上)。
none:容器没有网络功能,适用于不需要网络的批处理任务。
overlay:用于 Docker Swarm 跨主机容器通信,基于 VXLAN 封装。
macvlan:为容器分配独立的 MAC 地址,直接接入物理网络,容器对外表现为独立的物理设备。
生产环境中 bridge 和 overlay 最为常用。开发环境使用 bridge,集群环境使用 overlay。
# 网络管理命令
# 列出所有网络
docker network ls
# 创建自定义 bridge 网络
docker network create --driver bridge --subnet 172.20.0.0/16 my-network
# 创建 overlay 网络(Swarm 模式)
docker network create --driver overlay --attachable my-overlay
# 查看网络详情
docker network inspect my-network
# 容器加入网络
docker network connect my-network my-container
# 容器离开网络
docker network disconnect my-network my-container
# 清理未使用的网络
docker network prune16. 容器之间如何通信?跨主机容器如何通信?
同一主机上的容器通信:
- 默认 bridge 网络下通过 IP 通信
- 自定义 bridge 网络下可以通过容器名通信(内置 DNS 解析)
- 同一 Pod(K8S)中的容器通过 localhost 通信
跨主机容器通信:
- Overlay 网络(Docker Swarm 原生支持,基于 VXLAN)
- Macvlan 网络(容器获得物理网络 IP)
- 第三方网络方案(Flannel、Calico、Cilium)
- 生产环境推荐使用 Kubernetes 网络方案
# 同一主机容器通信示例
docker network create app-network
docker run -d --name redis --network app-network redis:7
docker run -d --name api --network app-network myapp
# api 容器中可以直接用 redis 作为主机名
# Redis 连接: redis://redis:6379
# 跨主机通信(Docker Swarm overlay)
docker swarm init
docker network create --driver overlay --attachable cross-host-net
# 在节点 1 上运行服务
docker run -d --name service-a --network cross-host-net myapp
# 在节点 2 上运行服务
docker run -d --name service-b --network cross-host-net myapp
# service-a 和 service-b 可以通过容器名互相访问数据卷相关
17. Docker Volume 和 Bind Mount 的区别是什么?
| 对比项 | Docker Volume | Bind Mount |
|---|---|---|
| 管理方式 | 由 Docker 引擎管理 | 由用户自行管理 |
| 存储位置 | /var/lib/docker/volumes/ | 宿主机任意路径 |
| 生命周期 | 独立于容器,需手动清理 | 取决于宿主机文件系统 |
| 可移植性 | 跨平台兼容性好 | 依赖宿主机路径 |
| 适用场景 | 生产环境持久化存储 | 开发环境代码热更新 |
| 性能 | 略有开销(存储驱动层) | 直接文件访问,性能好 |
# Volume 常用操作
# 创建命名卷
docker volume create app-data
# 查看所有卷
docker volume ls
# 查看卷详情
docker volume inspect app-data
# 使用命名卷运行容器
docker run -d -v app-data:/var/lib/data myapp
# 使用绑定挂载(开发环境代码热更新)
docker run -d -v $(pwd)/src:/app/src myapp
# 使用只读挂载
docker run -d -v app-config:/etc/myapp:ro myapp
# 清理未使用的卷
docker volume prune
docker volume prune --filter "label!=keep"
# 查看卷占用空间
docker system df -v18. 如何备份和恢复 Docker Volume?
备份 Volume 的方法:使用临时容器挂载 Volume 并打包:
# 备份
docker run --rm -v mydata:/data -v $(pwd):/backup alpine \
tar czf /backup/mydata.tar.gz -C /data .
# 恢复
docker volume create mydata_restored
docker run --rm -v mydata_restored:/data -v $(pwd):/backup alpine \
tar xzf /backup/mydata.tar.gz -C /data也可以使用 docker volume inspect 查看 Volume 的实际存储路径,然后使用 rsync 进行增量备份。
# 增量备份(使用 rsync)
VOLUME_PATH=$(docker volume inspect mydata --format '{{.Mountpoint}}')
rsync -avz --progress $VOLUME_PATH/ /backup/mydata/
# 定时备份脚本(crontab)
# 0 2 * * * /opt/scripts/backup-volumes.sh >> /var/log/volume-backup.log 2>&1
# 备份脚本示例
#!/bin/bash
BACKUP_DIR="/backup/volumes/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
for volume in $(docker volume ls -q); do
echo "Backing up volume: $volume"
docker run --rm -v $volume:/data -v $BACKUP_DIR:/backup alpine \
tar czf /backup/${volume}.tar.gz -C /data .
done
# 清理 30 天前的备份
find /backup/volumes -type d -mtime +30 -exec rm -rf {} +
echo "Volume backup completed at $(date)"Docker Compose 深入
19. Docker Compose 如何实现环境变量管理?
Docker Compose 支持多种环境变量来源:
# 1. .env 文件(最常用)
# .env 文件放在 docker-compose.yml 同级目录
COMPOSE_PROJECT_NAME=myapp
DB_PASSWORD=Strong@Pass123
REDIS_PASSWORD=Redis@Pass456
APP_ENV=production
# 2. docker-compose.yml 中引用环境变量
# services:
# api:
# environment:
# - DB_PASSWORD=${DB_PASSWORD}
# - APP_ENV=${APP_ENV:-development} # 带默认值
# env_file:
# - .env.local # 从文件加载
# 3. 命令行指定
# DB_PASSWORD=xxx docker-compose up -d
# 4. 多环境配置文件
# docker-compose.yml — 基础配置
# docker-compose.override.yml — 开发环境覆盖(自动加载)
# docker-compose.prod.yml — 生产环境覆盖
# docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d20. 如何实现 Docker 容器的日志管理?
# Docker Compose 日志配置
services:
api:
logging:
driver: "json-file"
options:
max-size: "10m" # 单个日志文件最大 10MB
max-file: "5" # 最多保留 5 个日志文件
compress: "true" # 压缩旧日志
labels: "service"
tag: "{{.Name}}/{{.ID}}"
# 使用 Fluentd 收集日志
fluentd:
image: fluent/fluentd:v1.16
volumes:
- ./fluent.conf:/fluentd/etc/fluent.conf
ports:
- "24224:24224"# Docker 日志管理命令
# 查看容器日志
docker logs -f --tail 100 api
# 查看指定时间范围的日志
docker logs --since "2024-01-01" --until "2024-01-02" api
# 查看 Docker 日志驱动
docker inspect --format '{{.HostConfig.LogConfig.Type}}' api
# 全局日志配置(/etc/docker/daemon.json)
# {
# "log-driver": "json-file",
# "log-opts": {
# "max-size": "10m",
# "max-file": "3"
# }
# }
# 清理容器日志文件
truncate -s 0 /var/lib/docker/containers/*/*-json.log综合进阶
19. Docker 和虚拟机的区别是什么?
| 对比维度 | Docker 容器 | 虚拟机 |
|---|---|---|
| 虚拟化层级 | 操作系统级虚拟化 | 硬件级虚拟化 |
| 内核 | 共享宿主机内核 | 每个 VM 有独立内核 |
| 隔离性 | 进程级隔离(Namespace + Cgroups) | 完全隔离(Hypervisor) |
| 启动速度 | 秒级 | 分钟级 |
| 资源占用 | MB 级 | GB 级 |
| 性能 | 接近原生 | 有虚拟化开销(约 5%-10%) |
| 密度 | 单机可运行数百个容器 | 单机通常运行数十个 VM |
| 安全性 | 较低(共享内核) | 较高(独立内核) |
容器适合微服务架构和快速弹性伸缩,虚拟机适合强隔离需求和不同操作系统场景。生产环境中两者经常配合使用:虚拟机提供基础隔离,容器提供应用部署灵活性。
20. 如何保证 Docker 容器的安全?
容器安全的最佳实践:
镜像安全:使用官方或可信的基础镜像,定期更新镜像修复漏洞(使用 docker scan 或 Trivy 扫描);使用多阶段构建减少攻击面;启用 Content Trust 验证镜像签名。
运行时安全:以非 root 用户运行应用(Dockerfile 中添加 USER appuser);使用 Read-only 文件系统(--read-only)限制写入;限制容器资源使用(--cpus、--memory);使用 --cap-drop ALL 移除所有 Linux capabilities,只添加必要的。
网络安全:不挂载 Docker Socket(/var/run/docker.sock),这等同于给了容器宿主机的 root 权限;使用私有网络隔离不同服务。
审计与监控:启用 Seccomp 和 AppArmor 安全策略;使用私有镜像仓库并配置镜像漏洞扫描;定期审计容器权限和网络配置。
优点
缺点
总结
Docker 面试题涵盖了镜像管理、容器操作、Dockerfile 编写、Docker Compose 编排、网络和数据卷等核心领域。掌握这些知识点不仅有助于通过面试,更是实际工作中高效使用 Docker 的基础。建议在学习过程中多动手实践,结合自身项目经验理解每个知识点的应用场景,形成完整的 Docker 知识体系。
这组题真正考什么
- 面试官往往不只是考定义,而是在看你能否把基础概念放回真实项目场景。
- 这类题经常沿着基础概念、性能优化、安全实践往下追问。
- 高分答案通常有三层:结论、原因、项目中的例子。
60 秒答题模板
- 先用一句话给结论。
- 再补关键原理或底层机制。
- 最后说适用边界、常见坑或项目中的使用经验。
容易失分的点
- 只会背术语,不会举例。
- 回答太散,没有结构。
- 忽略版本差异和工程背景。
刷题建议
- 把答案拆成"定义、适用场景、风险点、实战例子"四段来复述。
- 遇到基础题时,尽量补一个框架级别的落地场景。
- 高频概念题建议自己再追问一层:底层原理、常见坑、性能代价。
高频追问
- 如果面试官继续追问底层实现,你能否解释运行机制或源码层面的关键点?
- 如果题目放到实际生产环境里,这个结论是否还成立?
- 是否存在版本差异、特殊边界条件需要主动说明?
复习重点
- 把每道题的关键词整理成自己的知识树,而不是只背原句。
- 对容易混淆的概念要做横向比较,例如机制差异、适用边界和性能代价。
- 复习时优先补"为什么",其次才是"怎么用"和"记住什么术语"。
面试作答提醒
- 先给结论,再补原因和例子。
- 回答基础题时不要只说"能用",最好补一句为什么这样选。
- 如果记不清细节,优先说出适用边界和排查思路。
