Help us understand the problem. What is going on with this article?

Go勉強(3) kubernetes client-goのexamplesを読んでみる

シリーズ

Go勉強(1) mac+VSCode+Go環境を設定
Go勉強(2) kubernetes client-goのexamplesをbuildする
Go勉強(3) kubernetes client-goのexamplesを読んでみる
Go勉強(4) kubernetes client-goでPodのwatcher(TUI)を書いてみる
Go勉強(5) kubernetes client-goでPodのwatcher(TUI)を書いてみる2

はじめに

Golang自体の勉強をかねて、kubernetesへのアクセスライブラリclient-goを使ったサンプルソースを読んでみます。

■参考
初めてのGo
他言語プログラマがgolangの基本を押さえる為のまとめ
他言語から来た人がGoを使い始めてすぐハマったこととその答え
Goで学ぶポインタとアドレス
「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件

サンプルコード

https://github.com/kubernetes/client-go/blob/master/examples/out-of-cluster-client-configuration/main.go
このコード読みます。 全行掲載します。

client-go/examples/out-of-cluster-client-configuration/main.go
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Note: the example only works with the code within the same release/branch.
package main

import (
    "flag"
    "fmt"
    "os"
    "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"
    //
    // Uncomment to load all auth plugins
    // _ "k8s.io/client-go/plugin/pkg/client/auth"
    //
    // Or uncomment to load specific auth plugins
    // _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
    // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    // _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
    // _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)

func main() {
    var kubeconfig *string
    if home := 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()

    // use the current context in kubeconfig
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err.Error())
    }

    // create the clientset
    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("There are %d pods in the cluster\n", len(pods.Items))

        // Examples for error handling:
        // - Use helper functions like e.g. errors.IsNotFound()
        // - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
        namespace := "default"
        pod := "example-xxxxx"
        _, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
        if errors.IsNotFound(err) {
            fmt.Printf("Pod %s in 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)
    }
}

func homeDir() string {
    if h := os.Getenv("HOME"); h != "" {
        return h
    }
    return os.Getenv("USERPROFILE") // windows
}

コードリーディング

コードを見ながら気になるところをコメントしていきます。

import パッケージ

importされいるパケージです。 リファレンスはhttps://godoc.org/ から検索できるようです。

パッケージ 説明
flag コマンドラインフラグ解析を実装します。
fmt Cのprintfおよびscanfに類似した機能を持つフォーマットされたI / Oを実装します。 「動詞」の形式はCから派生していますが、より単純です。
os オペレーティングシステムの機能に対するプラットフォームに依存しないインターフェイスを提供します。エラー処理はGoに似ていますが、設計はUnixに似ています。失敗した呼び出しは、エラー番号ではなくエラータイプの値を返します。
path/filepath ターゲットオペレーティングシステム定義のファイルパスと互換性のある方法でファイル名パスを操作するためのユーティリティルーチンを実装します。
time 時間を測定および表示する機能を提供します。
k8s.io/apimachinery/pkg/api/errors APIフィールド検証の詳細なエラータイプを提供します。
k8s.io/apimachinery/pkg/apis/meta/v1 すべてのバージョンに共通のAPIタイプが含まれています。
k8s.io/client-go/kubernetes 自動生成されたクライアントセットです。
k8s.io/client-go/tools/clientcmd 固定構成、.kubeconfigファイル、コマンドラインフラグ、またはマージされた組み合わせから作業クライアントを構築するためのワンストップショッピングを提供します。

main関数

mainパッケージのmain関数がエントリーポイントとなって最初に実行されます。 javaのstatic main関数と同じですね。

func main() {

ポインタ

C言語ではかなり苦しめられるポインタですが、Goの場合はポインタ渡し(参照渡し)・値渡しくらいのことを理解しとけばそんなに難しくなさそうです。

    var kubeconfig *string
        :
    kubeconfig = flag.String( ... )
        :
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)

flagが返す文字列のポインタを受け取って(ポインタ渡し)、clientcmdに文字列のコピーを渡してます(値渡し)。
↓ こうすると文字列のコピーを受け取って(値渡し)、さらにコピーを渡す(値渡し)ことになりますね。
文字列にポインタ渡しを使うのは無駄なコピー処理やメモリーを節約するのが目的でしょうね。
ポインタ渡しされた文字列を変更できるのかな。C言語的な考えだと文字列伸ばすと後ろのメモリが壊れるからダメなような。(後で調査)

    var kubeconfig string
        :
    kubeconfig = *flag.String( ... )
        :
    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)

if文 初期化ステートメント

    if home := homeDir(); home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    }

このif home := homeDir();の部分。 見慣れない構文です。 「初期化ステートメント」と言うそうです。
Goではswitch文にも同様に記述できます。
他言語でよくあるfor(int i=0; i<10; i++) {i=0の部分と同じ位置付けのものです。

    home := homeDir()
    if home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    }

