Pod 才是 Kubernetes 的最小调度单位
Kubernetes 不直接把单个容器调度到 Node,而是调度 Pod。一个 Pod 可以包含一个或多个容器,这些容器会被当作一个整体:
- 被调度到同一台 Node;
- 共享同一个网络命名空间;
- 拥有相同的 Pod IP;
- 可以通过 Volume 共享文件;
- 一起创建,也一起消失。
本项目每个号池只有一个 runtime Pod,但里面同时运行:
| |
这不是三个 Pod,而是一个 Pod 中的一个初始化容器和两个长期运行容器。
为什么 CPA 与 cpa-manager 放在同一个 Pod
cpa-manager 需要紧密访问 CPA,并与它共享持久数据。放在同一个 Pod 后,二者可以通过 loopback 通信:
| |
这里不经过 Service、集群 DNS 或 CNI 网络。对 cpa-manager 来说,CPA 就像运行在同一台机器上的另一个本地进程。
共享 Pod 的好处:
- 本地通信简单,延迟低;
- 两个容器一起调度,不会出现 Manager 在 A 节点、CPA 在 B 节点;
- 可以自然共享同一个 RWO PVC;
- 生命周期耦合关系与业务现实一致。
代价也很明确:
- 两者不能独立扩缩容;
- Pod 重建时二者一起重启;
- 任一容器一直不 Ready,整个 Pod 就不 Ready;
- 两个容器的资源总和决定这个 Pod 的调度需求。
因此“多容器 Pod”适合强耦合进程,不应只为了少写一个 Deployment 就随便合并。
共享网络不等于共享端口
同一个 Pod 的容器共享 IP 和端口空间。CPA 监听 8317,cpa-manager 监听 18317:
| |
因为端口空间共享,两个容器不能同时监听同一个 IP 和端口。若都尝试监听 0.0.0.0:8317,后启动的进程会因为端口被占用而失败。
容器在 YAML 中声明 containerPort,主要用于表达和命名端口,并不会像 Docker 的 -p 那样自动把它暴露到集群外。真正的稳定入口由 Service 提供。
InitContainer:开店前先布置柜台
InitContainer 在普通容器启动前依次执行。只有所有 InitContainer 成功退出,主容器才会启动。
本项目的 initialize-config 负责:
- 在共享数据卷中创建目录;
- 从 Secret 挂载文件读取凭据;
- 根据 Operator Configuration 注入的 Mihomo URL 生成 CPA 配置;
- 只在配置文件不存在时写入,避免每次重启覆盖运行数据。
关键逻辑可以概括为:
| |
这叫幂等初始化:执行一次和重复执行多次,最终结果一致。Pod 因节点维护被重建时,PVC 上已有配置不会被覆盖。
InitContainer 失败时,主容器不会启动。排查时可查看:
| |
不要只看 cpa 容器日志;如果初始化阶段就失败,它甚至还没开始运行。
Volume:Pod 内的文件连接器
容器文件系统默认是临时的。Volume 让 Pod 把外部数据源或共享存储挂进容器。
本项目 runtime Pod 使用三种 Volume:
| Volume | 来源 | 用途 | 是否持久 |
|---|---|---|---|
data | StatefulSet 的 PVC | CPA 与 Manager 数据 | 是 |
credentials | Secret | 三个凭据文件 | 随 Secret 存在 |
bootstrap | ConfigMap | 初始化脚本 | 随 ConfigMap 存在 |
Volume 定义“数据从哪里来”,volumeMounts 定义“在某个容器中挂到哪里”。同一个 Volume 可以在不同容器里挂到不同路径。
一个 PVC,如何避免两个容器互相踩文件
data PVC 在 InitContainer 中挂载到 /data。主容器则使用 subPath 把其中不同部分挂到各自路径:
| |
CPA 获得:
| |
cpa-manager 获得:
| |
这样既共享同一个 PVC,又保持目录边界清楚。
需要注意:subPath 不是权限隔离机制。容器是否能看到其他目录取决于挂载方式,但底层仍是同一个卷;真正的安全还依赖进程身份、文件权限和应用设计。
Secret 与 ConfigMap 为什么挂成文件
本项目把凭据投影为 /run/secrets/... 下的文件,只选择约定的 key:
| |
相较于直接把敏感值写进命令参数,文件方式能减少它出现在进程参数、YAML 和日志中的机会。初始化脚本读取后把必要内容写入权限受限的持久配置。
ConfigMap 中的初始化脚本则以只读、可执行模式挂到 /bootstrap/initialize.sh。普通配置和敏感凭据被明确分开:脚本可以进 Git,Secret value 不可以。
Pod SecurityContext 与 Container SecurityContext
Pod 级配置决定共同的进程和文件身份:
| |
容器级配置进一步限制:
| |
fsGroup 很重要:PVC 挂载后,kubelet 会尽量让卷中的文件对这个组可访问,否则非 root 容器可能无法写入数据卷。
安全配置不是“字段越多越安全”。它必须与镜像实际行为匹配。如果应用硬编码要求 root 或尝试写只读目录,Pod 会启动失败。正确做法是修正镜像和写路径,而不是第一时间取消安全约束。
terminationGracePeriodSeconds 的意义
项目为 runtime Pod 设置 30 秒优雅终止时间。Pod 被删除时,大致经历:
- kubelet 向容器主进程发送
SIGTERM; - 应用有机会停止接收请求、刷新文件、关闭数据库;
- 最多等待 30 秒;
- 仍未退出时才会被强制终止。
对于使用 SQLite 或持久配置的应用,优雅退出比无状态 HTTP 服务更重要。应用也必须正确处理 SIGTERM,否则 YAML 中的等待时间只是空等。
镜像拉取策略
本项目按镜像字符串选择策略:
- 以
:latest结尾:Always; - 其他标签或 digest:
IfNotPresent。
开发时 latest 方便快速替换,生产更推荐不可变 digest:
| |
同一个 tag 的内容可能变化,导致重建后的 Pod 与之前运行的并非同一版本。digest 把“我想运行哪个镜像”变成可审计的精确声明。
如何观察多容器 Pod
| |
kubectl logs 面对多容器 Pod 时应明确 -c。--previous 用于查看同一个容器上一次崩溃实例的日志,对 CrashLoopBackOff 很有用。
还可以查看每个容器的状态:
| |
一个容易误解的地方:两个容器不等于高可用
CPA 与 cpa-manager 在同一个 Pod 中,是为了协作和共享状态,不是冗余副本。这个 StatefulSet 的 replicas 是 1,PVC 又是 RWO,所以数据面仍是单实例。
Operator 自己运行两个副本并通过 leader election 提供控制面接管能力,但那不会把号池 runtime 自动变成双活。控制面高可用和数据面高可用必须分开讨论。
理解 Pod 的关键不是记住字段,而是看清“哪些进程必须同生共死、共享什么、通过什么通信”。本项目把 CPA 和 cpa-manager 放进同一个 Pod,正是对这种强耦合关系的明确表达。