Fission CI 故障排查模式:错误字符串速查、NetworkPolicy 机制与 kind-ci Helm 开关

原创2026-09-24 13:45:591,633 阅读
文章标签:云原生后端

Fission CI 故障排查模式:错误字符串速查、NetworkPolicy 机制与 kind-ci Helm 开关

导读

本文是一份面向 Fission(Kubernetes 上的 Serverless 函数平台)贡献者与 CI 维护者的故障排查速查手册,整理自仓库中沉淀的 debug-ci 经验:Fission CI 反复出现的少数几类故障模式,以及它们背后的 NetworkPolicy 与 CI-only Helm 开关机制。读完本文,你将能够:从一条 CI 错误字符串直接对应到根因与修复路径(而不是通读整份日志);理解 networkPolicy.enabled=true 的 kind-ci 环境里 pod 标签、命名空间选择器与 router 内部监听器白名单的运作细节;掌握通过 kind-ci skaffold profile 以两步 JSONPatch 模式开启 CI-only Helm 特性的标准方法。


一、先认清"已知红标":这些失败不用追查

Fission 仓库维护者在长期实践中确认了两类故意常红的检查项,遇到它们不要花时间排查:

已知红标 说明
License Compliance(FOSSA) 在 main 分支上持续失败,报 "11 issues found",属于策略性常红状态
需要本地 Docker daemon 的测试 例如 pkg/storagesvc/client 下的 TestS3StorageService——本地没有 dockerd 时的失败纯粹是环境问题,与代码无关

原则:匹配到错误字符串后再读完整日志。本文的结构正是为这一原则服务——先用速查表定位,再深入机制。

关联参考文档:fission-build-pipeline.md 与 fission-integration-test-quirks.md。


二、症状 → 根因速查表(Symptom → Cause)

2.1 网络 / 连通性

症状 根因 更多
dial tcp <ip>:<port>: i/o timeout(Fission pod → storagesvc) NetworkPolicy 丢弃了请求,或 pod 标签与策略选择器不匹配 见下文"NetworkPolicy"
调用方 pod 日志中出现 dial tcp <router-internal-clusterIP>:8889: i/o timeout 调用 router 内部监听器的 fission-bundle 头不在白名单里 见下文"Router internal-listener allowlist"
portless: waiting for route "router.fission": ... not ready within 60s(或其他 *.fission 路由) 进程内 port-forward 找不到 Service 背后 Ready 的 pod——部署不健康或 FISSION_NAMESPACE 错误。请检查 Service 的 pods(没有 kubectl forward 可查) fission-integration-test-quirks.md
公共路由上 /fission-function/... 返回 404 请求打到了公共监听器;监听器拆分后这些路由已迁移到 router-internal.fission(请使用 f.Router(t) / f.RouterInternalBaseURL()) fission-integration-test-quirks.md
raw.githubusercontent.com/... 返回 404 Not Found 测试数据 URL 上游失效,不是回归 —

2.2 文件系统 / 权限

