8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

client-go・api・apimachineryの機能と関係性

Last updated at Posted at 2023-11-30

これは ZOZO Advent Calendar 2023 カレンダー Vol.5 の 1日目の記事です。

はじめに

KubernetesはGo言語で開発されており、開発に利用されるモジュールとしてclient-goが利用されることは割とよく知られているかと思います。
またclient-goを触ると必ず登場するのがapiapimachineryというモジュールです。

自分がはじめてGo言語でKubernetes APIを操作するプログラムを書いた際には、これらのモジュールの機能・関係性がいまいちよく理解できずにいました。

本記事ではGo言語を使ってKubernetes APIを操作するプログラムを書く際に利用するclient-goapimachineryapiモジュールについてその機能と関係性について調べたことをまとめました。

client-go

client-goはGo言語でkubeapi-serverへリクエストを送る際のクライアントとして機能します。

前提として、Kubernetesには複数のコントロールプレーンコンポーネントがあり、そのうちの1つとしてkubeapi-serverが提供されています。
kubeapi-serverはクラスタの内部・外部に対してHTTP APIを公開しており、こちらをクエリすることにより、PodやNodeといったKubernetesリソースのオブジェクトを操作することができます。

例えば、クラスタ外部からKubernetesオブジェクトの操作を行う場合、kubectl CLIを利用することが一般的かと思います。こちらのCLIもGo言語で開発されており、client-goが利用されています。
kubectlのFactoryインターフェースRESTClientメソッドではclient-goのRESTClient構造体のポインタを返すようになっており、getコマンドの実装を追ってみるとRESTClientメソッドでクライアントを生成し、作成したクライアントを利用してHTTP GETリクエストを呼んでいることがわかります。

client-goの中でもよく利用するのはClientset構造体かと思います。
次節ではClientset構造体について説明します。

Clientset

Clientsetの場合、ただ単一のClientではなくClient + setとなっているところが重要な点かと思っています。

client-goを利用する際、まず次のようなコードでclientsetを取得するかと思います。

package main

import (
	"context"
	"flag"
	"fmt"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

var kubeconfig string

func main() {
	flag.StringVar(&kubeconfig, "kubeconfig", "", "kubeconfig path")
	flag.Parse()
    // kubeconfig fileの読み込み
	config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)

	// clientsetの初期化
	clientset, _ := kubernetes.NewForConfig(config)

	// Podオブジェクトの取得
	pod, _ := clientset.CoreV1().Pods("hoge").Get(context.TODO(), "fuga", metav1.GetOptions{})
	fmt.Println(pod.ObjectMeta.Name)
}

ここでNewForConfig()の戻り値となるClientset構造体について深掘ります。
こちらの構造体は次のインターフェースを満たす実装を持っています。

client-go/kubernetes/clientset.go
type Interface interface {
	Discovery() discovery.DiscoveryInterface
 ...省略
	AppsV1() appsv1.AppsV1Interface
	AppsV1beta1() appsv1beta1.AppsV1beta1Interface
	AppsV1beta2() appsv1beta2.AppsV1beta2Interface
...省略
	CoreV1() corev1.CoreV1Interface
...省略
}

こちらのインターフェースにはKubernetes APIで定義されるGroup/Versionごとのメソッドが定義されていることがわかります。
そのうちの1つであるCoreV1()メソッドの戻り値の型はcorev1.CoreV1Interfaceとなっており、Corev1()メソッドはこちらのインターフェースを満たす実装を持つオブジェクトを返します。

client-go/kubernetes/typed/core/v1/core_client.go
type CoreV1Interface interface {
	RESTClient() rest.Interface
	ComponentStatusesGetter
	ConfigMapsGetter
	EndpointsGetter
	EventsGetter
	LimitRangesGetter
	NamespacesGetter
	NodesGetter
	PersistentVolumesGetter
	PersistentVolumeClaimsGetter
	PodsGetter
	PodTemplatesGetter
	ReplicationControllersGetter
	ResourceQuotasGetter
	SecretsGetter
	ServicesGetter
	ServiceAccountsGetter
}

インターフェースを構成する1番上のメソッドとして、kubectlの例で前述したRESTClient()メソッドが含まれていることがわかります。こちらはGroup/Versionごとのクライアントから内部的に利用され、こちらを使ってリクエストが作成されます。
RESTClient()メソッドの戻り値は次のインターフェースを満たします。

