今回作ったツール - kns
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-configuration
とout-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のオブジェクトは共通してtypemeta
とobjectmeta
という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を作成したいです。