Docker 面试进阶详解:构建、排障与安全
围绕镜像构建、容器生命周期、网络存储、资源限制、内网访问和发布治理,整理 Docker 的可验证工程实践。
Docker 面试进阶详解:构建、排障与安全
本文融合
25年下半年面试真题预测.pdf第 2.15 节的 Docker 题目。保留可迁移的排障和治理方法,删除课程宣传;文中的命令用于说明排查思路,执行清理、网络暴露和权限变更前必须确认目标环境。
1. 从进程模型理解 Docker
Docker 不是一台轻量虚拟机。容器通常是由 Linux namespace、cgroups 和镜像文件系统隔离出的进程组,共享宿主机内核。这个差异决定了几个面试结论:
- 容器隔离的是进程、网络、挂载和资源视图,不等同于硬件级安全边界;
- 镜像层适合分发静态文件,运行时数据应放到 Volume、数据库或对象存储;
- 容器崩溃后可以快速重建,但持久化、备份和迁移要由系统设计负责;
- 一个容器通常运行一个主进程,多个职责应拆成可独立扩缩容的服务。
Dockerfile
-> BuildKit 构建缓存
-> 分层 Image(不可变发布物)
-> Container(进程 + namespace + cgroup)
|-- Network
|-- Volume
|-- stdout/stderr
2. 镜像构建:可复现比“能跑”更重要
2.1 多阶段构建和依赖缓存
Node 服务可以采用下面的结构。具体 Node 版本应与项目运行时和依赖兼容,并在生产中固定到经过验证的 tag 或 digest:
# syntax=docker/dockerfile:1
FROM node:22-alpine AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM dependencies AS build
COPY . .
RUN npm run build
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
这样安排的原因:
- 依赖清单单独复制,源码变化时仍可复用安装层;
- 编译器、测试依赖和源码不进入最终运行镜像;
npm ci使用 lockfile,避免构建时悄悄升级依赖;- 非 root 用户降低应用被利用后的影响范围;
- exec 形式的
CMD让 Node 收到SIGTERM,可以执行优雅退出。
生产构建还应生成 SBOM 或依赖清单,扫描高危漏洞,并把基础镜像、包管理器和构建工具的版本纳入变更记录。镜像 tag 可被重新指向,关键发布物应记录 digest。
2.2 .dockerignore 和秘密管理
.git
node_modules
coverage
.next
dist
*.log
.env*
Dockerfile*
.dockerignore 减小构建上下文,也避免把本地密钥和无关文件送入 BuildKit。不要把数据库密码、JWT 密钥或云凭据写进 Dockerfile 的 ARG、镜像层或仓库;构建密钥和运行时密钥应由 CI/CD 或编排平台注入,并限制读取范围。
3. 启动失败的排查顺序
3.1 Docker daemon 无法连接
典型错误是 Cannot connect to the Docker daemon。先判断是服务未启动、当前用户没有 socket 权限,还是 CLI 指向了错误的 context:
docker context show
docker info
docker ps
在 Linux 主机上再检查 daemon 服务和 socket 权限;在 Docker Desktop 等环境中则检查桌面引擎是否已启动。把用户加入 docker 组等同于授予接近 root 的主机控制能力,必须经过权限评估,不能作为无条件修复。
3.2 容器启动后立即退出
容器的生命周期跟随 PID 1。常见原因是入口命令执行完、配置错误、端口冲突或应用启动异常:
docker ps -a
docker logs --tail 200 <container>
docker inspect <container> --format '{{.State.ExitCode}} {{.State.Error}}'
不要使用 tail -f /dev/null 伪造“常驻进程”掩盖问题。应用应以前台进程运行,例如 Nginx 使用 daemon off;,Node 使用 node dist/server.js。容器收到停止信号后,应用要在 deadline 内关闭 HTTP server、连接池、定时器和消息订阅。
3.3 镜像拉取或构建超时
先区分 DNS、代理、认证、仓库限流和镜像本身体积:
docker system df
docker pull <registry>/<image>:<tag>
docker build --progress=plain .
企业环境可配置经过批准的私有镜像代理或 registry mirror。镜像加速地址会影响供应链信任和可用性,不能把网络上找到的镜像源直接写入生产 daemon 配置。大镜像应通过多阶段构建、精简依赖和合理的基础镜像降低拉取成本。
3.4 清理磁盘前先确认对象
docker system df
docker image ls
docker container ls -a
docker volume ls
image prune 可能删除未被容器引用的镜像,volume prune 可能删除看似未挂载但仍需要恢复的数据卷,system prune 影响范围更大。生产环境应先做备份和审批,按标签、年龄或明确 ID 清理,而不是直接执行全局 prune。
4. 网络故障与内网访问
4.1 端口、服务发现和 DNS
宿主机 127.0.0.1:8080 --publish--> 容器 app:3000
同一 user-defined network:
frontend -> http://api:3000
- 容器间调用优先使用 user-defined network 的服务名,不要把容器 IP 写死;
EXPOSE不等于发布端口,-p才会建立宿主机映射;- 只允许需要的端口和网络方向,管理端口不应暴露公网;
- DNS 失败时在容器内检查
/etc/resolv.conf、默认路由和代理配置,确认是解析失败还是目标服务拒绝连接。
可以用临时工具容器做分层排查,但不要把公共 DNS 地址当成所有生产网络的默认修复:
docker run --rm --network container:<target> busybox nslookup api.example.com
docker run --rm --dns <approved-dns> busybox nslookup api.example.com
4.2 FRP 和 SSH 隧道的边界
PDF 展示了 FRP 的公网服务端、内网客户端和 HTTP/TCP 转发,也提示 SSH 隧道不安全。面试时应回答架构和风险,而不是背配置:
外部请求 -> 受控公网入口/FRP 服务端 -> 加密隧道 -> 内网代理 -> 目标容器
落地前要确认:
- 隧道两端使用强认证、最小权限和加密传输;
- 只暴露必要的域名、路径或端口,并限制来源 IP;
- 记录连接、转发和失败日志,设置空闲超时和带宽上限;
- 不把 Docker daemon socket、数据库管理端口或内部元数据服务直接映射公网;
- 优先使用组织批准的 VPN、零信任网关或云厂商内网连接方案。
临时 SSH 端口转发适合受控的调试窗口,不应作为长期生产发布架构。任何隧道都不能替代应用鉴权和资源级授权。
5. 资源限制与健康检查
5.1 CPU 和内存
容器不设上限时,单个进程可能争抢宿主机资源。排查时结合容器和主机指标:
docker stats <container>
docker top <container>
运行时可以设置 CPU、内存和进程数限制,例如:
docker run --cpus=1 --memory=512m --pids-limit=256 app:prod
限制不是性能优化本身。需要观察 OOM kill、GC、事件循环延迟、请求 P95/P99 和重启次数,并根据负载测试调整。内存限制过小会导致频繁 OOM,过大则可能掩盖泄漏。
5.2 健康检查和优雅退出
健康检查应区分“进程活着”和“可以接收流量”:
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://127.0.0.1:3000/health/ready || exit 1
/health/live 通常只检查进程,/health/ready 可以检查必要依赖是否已连接。不要在健康检查中执行昂贵查询,也不要把所有外部依赖短暂抖动直接变成容器重启风暴。编排平台应给应用足够的 termination grace period,让连接排空和后台任务收尾。
6. 存储、日志和可观测性
存储
- 临时文件可用
tmpfs,并设置大小上限; - 需要跨容器和重启保留的数据使用 named volume、数据库或对象存储;
- bind mount 适合本地开发和明确的主机共享,不要默认把宿主机根目录挂进容器;
- 数据库备份必须独立于 Volume 本身,Volume 不是备份策略。
日志和指标
应用日志写 stdout/stderr,用平台统一采集、轮转和检索。结构化日志至少包含时间、服务、版本、请求 ID、状态和耗时,敏感字段要在应用边界脱敏。指标可包括:
- 容器重启、退出码和 OOM 次数;
- CPU、内存、文件系统和网络使用率;
- 请求吞吐、错误率、P95/P99 延迟;
- 镜像拉取、部署和健康检查失败率。
日志、指标和链路追踪要能关联同一发布版本;只看 docker ps 不能证明服务健康。
7. 安全与供应链
- 基础镜像和依赖使用固定版本,定期重建获取安全修复;
- 运行时使用非 root 用户,删除不需要的 shell、包管理器和调试工具;
- 不在镜像、日志和命令行参数中暴露密钥;
- 使用镜像签名、漏洞扫描和 SBOM,审核第三方镜像来源;
- 限制容器 capabilities、挂载点、网络出口和 Linux 权限;
- Docker socket 具有主机级控制能力,除非明确隔离,否则不要挂载到业务容器;
- 对公网入口配置 TLS、鉴权、限流和审计,容器隔离不能替代应用安全。
8. 面试题答法
Q: 容器为什么启动后就退出?
A: 容器跟随 PID 1 的生命周期。先看 docker ps -a、退出码和日志,判断入口命令结束、配置错误、依赖不可用还是权限问题;让应用以前台进程运行,并实现信号处理和优雅退出,不能用假进程掩盖异常。
Q: 容器无法访问外网怎么查?
A: 分层检查容器网络、默认路由、DNS、代理、防火墙和目标服务。先在同一 network 用临时容器验证解析和 TCP,再看应用超时与证书;不要只修改 DNS,也不要把公共 DNS 当成生产通用方案。
Q: 如何优化 Docker 镜像?
A: 先用构建分析确认大层,再采用多阶段构建、缓存 lockfile、精简运行依赖、.dockerignore 和固定基础镜像;同时扫描漏洞、使用非 root 用户并验证运行时健康,不能只追求镜像体积。
Q: FRP 或 SSH 隧道能解决生产访问问题吗?
A: 它们解决的是网络可达性,不解决鉴权、审计和数据安全。生产应优先使用批准的 VPN/零信任入口,隧道只开放最小端口、加密认证、限时并监控,不能把 Docker socket 或数据库管理端口直接暴露。
Q: Docker 和虚拟机有什么区别?
A: 容器通常共享宿主机内核,通过 namespace/cgroup 隔离进程和资源,启动快、密度高但隔离边界不同;虚拟机包含独立内核,隔离和兼容性更强但开销更大。选型还要看安全等级、内核需求和运维能力。
9. 一套可执行的发布检查
构建可复现(lockfile、digest、SBOM)
-> 镜像扫描和非 root 检查
-> 资源、网络、Volume 和 secrets 边界确认
-> 健康检查、优雅退出和回滚演练
-> 日志/指标/trace 可关联版本
-> 灰度发布后观察错误率、延迟、OOM 和重启