Kustomize 部署、生命周期操作与排障

先渲染,再写入集群

Kustomize 不是模板语言,它以 Kubernetes YAML 为基础,通过 resources、patches、images、namespace 等机制组合最终 manifests。kubectl 已内置常用 Kustomize 能力:

1
2
kubectl kustomize <目录>
kubectl apply -k <目录>

前者只渲染到标准输出,不修改集群;后者把渲染结果提交给 API Server。

一个稳妥习惯是:

1
2
3
4
先看最终会提交什么
  → 做本地检查
  → 再 apply
  → 最后观察状态

本项目的 Kustomize 分层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
config/
  ├─ crd/       SubPool CRD
  ├─ rbac/      ServiceAccount、权限与绑定
  ├─ manager/   Operator Deployment、metrics Service、PDB
  └─ default/   组合上述资源,加 Namespace 与 Operator Configuration

deploy/
  ├─ operator/  Operator 正式入口,并覆盖镜像
  └─ gateway/   Shared Mihomo 与 Envoy Gateway

examples/
  ├─ subpool-internal.yaml
  └─ mihomo/    引用共享 Mihomo manifests 的兼容入口

deploy/operatordeploy/gateway 是独立入口。安装 Operator 不会自动安装 Gateway,也不会创建 SubPool 或 Secret。

这能防止一次看似普通的控制面安装,顺便启动真实代理或创建业务实例。

resources 是组合,不是复制

例如 config/default/kustomization.yaml 引用:

1
2
3
4
5
6
resources:
  - namespace.yaml
  - ../crd
  - ../rbac
  - operator-config.yaml
  - ../manager

Kustomize 递归加载这些资源,形成一个最终对象集合。deploy/operator 再引用 config/default,并通过 images 修改 Operator 镜像。

因此修改基础文件会影响所有引用它的入口。排查最终内容时,不要只看当前目录那几行 kustomization,要渲染完整结果。

images transformer 做什么

项目通过:

1
2
3
4
images:
  - name: registry.example.invalid/example-org/subpool-operator
    newName: example-org/subrelay-operator
    newTag: "example-tag"

把基础 Deployment 中的镜像名称和 tag 替换为环境要使用的版本。

远程集群必须能从它自己的节点网络拉取该镜像。本地电脑能 docker pull,不代表集群 Node 一定能拉取。

生产更推荐不可变版本或 digest,并在发布前确认:

  • registry 可达;
  • 私有仓库凭据已配置;
  • CPU architecture 匹配;
  • 镜像 tag/digest 正确;
  • imagePullPolicy 符合预期。

Dockerfile 的两阶段构建

项目先在 Go 镜像中编译静态 manager,再复制到 distroless nonroot 镜像:

1
2
3
4
golang:1.26.0
  → go build /out/manager
  → gcr.io/distroless/static:nonroot
  → USER 65532:65532

优点是最终镜像不包含完整编译工具链和 shell,体积与攻击面更小。代价是进入容器临时执行 shell 命令并不可用,排障应依赖日志、metrics、健康端点和 Kubernetes 对象,而不是假设能 sh 进去。

部署前本地验证

项目提供:

1
2
3
4
5
GOCACHE=/tmp/subrelay-operator-go-build-cache go test ./...
bash deploy/tests/render.sh
kubectl kustomize deploy/operator >/dev/null
kubectl kustomize deploy/gateway >/dev/null
kubectl kustomize examples/mihomo >/dev/null

这些命令不向集群写资源。它们分别验证 Go 行为和 manifests 能否渲染,并检查渲染结果是否违反项目约束。

本地通过不保证集群部署一定成功,因为 StorageClass、Gateway API、镜像仓库和 CNI 等依赖属于目标环境。

推荐部署顺序

第一步:构建并准备 Operator 镜像

1
docker build -t subpool-operator:latest .

远程集群需推送到可拉取 registry,并更新 deploy/operator/kustomization.yaml

第二步:渲染并安装 Operator Bootstrap

1
2
kubectl kustomize deploy/operator
kubectl apply -k deploy/operator

这会安装:

  • subpool-system Namespace;
  • SubPool CRD;
  • RBAC;
  • immutable Operator Configuration;
  • Operator Deployment;
  • metrics Service;
  • Operator PDB。

检查:

1
2
3
4
kubectl -n subpool-system get deployment,pod,service
kubectl get crd subpools.subrelay.example.com
kubectl -n subpool-system logs deployment/subpool-operator \
  --all-containers=true --tail=200

第三步:按需安装共享 Gateway

先通过安全流程创建 mihomo-gateway-providers Secret,再执行:

1
kubectl apply -k deploy/gateway

这会安装 subpool-gateway Namespace、Shared Mihomo、GatewayClass 和 Gateway。它不会安装 Envoy Gateway controller,也不包含 Secret value。

检查:

1
2
3
4
kubectl -n subpool-gateway get deployment,pod,service,gateway
kubectl get gatewayclass subpool-envoy
kubectl -n subpool-gateway get endpointslice \
  -l kubernetes.io/service-name=mihomo-gateway

第四步:创建第一个 Internal Pool

1
2
3
kubectl apply -f examples/subpool-internal.yaml
kubectl get sp demo-pool
kubectl describe sp demo-pool

Internal 是最适合第一次验证的模式,因为它不依赖外部 DNS、TLS 和 Gateway route。

创建后的观察顺序

1
2
3
4
5
kubectl get sp demo-pool -o yaml
kubectl get namespace subpool-demo-pool
kubectl -n subpool-demo-pool get \
  statefulset,service,pvc,pod,networkpolicy,poddisruptionbudget
