4
0

More than 3 years have passed since last update.

Go module の Dependency 問題を解決する

Last updated at Posted at 2021-03-14

今日は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 のサイトにいくと、意味が分からないが

image.png

最新 v12.0.0 とかあるのだが、(日付に注意)クリックすると、最新のバージョン全然ちゃうがな。
しかも、最新のプレリリースは、v11.0.0+incompatible を回避してくれて最新バージョンになるみたい。
image.png

結局のところ、解決策は 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
4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0