今日はGoの依存関係で躓いたので、次回躓かないようにいろいろ試してみる。
go mod init
の後次の簡単なk8s client のプログラムを書いて実行する。サンプルのまんまなのに、失敗する。
package main
import (
"flag"
"fmt"
"path/filepath"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
func main() {
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err.Error())
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
for {
pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
if err != nil {
panic(err.Error())
}
fmt.Printf("Tehre are %d pods in the cluster\n", len(pods.Items))
namespace := "default"
pod := "nginx"
_, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
if errors.IsNotFound(err) {
fmt.Printf("Pod %s is namespace %s not found\n", pod, namespace)
} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
fmt.Printf("Error getting pod %s in namespace %s: %v\n",
pod, namespace, statusError.ErrStatus.Message)
} else if err != nil {
panic(err.Error())
} else {
fmt.Printf("Found pod %s in namespace %s\n", pod, namespace)
}
time.Sleep(10 * time.Second)
}
}
result
$ go run main.go
build command-line-arguments: cannot load github.com/googleapis/gnostic/OpenAPIv2: module github.com/googleapis/gnostic@latest found (v0.5.4), but does not contain package github.com/googleapis/gnostic/OpenAPIv2
なんだこれ?多分該当のバージョンが壊れていることが推察される。
今は解決しているわけだが、その場その場の解決策がわかっても意味がないので、実際に自分がやったことを書いてみる。
最初に思ったことは、github.com/googleapis/gnostic/OpenAPIv2
を使っている依存関係を調べてそのライブラリをバージョンアップ、もしくはダウンすればよいということ。go では明確に依存関係のグラフみたいなものが出るのもが見つからなかった。go mod why
が説明的にはそうなのだが、うまく動作していないように感じる。StackOverflowも同じようなことを言っていた。
いろいろしたが、本来するべきだった手順は、きっと go.mod
を見ることだろう。
module simplearchitect.com
go 1.13
require (
github.com/imdario/mergo v0.3.12 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
k8s.io/api v0.20.4 // indirect
k8s.io/apimachinery v0.20.4
k8s.io/client-go v11.0.0+incompatible
k8s.io/klog v1.0.0 // indirect
k8s.io/utils v0.0.0-20210305010621-2afb4311ab10 // indirect
)
// indirect というのがあるが、これは私が直接使っていないもの。+incompatible
は go module に対応していないものなので怪しいところ。もし go module の依存関係がツリーで出てくるものがあれば最高なのだが、見つからなかったので、直接使っているライブラリの go.mod をGitHub のページに行って、それぞれ調査する。少なくとも、master/main のgithub.com/googleapis/gnostic
のバージョンは v0.4.1
であり、v0.5.4
ではない。k8s.io/client-go
k8s.io/apimachinery
の双方が依存しているらしいが、どちらも、v0.4.1
を使っている。不思議である。
では、次のステップとして、github.com/googleapis/gnostic@v0.4.1
を試してみるが、同じエラーが出る。おそらく何かが v0.5.4
を参照しているからであろう。次に考えたのが v0.5.4
を削除してみようということ。go mod tidy
を使うと参照がキレた依存関係を整理してくれる。
$ go mod tidy
simplearchitect.com imports
k8s.io/client-go/kubernetes imports
k8s.io/client-go/discovery imports
github.com/googleapis/gnostic/OpenAPIv2: module github.com/googleapis/gnostic@latest found (v0.5.4), but does not contain package github.com/googleapis/gnostic/OpenAPIv2
simplearchitect.com imports
k8s.io/client-go/kubernetes imports
k8s.io/client-go/kubernetes/typed/auditregistration/v1alpha1 imports
k8s.io/api/auditregistration/v1alpha1: module k8s.io/api@latest found (v0.20.4), but does not contain package k8s.io/api/auditregistration/v1alpha1
simplearchitect.com imports
k8s.io/client-go/kubernetes imports
k8s.io/client-go/kubernetes/typed/settings/v1alpha1 imports
k8s.io/api/settings/v1alpha1: module k8s.io/api@latest found (v0.20.4), but does not contain package k8s.io/api/settings/v1alpha1
うむ。これはなかなかわかりやすい。近づいている。例のライブラリは、k8s.io/client-go
が使用している。では、v11.0.0
はどんなバージョンだろうとclient-go のサイトにいくと、意味が分からないが
最新 v12.0.0 とかあるのだが、(日付に注意)クリックすると、最新のバージョン全然ちゃうがな。
しかも、最新のプレリリースは、v11.0.0+incompatible
を回避してくれて最新バージョンになるみたい。
結局のところ、解決策は go get k8s.io/client-go@v0.20.4
だったのだがなぜそうなるんだろう?
Major version suffix
go mod
の公式ドキュメントを読んでいると、Major Version Suffix というコンセプトが出てきた。これは、依存性の問題を避けるための解決策で、通常ライブラリが異なるバージョンを使っている場合、より上のバージョンが採用される。ところがそうだとバージョンのコンフリクトの問題が発生する。であるので、Goは メジャーバージョンが2以上の場合は、メジャーバージョンサフィックスをつけるように変更を行った。つまり、
example.com/mod
は、@v1.0.0
もしくは @v0.0.0
代のバージョンで使える表記であるが、メジャーバージョンが2以上の場合は、example.com/mod/v2
として使う必要がある。これによって、メジャーバージョンが違うと、パスが異なるので、同じライブラリの異なるバージョンが存在できるようになる。つまり v11.0.0.0+incompatible
の意味合いは、モジュールに対応していないからではなく、v11.0.0.0
のライブラリは本来バージョンサフィックス付きで使われるべきなので、k8s.io/client-go/v11 としてインポートするべき(だけとできないのだろう)
インポート部分を以下のように変えてみる。
import (
"flag"
"fmt"
"path/filepath"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/v11/kubernetes"
"k8s.io/client-go/v11/tools/clientcmd"
"k8s.io/client-go/v11/util/homedir"
)
result
$ go run main.go
build command-line-arguments: cannot load k8s.io/client-go/v11/kubernetes: module k8s.io/client-go@latest found (v11.0.0+incompatible), but does not contain package k8s.io/client-go/v11/kubernetes
すると、どこかで見たようなエラーメッセージが出てきた。本来、v11 のサフィックスでアクセスできるようにするべきが、そうなってないので、コンテンツが無いように見えるということである。だから、おそらく k8s.io/client-go
のプロジェクトは、バージョンサフィックスに対応するためにバージョン番号をv11.0.0 から、v0.20.0 に変更して、バージョンサフィックス問題を避けているのだろう。(多分)ところが、v11.0.0.0 は既にリリースされているので、普通に使うとlatest
ではそれがインポートされて incompatible
問題が発生したということであろう。
自分のメンテしているアプリが、メジャーバージョン上げたいときは、ディレクトリを掘る必要があるっぽい。
まとめ
- メジャーバージョンが大きい場合は、現在の
go mod
ではバージョンサフィックスが必要なはずなので、無い場合は、そこが怪しい。 - 最初に確認するべきは
go.mod
- go mod tidy で、依存関係が壊れている個所を特定する
- 変なメジャーバージョンがある場合は、リポジトリの最新を見て、最新のものをインストールする。
go get k8s.io/client-go@v0.20.4
等