📝

Kubernetes カスタムリソースをGoで操作する

2024/12/11に公開

本記事は、さくらインターネット Advent Calendar2024の11日目の記事です。

Kubernetesリソースはclient-goを使うことで、Kubernetes APIにリクエストしプログラム内で操作することができますが、CRDにより独自に定義されているKubernetes カスタムリソースは少し違う方法で扱います。
Kubernetes カスタムリソースをGoで操作する方法を記載します。

ライブラリ側で公開されているClientSetを用いる

Kubernetesカスタムリソースはライブラリ側でClientSetが提供されている場合があります。
例としては、Traefikのカスタムリソースは以下で定義されているClientSetを用いることで操作できます。(こちらはcode-generatorで生成されています。これを用いることでCRDに沿ったClientSetを作成することができます。)
https://github.com/traefik/traefik/blob/master/pkg/provider/kubernetes/crd/generated/clientset/versioned/clientset.go#L45

以下はClientSetを用いてTraefikのIngressRouteを作成する例です。

package main

import (
	"context"
	"log"

	traefikClient "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/generated/clientset/versioned"
	traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	kubeconfig := "~/.kube/config"
	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		log.Fatal(err)
	}

	traefikClient, err := traefikClient.NewForConfig(config)
	if err != nil {
		log.Fatal(err)
	}
	ingressRoute := &traefikv1alpha1.IngressRoute{
		ObjectMeta: meta.ObjectMeta{
			Name:      "test-ingressroute",
			Namespace: "test-ns",
		},
		Spec: traefikv1alpha1.IngressRouteSpec{
			EntryPoints: []string{"web"},
			Routes: []traefikv1alpha1.Route{
				{
					Match:    "Host(`example.com`)",
					Kind:     "Rule",
					Priority: 10,
					Services: []traefikv1alpha1.Service{
						{
							LoadBalancerSpec: traefikv1alpha1.LoadBalancerSpec{
								Name:      "loadbalancer",
								Port:      intstr.FromInt(80),
								Namespace: "test-ns",
							},
						},
					},
				},
			},
		},
	}
	_, err = traefikClient.TraefikV1alpha1().IngressRoutes("test-ns").Create(context.Background(), ingressRoute, meta.CreateOptions{})
	if err != nil {
		log.Fatal(err)
	}
}

実行すると、指定したnamespaceにingressrouteが作成されていることが確認できます。

> go run main.go
> kubectl get ingressroute -n test-ns
NAME                AGE
test-ingressroute   25s

このようにライブラリ側で提供されている型定義を用いて、Kubernetes カスタムリソースを操作することができます。

Dynamic Clientを用いる

client-goではDynamic ClientというClientも提供されています。
https://github.com/kubernetes/client-go/blob/master/dynamic/simple.go
こちらは通常のClientとは異なり、Unstructuredという型を用いることで、Kubernetes標準リソースの型に縛られずにKubernetes APIへリクエストすることができます。

以下はDynamic Clientを用いて先ほどのIngressRouteを作成する例です。

package main

import (
	"context"
	"log"

	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	kubeconfig := "~/.kube/config"
	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		log.Fatal(err)
	}

	dynamicClient, err := dynamic.NewForConfig(config)
	if err != nil {
		log.Fatal(err)
	}

	ingressRouteGVR := schema.GroupVersionResource{
		Group:    "traefik.io",
		Version:  "v1alpha1",
		Resource: "ingressroutes",
	}

	ingressRoute := &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "traefik.io/v1alpha1",
			"kind":       "IngressRoute",
			"metadata": map[string]interface{}{
				"name":      "test-ingressroute",
				"namespace": "test-ns",
			},
			"spec": map[string]interface{}{

				"entryPoints": []interface{}{"web"},
				"routes": []interface{}{
					map[string]interface{}{
						"match":    "Host(`example.com`)",
						"kind":     "Rule",
						"priority": 10,
						"services": []interface{}{
							map[string]interface{}{
								"name":      "loadbalancer",
								"port":      80,
								"namespace": "masumura-test",
							},
						},
					},
				},
			},
		},
	}

	_, err = dynamicClient.Resource(ingressRouteGVR).Namespace("test-ns").Create(context.Background(), ingressRoute, meta.CreateOptions{})

	if err != nil {
		log.Fatal(err)
	}
}

Dynamic Clientを用いる場合は、標準リソースをclient-goで操作する場合とは異なり、
GroupVersionResourceを定義し、Unstructured typeで定義したオブジェクトを用いてAPIへリクエストします。Unstructured typeでは、map[string]interface{}により、あらゆる型のオブジェクトを定義できるようになっています。

まとめ

GoでKubernetes カスタムリソースを操作する方法を紹介しました。
基本的には、存在する場合は型定義がされている専用のClientSetを用いるのがよいかと思いますが、Dynamic Clientを扱うことでも操作することができます。

Discussion