
简介
Argo Rollouts 的 Dashboard 页面里,NAMESPACE 那个位置从下拉框变成了一个禁用的输入框,只能看到默认的 namespace,没法切到别的地方看 rollout。翻源码、查权限、开调试日志折腾了一圈,最后发现凶手是一个 rollout 的资源字段写错了类型,导致整个集群级的列表解码失败。这篇记录一下排查过程。
现象
Dashboard 打不开 namespace 下拉框,直接 curl 后端的接口看数据:
curl https://rollout.example.com/api/v1/namespace
返回:
{"namespace":"argo-rollouts"}
接口叫 /api/v1/namespace,很容易以为它返回的是所有 namespace 的列表。其实不是,它返回两个字段,namespace 是当前选中的 namespace,availableNamespaces 是可供切换的列表。正常情况响应里应该带第二个字段,比如:
{"namespace":"argo-rollouts","availableNamespaces":["ns-a","ns-b","ns-c"]}
现在 availableNamespaces 整个没出现。这里有个细节,protobuf 转 JSON 的时候,空的 repeated 字段会被直接省略掉,所以「字段没了」就等于「字段是空的」。前端拿到空数组,就把下拉框渲染成了禁用的输入框,看起来就是 namespace 切不了。
坑1:先怀疑权限,其实 RBAC 没问题
第一反应是 dashboard 用的 ServiceAccount 权限不够,列不了所有 namespace 的 rollout。
查了一遍:
kubectl get clusterrole argo-rollouts-dashboard -o yaml
ClusterRole 里有 rollouts 的 get/list/watch,而且是 ClusterRole,全集群生效。再用 kubectl 验证一下:
kubectl auth can-i list rollouts -A --as=system:serviceaccount:argo-rollouts:argo-rollouts-dashboard
返回 yes。
还不放心,起了个调试 pod 挂同一个 ServiceAccount,直接用 curl 打 K8s API:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -sk -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/apis/argoproj.io/v1alpha1/rollouts
200,响应有 1.6MB,rollout 列表全都在。权限这条路排除。
坑2:怀疑网络协议和版本,都不是
client-go 默认走 HTTP/2,还带 gzip,怀疑是不是传输层有问题。用同一个 SA 在集群里分别用 HTTP/1.1 和 HTTP/2 试,响应都是完整的 1.6MB。gzip 解压也正常。
再怀疑是不是 pod 状态问题,拿同一个镜像、同一个 ServiceAccount 重新起了个全新的 dashboard pod,访问接口,结果一样,availableNamespaces 还是空的。版本换到 master 分支看代码,实现和线上版本一模一样,不是已知修复。
找到突破口:另一个接口报了解码错误
dashboard 进程自己列 rollout 是能成功的,因为按 namespace 查的接口是好的:
# {namespace} 换成任意一个部署了 rollout 的 namespace 试
curl http://127.0.0.1:3100/api/v1/rollouts/{namespace}/info
有数据。说明客户端本身没坏。逐个 namespace 试过去,试到某一个 namespace 的时候,接口直接报错:
{
"code": 2,
"message": "json: cannot unmarshal string into Go struct field ResourceRequirements.items.spec.template.spec.containers.resources.limits of type v1.ResourceList"
}
这个报错信息很关键。它说的是某个对象的 spec.template.spec.containers[].resources.limits 字段,期望是个 map,但实际是字符串,解码失败。
真凶:resources.limits 写成了字符串
把集群里所有 rollout 的 JSON dump 出来,逐个检查 template 里的 resources 字段:
kubectl get rollouts -A -o json
找到唯一一个坏的:
resources:
limits: "memory:6Gi"
正确的写法应该是:
resources:
limits:
memory: 6Gi
一个字符串,一个 map。就这一个对象,把整个集群级的列表查询搞挂了。
为什么一个坏对象能让整个列表挂掉
Argo Rollouts 服务端拿 namespace 列表的方式是这样的,源码里:
rolloutList, err := s.Options.RolloutsClientset.
ArgoprojV1alpha1().
Rollouts("").
List(ctx, v1.ListOptions{})
if err == nil {
for _, r := range rolloutList.Items {
namespaces = append(namespaces, r.Namespace)
}
}
return &rollout.NamespaceInfo{Namespace: current, AvailableNamespaces: namespaces}, nil
它先做一次集群级的 Rollouts("").List,把结果里所有 rollout 的 namespace 去重,当作可切换的列表。
问题在于 client-go 的 typed client 对列表结果是整体解码的。一个对象的字段类型不对,整个列表的解码就失败,err 不是 nil,循环根本不执行,availableNamespaces 就是空的。而代码里 if err == nil 把这个错误静默吞掉了,接口照样返回 200,只是列表是空的。
按 namespace 查的接口没挂,是因为它只查单个 namespace 的 rollout,碰巧那个坏对象不在里面。
顺带一个设计事实
排查过程中翻了源码,availableNamespaces 的语义是「有 Rollout 资源的 namespace」,不是集群里的所有 namespace。所以就算这个 bug 修好,下拉框里也只会出现部署了 Rollout 的 namespace,想列出全部 namespace 还是得靠 kubectl:
kubectl get ns
总结
一个不起眼的 YAML 类型错误,能让 dashboard 的 namespace 列表整个消失,而且服务端不报错,只返回空数组。这种静默吞错的设计,排查起来确实费劲。
这次的经验:
- 接口返回 200 不代表逻辑正确,空数组也可能是错误被吞了
- typed client 对列表整体解码,一个坏对象能拖垮整个列表
- 查权限、查网络、查版本都正常的时候,去翻一下别的接口的报错,往往能直接定位
- 集群里 YAML 写错类型这种事,最好在 CI 或者 ArgoCD 校验阶段就拦住,别让坏对象进集群
欢迎关注我的博客 www.bboy.app
Have Fun
