Docker 面试题精选
生成时间:2026-06-06 20:54 | 共 10 题
1. 请解释 Docker 的整体架构,包括 Client、Daemon 和 Registry 的职责与交互方式
难度: ⭐
标准答案:
Docker 采用 C/S(Client/Server)架构,由三个核心组件组成:
- Docker Client:用户与 Docker 交互的入口,通过 CLI(如
docker run)或 SDK 发送请求给 Docker Daemon。Client 与 Daemon 可以在同一主机,也可通过网络远程连接。 - Docker Daemon(dockerd):后台服务进程,负责接收 Client 请求并执行实际的容器管理操作——镜像构建、容器创建/启动/停止、网络和存储管理等。Daemon 是 Docker 的"大脑"。
- Docker Registry:镜像的存储与分发服务。Docker Hub 是默认的公共 Registry;企业可搭建私有 Registry(如 Harbor)。Daemon 通过
docker pull从 Registry 拉取镜像,通过docker push推送镜像。
交互流程:Client → REST API → Daemon → Registry。Client 不直接与 Registry 通信,所有操作由 Daemon 代理完成。
延伸思考: Docker Desktop 的架构与传统 Linux Docker 有何区别?提示:Docker Desktop 在 macOS/Windows 上通过虚拟机(Hyper-V / Apple Silicon 上的 QEMU)运行 Linux 内核,Client 在宿主机,Daemon 在 VM 内。
2. Dockerfile 中 COPY 和 ADD 指令有什么区别?为什么推荐优先使用 COPY?
难度: ⭐
标准答案:
| 特性 | COPY | ADD |
|---|---|---|
| 基本复制 | ✅ | ✅ |
| 从 URL 复制 | ❌ | ✅ |
| 自动解压 tar 归档 | ❌ | ✅ |
| 多阶段构建中引用 | ✅ --from= | ✅ --from= |
推荐 COPY 的原因:
- 语义明确:COPY 只做一件事——将文件从构建上下文复制到镜像中,行为可预测。
- ADD 的副作用:ADD 会自动解压 tar 文件,如果你的本意是复制一个
.tar.gz文件而不是解压它,就会产生意外行为。 - ADD 从 URL 下载是不可缓存且不可预测的,应使用
RUN curl替代,这样可以更好地控制缓存层。 - Docker 官方最佳实践文档明确推荐:除非需要自动解压功能,否则一律使用 COPY。
延伸思考: 如果需要在构建时从远程下载文件,ADD <url> 和 RUN curl + COPY 哪个更好?为什么?提示:后者可以更好地利用 Docker 缓存层和校验文件完整性。
3. 什么是多阶段构建(Multi-stage Build)?它解决了什么问题?请给出一个典型示例
难度: ⭐⭐
标准答案:
多阶段构建允许在一个 Dockerfile 中定义多个 FROM 指令,每个 FROM 开始一个新的构建阶段。关键是可以用 COPY --from=<stage> 从前一个阶段复制只需要的产物到最终镜像,丢弃构建依赖。
解决的问题:
- 构建环境(编译器、SDK、源码)不需要留在运行时镜像中
- 显著减小最终镜像体积(从数 GB 降至数十 MB)
- 无需维护单独的 Dockerfile 或编写复杂的构建脚本
典型示例(Go 应用):
# 阶段1:构建
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o myapp
# 阶段2:运行
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/myapp /usr/local/bin/myapp
ENTRYPOINT ["myapp"]最终镜像仅包含 alpine 基础 + 二进制文件,约 20MB,而非 golang 镜像的 1GB+。
延伸思考: 多阶段构建中,如何给阶段命名以提升可读性?COPY --from=builder 和 COPY --from=0 各有什么优劣?提示:命名阶段语义更清晰,数字索引在阶段顺序调整时容易出错。
4. Docker 的三种主要网络模式(bridge、host、overlay)分别适用于什么场景?
难度: ⭐⭐
标准答案:
| 网络模式 | 原理 | 适用场景 | 注意事项 |
|---|---|---|---|
| bridge | 默认模式。每个容器分配独立网络命名空间,通过虚拟网桥(docker0)互联,通过 NAT 访问外部 | 单机多容器通信,最常用 | 端口需 -p 映射才能从宿主机访问 |
| host | 容器直接使用宿主机网络栈,无网络隔离 | 需要极低网络延迟的场景(如高性能计算、网络监控工具) | 端口冲突风险高,安全性低,失去了网络隔离 |
| overlay | 跨主机的 VXLAN 隧道网络,由 Docker Swarm 或独立使用 | 多主机集群中容器间通信 | 需要键值存储(如 Consul)或 Swarm 模式;性能有 VXLAN 开销 |
补充:
none:完全禁用网络,适用于安全敏感的批处理任务。macvlan:容器拥有独立 MAC 地址,直接出现在物理网络中,适用于遗留系统对接。
延伸思考: 在 Kubernetes 中为什么不使用 Docker 的 overlay 网络?提示:K8s 使用 CNI 插件(如 Calico、Cilium)实现更灵活的网络策略和跨节点通信。
5. Docker Volume 和 Bind Mount 有什么区别?生产环境应该如何选择?
难度: ⭐⭐
标准答案:
| 特性 | Volume | Bind Mount |
|---|---|---|
| 存储位置 | /var/lib/docker/volumes/(Docker 管理) | 宿主机任意路径 |
| 创建方式 | docker volume create 或 -v vol_name:/path | -v /host/path:/container/path |
| 可移植性 | ✅ 跨平台,不依赖宿主机目录结构 | ❌ 依赖宿主机特定路径 |
| 性能 | Linux 下与 Bind Mount 相当 | macOS/Windows 下可能较慢(文件系统转译) |
| 备份/迁移 | docker run --rm -v vol:/data busybox tar czf ... | 直接操作宿主机文件 |
| 安全性 | Docker 管理,非 root 难以直接访问 | 容器可修改宿主机敏感文件 |
| 生命周期 | 独立于容器,docker volume prune 清理 | 随宿主机目录 |
生产环境建议:
- 优先使用 Named Volume:数据持久化、数据库存储、配置文件等——可移植、易备份。
- Bind Mount 适用于:开发环境代码热更新、需要精确控制宿主机路径的场景。
- 避免匿名 Volume(
-v /path不指定名称):难以管理和清理。 - tmpfs:敏感临时数据(如密钥),不写入磁盘。
延伸思考: 在 Kubernetes 中,PV/PVC 对应 Docker 的哪种挂载方式?K8s 的 StorageClass 如何动态供给存储?提示:PV 可以基于 Volume(NFS、云盘等),也可以基于 HostPath(类似 Bind Mount)。
6. 如何使用 Docker Compose 编排一个包含 Web 服务、数据库和缓存的微服务应用?
难度: ⭐⭐
标准答案:
# docker-compose.yml
version: "3.9"
services:
web:
build: ./web
ports:
- "8080:8080"
environment:
- DB_HOST=db
- REDIS_HOST=cache
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped
networks:
- frontend
- backend
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- backend
cache:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redisdata:/data
restart: unless-stopped
networks:
- backend
volumes:
pgdata:
redisdata:
networks:
frontend:
backend:
internal: true # 后端网络不对外暴露关键点:
depends_on+healthcheck确保启动顺序和依赖就绪- 环境变量通过
${}引用.env文件,避免硬编码密码 internal: true网络限制后端服务不被外部访问- Named Volume 持久化数据
restart: unless-stopped保证服务自愈
延伸思考: depends_on 能保证服务"可用"吗?数据库启动了但还没完成初始化怎么办?healthcheck 和 wait-for-it.sh 脚本各有什么优劣?
7. Docker 容器的安全加固有哪些关键措施?
难度: ⭐⭐⭐
标准答案:
容器安全是纵深防御,需从多个层面入手:
1. 镜像安全
- 使用可信基础镜像(官方镜像 > 社区镜像),优先选择
alpine/distroless等最小化镜像 - 定期扫描镜像漏洞:
docker scout、Trivy、Clair - 固定镜像版本标签(
python:3.12.4-slim而非python:latest) - 使用多阶段构建,最终镜像不含编译工具
2. 运行时安全
- 非 root 运行:Dockerfile 中添加
USER appuser,或docker run -u 1000:1000 - 只读文件系统:
docker run --read-only,配合--tmpfs /tmp挂载临时目录 - 限制能力:
--cap-drop ALL --cap-add NET_BIND_SERVICE,仅保留必要 Linux capability - 禁止特权模式:避免
--privileged,它会赋予容器几乎所有宿主机权限 - 资源限制:
--memory 512m --cpus 1,防止单容器耗尽主机资源 - AppArmor/SELinux 策略:为容器定制强制访问控制策略
3. 网络安全
- 最小端口暴露,仅映射必要端口
- 使用 Docker 网络隔离,
internal网络禁止外部访问 - 启用 TLS 加密 Daemon 通信
4. 宿主机安全
- 保持 Docker Engine 更新
- 限制 Docker Socket 权限(
/var/run/docker.sock不要挂载进容器) - 启用 Seccomp 配置文件
延伸思考: --privileged 与 --cap-add ALL 有什么区别?为什么 --privileged 更危险?提示:privileged 还会解除设备访问限制、挂载命名空间限制等,远超 capability 范畴。
8. 如何对 Docker 容器进行性能调优?请从 CPU、内存、I/O 和网络四个维度分析
难度: ⭐⭐⭐
标准答案:
CPU 调优:
--cpus=2:限制可用 CPU 核心数(CFS 调度器配额)--cpuset-cpus=0,1:绑定到特定 CPU 核心,减少上下文切换,提升缓存命中率--cpu-shares=512:相对权重分配(仅在 CPU 竞争时生效,不保证绝对值)
内存调优:
--memory=2g --memory-swap=2g:限制内存 + 禁止 swap(swap=memory 表示无额外 swap)--memory-reservation=1g:软限制,内存紧张时才触发回收--oom-kill-disable=true:禁止 OOM Killer 杀容器(需同时设--memory,否则无意义)--kernel-memory=128m:限制内核内存(高级场景,如大量 socket 连接)
I/O 调优:
- 存储驱动选择:overlay2(默认,性能最佳);生产环境避免 devicemapper 的 loop 模式
- Volume 性能:Named Volume > Bind Mount(macOS/Windows 上差异显著)
- 日志限制:
--log-driver json-file --log-opt max-size=10m --log-opt max-file=3,防止日志撑爆磁盘 - tmpfs:高频临时 I/O 使用
--tmpfs /tmp:size=1g,直接在内存中
网络调优:
- host 网络:对网络 I/O 要求极高的场景,绕过 NAT 和网桥
- DNS 优化:
--dns 8.8.8.8指定高效 DNS;减少容器内 DNS 查询开销 - 连接跟踪:高并发短连接场景,调整宿主机
net.netfilter.nf_conntrack_max - MTU 调整:
--mtu 1400适配底层网络,避免分片
监控工具:
docker stats:实时资源概览docker top:容器内进程- cAdvisor + Prometheus + Grafana:完整监控栈
延伸思考: 在 Kubernetes 中,Requests 和 Limits 与 Docker 的 --cpu-shares 和 --cpus 有什么对应关系?为什么 K8s 推荐"Requests = Limits"以避免 CPU 抖动?
9. 如何将 Docker 镜像从 1GB 瘦身到 50MB 以下?请给出完整的策略和步骤
难度: ⭐⭐⭐
标准答案:
镜像瘦身七步法:
第1步:选择最小基础镜像
# ❌ 1.2GB
FROM ubuntu:22.04
# ✅ 80MB
FROM python:3.12-slim
# ✅ 50MB
FROM python:3.12-alpine
# ✅ 20MB(无 shell,极难调试但最安全)
FROM gcr.io/distroless/python3第2步:多阶段构建
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]第3步:合并 RUN 层 + 清理缓存
# ❌ 三层
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# ✅ 一层
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*第4步:利用构建缓存
# 先复制依赖文件,依赖不变则复用缓存
COPY package*.json ./
RUN npm ci
# 再复制源码,源码变不影响依赖层缓存
COPY . .第5步:.dockerignore 排除无关文件
.git
node_modules
*.md
.env
Dockerfile
docker-compose*.yml第6步:使用 docker-slim 自动瘦身
docker-slim build --target myapp:fat --tag myapp:slim
# 可减小 30%-70% 体积,通过运行时分析只保留必需文件第7步:分析镜像层
docker history myapp:latest
dive myapp:latest # 交互式查看每层内容,发现可优化点实战效果: 典型 Node.js 应用:node:20 (1.1GB) → 多阶段+alpine → ~120MB → distroless → ~80MB → docker-slim → ~40MB
延伸思考: Alpine 镜像使用 musl libc 而非 glibc,可能导致哪些兼容性问题?提示:Node.js 原生模块(如 sharp、bcrypt)需要编译,musl 环境下可能失败;DNS 解析行为差异等。
10. 如何在 CI/CD 流水线中集成 Docker?请以 GitHub Actions 为例,给出完整的构建、测试、推送流程
难度: ⭐⭐⭐
标准答案:
# .github/workflows/docker-ci.yml
name: Docker CI/CD
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
# 1. 检出代码
- name: Checkout
uses: actions/checkout@v4
# 2. 设置 Docker Buildx(支持多平台构建)
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# 3. 登录容器仓库
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 4. 提取 Docker 元数据(标签、标注)
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=
# 5. 构建并推送
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
# 6. 安全扫描
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'关键最佳实践:
- 镜像标签策略:禁止
latest唯一标签;使用语义版本 + Git SHA 双重标识 - 构建缓存:
cache-from/cache-to: type=gha利用 GitHub Actions 缓存,加速重复构建 - 多平台构建:Buildx 支持
linux/amd64,linux/arm64,适配云服务器和 ARM 设备 - 安全扫描:Trivy 扫描 CRITICAL/HIGH 漏洞,
exit-code: 1阻断发布 - 最小权限:
GITHUB_TOKEN仅需packages:write,不使用 Personal Access Token - PR 仅构建不推送:
push: ${{ github.event_name != 'pull_request' }}
延伸思考: 如何实现蓝绿部署或金丝雀发布?提示:结合 Kubernetes 的 Deployment strategy(RollingUpdate 参数调整)或使用 Argo Rollouts / Flagger 实现渐进式发布。
EOF