client-go/rest/client.go
type Interface interface {
	GetRateLimiter() flowcontrol.RateLimiter
	Verb(verb string) *Request
	Post() *Request
	Put() *Request
	Patch(pt types.PatchType) *Request
	Get() *Request
	Delete() *Request
	APIVersion() schema.GroupVersion
}

Get()Post()といったメソッドを持っており、インターフェースで定義された各メソッドはHTTPリクエストによりKubernetes APIを操作するRequestオブジェクトを返すことがわかるかと思います。

上記ではCoreV1()メソッドのみを追いましたが、他のメソッドについても同様で、メソッド呼び出しをチェーンすることで各Group/Versionに対応するクライアントを取得し、特定リソースについてKubernetes APIで操作することができます。

これらのことからClientset構造体は様々なGroup/Versionのリソースごとのクライアントの集合となっており、こちらを利用することで様々なリソースに対してKubernetes APIによる操作を行えることがわかります。

Clientsetは一例ですが、client-goモジュールについてはGo言語でKubernetes APIを操作する際のクライアントを提供してくれるものと認識しておくと良さそうです。

api

apiモジュールはKubernetes APIで定義されたリソースをGoで操作するために必要なGoの構造体の集まりです。
client-goでもリソースごとの構造体を取得する際にこちらのモジュールが参照されています。

モジュールのディレクトリ階層はKubernetes APIのGroup-Version-Resource構造と一致しています。
例えばDeploymentリソースの場合、manifest作成時のapiVersionフィールドにはapps/v1を指定するかと思いますが、Deploymentリソースに対応するGoの構造体はapiライブラリのk8s.io/api/app/v1パッケージに定義されています。
k8s.io/api/app/v1パッケージの場合、パッケージには次の.goファイルが含まれています。

  • types.go
  • register.go
  • doc.go
  • generated.pb.go
  • types_swagger_doc_generated.go
  • zz_generated.deepcopy.go

ここではtypes.goとregister.goについて中身を見ます。

types.go

types.goは各パッケージにおいて中心的な役割をするファイルとなっており、全てのKubernetesリソース(Kind)の構造体と関連するサブ構造体が定義されています。
Deploymentリソースに対応する構造体もk8s.io/api/app/v1パッケージのtypes.goの中に定義されており、次のような構造体となっています。

