kyaml2go を使って yaml から簡単にGolang のコードを生成して遊びたい

前書き

curlgolang で行いたい時にはcurl-to-Go を利用することができる。

ConoHa のAPIを利用して トークン発行 を発行するしたい場合には以下のようなcurl を実行する必要がある トークン発行 - Identity API v2.0 / ConoHa API を参照

curl -i -X POST \
-H "Accept: application/json" \
-d '{"auth":{"passwordCredentials":{"username":"ConoHa","password":"paSSword123456#$%"},"tenantId":"487727e3921d44e3bfe7ebb337bf085e"}}' \
https://identity.tyo1.conoha.io/v2.0/tokens

curl-to-Go を利用すると以下のようなコードが生成されて労力を最小限にすることができる。労力が最小なことは嬉しい。ちなみにCLI も利用できるのでかなり使っている。

// Generated by curl-to-Go: https://mholt.github.io/curl-to-go

body := strings.NewReader(`{"auth":{"passwordCredentials":{"username":"ConoHa","password":"paSSword123456#$%"},"tenantId":"487727e3921d44e3bfe7ebb337bf085e"}}`)
req, err := http.NewRequest("POST", "https://identity.tyo1.conoha.io/v2.0/tokens", body)
if err != nil {
    // handle err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    // handle err
}
defer resp.Body.Close()

まぁこの前書きはマジで何も関係ない

概要

kubernetes API にアクセスする時には様々な方法がある。kubectlcurl などでも行う事ができる。でも、kubectl 使うことが一般的だとは思う。しかし、もう少し複雑な事をさせたいと思ったらのなら何かしらのプログラミング言語で実施したくなります。Kubernetes APIにアクセスするにはk8s.io/client-goを使うのはわりと自然な流れかと思いますが。この場合client-goを使うコードを1から書いてもよいですが、kyaml2goを使うと、yamlファイルからclient-goを利用するコードを生成してれるので特定の操作を簡単に行う事ができます。下記のようなyaml があったとしたら そのyamlcreate update get delete してくれるコードを生成してくれます。client-go について詳しく学習したい場合には Programming Kuberentes の三章であるBasics of client-go を読んでください。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

create を選択した場合(Web UI であるkyaml2go: Kubernetes client-go code generator から引用)

// Auto-generated by kyaml2go - https://github.com/PrasadG193/kyaml2go
package main

import (
    "fmt"
    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    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"
    "os"
    "path/filepath"
)

func main() {
    // Create client
    var kubeconfig string
    kubeconfig, ok := os.LookupEnv("KUBECONFIG")
    if !ok {
        kubeconfig = filepath.Join(homedir.HomeDir(), ".kube", "config")
    }

    config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
    if err != nil {
        panic(err)
    }
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err)
    }
    kubeclient := clientset.AppsV1().Deployments("default")

    // Create resource object
    object := &appsv1.Deployment{
        TypeMeta: metav1.TypeMeta{
            Kind:       "Deployment",
            APIVersion: "apps/v1",
        },
        ObjectMeta: metav1.ObjectMeta{
            Name: "nginx-deployment",
            Labels: map[string]string{
                "app": "nginx",
            },
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: ptrint32(3),
            Selector: &metav1.LabelSelector{
                MatchLabels: map[string]string{
                    "app": "nginx",
                },
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: map[string]string{
                        "app": "nginx",
                    },
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{
                        corev1.Container{
                            Name:  "nginx",
                            Image: "nginx:1.14.2",
                            Ports: []corev1.ContainerPort{
                                corev1.ContainerPort{
                                    HostPort:      0,
                                    ContainerPort: 80,
                                },
                            },
                            Resources: corev1.ResourceRequirements{},
                        },
                    },
                },
            },
            Strategy:        appsv1.DeploymentStrategy{},
            MinReadySeconds: 0,
        },
    }

    // Manage resource
    _, err = kubeclient.Create(object)
    if err != nil {
        panic(err)
    }
    fmt.Println("Deployment Created successfully!")
}

func ptrint32(p int32) *int32 {
    return &p
}

CLIもあるのでCLI をインストールして実際に見ていく