kubectl -n subpool-demo-pool get events --sort-by=.lastTimestamp

等待 Ready:

1
2
kubectl wait --for=condition=Ready \
  subpool/demo-pool --timeout=300s

若超时,立即转为 Conditions 驱动的排查,不要反复 apply 同一文件期待随机恢复。

Internal 模式的访问

1
2
3
kubectl -n subpool-demo-pool port-forward service/cpa 8317:8317
kubectl -n subpool-demo-pool port-forward \
  service/cpa-manager 18317:18317

两个命令需要分别占用一个持续运行的终端。它们只适合开发与受控管理访问。

更新操作

镜像或 CPU/内存资源应修改 SubPool spec,由 Operator 同步到 StatefulSet。生产镜像优先使用 digest。

不要直接编辑受管 StatefulSet。即使暂时成功,下一轮 reconcile 也会按 SubPool 恢复。

storage.size 当前不可修改。若业务需要更大容量,必须先设计受支持的数据迁移或未来扩容流程,不能通过手工改 PVC 与 CR 制造状态分叉。

暂停与恢复

暂停:

1
2
kubectl patch subpool demo-pool --type=merge \
  -p '{"spec":{"suspend":true}}'

预期结果:

  • StatefulSet replicas 为 0;
  • HTTPRoute 被删除;
  • Namespace、PVC、Secret 保留;
  • Ready=False,reason 为 Suspended。

恢复:

1
2
kubectl patch subpool demo-pool --type=merge \
  -p '{"spec":{"suspend":false}}'

Operator 会重新创建运行 Pod,并继续使用旧 PVC。

删除前先停一下

1
kubectl delete subpool demo-pool

删除会销毁专属 Namespace、PVC、Credential Secret 和运行数据。当前项目不提供备份恢复。

不要使用 --force,不要手工删除 subrelay.example.com/finalizer。如果只是临时不用,选择 suspend。

删除卡住时检查:

1
2
3
kubectl get sp demo-pool -o yaml
kubectl get namespace subpool-demo-pool -o yaml
kubectl -n subpool-demo-pool get pvc -o yaml

关注 DeletionBlocked、Namespace/PVC finalizers 以及负责相关资源的第三方 controller。

Conditions 驱动的排障地图

Condition/Reason首要对象
OperatorConfigurationInvalidsubpool-system 中版本化 ConfigMap 与 Operator 参数
InvalidPoolResourcesSubPool 的 requests/limits
ResourcesExceedBaselineSubPool 合计申请与 maxPerPool
NamespaceConflict派生 Namespace labels、ownerReferences、UID
CredentialConflictsubpool-credentials ownerReferences
CredentialInvalidSecret 必需 key 是否存在且非空,不读取 value
WorkloadMissingStatefulSet 是否创建
WorkloadsNotReadyPod、PVC、镜像、探针和 Event
ServiceMissingCPA 与 cpa-manager Service
EndpointsUnavailableShared Mihomo Pod 与 EndpointSlice
RouteNotAcceptedHTTPRoute parent conditions 与 allowedRoutes
GatewayNotProgrammedEnvoy Gateway controller 和 Gateway status
NamespaceTerminatingNamespace 内资源及第三方 finalizers

三层排障法

第一层:API 与控制器

1
2
3
kubectl get sp demo-pool -o yaml
kubectl -n subpool-system logs deployment/subpool-operator \
  --all-containers=true --tail=200

确认 CR 被接受、generation 被观察、Operator 没有 Forbidden 或 reconcile error。

第二层:Kubernetes 资源

1
2
3
kubectl -n subpool-demo-pool get statefulset,pod,pvc,service
kubectl -n subpool-demo-pool describe pod runtime-0
kubectl -n subpool-demo-pool get events --sort-by=.lastTimestamp

确认调度、存储、镜像和探针。

第三层:数据路径

1
2
入站:Gateway → HTTPRoute → CPA Service → Endpoint → Pod
出站:CPA → Mihomo Service → Endpoint → Mihomo → 上游代理

每次只验证相邻两个跳点,避免一句“网络不通”覆盖十种不同故障。

不要用破坏架构的方式排障

以下动作可能让表面症状消失,却破坏项目边界:

  • 删除 default-deny NetworkPolicy;
  • 给 CPA 临时开直接公网出站后忘记恢复;
  • 把 CPA Service 改成每 Pool LoadBalancer;
  • 给 cpa-manager 创建公网 Route;
  • 强删 Namespace 或 finalizer;
  • 手工覆盖 Credential Secret;
  • 直接修改 Operator 管理的 StatefulSet。

更可靠的做法是记录具体 Condition、对象、Event 和时间点,修复负责该层的依赖,再验证状态是否自然收敛。

最终验收清单

  • Operator 两个 Pod 正常,leader election 无持续错误;
  • SubPool CRD 已安装,kubectl get sp 可用;
  • Operator Configuration 名称与 Deployment 参数一致;
  • Shared Mihomo 有 Ready Endpoint;
  • SubPool 派生 Namespace 所有权正确;
  • runtime StatefulSet、PVC、两个 Service 均存在;
  • Pod probes Ready,重启次数无异常增长;
  • Internal 模式 port-forward 可用,或 Gateway 模式 Route Accepted 且 Gateway Programmed;
  • metadata.generation == status.observedGeneration
  • Ready=True 的含义没有被误解为账号和真实模型请求也已验证。

部署完成不是“apply 命令退出码为 0”,而是从声明、控制器、资源到数据路径都有证据证明系统达到了它承诺的状态。

使用 Hugo 构建
主题 StackJimmy 设计