Docker 与部署
Docker 与部署
入门 / 中等 / 高难各 20 题,★ 为高频。项目事实核对于 2026-10-21,只说明本地源码与配置,不据此保证每个线上实例已经使用同样参数。
项目事实速查
| 事实 | 源码(相对 ../xknow-ai/) |
|---|---|
| buildx 默认构建 linux/amd64、linux/arm64 | docker/build.sh、docker/README.md |
| 同一镜像由 MODE 分配 Web、worker、beat 等进程;APP_SCOPE 区分应用范围 | docker/entrypoint.sh、app/app.py |
| NEED_MIGRATE=true 时先执行迁移,迁移子进程临时使用 APP_SCOPE=console;backend 配置承担迁移 | docker/entrypoint.sh、deploy/README.md |
| Swarm 的 Web/worker 用 start-first,Beat 用 stop-first;Web stop_grace_period=120s,worker=3900s,beat=30s | docker-compose/docker-compose-prd.yaml |
| Web SIGTERM 先标记 draining,ready 返回 503,默认等 15 秒再交给 Gunicorn 原处理器 | app/gunicorn_conf.py、app/utils/shutdown.py、健康检查入口 |
| Web 默认 graceful-timeout=90;Celery 自定义停机轮询上限 3600 秒 | docker/entrypoint.sh、app/extended/ext_celery.py |
| 沙箱命名空间隔离需要 SYS_ADMIN;共享目录有多节点存储局限 | docker/sandbox-init.sh、compose 的 cap_add/volumes、TODO.md |
官方核对:多架构构建、Swarm 滚动更新、Compose 服务配置、容器资源限制。示例配置应按实际 Docker / Compose / Swarm 版本确认支持情况。
Docker 与部署 入门(20 题)
1. ★ 镜像和容器的区别?
镜像是分层的文件系统与启动配置;容器是用它运行的进程实例,通常再加可写层。删除容器不等于删除镜像;重要数据要放持久卷或外部存储,不能只留在可写层。
2. ★ 容器和虚拟机有什么区别?
容器共享宿主机内核,靠 namespace 隔离视图、cgroup 管资源;虚拟机有自己的客户机内核。容器启动轻,但内核边界与权限配置很重要;macOS 的 Linux 容器实际运行在 Linux 虚拟机内。
3. Dockerfile 常用指令有哪些?
FROM 选基础镜像,WORKDIR 设工作目录,COPY 放文件,RUN 在构建时执行命令,ENV 设默认环境变量,USER 切身份,ENTRYPOINT/CMD 定启动方式。构建与启动是不同阶段。
4. ★ RUN、CMD、ENTRYPOINT 怎么区分?
RUN 改变构建层;ENTRYPOINT 定主程序,CMD 提供默认命令或参数。推荐 exec 数组形式,减少 shell 包装;shell 脚本最后用 exec,让应用收到容器停止信号。
5. ★ 为什么把依赖清单先 COPY?
依赖文件变化较少,先复制并安装依赖,再复制业务代码,可让普通代码改动复用安装缓存。依赖锁文件也要纳入缓存键;不要把整个工作区先复制进去导致每次都重装。
6. .dockerignore 有什么作用?
减少发送给构建器的上下文,排除 .git、虚拟环境、缓存和不需要的产物。也应排除私密配置;它不是秘密管理系统,构建所需凭证应通过 BuildKit secret 等方式临时提供。
7. ★ 多阶段构建解决什么问题?
在构建阶段安装编译器、生成依赖或静态产物,再把运行所需文件复制进最终阶段。能缩小运行镜像;仍要包含共享库、证书、字体等真实运行依赖,不能只看镜像大小。
8. 端口映射和 EXPOSE 有什么区别?
EXPOSE 是镜像元数据,不会自动开放宿主端口;-p 才发布端口。容器进程一般要监听 0.0.0.0;只监听容器内 127.0.0.1 时,发布端口也可能访问不到。
9. ★ bind mount 和 named volume 怎么选?
bind mount 显式使用宿主路径,便于查看但依赖该节点目录和权限;named volume 由 Docker 管理。两者默认都不会神奇地跨 Swarm 节点同步,本地 volume 的数据仍绑在节点上。
10. 环境变量什么时候读?
通常在进程启动或模块导入时读取。修改 .env 不会自动修改运行容器的环境,需按编排方式重建或更新;前端公开变量还可能在构建时固化,要分清构建期和运行期。
11. ★ 怎么查看日志和运行状态?
docker ps 看容器,docker logs 看标准输出,docker inspect 看状态和配置,docker stats 看资源。Swarm 再看 service ps/logs;查看配置时避免把密码、token 整段复制进排障记录。
12. restart policy 能解决程序错误吗?
它只能按条件重启失败进程,不能修复坏迁移、缺配置或依赖不可达。无限重启可能掩盖故障;应记录退出码与首次错误,限制重启节奏,并在健康探测中区分启动期与运行期。
13. ★ Compose 和 Swarm 的区别?
Compose 描述并运行多容器应用,常用于单机;Swarm 提供跨节点 service、调度、副本和滚动更新。docker compose up 与 docker stack deploy 对配置字段的支持和行为不完全相同。
14. 镜像 tag 和 digest 有什么区别?
tag 是可移动名字,digest 指向具体内容。发版用不可变版本并记录 digest,方便追溯与回滚;多个架构可能先由 manifest list/index digest 选择具体平台的镜像 digest。
15. ★ amd64 和 arm64 镜像为什么不能乱用?
本机二进制和 Python 原生扩展要匹配目标架构。多架构镜像让运行时选合适变体;只有一个平台时,另一平台需要模拟或重新构建。出现 exec format error 应先查架构和入口脚本格式。
16. 健康检查检查什么?
确认实例是否达到服务所需状态,避免只查进程还在。区分启动探测、存活与就绪的语义;Docker/Swarm 与其他编排器机制不同,代理是否依据健康状态摘流量也要实测。
17. ★ 一份镜像怎么跑多个角色?(项目)
entrypoint 按 MODE 启动 Gunicorn、Celery worker、Beat 等,APP_SCOPE 控制加载范围。每个角色独立 service、资源和副本配置,同一代码版本利于一致发布;不表示要把所有进程塞进同一容器。
18. 为什么 Beat 只开一个副本?(项目)
相同调度表多实例同时跑可能重复投递。项目以单副本 Beat 调度,worker 可多副本执行;任务仍要幂等。单副本重启期间可能错过触发,是否补偿要看具体调度和业务对账。
19. ★ SIGTERM 和 SIGKILL 的区别?
SIGTERM 给进程处理停机的机会;SIGKILL 无法捕获,清理函数和 finally 都可能不执行。Docker 通常先发停止信号,宽限结束再强杀;应用、代理与容器的时间预算必须配合。
20. 为什么不能把数据库数据只放镜像里?
镜像用于分发程序,运行时数据写入容器可写层后,重建容器可能丢失。数据库要有持久卷、备份及恢复验证;有卷只能应对容器替换,不能防误删、磁盘损坏或节点丢失。
Docker 与部署 中等(20 题)
1. ★ buildx 多平台如何工作?(项目)
可通过 QEMU 模拟、多台原生构建节点或交叉编译生成各平台镜像,再组成一个清单。项目脚本默认 amd64+arm64;构建完成还要分别验证运行依赖,不能只看 manifest 有两个条目。
2. --load 和 --push 有什么区别?
load 导入本地镜像存储,push 上传 registry;多平台能否直接 load 取决于驱动和镜像存储。项目脚本对多平台加 --load 做了限制,别把脚本限制泛化成所有新版 Docker 都不支持。
3. ★ 怎样让构建缓存更稳定?
固定基础镜像/依赖版本,分离依赖层,使用缓存挂载与远程缓存,减少无关文件进入上下文。缓存不是备份;凭证不能写进缓存层,构建产物仍需可从锁定输入重新生成。
4. secrets 和 ARG/ENV 有什么区别?
ARG 用于构建,ENV 进入运行配置;都不适合长期保存密钥,层、构建记录或 inspect 可能暴露。构建用临时 secret,运行用受控 secret 挂载或密钥服务,限制读取身份并支持轮换。
5. ★ start-first 是否保证零中断?(项目)
它先启动新 task 再停旧 task,有助于保持容量,但不保证应用已就绪、代理已摘旧流量或长连接已结束。还要有可用余量、健康探测、兼容协议及停机排空,单看这一个字段不够。
6. 优雅停机的顺序怎么设计?(项目)
项目 Web 收到 SIGTERM 后标记 drain,让 ready 返回 503,等默认 15 秒供代理感知,再触发 Gunicorn 原退出流程。总宽限要覆盖摘流量、在途请求和安全余量,不能把 90 秒全分给业务请求。
7. ★ Web 和 worker 的停止时间能统一吗?(项目)
不能照抄。配置里 Web 是 120 秒,worker 是 3900 秒,Beat 是 30 秒;worker 自定义等待上限 3600 秒。长 Agent 仍可能超过 Web 宽限,业务恢复与任务对账不能省。
8. backend 跑迁移就不会并发迁移吗?(项目)
当前约定把迁移责任放 backend,但 backend 多副本或 start-first 仍可能同时启动。可靠方案是独立一次性迁移作业或全局迁移锁;脚本没有实现全局锁,不能声称已有严格串行保证。
9. ★ 数据库变更如何兼容滚动发布?
采用扩展再收缩:先加兼容字段/索引,再发布能同时读旧新的代码,回填并切读,最后删旧结构。回滚应用前检查旧代码能否读新数据;一次不可逆 DDL 会让镜像回滚失去意义。
10. Docker 里进程为什么没有收到 SIGTERM?
检查 PID 1 是 shell 还是应用,脚本是否用 exec、信号是否被转发。项目 entrypoint 使用 exec;多进程应用还需要回收子进程,必要时用 init。不要为了快退出直接改成 kill -9。
11. 容器 CPU 限制和并发数如何配合?
CPU quota 限制可用计算时间,不等于分配了同样数量的独占核。过多 worker 会争抢并受节流,IO 任务还受连接池和下游限额约束;结合吞吐、尾延迟和 throttling 选并发。
12. ★ OOMKilled 怎么定位?
看容器退出原因、cgroup 内存限制和时间曲线,区分 RSS、缓存、子进程峰值及泄漏。先限制单任务内存与并发,再优化或隔离解析任务;只调高限制可能把宿主机也拖垮。
13. overlay 网络和端口发布是什么关系?
overlay 让跨节点容器通信,服务发现提供服务地址;published port 让集群外访问。排查要分 DNS、服务 VIP/直连、容器监听和入口网络,不能认为端口已发布就证明后端进程健康。
14. 代理转发 SSE 要注意什么?
避免缓冲和压缩策略造成长时间攒包,设置合适读空闲超时、心跳和连接上限。实际观察首字、事件间隔和断线重连;普通短 GET 成功不代表流式链路配置正确。
15. ★ 为什么沙箱要 SYS_ADMIN?(项目)
当前隔离脚本需要建立 mount/pid 等命名空间并执行挂载操作,因此配置能力。这个能力很宽,不能叫无风险沙箱;只在必要执行角色开放,并配合非特权执行、路径隔离和挂载控制。
16. 非 root 容器有什么价值与局限?
降低进程直接修改宿主资源的机会,但 bind mount 权限、额外 capabilities、内核漏洞仍影响边界。不要为了绕目录权限把容器全部改回 root;先匹配 UID/GID 和需要写的路径。
17. 日志怎么集中和限量?
应用写标准输出或受控日志出口,采集系统加 service、版本、实例、trace_id。设置轮转和容量,避免 JSON 日志撑满系统盘;对 prompt、token、个人数据做必要的字段筛选。
18. ★ 配置变更为什么也算发布?
模型地址、队列列表、超时或权限开关会直接改变行为。记录配置版本与镜像版本,先校验必填项再启动;运行配置变更要有灰度与回退,不能只追踪代码提交。
19. Swarm 单节点故障后数据为什么没跟着来?
调度器能把容器拉到别处,但本地 bind mount/volume 数据留在原节点。用共享存储、节点约束或外部对象存储,并明确可用性取舍;服务副本数与数据副本数不是一回事。
20. ★ 怎么做发布后的最小验收?
核对 digest、配置版本、迁移版本和副本健康;走登录、权限读取、一次流式对话、任务投递与产物读取。看新错误率和尾延迟再放量;健康接口 200 不能替代关键业务路径。
Docker 与部署 高难(20 题)
1. ★ 滚动更新出现间歇 502 怎么查?
对齐新旧 task 启停、ready 状态和代理 upstream 日志,检查旧连接是否还被转发、新实例是否过早接流量。验证 15 秒摘流等待覆盖探测周期与失败阈值;不要只增 Gunicorn timeout。
2. SSE 请求没结束,容器却到期强杀怎么办?
把「HTTP 连接存活」与「业务运行存活」分开,事件和检查点持久化,客户端重连重放。Web 停机宽限不能无限延长;需要运行租约/恢复机制时应明确设计,当前僵尸清扫不等于接管成功。
3. ★ 发布后卡片一直生成中怎么办?(项目)
源码每 5 分钟清扫静默超过阈值的 generating 卡,默认静默阈值 45 分钟,收尾失败并发结束事件。它修复展示和占用额度,不能恢复中断计算;租约加接管仍属于后续方案。
4. worker 还有任务却已经退出?(项目)
核对 task 级 ACK 配置、active_requests、停机钩子和 3900 秒容器宽限。bench 显式早确认,其他任务多为晚确认,不能统一说都会重投;故障后要按业务状态对账。
5. ★ migration 成功,新镜像启动失败如何回滚?
先判断 schema 是否向后兼容;兼容时回退镜像并保留扩展结构,避免盲目 downgrade 丢数据。不兼容变更要按预案前滚修复或恢复数据;镜像回滚不等于数据库也回到了原样。
6. 新旧 worker 处理同一队列导致参数错误?
滚动期任务格式要兼容,加参数给默认值,删除参数分阶段。重大变更用新任务名/新队列,新消费者先就绪再切生产端,旧队列排空后下线;保留幂等键避免迁移重复执行。
7. 镜像在 arm64 能跑,amd64 导入库失败?
核对 wheel/共享库是否来自目标平台,是否把宿主 node_modules/.venv COPY 进镜像,以及模拟构建是否漏依赖。两平台分别做启动和核心功能冒烟,不能只验证构建指令返回 0。
8. ★ registry tag 相同但副本行为不同?
tag 可能被覆盖,节点缓存或部署记录也可能不同。核对实际平台 digest 与镜像清单关系、环境配置和挂载文件;用不可变 tag/digest 发布,记录每个 task 的镜像身份再做回退。
9. 两副本都健康,用户文件偶尔找不到?(项目)
检查请求是否落在不同节点,而沙箱目录只是各节点本地挂载。单机共享目录对同机副本有效,不代表跨节点一致;短期做调度约束,长期采用支持所需文件语义的共享存储或产物服务。
10. ★ 给 Agent 执行环境开放 Docker socket 行吗?
socket 能控制宿主 Docker,常接近宿主高权限,普通非 root 用户也无法消除这个风险。按需求使用受限执行服务、独立宿主或更强隔离,工具接口只暴露所需操作并校验范围。
11. 一次构建为什么能耗尽宿主磁盘?
镜像层、BuildKit cache、日志、导出产物和卷分别统计,先查大头。删除前确认运行依赖和保留版本;定期按容量策略回收。不能把 docker system prune 的大范围删除当成日常排障第一步。
12. 容器内 DNS 偶发失败怎么排?
比较容器与宿主解析、服务名和外部域名、UDP/TCP、超时重试,检查连接跟踪及 DNS 压力。记录失败的确切域名与时间,区分解析失败、TCP 建连失败和应用层 5xx。
13. ★ 服务未就绪却被自动回滚?
检查初始化耗时、healthcheck start_period/interval/retries 与 update monitor 是否匹配。启动探测过短会误判,过长会延迟故障发现;先优化启动重任务,再用实际分布设探测预算。
14. start-first 为什么可能让服务更差?
新旧副本同时在时,CPU、内存、连接池和下游并发短暂增加;固定 host port 或单写资源也可能冲突。留发布容量、降低并行度,或为特殊单例角色用合适的更新顺序。
15. ★ Beat start-first 会不会重复触发?
会有新旧实例短暂重叠的风险,单副本不等于更新时绝无重叠。单例调度应使用合适停启顺序或领导租约,业务层按触发槽位幂等。要核对 Beat 自己的 update_config,不能套 Web 的策略。
16. 数据库连接为什么发版瞬间打满?
start-first 使新旧实例连接上限相加,Gunicorn/Celery 每个进程还各有 Engine。按发布峰值估算连接预算,减少启动预热尖峰并控制迁移连接;增大 pool_size 可能雪上加霜。
17. 灰度发布怎样兼容有状态会话?
会话和事件放共享存储,格式兼容新旧版本,必要时按会话固定路由。粘性路由只能减少切换,不能替代崩溃恢复;灰度指标按版本分组,防止整体平均掩盖小流量新版本错误。
18. ★ 怎么做服务故障演练?
在隔离的本机副本模拟优雅停机、强杀、依赖短断、重复投递与磁盘压力,观察连接重放、任务恢复和权限边界。先定义可接受的数据丢失/恢复时间,再验证,不能只看容器是否重新启动。
19. 如何安排回滚与前滚?
无数据格式破坏、故障集中在新代码时优先回退;schema 已不可逆或旧代码不兼容时考虑前滚修复。保留兼容窗口、旧镜像和恢复操作记录;发布前把判断条件写清,而非故障时临时猜。
20. ★ 面试怎么回答「你们是否零停机」?
可以说采用 start-first、ready 摘流与优雅停机降低中断,但长任务、单例切换、数据库兼容和跨节点文件仍有边界。项目 Web/worker 的宽限分开讲;没有压测或故障数据就不承诺绝对零中断。
练习与验证边界
本篇以源码配置和口述场景为主,没有执行 stack deploy、镜像推送、数据库迁移或改动运行服务。只读配置解析、入口脚本语法检查和核对结果记录在 验证记录.md。部署命令涉及真实环境,复习时不要为验证文档直接执行发布。
九、Docker / 部署
入门
- 镜像和容器? 镜像是只读模板,容器是镜像的运行实例。
- Dockerfile 常用指令?
FROM、RUN、COPY、ENV、WORKDIR、EXPOSE、ENTRYPOINT/CMD。
中等
- 怎么让镜像更小、构建更快? 多阶段构建;把变化少的层(安装依赖)放前面,充分利用层缓存;
.dockerignore;用 slim 基础镜像。 - 一份镜像怎么跑多种角色? 我们的入口脚本按环境变量
MODE(backend / worker / beat)和APP_SCOPE(web / console / gateway)决定启动 gunicorn 还是 celery、加载哪些模块。好处:一次构建,多处部署,版本一致。
高难
- 多副本下数据库迁移谁来跑? 只让一个服务跑(
NEED_MIGRATE=true),其他服务关闭,防止并发迁移冲突;迁移和发版的先后看 DDL 方向(加列先迁移,删列先发版)。 - 沙箱进程要用
unshare,容器需要什么权限? 需要SYS_ADMINcapability。这是一个安全取舍:给容器更高权限,换取容器内用户之间的隔离;所以只给需要沙箱的服务加。