症状 根因 更多
/packages/... 上的 permission denied / openfdat …: permission denied 共享卷上的跨容器 UID 不匹配;0o750 会阻断 fetcher 访问,需要 0o755 fission-build-pipeline.md
no files found for globs: [/packages/<deploy>/*] Deploy 目录为空或 fetcher 无法读取 fission-build-pipeline.md
failed to get source directory info: stat /packages/<path>: no such file or directory 构建脚本从未写入该路径;流水线在更早环节已中断 fission-build-pipeline.md

2.3 构建流水线

症状 根因 更多
package "<pkg>" never reached build status "succeeded" buildermgr → builder → fetcher 的某一步失败 fission-build-pipeline.md
Error uploading deployment package: ... fetcher 的 UploadHandler 失败——被包裹的错误才是真正原因 fission-build-pipeline.md
builder 日志中 bufio.Scanner: token too long 构建脚本某行超过 64 KB(pip 进度条常触发) fission-build-pipeline.md
error: invalid buildCommand: contains shell metacharacter(HTTP 400) 2026-05 之后 BuildCommand 白名单拒绝 shell 元字符;请把 && 之类改写进脚本 shebang 主体 pkg/builder/builder.go 的 resolveBuildCommand
CreateEnv(Builder=...) 之后出现 dial tcp <env-svc>:8000: i/o timeout builder/runtime pod 就绪竞态 fission-integration-test-quirks.md

2.4 Lint / Go module

症状 根因
printf: non-constant format string in call to ... (govet) 调用方把 fmt.Sprintf(...) 当作 printf 辅助函数的格式参数传入。应直接传 format, args...,或用 "%s", str 包裹动态字符串
github.com/docker/docker 报 invalid version: unknown revision v29.x.y Docker 用 docker-vX.Y.Z 前缀重新打了该 import 路径的 tag,代理未收录。将 docker/cli 升到 v29.x——其传递依赖 moby/moby/v2 会让 docker/docker 整体从 go.mod 消失
go: github.com/foo/bar@vX: invalid version module 路径迁移或被 retract。到上游 releases/latest 与真实 git tags 核对

三、网络类故障深入:NetworkPolicy

当 Fission pod 无法访问另一个 Fission 服务、症状是 dial tcp <ip>:<port>: i/o timeout 时,NetworkPolicy 是第一怀疑对象。

关键背景:networkPolicy.enabled=true 只出现在 kind-ci skaffold profile 中(skaffold.yaml 的 base setValues 中默认是 "false")。因此一个策略 bug 可以通过单测和 helm lint,只在集成测试环节暴露。

kubectl get networkpolicies -n fission
kubectl describe networkpolicy <name> -n fission

3.1 pod 标签才是唯一真相

from: [{ podSelector: ... }] 规则匹配的是 pod 标签,而不是 Service/Deployment 名字。控制器与其创建的 worker pod 使用不同的标签约定:

Pod 类型 标签
buildermgr / executor 控制器 svc=buildermgr / svc=executor
router 控制器 svc=router, application=fission-router
storagesvc svc=storagesvc, application=fission-storage
每个 env 的 builder pod(builder + fetcher sidecar) owner=buildermgr, envName=<env>, envNamespace=<ns>, envResourceVersion=<rv>
Function pod executorType=poolmgr|newdeploy|container, functionName=<name>, executorInstanceId=<id>, managed=<bool>

关键规则:要选中 worker pod 里的 fetcher sidecar,选择器必须匹配 owner=buildermgr(env-builder pod)和 executorType in [...](function pod)——而不是 svc=*(控制器不直接访问 storagesvc)。

这些标签常量定义在源码中:

3.2 跨命名空间 vs 同命名空间选择器

storagesvc 位于 fission 命名空间;env-builder 与 function pod 位于用户命名空间(测试中为 default)。

podSelector 规则只匹配策略自身命名空间内的 pod,除非与 namespaceSelector 配对:

- from:
    - namespaceSelector: {}        # 匹配任何命名空间中带标签的 pod
      podSelector:
        matchLabels: { owner: buildermgr }

对于同命名空间规则(调用方与目标都在 fission 内),单独使用 podSelector 即可——它会隐式限定在策略所属命名空间内。

不要"为了显式"而加上 namespaceSelector.matchLabels.kubernetes.io/metadata.name: <ns>:该标签由某个 admission 插件自动填充,并非在所有 k8s 版本/发行版上都保证启用,插件关闭时规则会静默失效。

规范示例见 charts/fission-all/templates/networkpolicy.yaml 对应的各组件模板(如 charts/fission-all/templates/buildermgr/networkpolicy-builderpods.yaml、charts/fission-all/templates/executor/networkpolicy-functionpods.yaml)。

3.3 Router 内部监听器白名单:每个新内部客户端都要登记

charts/fission-all/templates/router/networkpolicy.yaml 将到内部监听器(端口 8889,提供 /fission-function/...)的入站流量显式限制在一组源 pod 标签白名单内:svc: kubewatcher | timer | mqtrigger | mqtrigger-keda | canaryconfig | executor | buildermgr | mcp(还有按条件启用的 svc: workflow 与 svc: router)。

任何新的调用内部监听器的 fission-bundle 头,都必须加入该 from 块,否则其签名请求会被静默丢弃——只在集成测试中表现为调用方 pod 日志(而非 router 的)里的 dial tcp <router-internal-clusterIP>:8889: i/o timeout。

这类是同命名空间规则,所以单独使用 podSelector 是正确的。

在 fission 命名空间内运行的调用方(例如 MCP)的 pod 日志位于 CI 的 kind-logs-<run>-<ver> 制品中,而不在 default 作用域的诊断转储里。

3.4 CNI 到底有没有在强制?

kindnet 从 kind v0.27 / k8s 1.30+ 开始强制实施 NetworkPolicy。在更老的 kind 或未装附加组件的 EKS 上,策略会被 API 接受但从不执行。

若策略看起来正确但行为不变:

kubectl get pods -n kube-system -o name | grep -E 'kindnet|cilium|calico|weave'

四、构建流水线故障深入

4.1 BuildCommand 的 shell 元字符白名单

速查表中 error: invalid buildCommand: contains shell metacharacter(HTTP 400)对应源码实现 pkg/builder/builder.go 的 resolveBuildCommand:

  • 空字符串回退到 defaultBuildCommand(/build,见 builder.go),不带参数;
  • 非空字符串按空白切分,第一个 token 为可执行文件,其余为参数;
  • 输入中任何位置出现 shell 元字符即被拒绝——常量定义在 builder.go:";|&$()<>\n\r"`。

设计动机:os/exec 不会调用 shell,这些字符在任何合法构建命令中都不该出现;前置拒绝可以封死命令注入面(即使下游调用方将来用 sh -c 包裹)。resolveBuildCommand 的用例覆盖在 pkg/builder/builder_test.go(含空串回退与元字符拒绝)。

实战改写方式:把原本写在 BuildCommand 里的 &&、; 等 shell 语法下沉到构建脚本的 shebang 主体中(例如把命令序列写进 build.sh 的 #!/bin/sh 之后),BuildCommand 只保留脚本路径与简单参数。

4.2 /packages 共享卷权限模型

env-builder pod 是两个容器共享 /packages 这个 emptyDir:

  • builder——env 镜像(如 python-builder),通常是 root(UID 0);
  • fetcher——Fission 的 sidecar,UID 10001(values.yaml 的 podSpec.securityContext.runAsUser)。

构建脚本(builder 侧)写入部署产物,fetcher 再读回上传。陷阱:父目录若被创建为 0o750(rwxr-x---),属主是 root,fetcher(UID 10001、不同组)无法遍历,产生三类症状(权限拒绝、stat 目录不存在、glob 找不到文件——最后一种是因为 fetcher 能 stat 目录但列不出内容)。

修复:/packages 根下的目录必须世界可读——目录用 0o755(文件继承 umask,0o644 即可;只有目录遍历位关键)。该模式由 pkg/utils/zip.go 的 Unarchive(os.MkdirAll(..., os.ModeDir|0o755))强制,构建脚本的 cp -r ${SRC_PKG} ${DEPLOY_PKG} 会保留权限,因此 Unarchive 的模式会传导下去。

判断路径是否被 fetcher sidecar 可达的通用法则:

  • /packages/... 上 → 目录必须 0o755;
  • 用户本地文件系统(CLI 工具:dump.go、package util、support resources)→ 0o700/0o600 即可;
  • 由节点级日志采集读取的日志卷 → 0o755。

为什么不把两个容器跑成同一 UID:env-builder 镜像在上游预构建,许多以 root 身份安装依赖;强制非 root 会破坏用户环境。控制面 pod 以 UID 10001 运行以缩小爆炸半径,于是 UID 不对称是既定事实,只能靠世界可读目录。单测无法模拟 UID 隔离,这一层只能由集成套件把关。

4.3 日志分别在哪里

下载的 go-integration-logs-* 制品中,每个测试的诊断目录包含:

文件 来源
logs-<env>-<rv>-<hash>-builder.log builder 容器——env 镜像里的 /builder 二进制,记录 ===== START ===== / ===== END ===== 标记之间的构建脚本输出
logs-<env>-<rv>-<hash>-fetcher.log env-builder pod 的 fetcher sidecar,处理取包(输入)与上传(输出)
logs-poolmgr-<fn>-*-fetcher.log / logs-newdeploy-<fn>-*-fetcher.log function pod 的 fetcher sidecar,只在特化(specialise)时下载,从不上传
logs-poolmgr-<fn>-*-<env-runtime>.log 用户函数的运行时容器

控制器日志(buildermgr、executor、router、storagesvc)来自 kind-logs-<runId>-v<k8s> 制品下 fission/ 命名空间的 pod 日志——只有怀疑是 step-1 控制器问题时才下载。

阅读 builder 日志:成功路径为 builder received request → building source package → START → 脚本输出 → END → build request complete。失败形态:error starting cmd(exec.Command 启动前就失败——脚本权限/PATH 问题)、error waiting for cmd(进程非零退出,读 START/END 之间)、没有 END 标记(捕获被截断:bufio.Scanner 撞上 64 KB 行上限,或 pod 在构建中途被杀)。

4.4 哪个二进制实际在跑(env 镜像不随 PR 重建)

二进制有两种进入集群的方式:

  1. 控制面镜像——make skaffold-deploy(goreleaser)按 PR 构建:fission-bundle(多头:buildermgr、executor、router、storagesvc、kubewatcher、timer、mqtrigger、canary、webhook 等)、fetcher、pre-upgrade-checks、reporter(见 skaffold.yaml 的 artifacts 列表);
  2. 环境镜像——由 env 维护者预先构建并发布到 GHCR,独立于本仓库:python-builder、node-builder-22、go-builder-1.23 等,每个都是 <语言基础镜像> + 内嵌 /builder 二进制 + 构建脚本。

要点:这些镜像里的 /builder 二进制是在镜像构建推送时编译的,不是来自当前 PR 的源码。 因此修改 pkg/builder/builder.go 不会改变 CI 集成测试中 env-builder pod 的行为,直到镜像被重新发布。若集成测试表现出旧行为而单测通过、lint 干净,几乎可以断定是这个原因——用 builder pod 日志中的 caller":"builder/builder.go:NN" 字段与本地行号对照确认。

pkg/builder 的 Go 测试(builder_test.go)跑在 make test-run(隔离的包逻辑),不在集成任务中。真正在集群里验证 cmd/builder/pkg/builder 改动的途径:写一个避开 env 镜像的 Go 单测/集成测试;用打过补丁的二进制构建本地 env-builder 镜像(Dockerfile 在独立的 fission/environments 仓库)并在测试 Environment CR 中覆盖 Builder.Image;或把行为搬进 pkg/buildermgr(控制器在 fission-bundle 内,随 PR 重建)。


五、CI-only Helm 特性:kind-ci skaffold profile 两步模式

有些 chart 特性对用户默认关闭,但 CI 应该开启以覆盖代码路径。仓库通过 kind-ci profile 翻转它们(完整 patch 列表见 skaffold.yaml):

  • base setValues 中声明但默认关闭:canaryDeployment.enabled: "false"、podMonitor.enabled: "false"、serviceMonitor.enabled: "false"、networkPolicy.enabled: "false"(2026-05)、storagesvc.archivePruner.interval: "60"、mcp.enabled: "true" + mcp.allowInsecure: "true"(2026-06,写在 base setValues 中,使 MCP 头在每个 kind profile 都运行);
  • kind-ci profile 中通过 JSONPatch 翻转为开:canaryDeployment.enabled: true、podMonitor.enabled: true、serviceMonitor.enabled: true、storagesvc.archivePruner.interval: 1、networkPolicy.enabled: true 等。

两步标准模式(skaffold 的 JSONPatch replace 操作要求路径已存在):

  1. 在 base manifests.helm.releases[0].setValues 映射里以 chart 默认值声明该值(例如 networkPolicy.enabled: "false");
  2. 在 kind-ci profile 中添加 replace patch:
- op: replace
  path: /manifests/helm/releases/0/setValues/networkPolicy.enabled
  value: true

跳过第 1 步会导致渲染失败:报 path does not exist。路径使用点分隔的键段,因为 base 映射就是这样组织的。若该特性也需要在旧版本上测试,同步镜像到 kind-ci-old profile。

验证渲染:

helm lint charts/fission-all
helm template charts/fission-all --set networkPolicy.enabled=true | sed -n '/^kind: NetworkPolicy/,/^---/p' | head -50
helm template charts/fission-all --set networkPolicy.enabled=false | grep -c 'kind: NetworkPolicy'   # 0

如果该特性对用户可调,还要把它加进 charts/fission-all/values.yaml;如果是纯内部 CI 旋钮,skaffold patch 就足够了。


六、附:集成测试快速排查清单

以下清单摘自配套文档 fission-integration-test-quirks.md,配合本文速查表使用:

  1. 单独重跑——go test -tags=integration -run TestFooBar -v -count=1 ./test/integration/suites/common/...。单独通过而套件中失败 ⇒ 并行负载问题(更多 pod、更多 kube-proxy 延迟);
  2. 读诊断转储——$LOG_DIR/<sanitized-test-name>/ 下有 events.yaml、pods.yaml、容器日志、CR yaml。caller":"…" 字段告诉你实际跑的是哪个二进制版本;
  3. 构建失败查 Status.BuildLog——记录在 packages.yaml 以及 WaitForPackageBuildSucceeded 的失败消息里;
  4. 逐级排查分层竞态修复——builder-pod 测试出现 dial tcp …:8000: i/o timeout,多半需要更长的 settle 时间,而不是新机制(CreateEnv 内置的就绪等待链包含 WaitForBuilderReady、waitForRuntimePoolReady、EndpointSlice 就绪 + 5 秒 settle,以及 WaitForPackageBuildSucceeded 对瞬时超时的 3 次重试,见 pkg/buildermgr/common.go 附近的实现);
  5. FIXME 下跳过的测试——TestIdleObjectsReaper 当前被跳过,不解决并行 CI 的 fsvc-TTL 刷新问题(commit 0fc53807)前不要重新启用。

七、核心要点回顾

  • 先匹配错误字符串,再读完整日志——本文速查表覆盖了 Fission CI 反复出现的全部主要故障模式;
  • dial tcp …: i/o timeout 优先怀疑 NetworkPolicy,而策略只对 pod 标签生效;kind-ci 环境里网络策略是开启的,单测/helm lint 拦不住策略 bug;
  • router 内部监听器(8889)采用显式白名单,新增的 fission-bundle 内部客户端必须同步登记到 charts/fission-all/templates/router/networkpolicy.yaml 的 from 块;
  • /packages 共享卷目录必须 0o755,否则 UID 10001 的 fetcher 无法读取构建产物;
  • env 镜像不随 PR 重建——改 pkg/builder/builder.go 不会立即改变集成测试中的 builder 行为,核对日志 caller 行号是确认手段;
  • CI-only Helm 特性走"base 声明默认值 + kind-ci JSONPatch replace"两步模式,路径缺失会导致渲染失败。
登录后查看全文
fission