Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

背景

我们的项目是一个基于Kubernetes的云原生项目,会使用到Kubernets的一些组件和依赖。我们的项目是一个基于Kubernetes的云原生项目,会使用到Kubernets的一些组件和依赖。

最开始的项目依赖大概是这样的:

Code Block
languagexml
require (
	git.woa.com/khaos/pkg v1.4.3-0.20220819031955-4ad837d439ef
	github.com/ClickHouse/clickhouse-go v1.4.8
	github.com/gogf/gf/contrib/drivers/mysql/v2 v2.1.0
	github.com/gogf/gf/v2 v2.1.4
	github.com/golang/mock v1.4.3
	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
	github.com/json-iterator/go v1.1.10
	github.com/modern-go/reflect2 v1.0.1
	github.com/olekukonko/tablewriter v0.0.5
	github.com/opencontainers/go-digest v1.0.0
	github.com/robfig/cron/v3 v3.0.1
	github.com/sirupsen/logrus v1.7.0
	github.com/smartystreets/assertions v1.0.1
	github.com/smartystreets/goconvey v1.6.4
	github.com/spf13/cobra v1.1.3
	github.com/tencentyun/cos-go-sdk-v5 v0.7.35
	github.com/tencentyun/tcecloud-sdk-go v3.0.8+incompatible
	go.opentelemetry.io/otel v1.7.0
	go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a
	golang.org/x/tools v0.1.11-0.20220504162446-54c7ba520b92
	google.golang.org/grpc v1.33.1
	google.golang.org/protobuf v1.27.1
	gopkg.in/yaml.v2 v2.4.0
	k8s.io/api v0.19.8
	k8s.io/apimachinery v0.19.8
	k8s.io/client-go v11.0.0+incompatible
)

replace (
	google.golang.org/grpc => google.golang.org/grpc v1.29.1
	k8s.io/api => k8s.io/api v0.19.6
	k8s.io/apimachinery => k8s.io/apimachinery v0.19.6
	k8s.io/client-go => k8s.io/client-go v0.19.6
)

其中的replace语句是由于项目依赖的内部服务所以依赖的kubernetes api版本比较低,短期内无法升级,因此只能添加replace语句来workaround。这种低版本的组件依赖也提高了后面第三方组件的版本冲突风险。

引入ArgoWorkflow服务依赖

随着项目的发展,业务上需要流程编排服务。当然作为大自然的代码搬运师,我们从github上找到了适合的、贴合我们云原生理念的ArgoWorkflow服务。它作为独立的服务运行,我们只需要在当前的项目中依赖其SDK即可,但是在依赖SDK的过程中出现了组件版本冲突。先看看我们有问题的项目依赖:

Code Block
languagexml
require (
	git.woa.com/khaos/pkg v1.4.3-0.20220819031955-4ad837d439ef
	github.com/ClickHouse/clickhouse-go v1.4.8
	github.com/HdrHistogram/hdrhistogram-go v1.1.2
	github.com/argoproj/argo-workflows/v3 v3.1.5
	github.com/argoproj/pkg v0.9.1
	github.com/aws/aws-sdk-go v1.33.16
	github.com/go-resty/resty/v2 v2.7.0
	github.com/gogf/gf/contrib/drivers/mysql/v2 v2.1.0
	github.com/gogf/gf/v2 v2.1.4
	github.com/golang/mock v1.4.3
	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
	github.com/json-iterator/go v1.1.10
	github.com/modern-go/reflect2 v1.0.1
	github.com/olekukonko/tablewriter v0.0.5
	github.com/opencontainers/go-digest v1.0.0
	github.com/robfig/cron/v3 v3.0.1
	github.com/sirupsen/logrus v1.7.0
	github.com/smartystreets/assertions v1.0.1
	github.com/smartystreets/goconvey v1.6.4
	github.com/spf13/cobra v1.1.3
	github.com/tencentyun/cos-go-sdk-v5 v0.7.35
	github.com/tencentyun/tcecloud-sdk-go v3.0.8+incompatible
	go.opentelemetry.io/otel v1.7.0
	go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a
	golang.org/x/tools v0.1.11-0.20220504162446-54c7ba520b92
	google.golang.org/grpc v1.33.1
	google.golang.org/protobuf v1.27.1
	gopkg.in/yaml.v2 v2.4.0
	k8s.io/api v0.19.8
	k8s.io/apimachinery v0.19.8
	k8s.io/client-go v11.0.0+incompatible
	k8s.io/component-base v0.19.6
	sigs.k8s.io/yaml v1.2.0
)

