LoginSignup
4

More than 3 years have passed since last update.

client-goを利用してKubernetesのnamespaceを切り替える対話式CLIツールを作成した。

Last updated at Posted at 2019-10-03

今回作ったツール - kns

kns.gif

Kubernetesによく触るのですが、Golangを書いたことがなかったので、ずっと触りたかったClient-goを使ってnamespaceを切り替えるツールを作成しました。

namespaceを切り替えるツールとしてはkubectxに同梱されているkubensが有名かと思いますが、bashで書かれているので、Golangで作るのに丁度いい題材となりました。
https://github.com/ahmetb/kubectx/blob/master/kubens

使い勝手はgifをみていただければと思いますが、シンプルで割りかし良いと思っています。
この手のツールは作っても使わないことが多いので・・・

今回のツール作成の際のポイントを少しまとめたいと思います。
KubernetesでGolangに興味を持ったエンジニアのご参考になれば幸いです。

client-goでKubernetes API Serverにアクセス

基本的にはclient-goのリポジトリにあるサンプルコードを参考に作りました。
https://github.com/kubernetes/client-go/tree/master/examples

こちらにin-cluster-client-configurationout-of-cluster-client-configurationがあるのですが、今回はコマンドラインツールのためout-of-cluster-client-configurationを参考にします。

基本的にクライアントのkubeconfigの情報を利用してAPI Serverに繋ぎに行きます。
(通常kubeconfigは~/.kube/configにあります。)

namespace名を取得するまでの簡単流れは以下の通りです。
大事なところだけ見ると結構シンプルです。


import (
        "os"
        "gopkg.in/yaml.v2"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/client-go/kubernetes"
        "k8s.io/client-go/tools/clientcmd"
)

// kubeconfigのパスを取得
var kubeconfigPath *string
*kubeconfigPath = filepath.Join(os.Getenv("HOME"), ".kube", "config")

// kubeconfigを元にclientcmd.BuildConfigFromFlagsでconfigオブジェクトを取得
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)

// clientsetなるオブジェクトを取得。(こいつで各種API操作が可能です)
clientset, err := kubernetes.NewForConfig(config)

// 今回はnamespaceのリストを以下のコマンドで取得
nss, err := clientset.CoreV1().Namespaces().List(metav1.ListOptions{})

// namespaceの名前を取得。
ns_name = nss.Items[i].ObjectMeta.Name

取得したnsの持っている属性はgodocから見れます
https://godoc.org/k8s.io/api/core/v1#Namespace
ただし、Kubernetesのオブジェクトは共通してtypemetaobjectmetaという2つの属性を持っていますので、一つ一つ調べる必要はありません。

・Typemeta
https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#TypeMeta

・Objectmeta
https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#ObjectMeta

Kubernetesのマニフェストでお馴染みの属性たちです。今回は名前が欲しかったので、ObjectMeta.Nameで取得しました。

podやserviceなど他のオブジェクトの操作方法はgodocをご参照ください。
(以下はcore/v1のリンクです。)
https://godoc.org/k8s.io/client-go/kubernetes/typed/core/v1

Golangでyamlの読み書き

kubeconfigはyamlで書かれているので、yamlを読み書きする必要があります。

client-goのパッケージにkubeconfigを操作するものがあるのですが、今回はGolangの勉強をかねているので、あえてyamlのパース部分を実装しました。


import (
    "io/ioutil"
    "os"
    "path/filepath"
    "strconv"

    "gopkg.in/yaml.v2"
)

// Kubeconfigの構造体を定義
type Kubeconfig struct {
    APIVersion     string `yaml:"apiVersion"`
    Kind           string
    Clusters       []interface{}
    Contexts       []Context
    CurrentContext string `yaml:"current-context"`
    Preferences    interface{}
    Users          interface{}
}

// Contextの構造体を定義
type Context struct {
    Context struct {
        Cluster   string
        Namespace string
        User      string
    }
    Name string
}

// read kubeconfig file
func readKubeconfig(kubeconfigPath string) (kConfig Kubeconfig) {
    buf, err := ioutil.ReadFile(kubeconfigPath)
    if err != nil {
        panic(err)
    }

    // yaml to struct
    err = yaml.Unmarshal(buf, &kConfig)
    if err != nil {
        panic(err)
    }
    return kConfig
}

// write kubeconfig to file
func writeKubeconfig(kubeconfigPath string, kConfig Kubeconfig) (err error) {
    err = nil

    // struct to yaml
    out, err := yaml.Marshal(kConfig)
    if err != nil {
        panic(err)
    }
    // Write kubeconfig file
    err = ioutil.WriteFile(kubeconfigPath, out, 0600)
    if err != nil {
        panic(err)
    }
    return err
}

func main() {
        ...
        // 読み込み
        kConfig := readKubeconfig(*kubeconfigPath)
        ...
        // 構造体の中身を編集
        kConfig.Contexts[i].Context.Namespace = namespace
        ...
        // 書き込み
        err := writeKubeconfig(*kubeconfigPath, kConfig)
        ...
}

構造体を定義するとyaml.Unmarshal()で、構造体の名前だけでうまくyamlをパースしてくれます。反対もyaml.Marshal()で簡単にファイル出力可能な形式にしてくれます。

ただしちょっといくつかの制約があり、少し調べました。
 ・属性名は大文字から始める。
 ・構造体で使えないハイフンなどは読み取ってくれないので、その場合はyaml:"current-context"で対象を指定する。

type Kubeconfig struct {
    APIVersion     string `yaml:"apiVersion"`
    Kind           string
    Clusters       []interface{}
    Contexts       []Context
    CurrentContext string `yaml:"current-context"`
    Preferences    interface{}
    Users          interface{}
}

interface{}はまだ使いこなせていないのですが、型指定を曖昧にできるものという認識で、今回は使わない属性はinterface{}にしました。(合っていなければご指摘ください。。)

対話式のコマンドライン

最後に対話式コマンドラインについてです。
インタラクティブな操作がしたかったのですが、思いのほか簡単に書けました。

こちらの記事を参考に、bufioパッケージのscannerというものを使いました。
https://blog.linkbal.co.jp/1971/

import (
    "bufio"
    "errors"
    "fmt"
    "os"
    "strconv"
)

// show dialog to ask
func askNamespaceNum(max int) (i int, qerr error) {
    fmt.Print("** Which namespace do you want to switch? (exit: q)\n")
    fmt.Print("Select[n] => ")
    i = 99
    qerr = nil

    // scannerの取得 
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        // 入力内容の取得
        input := scanner.Text()

        if atoi, err := strconv.Atoi(input); err == nil {
            i = atoi
            if 0 <= i && i < max {
                break
            }
            fmt.Print("Select[n] => ")
        } else if input == "q" {
            qerr = errors.New("quit")
            // breakすると入力待ち終了
            break
        } else {
            fmt.Print("Select[n] => ")
        }
    }

    if err := scanner.Err(); err != nil {
        panic(err)
    }
    return i, qerr
}

Podの一覧からkubectl execしたり、kubectl logsしたり出来たらいいなと色々思いつきます。

最後に

Kubernetesやコンテナ周りはほとんどGolangの世界なので、それらの深い理解のためにGolangの学習は大きいと思います。
Golang自体非常に読みやすいので、少しわかるだけでもさらにソースコードリーディングが捗ります。

今後はCustom ControllerやOperatorを作成したいです。

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