第17章:Docker 大厂面试题精选(腾讯/阿里/字节/美团)
本文档按真实面试流程组织,从浅到深、从基础到场景,模拟面试官的提问方式和追问逻辑。每道题给出面试官想听到的回答和踩坑点,而非教科书式的标准答案。
第一轮:基础摸底(5-10 分钟)
面试官目的:确认你不是背八股文,而是真的用过 Docker。
Q1:你平时用 Docker 做什么?说说你项目里 Docker 的使用场景
❌ 背书式回答:
“Docker 是一个开源的应用容器引擎,基于 Go 语言开发,实现了容器级别的虚拟化…”
✅ 面试官想听的:
"我之前在 XX 项目里,主要用 Docker 做了三件事:
- 统一开发环境——团队成员本地环境不一致(有的 Mac 有的 Ubuntu),用 docker-compose 一键拉起 MySQL + Redis + 应用,新人入职半天就能跑起来
- CI/CD 流水线——代码提交后 Jenkins 自动 build 镜像 → 跑测试 → 推到 Harbor 仓库 → 部署到测试环境
- 线上部署——生产环境用 K8s 管理容器,每个微服务一个镜像,支持滚动更新和自动扩缩容"
考察点:真正用过的人一定会提到具体场景和痛点,而不是复述定义。
Q2:容器和虚拟机到底有什么区别?你项目里怎么选的?
❌ 背书式回答:
“虚拟机通过 Hypervisor 虚拟化硬件,容器共享宿主机内核…”
✅ 面试官想听的:
"核心区别就一句话:虚拟机是模拟一台完整的电脑,容器是隔离一个进程。
举个实际例子:我们之前有一台 4 核 8G 的服务器,跑虚拟机最多开 3-4 个(每个 VM 要分配 2G 内存 + 1 个虚拟内核),换成 Docker 后同样配置能跑 15-20 个容器,因为容器不需要独立的 OS 内核。
选型上:需要跑不同操作系统(比如同时跑 Windows 和 Linux 服务)用虚拟机;同一个 Linux 环境下跑微服务用 Docker,省资源、启动快。
但 Docker 的隔离性确实不如虚拟机,如果安全性要求极高(比如多租户隔离),可能还是需要虚拟机或者 gVisor 这种安全容器。"
考察点:能否结合实际资源数据说明差异,理解各自的适用边界。
Q3:Docker 镜像为什么要分层?有什么好处?
❌ 背书式回答:
“镜像采用分层存储,每条 Dockerfile 指令生成一个只读层,使用 Copy-on-Write 机制…”
✅ 面试官想听的:
"分层最大的好处是共享和缓存。举个例子:
我们团队有 20 个 Python 微服务,它们的基础镜像都是python:3.11-slim。如果用分层存储,这 20 个镜像共享同一个基础层,本地磁盘只存一份。docker pull的时候,基础层也只下载一次,后续只拉各服务自己的代码层。
构建的时候也一样,我写 Dockerfile 时会把变化少的(比如pip install依赖)放前面,变化多的(比如COPY . .)放后面。这样改一行代码,重新 build 时前面的依赖层直接命中缓存,构建时间从 5 分钟缩短到 30 秒。
本质上就是增量存储 + 增量传输 + 增量构建。"
考察点:能否结合具体数据说明分层的实际价值。
第二轮:命令与实操(10-15 分钟)
面试官目的:考察动手能力,你是不是真写过 Dockerfile、排过线上问题。
Q4:写一个生产级的 Dockerfile,要求镜像尽量小、安全、能健康检查
面试官期待看到的写法:
# 阶段1:构建 FROM python:3.11-slim AS builder WORKDIR /app # 先复制依赖文件,利用缓存 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # 阶段2:运行 FROM python:3.11-slim RUN groupadd -r appuser && useradd -r -g appuser appuser WORKDIR /app # 从 builder 复制已安装的依赖 COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY --from=builder /app /app # 非 root 用户运行 USER appuser EXPOSE 5000 # 健康检查 HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ CMD curl -f http://localhost:5000/health || exit 1 CMD ["python3", "app.py"]面试官会追问:
Q4-1:为什么用python:3.11-slim而不是python:3.11?
“slim 版本去掉了编译器、man 手册等非必要包,镜像从约 900MB 缩到 150MB。线上不需要 gcc 这些东西,只有编译某些 C 扩展时才需要,所以构建阶段用完整版,运行阶段用 slim。”
Q4-2:为什么先 COPY requirements.txt 再 COPY 代码?
“Docker 构建缓存是按层判断的。如果先 COPY 全部代码再 pip install,改一行代码就会导致整个 pip install 重新执行。先复制 requirements.txt,只要依赖文件没变,pip install 那层就一直命中缓存。”
Q4-3:ENTRYPOINT 和 CMD 你一般怎么选?
"简单服务直接用 CMD,比如CMD ["python3", "app.py"]。如果需要固定程序但参数可变,用 ENTRYPOINT + CMD 组合,比如:
ENTRYPOINT ["python3"] CMD ["app.py"]docker run myapp→ 执行python3 app.py
docker run myapp test.py→ 执行python3 test.py
需要写 entrypoint 脚本做初始化(比如等数据库就绪、初始化配置)时,用 ENTRYPOINT 指向脚本。"
Q5:docker compose up之后服务起不来,你怎么排查?
✅ 面试官想听的排查思路:
"我会按这个顺序来:
第一步:看日志
dockercompose logs<服务名># 或者只看最近的dockercompose logs--tail50<服务名>第二步:看容器状态
dockercomposeps# 重点看 STATUS 列:Up 还是 Exit (code)# Exit 0 = 正常退出(可能是 CMD 问题)# Exit 1 = 应用错误# Exit 137 = OOM Killed# Exit 139 = 段错误第三步:进容器排查
dockercomposeexec<服务名>/bin/bash# 检查环境变量、配置文件、网络连通性curlhttp://mysql:3306# 测试依赖服务是否可达第四步:检查资源
dockerstats# 看 CPU、内存是否打满常见原因:
- depends_on 顺序问题——应用起来了但 MySQL 还没 ready,代码里没做重试
- 端口冲突——宿主机某端口被占用
- 环境变量没传——
.env文件缺失或变量名写错 - 网络问题——容器间 DNS 解析失败,特别是用了默认 bridge"
Q6:容器退出码 137 和 139 分别是什么?怎么处理?
✅ 实际回答:
"137 = 128 + 9 = SIGKILL,通常是OOM Killed——内存超限被内核杀掉了。
排查方法:
# 1. 确认退出码dockerinspect<容器>--format='{{.State.ExitCode}}'# 2. 查 dmesg 看内核日志dmesg|grep-i"oom\|killed"# 会看到类似:Out of memory: Killed process 12345 (python3)# 3. 临时方案:调高内存限制dockerrun-d--memory=2g myapp# 根本方案:用 profiling 工具找内存泄漏139 = 128 + 11 = SIGSEGV,段错误,通常是应用本身的 bug(空指针、数组越界),需要看核心转储文件定位代码问题。
还有常见退出码:
- 0:正常退出
- 1:应用错误
- 126:权限问题
- 127:命令找不到(CMD 路径写错了)"
Q7:线上容器日志撑爆了磁盘,怎么办?
✅ 实际回答:
"这个问题我处理过。当时某服务循环打日志,几天就把宿主机磁盘写满了,连 SSH 都登不上。
临时处理:
# 1. 清空指定容器的日志(不删除容器)truncate-s0/var/lib/docker/containers/<容器ID>/<容器ID>-json.log# 或者直接杀掉日志文件的 fd# 找到容器的容器 IDCONTAINER_ID=$(dockerinspect--format='{{.Id}}'<容器名>)cat/dev/null>/var/lib/docker/containers/$CONTAINER_ID/${CONTAINER_ID}-json.log根治方案:
- Docker daemon 全局配置日志轮转(
/etc/docker/daemon.json):
{"log-driver":"json-file","log-opts":{"max-size":"100m","max-file":"3"}}- 应用层——不要疯狂打日志,设置合理日志级别
- 接入日志收集(ELK/Loki),日志打到 stdout 后被收集走,不用长期存本地"
第三轮:网络与原理(15-20 分钟)
面试官目的:考察你对 Docker 底层的理解深度。
Q8:Docker 默认的 bridge 网络和自定义 bridge 网络有什么区别?为什么生产一定要用自定义的?
✅ 实际回答:
"最大的区别是DNS 解析。
默认 bridge 网络里,容器之间只能用 IP 互相访问,不能用容器名。比如你启动了一个 MySQL 容器叫db,另一个容器里配DB_HOST=db是解析不了的,必须写DB_HOST=172.17.0.x,IP 一变就挂了。
自定义 bridge 网络自带 DNS 服务(127.0.0.11),容器名自动解析为 IP,所以DB_HOST=db直接就能用。
还有一个问题是隔离性。默认 bridge 网络里所有容器都在同一个网络,互相都能访问。自定义网络可以做网络分组,比如把前端服务放frontend网络,后端服务放backend网络,数据库只在backend里,外部访问不到。
# 正确做法dockernetwork create frontenddockernetwork create backenddockerrun-d--namenginx--networkfrontend nginxdockerrun-d--nameapp--networkbackend myappdockerrun-d--namedb--networkbackend mysql# nginx 连到 backend 访问 app,但 db 无法从外部直接访问Q9:容器之间怎么通信的?底层原理是什么?
✅ 实际回答:
"以最常见的 bridge 模式为例:
容器A ←→ veth pair ←→ docker0 网桥 ←→ veth pair ←→ 容器B
1. 每个容器有自己的 Network Namespace(独立的网络栈、IP、路由表) 2. Docker 为每对容器创建 veth pair(虚拟网卡对),一端在容器里,一端在 docker0 网桥上 3. 容器间通信本质上是通过 docker0 网桥转发,跟局域网交换机一个道理 4. 出站流量通过 iptables 的 SNAT/MASQUERADE 规则做地址转换 5. 端口映射(-p 8080:80)通过 iptables DNAT 实现验证方法:
# 在宿主机上看 veth pairiplinkshow|grepveth# 看 iptables 规则iptables-tnat-L-n|grepDNAT# 输出:DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80Q10:端口映射-p 8080:80底层发生了什么?
✅ 实际回答:
"Docker 帮你在宿主机的 iptables 里加了一条 DNAT 规则:
当有人访问宿主机IP:8080时,iptables 自动把目标地址改成172.17.0.2:80(容器 IP),然后转发到容器。
# 验证iptables-tnat-L-n|grep8080# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80所以如果宿主机的防火墙或安全组没开放 8080 端口,即使 Docker 做了映射,外部也访问不了。我之前踩过这个坑——Docker 配置没问题,但云服务器安全组没放行端口。"
第四轮:安全与生产(15-20 分钟)
面试官目的:考察生产环境实战经验和安全意识。
Q11:你们生产环境 Docker 容器有哪些安全加固措施?
✅ 实际回答(挑几个说,不要背清单):
"我们主要做了这几件事:
1. 不用 root 跑容器
RUN groupadd -r appuser && useradd -r -g appuser appuser USER appuser因为容器里 root 和宿主机 root 是同一个 UID,如果容器被攻破,攻击者可能拿到宿主机 root 权限。
2. 最小权限原则
dockerrun --cap-drop ALL --cap-add NET_BIND_SERVICE nginx先 drop 所有 capabilities,只加需要的。大部分容器只需要绑定低端口的能力。
3. 限制资源,防止被薅
dockerrun--cpus="2"--memory="1g"--pids-limit=100myapp不限制的话,一个容器内存泄漏能拖垮整台机器。pids-limit 防 fork bomb。
4. 密钥不进镜像
密码、API Key 这些通过环境变量注入或者用 Docker Secrets,绝对不写在 Dockerfile 或代码里。
5. 镜像扫描
CI 流水线里接了 Trivy,HIGH/CRITICAL 漏洞直接阻断构建。"
Q12:Docker Secrets 是什么?和环境变量有什么区别?
✅ 实际回答:
"环境变量有个问题:docker inspect能直接看到,而且镜像构建历史里可能泄露。
Docker Secrets 是 Swarm 模式下的功能,密钥存在内存里(/run/secrets/),不会持久化到镜像层,也不会被docker inspect暴露。
services:db:environment:MYSQL_PASSWORD_FILE:/run/secrets/db_passwordsecrets:-db_passwordsecrets:db_password:file:./secrets/db_password.txt如果不用 Swarm,生产中一般用 HashiCorp Vault 或者云厂商的密钥管理服务(比如阿里云 KMS)。"
Q13:你们的 Docker 镜像仓库是怎么管理的?Harbor 用过吗?
✅ 实际回答:
"我们用的是 Harbor(开源的企业级镜像仓库),部署在内网。
主要解决了几个问题:
- 镜像分发速度——Docker Hub 在国内太慢了,Harbor 部署在内网,push/pull 都是内网带宽
- 权限控制——不同项目组只能 push 自己 namespace 下的镜像
- 漏洞扫描——Harbor 集成了 Trivy,镜像 push 上去自动扫描
- 镜像签名——开启了 Content Trust,只允许拉取签名过的镜像
- 垃圾回收——定期清理没被引用的镜像层,释放磁盘空间
# 配置 Docker 客户端信任 Harbor# /etc/docker/daemon.json{"insecure-registries":["harbor.internal.com:443"],"registry-mirrors":["https://harbor.internal.com"]}```"第五轮:故障排查与场景题(15-20 分钟)
面试官目的:考察真实故障处理能力和工程思维。
Q14:线上容器突然访问不了了,你怎么排查?
✅ 面试官想听的排查路径:
"我按这个顺序来,从外到内:
1. 确认容器是否在运行
dockerps|grep<容器名># 如果不在,docker compose ps 看状态和退出码2. 看日志
dockerlogs--tail100<容器名># 看有没有报错、OOM、启动失败3. 进容器测网络
dockerexec-it<容器名>ping<目标地址>dockerexec-it<容器名>curl-vhttp://localhost:<端口>dockerexec-it<容器名>cat/etc/resolv.conf# 确认 DNS 配置是否正确4. 检查端口映射和 iptables
dockerport<容器名># 确认映射是否正确iptables-tnat-L-n|grep<容器IP># 确认 DNAT 规则是否存在5. 检查防火墙/安全组
如果是云服务器,还要去控制台看安全组规则是否放行了对应端口。
6. 检查宿主机资源
df-h# 磁盘满了?free-m# 内存不够?dockerstats# CPU 打满了?7. 依赖服务检查
比如 app 连不上 mysql,可能是 mysql 容器挂了或者网络不通。"
Q15:你的应用需要连接 MySQL,docker-compose.yml 里怎么配置才能确保 MySQL 先起来?
✅ 实际回答:
"很多人以为depends_on就够了,其实depends_on只等容器启动,不等服务就绪。MySQL 容器起来了不代表 3306 端口能连。
方案一:depends_on + healthcheck(推荐)
services:mysql:image:mysql:8.0healthcheck:test:["CMD","mysqladmin","ping","-h","localhost"]interval:10stimeout:5sretries:5app:depends_on:mysql:condition:service_healthy# 等 healthcheck 通过方案二:应用层做重试
不管编排层怎么配,应用代码里都应该有数据库连接重试逻辑,因为线上 MySQL 可能随时重启。
importtimeforiinrange(10):try:db=connect(db_url)breakexcept:time.sleep(3)生产环境一定要两种都做,因为 healthcheck 也可能误判。"
Q16:你们怎么做的灰度发布?用 Docker 的话怎么做?
✅ 实际回答:
"我们之前没上 K8s 的时候,用 Docker Compose + Nginx 做过金丝雀发布:
思路:
services:app-stable:image:myapp:v1.0deploy:replicas:9# 90% 流量app-canary:image:myapp:v2.0deploy:replicas:1# 10% 流量nginx:image:nginx:alpineports:-"80:80"volumes:-./nginx.conf:/etc/nginx/nginx.conf:roNginx 配置 upstream 里两个后端,权重按副本数分配。
步骤:
- 先部署 v2.0 1 个副本,观察 10 分钟
- 看监控指标(错误率、延迟、CPU)
- 没问题就逐步增加 v2.0 副本数,减少 v1.0
- 全量切完后停掉 v1.0
- 有问题立刻回滚——把 v1.0 副本加回来,v2.0 停掉
现在上了 K8s,用 Deployment 的maxSurge和maxUnavailable参数做滚动更新更方便。"
第六轮:深入追问(10-15 分钟)
面试官目的:看你的技术深度和思考能力。
Q17:Docker 的 Cgroups 具体是怎么限制 CPU 和内存的?
✅ 实际回答(别背定义,结合现象说):
"CPU 限制的本质是时间片分配。比如--cpus=2对应 Cgroups 里的cpu.max = 200000 100000(每 100ms 周期内可以用 200ms 的 CPU 时间,等于 2 核)。如果一个容器配了--cpus=0.5,它每 100ms 只能用 50ms CPU,剩下的时间让给别的容器。
内存限制更直接:--memory=1g对应memory.max = 1073741824。一旦使用量超过这个值,内核直接 SIGKILL 进程,这就是 137 退出码的来源。
我踩过的坑:设--memory=1g但没设--memory-swap,Docker 默认 swap 是 memory 的 2 倍,所以容器实际能用 3G 内存。如果要完全禁用 swap,需要设--memory-swap=1g(和 memory 一样)。"
Q18:overlay2 存储驱动的工作原理是什么?
✅ 实实际回答:
"overlay2 把镜像的只读层和容器的可写层合并成一个统一视图:
lowerdir(只读层,可以多个) ↓ upperdir(可写层,容器的修改在这里) ↓ merged(联合挂载点,容器看到的完整文件系统) ↓ workdir(OverlayFS 内部使用)读取文件:先查 upperdir,没有再查 lowerdir,从上往下找
修改文件:从 lowerdir 复制到 upperdir(Copy-on-Write),在 upperdir 改
删除文件:在 upperdir 创建whiteout文件标记已删除
查看实际存储:
dockerinspect<容器>--format='{{.GraphDriver.Data}}'# 会看到 MergedDir、UpperDir、LowerDir、WorkDir 四个路径```" ---### Q19:容器里的进程 PID 是 1,和宿主机的 PID 1 有什么关系?**✅ 实际回答:** "没有关系。每个容器有独立的 PID Namespace。 - 容器内 PID1=你的应用主进程(比如 nginx master process) - 宿主机上看到的 PID 是一个完全不同的数字(比如12345)```bash# 宿主机上看psaux|grepnginx# root 12345 nginx: master process nginx -g daemon off;# 容器内看dockerexec<容器>psaux# PID 1 nginx: master process nginx -g daemon off;这就是 Namespace 隔离的效果——容器以为自己是 PID 1(系统的第一个进程),但实际上只是宿主机上的一个普通进程。
坑点:这就是为什么 Dockerfile 里 CMD 要用 exec 格式(CMD ["nginx"])而不是 shell 格式(CMD nginx)。shell 格式会在 nginx 前面套一层 sh,导致 sh 是 PID 1,nginx 是 PID 2,nginx 收不到 SIGTERM 信号,容器 stop 的时候只能等到超时被 SIGKILL。"
Q20:你有没有遇到过 Docker 相关的线上事故?怎么处理的?
这道题没有标准答案,面试官考察的是真实经历和反思能力。
参考回答框架(STAR 法则):
"Situation(背景):某天早上监控告警,线上某服务大量 502。
Task(问题):排查发现 3 个应用容器全部 OOM Killed 了。
Action(处理):
- 先临时重启容器恢复服务(
docker compose restart app) - 查 dmesg 确认是 OOM,看容器内存配置只有 512MB
- 拉取 profiling 数据,发现是某个接口有内存泄漏——每次请求创建大量临时对象没释放
- 临时调高内存限制到 1G,加了内存监控告警
- 跟开发一起修复了内存泄漏代码
Result(结果):
- 内存泄漏代码修复后发布,稳定运行
- 制定了规范:所有容器必须设置内存限制 + 内存告警
- CI 里加了 profiling 步骤,内存增长超阈值阻断构建"
附加题:系统设计(高级/架构岗)
Q21:从零搭建一套 Docker 容器化基础设施,你会怎么做?
考察系统设计能力,不需要面面俱到,挑重点说。
回答框架:
"镜像管理:Harbor 私有仓库 + Trivy 漏洞扫描 + 镜像签名
编排平台:Kubernetes(10+ 容器以上),小规模用 Docker Swarm 也行
CI/CD:GitLab CI + Harbor,流水线:lint → build → scan → push → deploy
监控:
- Prometheus 采集容器指标(CPU/内存/网络/磁盘)
- Grafana 做可视化看板
- AlertManager 接企业微信/钉钉告警
- cAdvisor 或 node-exporter 采集宿主机指标
日志:
- 容器日志输出到 stdout
- Promtail 收集 → Loki 存储 → Grafana 查询
- 比 ELK 轻量,资源占用少
链路追踪:Jaeger 或 SkyWalking
安全:
- 非 root 运行 + 只读文件系统 + 最小 capabilities
- 网络策略隔离(K8s NetworkPolicy)
- 密钥管理用 Vault
高可用:
- K8s 多 master + etcd 集群
- 应用多副本 + PodAntiAffinity 分散到不同节点
- PDB(Pod Disruption Budget)保证升级时至少可用副本数"
Q22:你们的微服务部署架构是什么样的?
考察架构全局视野。
回答要点:
"我们是典型的三层架构:
接入层:SLB(云负载均衡)→ Nginx Ingress → 网关(鉴权、限流、路由)
服务层:每个微服务一个 Deployment,3 副本起,Deployment + Service + Ingress。服务间调用用 gRPC + 服务发现(K8s Service 自带 DNS 解析)。
数据层:MySQL 主从(用 Operator 管理)、Redis Cluster、ES 三节点集群。
关键配置:
- 所有 Deployment 配
resources.limits(防止一个服务把节点资源吃光) - 所有 Deployment 配
readinessProbe(没准备好不接流量) - 所有 Deployment 配
livenessProbe(挂了自动重启) - 配 PDB 保证滚动更新时可用副本数"
面试技巧总结
回答原则
| 原则 | 说明 |
|---|---|
| 别背定义 | 说"我用过…"比"Docker 是…"好 100 倍 |
| 带数据 | "镜像从 1.2G 优化到 150M"比"优化了镜像大小"有说服力 |
| 说踩坑 | "我之前遇到过…"比"理论上应该…"更真实 |
| 承认边界 | "这个我没用过,但我理解原理是…"比硬编好 |
| 追问深挖 | 面试官追问时主动解释 why,不要只说 what |
不同级别侧重点
| 级别 | 重点 |
|---|---|
| 初级(1-3 年) | Docker 基础命令、Dockerfile 编写、简单 Compose 编排 |
| 中级(3-5 年) | 网络/存储原理、安全加固、CI/CD 集成、故障排查 |
| 高级(5 年+) | 架构设计、性能调优、生产事故处理、基础设施规划 |
建议:面试前对着每道题口述一遍,计时 2 分钟。能流畅说出就过关,卡壳的地方就是薄弱环节。