遊ぶ

環境情報

$ go version
go version go1.14.5 linux/amd64
$ docker version                                                                                                                                                                                                                  4290ms
Client:
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.13.8
 Git commit:        afacb8b7f0
 Built:             Tue Jun 23 22:26:12 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          19.03.11
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.12
  Git commit:       77e06fd
  Built:            Mon Jun  8 20:24:59 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
$ kind --version # https://kind.sigs.k8s.io/docs/user/quick-start/
kind version 0.8.1

環境構築

クラスタ-の構築

$ kind create cluster --name kyaml2go
Creating cluster "kyaml2go" ...
 ✓ Ensuring node image (kindest/node:v1.18.2) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kyaml2go"
You can now use your cluster with:

kubectl cluster-info --context kind-kyaml2go

Have a nice day!

クラスタ-の確認

$ kind get clusters
kyaml2go
$ kind get kubeconfig --name kyaml2go > kubeconfig.yaml
$ kubectl version  --kubeconfig=kubeconfig.yaml
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.0", GitCommit:"9e991415386e4cf155a24b1da15becaa390438d8", GitTreeState:"clean", BuildDate:"2020-03-25T14:58:59Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.2", GitCommit:"52c56ce7a8272c798dbc29846288d7cd9fbae032", GitTreeState:"clean", BuildDate:"2020-04-30T20:19:45Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}

cli のインストール

 git clone https://github.com/PrasadG193/kyaml2go.git 
 cd kyaml2go
 make

$(shell go env GOPATH)/bin/kyaml2go 以下にコマンドが生成されているのでそれにyaml を下記のように食わせる(牡蛎食べたい 腹減った)(サンプルには下記のような書き方が書いてあるが複数のアクションを同時に食わせるのは恐らく対応してない)

kyaml2go create/get/update/delete -f /path/to/resource_specs.yaml 

設定するファイル

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

kyaml2go の実行

kyaml2go create -f nginx-deployment.yaml > create-nginx-deployment.go

create-nginx-deployment.go 生成したものを書きだす。~/.config/fish/config.fish などで set KUBECONFIG ./kubeconfig.yaml:~/.kube/config を設定するのでカレントディレクトリにkubeconfig.yaml があった場合には$KUBECONFIGという環境変数にはそちらが優先されて設定されるので便利がいいですね。

// Auto-generated by kyaml2go - https://github.com/PrasadG193/kyaml2go
package main

import (
        "fmt"
        appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
        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"
        "os"
        "path/filepath"
)

func main() {
        // Create client
        var kubeconfig string
        kubeconfig, ok := os.LookupEnv("KUBECONFIG")
        if !ok {
                kubeconfig = filepath.Join(homedir.HomeDir(), ".kube", "config")
        }

        config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
        if err != nil {
                panic(err)
        }
        clientset, err := kubernetes.NewForConfig(config)
        if err != nil {
                panic(err)
        }
        kubeclient := clientset.AppsV1().Deployments("default")

        // Create resource object
        object := &appsv1.Deployment{
                TypeMeta: metav1.TypeMeta{
                        Kind:       "Deployment",
                        APIVersion: "apps/v1",
                },
                ObjectMeta: metav1.ObjectMeta{
                        Name: "nginx-deployment",
                        Labels: map[string]string{
                                "app": "nginx",
                        },
                },
                Spec: appsv1.DeploymentSpec{
                        Replicas: ptrint32(3),
                        Selector: &metav1.LabelSelector{

                                MatchLabels: map[string]string{
                                        "app": "nginx",
                                },
                        },
                        Template: corev1.PodTemplateSpec{
                                ObjectMeta: metav1.ObjectMeta{
                                        Labels: map[string]string{
                                                "app": "nginx",
                                        },
                                },
                                Spec: corev1.PodSpec{
                                        Containers: []corev1.Container{
                                                corev1.Container{
                                                        Name:  "nginx",
                                                        Image: "nginx:1.14.2",
                                                        Ports: []corev1.ContainerPort{
                                                                corev1.ContainerPort{
                                                                        HostPort:      0,
                                                                        ContainerPort: 80,
                                                                },
                                                        },
                                                        Resources: corev1.ResourceRequirements{},
                                                },
                                        },
                                },
                        },
                        Strategy:        appsv1.DeploymentStrategy{},
                        MinReadySeconds: 0,
                },
        }

        // Manage resource
        _, err = kubeclient.Create(object)
        if err != nil {
                panic(err)
        }
        fmt.Println("Deployment Created successfully!")
}