api/apps/v1/types.go
type Deployment struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Specification of the desired behavior of the Deployment.
	// +optional
	Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`

	// Most recently observed status of the Deployment.
	// +optional
	Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

Deployment構造体のフィールドのうちSpec, Statusフィールドはサブ構造体に該当し、こちらも同じtypes.goファイル内で定義されています。
metav1.TypeMeta, metav1.ObjectMetaフィールドについては全てのKindに共通するフィールドとなっており、こちらは後述するapimachineryライブラリで定義されています。

register.go

register.goではパッケージに関連するGroup/Versionを定義し、Group/Versionに含まれるKindのリストを定義しています。
リソースのGroup/Versionを特定する必要がある場合、register.goで定義されるSchemeGroupVersionオブジェクトから取得することができます。

api/apps/v1/register.go
import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

// GroupName is the group name use in this package
const GroupName = "apps"

// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}

またregister.goにはAddToScheme関数も定義されており、Group/VersionKindsをSchemeに追加する際に利用できます。
各Kubernetesリソースのバージョン・KindをSchemeに追加することにより、Kubernetes API上で定義されたGroup-Version-ResourceをGoの構造体にマッピングすることができ、Go言語で扱いやすくなっています。

apiモジュールについては、PodやDeploymentといったKubernetes APIのデフォルトリソースの型を利用したい場合に参照するものと認識しておくと良さそうです。

apimachinery

apimachineryはKubernetes APIオブジェクトの規則に従うAPIオブジェクトと共に利用する汎用的な機能を持ったモジュールです。
apiやclient-goといったモジュールから参照されており、前述のTypeMetaObjectMetaSchemeなどすべてのKubernetesリソースのオブジェクトで共通して利用されるインターフェースや関数・型を提供しています。

apimachineryの中でもよく使われるのがSchemaです。
Schemaは抽象化のための仕組みであり、以下のように利用されます。

  • Schemeによる抽象化
    • APIオブジェクトをGroup-Version-Kindsとして登録する
    • 異なるバージョンのAPIオブジェクト間で変換を行う
    • APIオブジェクトのシリアライズ・デシリアライズを行う

Scheme

apimachineryでScheme構造体は以下のように定義されています。

apimachinery/pkg/runtime/scheme.go
type Scheme struct {
	// gvkToType allows one to figure out the go type of an object with
	// the given version and name.
	gvkToType map[schema.GroupVersionKind]reflect.Type

	// typeToGVK allows one to find metadata for a given go object.
	// The reflect.Type we index by should *not* be a pointer.
	typeToGVK map[reflect.Type][]schema.GroupVersionKind
    ...省略
}

gvkToTypetypeToGVKの名前と型定義からわかる通り、こちらを利用してGoの構造体からGroup-Version-Kindの取得またその逆を行うことができます。

client-goの実装を追うと、実際にScheme構造体のオブジェクトを初期化している箇所が見られます。
client-go/kubernetes/scheme/register.goの中でKubernetes APIのデフォルトリソースについて、前節で述べたAddToScheme関数が呼ばれており、Schemeオブジェクトの初期化が行われていることがわかります。

client-go/kubernetes/schema/register.go
var Scheme = runtime.NewScheme()
var Codecs = serializer.NewCodecFactory(Scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
	admissionregistrationv1.AddToScheme,
	admissionregistrationv1alpha1.AddToScheme,
	admissionregistrationv1beta1.AddToScheme,
	internalv1alpha1.AddToScheme,
	appsv1.AddToScheme,
	appsv1beta1.AddToScheme,
	appsv1beta2.AddToScheme,
	authenticationv1.AddToScheme,
	authenticationv1alpha1.AddToScheme,
	authenticationv1beta1.AddToScheme,
	authorizationv1.AddToScheme,
	authorizationv1beta1.AddToScheme,
	autoscalingv1.AddToScheme,
	autoscalingv2.AddToScheme,
	autoscalingv2beta1.AddToScheme,
	autoscalingv2beta2.AddToScheme,
	batchv1.AddToScheme,
	batchv1beta1.AddToScheme,
	certificatesv1.AddToScheme,
	certificatesv1beta1.AddToScheme,
	certificatesv1alpha1.AddToScheme,
	coordinationv1beta1.AddToScheme,
	coordinationv1.AddToScheme,
	corev1.AddToScheme,
	discoveryv1.AddToScheme,
	discoveryv1beta1.AddToScheme,
	eventsv1.AddToScheme,
	eventsv1beta1.AddToScheme,
	extensionsv1beta1.AddToScheme,
	flowcontrolv1.AddToScheme,
	flowcontrolv1beta1.AddToScheme,
	flowcontrolv1beta2.AddToScheme,
	flowcontrolv1beta3.AddToScheme,
	networkingv1.AddToScheme,
	networkingv1alpha1.AddToScheme,
	networkingv1beta1.AddToScheme,
	nodev1.AddToScheme,
	nodev1alpha1.AddToScheme,
	nodev1beta1.AddToScheme,
	policyv1.AddToScheme,
	policyv1beta1.AddToScheme,
	rbacv1.AddToScheme,
	rbacv1beta1.AddToScheme,
	rbacv1alpha1.AddToScheme,
	resourcev1alpha2.AddToScheme,
	schedulingv1alpha1.AddToScheme,
	schedulingv1beta1.AddToScheme,
	schedulingv1.AddToScheme,
	storagev1beta1.AddToScheme,
	storagev1.AddToScheme,
	storagev1alpha1.AddToScheme,
}

var AddToScheme = localSchemeBuilder.AddToScheme

func init() {
	v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
	utilruntime.Must(AddToScheme(Scheme))
}

こちらで初期化したSchemeオブジェクトを参照することで、client-goではGoのオブジェクトの型からGroup-Version-Kindを取得することができ、Go言語でKubernetes APIへのリクエストの生成を行うことができています。

余談ですが、client-goでのClientsetでの説明時に述べた簡単な実装を元にclient-goのソースコードを追ってみると、client-goがRESTClientメソッドやSchemeを利用することで異なるKubernetesリソースのオブジェクトをほぼ同一の流れで処理できていることがわかります。

apimachineryについては、KubernetesリソースをGo言語で扱うための規則となるような汎用的なインターフェース・関数・型が用意されているモジュールと覚えておくと良さそうです。

まとめ

本記事ではGo言語でKubernetes周りの開発を行う際によく利用されるclient-goapiapimachineryについてそれぞれの機能と簡単な関係性について述べました。

特にapimachineryについては、Go言語でKubernetesを触る際に準拠すべき項目や汎用的に利用できる関数など多く提供されているため、Kubernetes関連のOSSのソースコードを読むなどして使い方の詳細を覚えておくと良さそうかと思いました。

参考

8
3
0

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
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?