Skip to content

Docker:构建、PID 1、网络与卷 ​

先明确Context、store/gateway、项目名与 counter 卷,再对照配套示例理解容器日志、UPSTREAM 和实际挂载。

构建输入和缓存 ​

Docker 构建上下文是发送给 builder 的输入集合,COPY 只能取上下文内未被 .dockerignore 排除的文件。缓存命中表示输入匹配,不代表基础镜像已更新、依赖安全或应用测试通过。本例白名单仅放行 app.py、requirements.txt 和 Dockerfile,防止把本机凭据带入构建。

Dockerfile 中每个阶段有自己的文件系统;多阶段构建通过 COPY --from 只带走所需产物,减少运行镜像的编译器与依赖。对纯 Python 标准库项目,强加多阶段没有收益,因此主示例采用单阶段。对编译项目,应把依赖描述先复制再安装、最后复制源码,以缩小缓存失效范围;不要为缓存命中把密钥写入层。

镜像 tag 是可移动引用,digest 是内容标识。相同 tag 在两次构建中可能解析到不同基础镜像;digest 固定也意味着安全补丁不会自动进入,需有更新与重建流程。CPU 架构与 libc 不匹配可导致 exec format error 或缺动态链接器,与容器网络无关。

PID 1 与停止信号 ​

Docker PID 1 决定容器寿命。exec form 的 ENTRYPOINT 让 Python 直接成为主进程,收到停止信号;shell form 若没有 exec,信号可能停在 shell。PID 1 还涉及孤儿子进程回收,频繁启动子进程的应用可考虑 init: true;本例不启动子进程,因此只实现 SIGTERM/SIGINT 与 HTTPServer.shutdown。

停止不是立即断电:Engine 先发送停止信号,等待 stop_grace_period,超时再强制终止。本例在独立线程调用 shutdown,避免在 serve_forever 所在线程的信号回调里死锁;单线程 HTTPServer 对一个请求执行完后停止接收。它不是高吞吐生产服务器,慢连接和并发能力需独立设计。

网络、DNS 与端口 ​

Docker 网络 DNS 在自定义 bridge 内按服务名解析,gateway 用 store:8080 访问同网络容器。localhost 始终指当前容器自己的网络命名空间,不能拿 localhost 当另一个服务。服务被替换后 IP 可变,稳定服务名比写死容器 IP 更可靠。

EXPOSE 是镜像元数据,不创建宿主监听。ports 才发布端口,本例只绑定宿主回环 127.0.0.1:18080;容器内部应用须监听 0.0.0.0:8080 才能接受其他容器连接。发布端口、容器 DNS 与应用监听是三个独立检查点。

卷、权限与数据库一致性 ​

Docker 卷 持久化要同时考虑生命周期与 UID。counter named volume 挂到 /data,由镜像预建目录的 10001:10001 权限初始化;已有卷保留自身权限,不会因新镜像 chown 自动修正。只读根文件系统仍允许显式挂载的可写数据卷,SQLite 的数据库和 journal 都必须能在 /data 创建。

bind mount 把 daemon 主机目录直接放进容器,依赖绝对路径、权限与 Linux 安全标签;volume 由 Docker 管理位置。两者都可能遮蔽镜像目录原内容。数据库运行时直接 tar 卷可能复制到事务中间态;应先停止写入或使用 SQLite 在线备份接口,并验证恢复到新卷。

Compose 启动与持续可用 ​

Docker Compose 健康启动 不等于就绪保证:service_started 只等进程启动;service_healthy 等指定 healthcheck 成功后再创建依赖方。这里 store 的 /ready 实际读 SQLite,gateway 的 /ready 请求 store,因此比仅探端口更贴近依赖就绪。但初次通过后依赖仍会故障,Compose 不会自动摘除宿主发布端口,也不会因为 unhealthy 自动重启服务;应用仍需超时、重连与错误处理。

healthcheck 结果来自当前检查时刻,不是未来请求的保证。gateway 的 /live 不依赖 store,可以区分自身进程活着与下游不可用;示例返回 503,不进行有副作用请求的盲目重试。

构建秘密 ​

Docker 构建秘密 应经 BuildKit secret mount 临时传入,ARG/ENV 可能进入镜像、元数据或历史。Dockerfile.secret 是独立教学构建目标,只检查挂载非空,不输出值,也不把 Secret 内容复制到后续阶段;最终阶段引用前一阶段的普通文件,让该构建阶段确实属于依赖图。Secret 挂载不会阻止恶意 RUN 主动泄密,仍要信任构建定义和基础镜像。

秘密内容变化通常不作为缓存失效依据;需要重取私有产物时,使用非敏感版本标识控制缓存,不能以“secret 已变所以一定重建”为假设。运行时 Compose 文件 Secret 和构建 Secret 是不同生命周期;本例主程序不需要 Secret,避免为了演示引入无用凭据。

机制练习与来源 ​

问题:store 先健康、随后退出,gateway 是否自动消失?答案:不会。依赖条件控制创建次序,持续请求由应用返回 503;是否重启、告警与停止发布需单独设计。

问题:镜像层里先 COPY 密钥再 RUN rm 能删干净吗?答案:不能,前层仍包含文件,应从输入和挂载路径避免入层。

延伸原手册:镜像、生命周期、网络、存储、Dockerfile、BuildKit。一手依据:Dockerfile reference、Volumes、Startup order、Build secrets。核对日期 2026-10-17。

外部网络、挂载目标与端口的归属 ​

旧 Compose 的 external 网络和单路径 volumes 为什么需要单独核对? 多服务文件必须读出资源由谁创建、由谁清理。历史配置先创建名为 cc 的网络,再把 default 声明为 external;这样的网络属于外部依赖,不能期待另一个项目的 up 自动补齐它,也不能因为服务名相同就推断两个项目互通。共享网络只连接确实需要互访的服务;地址表记录调用方、服务名、容器监听端口和宿主发布端口,容器间连接使用容器端口。旧监控片段中的 29000:9000 仅说明一条映射,不能证明进程监听 9000。Compose 网络说明了 external 网络和服务间寻址(核对 2026-10-17)。

挂载也不能只看到 volumes 就认定可备份。旧 Grafana 条目只有单个路径 /root/example-user/docker/var/lib/grafana,没有“宿主源:容器目标”的配对,因此不能按作者注释推断它将宿主目录绑定到了 /var/lib/grafana;应对照短语法与实际挂载目标。MySQL 的 my.cnf 与数据目录、Nginx 的配置/站点/日志、MinIO 的对象数据是不同生命周期。迁移时为每个条目写明类型、源、目标、读写属性、UID/GID 和备份归属,确认它覆盖了应用真正写入的位置;不能以复制 Compose 文件代替备份数据。卷与Compose volumes 字段给出语法依据。

继续阅读