func ptrint32(p int32) *int32 {
        return &p
}

clusterへの実行

create-nginx-deployment.go を実行してみたが go 1.14のgo run で乱雑に入るパッケージのいくつかが対応していないのでkyaml2goのgo.mod を参照して入れなおす

$ go run create-nginx-deployment.go
Deployment Created successfully!
$ go run get-nginx-deployment.go
Found object : &Deployment{ObjectMeta:{nginx-deployment  default /apis/apps/v1/namespaces/default/deployments/nginx-deployment 7a7fd262-09d1-49ef-89d2-5116c68d4e9e 53121316 1 2020-07-21 13:01:11 +0000 UTC <nil> <nil> map[app:nginx] map[deployment.kubernetes.io/revision:1] [] []  []},Spec:DeploymentSpec{Replicas:*3,Selector:&v1.LabelSelector{MatchLabels:map[string]string{app: nginx,},MatchExpressions:[]LabelSelectorRequirement{},},Template:{{      0 0001-01-01 00:00:00 +0000 UTC <nil> <nil> map[app:nginx] map[] [] []  []} {[] [] [{nginx nginx:1.14.2 [] []  [{ 0 80 TCP }] [] [] {map[] map[]} [] [] nil nil nil nil /dev/termination-log File IfNotPresent nil false false false}] [] Always 0xc000584068 <nil> ClusterFirst map[]   <nil>  false false false <nil> PodSecurityContext{SELinuxOptions:nil,RunAsUser:nil,RunAsNonRoot:nil,SupplementalGroups:[],FSGroup:nil,RunAsGroup:nil,Sysctls:[]Sysctl{},WindowsOptions:nil,} []   nil default-scheduler [] []  <nil> nil [] <nil> <nil> <nil> map[] []}},Strategy:DeploymentStrategy{Type:RollingUpdate,RollingUpdate:&RollingUpdateDeployment{MaxUnavailable:25%,MaxSurge:25%,},},MinReadySeconds:0,RevisionHistoryLimit:*10,Paused:false,ProgressDeadlineSeconds:*600,},Status:DeploymentStatus{ObservedGeneration:1,Replicas:3,UpdatedReplicas:3,AvailableReplicas:3,UnavailableReplicas:0,Conditions:[]DeploymentCondition{DeploymentCondition{Type:Available,Status:True,Reason:MinimumReplicasAvailable,Message:Deployment has minimum availability.,LastUpdateTime:2020-07-21 13:01:23 +0000 UTC,LastTransitionTime:2020-07-21 13:01:23 +0000 UTC,},DeploymentCondition{Type:Progressing,Status:True,Reason:NewReplicaSetAvailable,Message:ReplicaSet "nginx-deployment-7fd6966748" has successfully progressed.,LastUpdateTime:2020-07-21 13:01:23 +0000 UTC,LastTransitionTime:2020-07-21 13:01:11 +0000 UTC,},},ReadyReplicas:3,CollisionCount:nil,},}

resourceが生成されているのが確認できたと思う

kubectl でも同様に確認できたので

$ kubectl get pod                                                                                                                                                                                                                      
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7fd6966748-9km6c   1/1     Running   0          9m26s
nginx-deployment-7fd6966748-c9sng   1/1     Running   0          9m26s
nginx-deployment-7fd6966748-thk76   1/1     Running   0          9m26s

削除しておく

$ go run delete-nginx-deployment.go 
Deployment Deleted successfully!

ソースコードなどはココに載せておきます。

github.com

最後に

特にないけど Kind で構築したclusterを削除する

kind get clusters
kyaml2go
kind delete cluster --name kyaml2go
Deleting cluster "kyaml2go" ...