client-go、api、apimachinery 的功能和关系可以概括如下:

这是ZOZO 2023年圣诞节日历 Vol.5的第1天文章。

首先

我认为大家可能很清楚地知道,Kubernetes是使用Go语言开发的,并且在开发过程中经常使用client-go模块。此外,当涉及到使用client-go时,经常会涉及到api和apimachinery这两个模块。

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

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

客户端-前进

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的情况下,我认为重要的是它不仅仅只是一个单独的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结构体。
该结构体具有满足以下接口的实现。

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

可以在这个接口中看到根据 Kubernetes API 定义的每个 Group/Version 方法。
其中之一是 CoreV1() 方法,其返回类型为 corev1.CoreV1Interface,CoreV1() 方法返回一个满足该接口实现的对象。

type CoreV1Interface interface {
	RESTClient() rest.Interface
	ComponentStatusesGetter
	ConfigMapsGetter
	EndpointsGetter
	EventsGetter
	LimitRangesGetter
	NamespacesGetter
	NodesGetter
	PersistentVolumesGetter
	PersistentVolumeClaimsGetter
	PodsGetter
	PodTemplatesGetter
	ReplicationControllersGetter
	ResourceQuotasGetter
	SecretsGetter
	ServicesGetter
	ServiceAccountsGetter
}

通过kubectl的例子中提到的RESTClient()方法,可以看出它作为构成接口的顶级方法。该方法在每个Group/Version的客户端内部使用,并通过该方法创建请求。

RESTClient()方法的返回值满足以下接口。

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 (Application Programming Interface)以及中国的本地化释义。

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

模块的目录层次与Kubernetes API的Group-Version-Resource结构相匹配。
例如,对于Deployment资源,在创建清单时,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的内容。

类型.go

types.go在每个包中担当着核心角色,定义了与所有Kubernetes资源(Kind)及其相关的子结构体。
与Deployment资源对应的结构体也在k8s.io/api/app/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オブジェクトから取得することができます。

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/VersionとKindsをSchemeに追加する際に利用できます。
各Kubernetesリソースのバージョン・KindをSchemeに追加することにより、Kubernetes API上で定義されたGroup-Version-ResourceをGoの構造体にマッピングすることができ、Go言語で扱いやすくなっています。

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

应用程序接口

apimachinery是一个带有通用功能的模块,用于与遵循Kubernetes API对象规则的API对象一起使用。
它被诸如api和client-go等模块引用,提供了在前面提到的TypeMeta、ObjectMeta和Scheme等所有Kubernetes资源对象中共同使用的接口、函数和类型。

在apimachinery中,常用的是Schema。
Schema是用于抽象化的机制,它可以用于以下方式。

    • Schemeによる抽象化

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

计划

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

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
    ...省略
}

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

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

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-go・api・apimachineryについてそれぞれの機能と簡単な関係性について述べました。

特别是在使用Go语言触摸Kubernetes时,它提供了许多与apimachinery相关的内容,如遵守的条款和通用可用的函数等。因此,我认为阅读Kubernetes相关的OSS源代码并记住如何使用会很有帮助。

请参阅

    • Kubernetes Programming with Go

 

    Programming Kubernetes: Developing Cloud-native Applications
广告
将在 10 秒后关闭
bannerAds