我在「go-multierror: 更方便的处理你的错误列表」一文中讲解了在 Go 中如何使用 go-multierror 包聚合错误列表,本文将介绍另一种方案,来看看 Kubernetes 是如何聚合错误列表的。
Kubernetes 在 apimachinery 项目中专门提供了 Aggregate 类型来处理聚合错误,我们一起来看下它的使用方法和实现原理。
使用示例
Kubernetes Aggregate 使用示例如下:
1 | package main |
这里我们写一段测试程序,模拟出现多个错误的情况,使用 errs 记录错误列表。然后用 utilerrors.NewAggregate(errs) 将错误列表转换成 Aggregate 类型。最后拿到 agg 后做了一系列打印测试。
执行示例代码,得到输出如下:
1 | $ go run main.go |
想必无需我多言,根据输出结果,我们能直观的观察到 Aggregate 的效果。
源码解读
接下来,我们将从源码入手,详细解读下 Aggregate 的实现原理。
Aggregate 接口
首先,Aggregate 其实是一个接口,其定义如下:
1 | type Aggregate interface { |
这个接口非常简单,嵌入了 error,并扩展了两个方法 Errors 和 Is。
Kubernetes 为 Aggregate 接口提供了如下构造函数:
1 | func NewAggregate(errlist []error) Aggregate { |
NewAggregate 函数可以将一个错误列表(注意,我这里说的“列表”就是 Go 中的 slice,表示一组错误,本文中还会多次使用这种说法)转换成 Aggregate 类型。并且其内部会过滤掉值为 nil 的错误。最终返回 Aggregate 接口的实现 aggregate 对象。
aggregate 实现
aggregate 类型是 Aggregate 接口的具体实现,其定义如下:
1 | type aggregate []error |
可以看到,aggregate 实际上的底层类型就是错误列表 []error。所以 aggregate(errs) 啥都没做,就仅仅转换了一下类型。接下来我们依次看下 aggregate 实现的方法。
首先要看的当然是 error 必备方法 Error 的实现:
1 | func (agg aggregate) Error() string { |
如果 aggregate 列表中的错误为空直接返回空字符串 "";如果只有一个错误,则返回这个唯一错误值的 Error() 结果;否则,说明存在多个错误,此时会结合 set 和 visit 方法对错误列表进行去重操作,最终返回 [err1, err2, ..., errn] 格式的错误信息。
visit 方法实现如下:
1 | func (agg aggregate) visit(f func(err error) bool) bool { |
visit 方法会接收一个函数 f,它会递归遍历聚合错误树 aggregate,并对每个错误执行判断函数 f,如果返回值为 true 表示存在满足条件的错误,false 则表示未找到。
从这里也能看出,aggregate 是可能出现嵌套情况的,嵌套深了,就会组成一个错误树结构。
aggregate 剩余的两个方法 Errors 和 Is 则实现非常简单,代码如下:
1 | func (agg aggregate) Errors() []error { |
Errors 方法没什么好说的,就是类型转换,而 Is 方法也同样调用了 visit 方法递归判断每一个错误对象,是否等于 target。
至此,aggregate 的所有方法就都讲解完成了。
而 k8s.io/apimachinery/pkg/util/errors 包其实还提供了其他功能供我们使用,它们分别是 FilterOut、Flatten、Reduce、AggregateGoroutines 以及 CreateAggregateFromMessageCountMap,接下来我们分别看一下它们各自的功能和实现。
FilterOut 过滤输出
FilterOut 函数实现如下:
1 | type Matcher func(error) bool |
FilterOut 从输入错误中移除所有匹配任意 Matcher 的错误。如果 err 为 nil 则直接返回;如果 err 实现了 Aggregate 接口,则递归处理错误列表。
这里调用的 filterErrors 函数实现如下:
1 | func filterErrors(list []error, fns ...Matcher) []error { |
filterErrors 函数内部遍历错误列表 list 并依次调用 FilterOut 函数,所以这是一个递归操作。
FilterOut 函数还调用了 matchesError 来执行匹配判断,其实现如下:
1 | func matchesError(err error, fns ...Matcher) bool { |
这里就是为 err 应用一遍所有的 Matcher 函数。
我们可以用 FilterOut 从错误列表中移除已知无害的错误(如 io.EOF),你也可以在 https://github.com/kubernetes/kubernetes/blob/v1.32.0/staging/src/k8s.io/component-helpers/auth/rbac/reconciliation/namespace.go#L37 看到 Kubernetes 对 FilterOut 的应用。
Flatten 展平错误
Flatten 函数实现如下:
1 | func Flatten(agg Aggregate) Aggregate { |
Flatten 接收一个嵌套任意层的 Aggregate,并递归的将其展平。
你也可以在 https://github.com/kubernetes/kubernetes/blob/v1.32.0/pkg/scheduler/apis/config/validation/validation.go#L81 看到 Kubernetes 对 Flatten 的应用。
Reduce 简化聚合错误
Reduce 函数实现如下:
1 | func Reduce(err error) error { |
如果给定错误 err 是一个 Aggregate 类型且只有一项,Reduce 将会返回错误或 nil,即返回 aggregate 中的第一项;如果 err 是 Aggregate 类型但是包含多项,则原样返回;非 Aggregate 类型错误直接返回。
你也可以在 https://github.com/kubernetes/kubernetes/blob/v1.32.0/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go#L729 看到 Kubernetes 对 Reduce 的应用。
AggregateGoroutines 并行收集错误
AggregateGoroutines 函数实现如下:
1 | func AggregateGoroutines(funcs ...func() error) Aggregate { |
AggregateGoroutines 并行运行提供的函数 funcs,并将所有非 nil 错误收集到返回的 Aggregate 中,如果所有函数都成功完成,则返回 nil。
你也可以在 https://github.com/kubernetes/kubernetes/blob/v1.32.0/staging/src/k8s.io/apiserver/pkg/audit/union.go#L56 看到 Kubernetes 对 AggregateGoroutines 的应用。
CreateAggregateFromMessageCountMap 统计错误频率
CreateAggregateFromMessageCountMap 函数实现如下:
1 | type MessageCountMap map[string]int |
CreateAggregateFromMessageCountMap 将给定的 MessageCountMap 转换为 Aggregate。其中 MessageCountMap 可以统计错误频率,key 是错误信息,value 就是错误出现的次数。
这个函数用处不多,我在 Kubernetes 项目 1.32.0 分支中并没有搜到对 CreateAggregateFromMessageCountMap 函数的使用,可以参考其测试代码查看效果。
总结
本文对 Kubernetes k8s.io/apimachinery/pkg/util/errors 包提供的错误处理功能进行了全面细致的讲解,不仅介绍了如何使用 Aggregate 聚合代码,还详细解读了其源码实现。我在文中贴出了几处 Kubernetes 源码中对文中介绍的函数的使用,你可以点击跳转过去查看。
Kubernetes 提供的 Aggregate 是比 HashiCorp 提供的 go-multierror 功能更强大的工具,并且开源协议也是更友好的 Apache 2.0 协议,推荐使用。
此外,开源项目 OneX 中就大量使用了 Aggregate 来聚合错误,这是一个非常优秀的开源项目,里面有大量基于 K8s 源码的实现,感兴趣的读者可以学习参考。
并且 OneX 作者还开通了知识星球专门讲解这个项目,欢迎你的加入,扫码直达星球。
你也可以加我微信了解详情。
本文示例源码我都放在了 GitHub 中,欢迎点击查看。
希望此文能对你有所启发。
延伸阅读
- OneX 项目 GitHub 源码:https://github.com/onexstack/onex
- k8s.io/apimachinery/pkg/util/errors GitHub 源码:https://github.com/kubernetes/apimachinery/tree/v0.32.0/pkg/util/errors
- go-multierror Documentation:https://pkg.go.dev/github.com/hashicorp/go-multierror@v1.1.1
- Go 错误处理指北:如何优雅的处理错误?:https://jianghushinian.cn/2024/10/01/go-error-guidelines-error-handling/
- Go 并发控制:errgroup 详解:https://jianghushinian.cn/2024/11/04/x-sync-errgroup/
- 开源协议简介:https://jianghushinian.cn/2023/01/15/open-source-license-introduction/
- go-multierror: 更方便的处理你的错误列表:https://mp.weixin.qq.com/s/8EVPiNbynuxWRc8GlmpXxw
- 本文 GitHub 示例代码:https://github.com/jianghushinian/blog-go-example/tree/main/error/k8s-apimachinery-util-errors
- 本文永久地址:https://jianghushinian.cn/2025/03/29/k8s-aggregate-error/
联系我
- 公众号:Go编程世界
- 微信:jianghushinian
- 邮箱:jianghushinian007@outlook.com
- 博客:https://jianghushinian.cn
- GitHub:https://github.com/jianghushinian