replace (
	google.golang.org/grpc => google.golang.org/grpc v1.29.1
	k8s.io/api => k8s.io/api v0.19.6
	k8s.io/apimachinery => k8s.io/apimachinery v0.19.6
	k8s.io/client-go => k8s.io/client-go v0.19.6
	k8s.io/code-generator => k8s.io/code-generator v0.19.6
	k8s.io/component-base => k8s.io/component-base v0.19.6
)

执行 go mod tidy 没有问题,但是编译的时候会报以下错误:

Image Added

看起来像是第三方组件版本不兼容问题引起。当项目依赖很多第三方组件的时候,如果不同的第三方组件又彼此依赖到另一个相同的第三方组件A并且版本不同时(如12),Golang的版本管理机制将会使用其中高版本(版本2)的组件A来执行构建。当组件A的不同版本之间存在兼容问题时(版本12不兼容),那么依赖低版本(版本1)的第三方组件在构建时将会出问题。

这个问题在Golang中比较常见,特别是在依赖的第三方组件越多时,这种风险将会变大。如果依赖的第三方组件版本越低,这种风险也会越大。但是对于业务项目而言,一般依赖了一个第三方组件,在项目运行稳定的情况下几乎很难去升级组件的版本。所以这种组件版本依赖冲突问题,往往也只有在项目引入新组件的时候容易出现。

我们对其中kubernetes经过了一系列版本依赖分析,找到了这些依赖中共同依赖的组件Aklog。因此我们通过加了一个replaceworkaround该问题:强制将klog的版本升级到v2.60.0

Code Block
languagexml
replace (
	k8s.io/klog/v2 => k8s.io/klog/v2 v2.60.0
)

这个问题看似告一段落。虽然replace的语句丑陋,但能快速解决问题,使得项目进度能够继续开展。虽然我们也有更完美的解决方案,即内部相关项目都升级kubernetes api的版本,但这种方案成本较大,难以快速解决问题。因此我们在项目的开发中往往会采用性价比高的折中方案。我们在解决问题的时候,往往需要从更高层次去分析问题,特别是成本衡量、投入产出比的因素,不能太关注于问题本身。

引入ArgoCD服务依赖

随着项目的发展,我们又需要一个CD服务,所以我们引入了ArgoCD服务,当然这个时候我们又要引入ArgoCDSDK,于是又开始遇到第三方组件各种版本兼容问题。我们的同学经过几天的摸索,搞出来以下组件依赖和replace版本替换:

Code Block
languagexml
require (
	git.woa.com/khaos/pkg v1.4.3-0.20220819031955-4ad837d439ef
	git.woa.com/maxqzhu/argocd-sdk v0.0.0-20220919094321-a21a39049255
	github.com/ClickHouse/clickhouse-go v1.4.8
	github.com/HdrHistogram/hdrhistogram-go v1.1.2
	github.com/argoproj/argo-cd/v2 v2.0.4
	github.com/argoproj/argo-workflows/v3 v3.1.5
	github.com/argoproj/pkg v0.9.1
	github.com/aws/aws-sdk-go v1.33.16
	github.com/clbanning/mxj/v2 v2.5.6 // indirect
	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
	github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
	github.com/go-git/go-billy/v5 v5.3.1
	github.com/go-git/go-git/v5 v5.4.2
	github.com/go-resty/resty/v2 v2.7.0
	github.com/gogf/gf/contrib/drivers/mysql/v2 v2.1.0
	github.com/gogf/gf/v2 v2.1.4
	github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
	github.com/golang/mock v1.4.4
	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
	github.com/json-iterator/go v1.1.10
	github.com/mattn/go-colorable v0.1.12 // indirect
	github.com/modern-go/reflect2 v1.0.1
	github.com/olekukonko/tablewriter v0.0.5
	github.com/opencontainers/go-digest v1.0.0
	github.com/robfig/cron/v3 v3.0.1
	github.com/sirupsen/logrus v1.7.0
	github.com/smartystreets/assertions v1.0.1
	github.com/smartystreets/goconvey v1.6.4
	github.com/spf13/cobra v1.1.3
	github.com/stretchr/testify v1.7.1
	github.com/tencentyun/cos-go-sdk-v5 v0.7.38
	github.com/tencentyun/tcecloud-sdk-go v3.0.8+incompatible
	go.opentelemetry.io/otel v1.7.0
	go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a
	golang.org/x/net v0.0.0-20220621193019-9d032be2e588 // indirect
	golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
	golang.org/x/text v0.3.8-0.20220509174342-b4bca84b0361 // indirect
	golang.org/x/tools v0.1.11-0.20220504162446-54c7ba520b92
	google.golang.org/grpc v1.33.1
	google.golang.org/protobuf v1.27.1
	gopkg.in/yaml.v2 v2.4.0
	gopkg.in/yaml.v3 v3.0.1 // indirect
	k8s.io/api v0.1920.84
	k8s.io/apimachinery v0.1921.81
	k8s.io/client-go v11.0.1-0.20190816222228-6d55c1b1f1ca+incompatible
	k8s.io/component-base v0.20.4
	sigs.k8s.io/yaml v1.2.0
)

replace (
	google.golang.org/grpc => google.golang.org/grpc v1.29.1
	k8s.io/klog/v2 => k8s.io/klog/v2 v2.60.0
)

replace (
	github.com/argoproj/gitops-engine => github.com/argoproj/gitops-engine v0.2.2
	k8s.io/api => k8s.io/api v0.19.6
	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.19.6
	k8s.io/apimachinery => k8s.io/apimachinery v0.19.6
	k8s.io/apiserver => k8s.io/apiserver v0.19.6
	k8s.io/cli-runtime => k8s.io/cli-runtime v0.19.6
	k8s.io/client-go => k8s.io/client-go v0.19.6
	k8s.io/cloud-provider => k8s.io/cloud-provider v0.19.6
	k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.19.6
	k8s.io/code-generator => k8s.io/code-generator v0.19.6
	k8s.io/component-base => k8s.io/component-base v0.19.6
	k8s.io/component-helpers => k8s.io/component-helpers v0.19.6
	k8s.io/controller-manager => k8s.io/controller-manager v0.19.6
	k8s.io/cri-api => k8s.io/cri-api v0.19.6
	k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.19.6
	k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.19.6
	k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.19.6
	k8s.io/kube-proxy => k8s.io/kube-proxy v0.19.6
	k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.19.6
	k8s.io/kubectl => k8s.io/kubectl v0.19.6
	k8s.io/kubelet => k8s.io/kubelet v0.19.6
	k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.19.6
	k8s.io/metrics => k8s.io/metrics v0.19.6
	k8s.io/mount-utils => k8s.io/mount-utils v0.19.6
	k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.19.6
)

可当执行编译的时候,报错依旧,老泪纵横:

Image Added

当同学几经波折准备放弃,开始采用骚操作来WorkAround的时候(将依赖的SDK剥离出来单独维护)。秉着坑踩得越多,责任也就越大。在一个月黑风高的夜晚,我加入了进来一起研究,毕竟,这个项目还要长久维护的。

这个问题看起来是由两个组件的版本不兼容引起的,处理起来估计会更棘手一些。

……

此处省略一万字。

……

经过一系列复杂的版本分析、依赖分析、兼容处理,对于此次版本冲突问题,加了以下replace语句来解决:

Code Block
languagexml
replace (
	github.com/argoproj/argo-cd/v2 => github.com/argoproj/argo-cd/v2 v2.0.1
	github.com/bombsimon/logrusr => github.com/gqcn/logrusr v1.3.0
	github.com/go-openapi/spec => github.com/go-openapi/spec v0.17.0
	sigs.k8s.io/kustomize/v2 => sigs.k8s.io/kustomize/v2 v2.1.0
)

引入ArgoWorkflow服务依赖

...

其中大家应该注意到一个组件 github.com/gqcn/logrusr ,这个组件是我从 github.com/bombsimon/logrusr 项目fork的一份,目前是放到了github上。并升级了内部依赖的 github.com/go-logr/logr 组件版本到当前项目整体依赖比较适中的版本v1.2.3,即将整体的冲突的版本通过强制升级的方式来解决。在执行强制升级的时候,组件之间依赖的接口和方法兼容非常重要,不是简单升级版本那么简单。此外,虽然本次组件版本冲突不得不维护一个自定义的组件仓库,但维护一个基本不变的组件比维护一个可能变化的组件成本和风险会更小一些。

总结

Golang开发中,这种版本冲突问题几乎很难避免,项目对三方组件依赖的越多,这种风险就越大。

此次分享的主要目的:

  • 解决问题的思路:分析问题,提出方案,选择方案
  • 解决问题的目的:更高层次思考问题、不局限问题本身
  • Golang开发中,常见的类似的问题如何处理的参考
  • 对于组件开发,特别是GoFrame框架开发而言,站在使用者角度考虑,应当尽量减少第三方组件依赖 https://goframe.org/