如何欺騙 Go Mod ?
hi,大家好,我是 haohongfan。
最近在做 prometheus 生態(tài)的 cortex 優(yōu)化工作,遇到一個(gè)比較坑的 go mod 的問(wèn)題,這里分享一下。
我為什么將標(biāo)題稱為:如何欺騙 Go mod 呢?這個(gè)挺有意思的,這里先賣個(gè)關(guān)子,不過(guò)確實(shí)是突破了 Go mod 的相關(guān)特性。(嗯,曹大的 Go mod 十宗罪又可以增加一宗了)
在正式展開這個(gè)話題之前,需要簡(jiǎn)單的介紹下 cortex 和 thanos 這兩個(gè)項(xiàng)目。
Prometheus 的局限性
說(shuō)到業(yè)務(wù)開發(fā)基本上都離不開監(jiān)控系統(tǒng),Prometheus 做為云原生的寵兒,以優(yōu)秀的設(shè)計(jì),靈活的使用方式,以優(yōu)異成績(jī)從 CNCF 順利畢業(yè),也是很多公司做監(jiān)控的首選。
但是呢,Promethues 也有其自身局限性,其中影響最大的就是其數(shù)據(jù)的高可用方案和集群方案。監(jiān)控也是業(yè)務(wù)系統(tǒng)的重中一環(huán),不能因?yàn)楸O(jiān)控系統(tǒng)宕機(jī)導(dǎo)致報(bào)警無(wú)法及時(shí)發(fā)出。
Prometheus 官方也有提出聯(lián)邦方案來(lái)解決集群?jiǎn)栴},但是這個(gè)方案極其復(fù)雜而且很多問(wèn)題還是解決不了,于是就造就了另外兩個(gè) CNCF 的沙箱項(xiàng)目:cortex 和 thanos。這兩個(gè)項(xiàng)目都是為了解決 Promethues 的集群,高可用的。
由于這兩個(gè)項(xiàng)目要解決問(wèn)題的目的是一致的,所以就會(huì)出現(xiàn)很多功能都是可以相互復(fù)用的,于是有趣的事情就發(fā)生了。
cortex
話說(shuō)因?yàn)槟承┑男枨?,不得已需要更改?thanos 的相關(guān)代碼。我本地調(diào)試的時(shí)候?qū)?cortex 依賴的 thanos 給 replace 了一下。
- replace github.com/thanos-io/thanos => /Users/hhf/goproject/cortex/thanos
再等我編譯的時(shí)候,就編譯不過(guò)了。
- # github.com/sercand/kuberesolver
- ../../../go/pkg/mod/github.com/sercand/kuberesolver@v2.1.0+incompatible/builder.go:108:82: undefined: resolver.BuildOption
- ../../../go/pkg/mod/github.com/sercand/kuberesolver@v2.1.0+incompatible/builder.go:163:32: undefined: resolver.ResolveNowOption
這就讓人很無(wú)奈,別著急,我們看看這個(gè) kuberesolver 是被誰(shuí)依賴的。
先看下被 replace 之前:
- ▶ go mod graph| grep kuberesolver
- github.com/weaveworks/common@v0.0.0-20210419092856-009d1eebd624 github.com/sercand/kuberesolver@v2.1.0+incompatible
- github.com/weaveworks/common@v0.0.0-20210112142934-23c8d7fa6120 github.com/sercand/kuberesolver@v2.1.0+incompatible
- github.com/weaveworks/common@v0.0.0-20200206153930-760e36ae819a github.com/sercand/kuberesolver@v2.1.0+incompatible
- github.com/weaveworks/common@v0.0.0-20201119133501-0619918236ec github.com/sercand/kuberesolver@v2.1.0+incompatible
- github.com/weaveworks/common@v0.0.0-20200914083218-61ffdd448099 github.com/sercand/kuberesolver@v2.1.0+incompatible
- github.com/weaveworks/common@v0.0.0-20200625145055-4b1847531bc9 github.com/sercand/kuberesolver@v2.1.0+incompatible
- github.com/thanos-io/thanos@v0.13.1-0.20200731083140-69b87607decf github.com/sercand/kuberesolver@v2.4.0+incompatible
可以看到正常版本下,kuberesolver@2.4.0 被 thanos 所依賴,kuberesolver@v2.1.0 被 weaveworks 所依賴。
replace 之后
- ▶ go mod graph| grep kuberesolver
- github.com/weaveworks/common@v0.0.0-20210419092856-009d1eebd624 github.com/sercand/kuberesolver@v2.1.0+incompatible
是不是很神奇,kuberesolver@v2.4.0 這個(gè)版本竟然消失了。由于 kuberesolver 的 v2.1.0 和 v2.4.0 是不兼容的,所以導(dǎo)致 replace 之后就無(wú)法編譯了。
Gomod replace 語(yǔ)義
其實(shí)這并不神奇,這個(gè)涉及到 Go mod 的 replace 語(yǔ)義,不過(guò)也是很容易讓人忽略的特性。
replace directives:(https://golang.org/ref/mod#go-mod-file-replace)
- replace directives only apply in the main module’s go.mod file and are ignored in other modules. See Minimal version selection for details.
其實(shí)很簡(jiǎn)單,replace 只對(duì)主模塊(也就是你的當(dāng)前項(xiàng)目)是生效的??梢宰鋈缦碌目偨Y(jié):
- 主模塊的 replace 對(duì)于被依賴的模塊是不生效的
- 被依賴的模塊的 go.mod 的 replace 對(duì)主模塊也是不生效的
所以,當(dāng) replace 之后,cortex 依賴的 thanos 的 replace 是不生效的。我們理一下依賴樹:
- 主模塊 cortex => require github.com/weaveworks/common v0.0.0-20210419092856-009d1eebd624
- weaveworks => requre github.com/sercand/kuberesolver v2.1.0+incompatible
- 于是整體上 kuberesolver 就只有了 v2.1.0 了
這個(gè)邏輯是跟 gomod 的 replace 語(yǔ)義是吻合的,也就是 replace 之后編譯不過(guò)是正確的。
欺騙 gomod
那就更加神奇了,為何 cortex 直接 require thanos 就能編譯成功,按照 gomod replace 語(yǔ)義來(lái)說(shuō),這也是編譯不過(guò)的才是正確的。
因?yàn)楦鶕?jù)文檔我們知道,replace 僅僅作用于主模塊,脫離了主模塊是一律不生效的,這個(gè)是毋庸置疑的。
我做了個(gè)實(shí)驗(yàn)放在了 https://github.com/georgehao/gomodtestmain ,有興趣的可以試一下,這個(gè)能驗(yàn)證 gomod 是遵循 gomod replace 語(yǔ)義 和 MVS (最小版本選擇)算法的。
問(wèn)題基本陷入了僵局,我們?nèi)绾纹凭帜?
繼續(xù)使用 go mod graph 功能,來(lái)查看 cortex 依賴的 thanos 的依賴樹。
- github.com/thanos-io/thanos@v0.19.1-0.20210729154440-aa148f8fdb28 gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307
- github.com/thanos-io/thanos@v0.13.1-0.20210401085038-d7dff0c84d17 github.com/Azure/azure-pipeline-go@v0.2.2
- github.com/thanos-io/thanos@v0.8.1-0.20200109203923-552ffa4c1a0d k8s.io/utils@v0.0.0-20191114200735-6ca3b61696b6
- github.com/thanos-io/thanos@v0.13.1-0.20210204123931-82545cdd16fe gopkg.in/yaml.v2@v2.3.0
- github.com/thanos-io/thanos@v0.13.1-0.20201030101306-47f9a225cc52 go.uber.org/goleak@v1.1.10
- github.com/thanos-io/thanos@v0.13.1-0.20200807203500-9b578afb4763 go.elastic.co/apm/module/apmot@v1.5.0
- ....
- github.com/thanos-io/thanos@v0.13.1-0.20200731083140-69b87607decf github.com/gogo/protobuf@v1.3.1
由于這個(gè)依賴樹太長(zhǎng)(700多行),我就不貼了,基本上也能看出來(lái),cortex 依賴了 thanos N 多個(gè)版本,其中在最后一個(gè)版本中的 go.mod 中我們發(fā)現(xiàn)了一個(gè)有意思的東西:
- require (
- github.com/sercand/kuberesolver v2.4.0+incompatible // indirect
- )
也就是鬧了半天,由于 thanos 某個(gè)很古老的版本的 gomod require kuberesolver@v2.4.0,讓 gomod 誤以為 cortex 依賴的 thanos 依然是 require 了 kuberesolver@v2.4.0 了。雖然 thanos 早就改成了 repace kuberesolver,但也就讓 cortex 順利編譯過(guò)去了。
這算不算 gomod 的 bug 呢?
為什么 cortex 會(huì)依賴 thanos 這么多版本呢?這就要回到開篇說(shuō)的 cortex 和 thanos 功能復(fù)用的問(wèn)題了。
目前 cortex 和 thanos 這個(gè)兩個(gè)項(xiàng)目,基本上是這么依賴的:
- cortex 1.9.0 -> thanos v0.19.1-0.20210729154440-aa148f8fdb28
- thanos v0.19.1-0.20210729154440-aa148f8fdb28 -> cortex v1.8.1-0.20210422151339-cf1c444e0905
- cortex v1.8.1-0.20210422151339-cf1c444e0905 -> thanos v0.13.1-0.20210401085038-d7dff0c84d17
- ....
cortex 與 thanos 之間的相互引用,就像俄羅斯套娃一樣,簡(jiǎn)直就是 gomod 的噩夢(mèng)。go mod replace 語(yǔ)義,竟然讓這兩個(gè)套娃給破解了。
如何解決
對(duì)應(yīng)如何cortex replace thanos 的問(wèn)題,其實(shí)知道問(wèn)題的根本所在,解決起來(lái)就很簡(jiǎn)單了,有兩種方式吧:
- 由于 gomod MVS 算法,我們直接在主項(xiàng)目 cortex 中指定 kuberesolver 的版本為 v2.4.1
- 方案 1 僅對(duì)于向下兼容的項(xiàng)目比較適用,如果某項(xiàng)目沒有這個(gè)責(zé)任心的話,這么做可能是會(huì)出問(wèn)題的,所以比較直接的解決辦法,直接修改 thanos 的 go.mod, 將 thanos 的所依賴的 kuberesolver 從 replace 挪到 require 中