他言語だったら↑こんな感じで書きますね。 初期化ステートメントは1行減らすための構文?って最初思いましたが、初期化ステートメントによって変数のスコープをif文内に閉じ込めることができるんですね。

        if errors.IsNotFound(err) {
            :
        } else if statusError, isStatus := err.(*errors.StatusError); isStatus {
            :
        } else {
            :
        }

この部分↑が初期化ステートメントじゃなかったら、↓こんな感じにネストすることになってイヤですね。

        if errors.IsNotFound(err) {
            :
        } else {
            statusError, isStatus := err.(*errors.StatusError);
            if  isStatus {
                :
            } else {
                :
            }
        }

Flag

Pythonなんかにもあるコマンドライン パラメータを受け取ったり、usageを表示します。

    kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    flag.Parse()

helpを出すとこんな感じになります。kubernetesのconfigファイルを指定する引数を渡せます。

$ ./app --help
Usage of ./app:
  -kubeconfig string
        (optional) absolute path to the kubeconfig file (default "/Users/haruo/.kube/config")

多値返却

pythonのように複数の戻り値を簡単に返せて簡単に受け取れます。
使わない戻り値は"_"で受けとります。

    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
        :
    clientset, err := kubernetes.NewForConfig(config)
        :
    _, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})

ん? これだとerrが二重宣言でビルドエラーじゃないの? と思いましたが、
試してみたら:=を使う時、左辺に新しく宣言する変数が一つでもあればエラーにならないみたいです。

エラー処理

C → java,C# → Goの順に経験すると、Cのif (rc != 0)だらけのコードに逆戻りするような感じがしてしまうのですが、Goのそれは一周まわった完成形って感じでしょうか。

    if err != nil {
        panic(err.Error())
    }

試しに誤ったconfigファイルを渡して無理やりエラーを発生させると、以下のような結果になりました。

$ ./app -kubeconfig /Users/xxxxx/.ssh/config
panic: error loading config file "/Users/xxxxx/.ssh/config": couldn't get version/kind; json parse error: json: cannot unmarshal string into Go value of type struct { APIVersion string "json:\"apiVersion,omitempty\""; Kind string "json:\"kind,omitempty\"" }

goroutine 1 [running]:
main.main()
        /Users/xxxxx/go/src/out-of-cluster-client-configuration/main.go:54 +0x83a

繰り返し

繰り返しを表すキーワードはforのみ。whileは無し。シンプルですね。

    for {
    }

これは無限ループです。 多言語だったらwhile(true)for(;;)って書いたりします。

キャスト

キャストとキャストできるかの判定を一発でできちゃいます。

        } else if statusError, isStatus := err.(*errors.StatusError); isStatus {
            :
        }

errerrors.StatusErrorにキャストできるかをisStatusに格納、
errerrors.StatusErrorにキャストした結果をstatusErrorに格納します。

hoge.java
        } else if (err instanceof StatusError) {
            StatusError statusError = (StatusError) err;
            :
        }

javaだったら↑こんな感じで書きます。 StatusErrorを3回も書かないといけないです。
でもisStatusは書かなくて済んでる。んー。

time

        time.Sleep(10 * time.Second)

time.Secondは以下のようにnano秒単位の倍数で定義されてます。

/usr/local/Cellar/go/1.13.1/libexec/src/time/time.go
const (
    Nanosecond  Duration = 1
    Microsecond          = 1000 * Nanosecond
    Millisecond          = 1000 * Microsecond
    Second               = 1000 * Millisecond
    Minute               = 60 * Second
    Hour                 = 60 * Minute
)

kubernetes

Client-goに関する部分です。 まだよくわかってないのでリファレンスとかだけ貼っておきます。
もっとわかってきたら書き足します。

    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    clientset, err := kubernetes.NewForConfig(config)

kube/configファイルのパスを渡してClientsetを取得します。

リファレンス ソース
rest.Config https://github.com/kubernetes/client-go/blob/master/rest/config.go#L52
clientcmd.BuildConfigFromFlags https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/client_config.go#L541
Clientset https://github.com/kubernetes/client-go/blob/master/kubernetes/clientset.go#L111
kubernetes.NewForConfig https://github.com/kubernetes/client-go/blob/master/kubernetes/clientset.go#L354
    pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
    pod, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})

Podの情報を取得します。 

リファレンス ソース
PodList https://github.com/kubernetes/api/blob/master/core/v1/types.go#L3536
Pod https://github.com/kubernetes/api/blob/master/core/v1/types.go#L3512
CoreV1Client.Pods https://github.com/kubernetes/client-go/blob/master/kubernetes/typed/core/v1/core_client.go#L88
PodInterface https://github.com/kubernetes/client-go/blob/master/kubernetes/typed/core/v1/pod.go#L39
metav1.ListOptions https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go#L328
metav1.GetOptions https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go#L421

終わりに

短いコードですけど、Goの特徴がいくつも出てきました。
Goの理解はまだまだですけど、次からコード書いていきます。

oruharo
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした