バックグラウンド
ご存知のとおり、kubernetesはetcdを使用して、Pod、Deployment、StatefulSetなどのAPIオブジェクトを保存します。著者は、kubernetesのAPIオブジェクトの設計は現在非常に進んでおり、etcdに基づくオブジェクトストレージの実現がこの設計の重要な基盤であると考えています。著者と多くの読者にはそのようなニーズがあります:
- 私が設計したシステムもetcdを採用したいと考えています。
- システムは、kubernetesによって設計されたAPIオブジェクトのアイデアも採用したいと考えています。これらのオブジェクトはシステム用に設計されており、kubernetesとは何の関係もありません。
kubernetesのような独自のコードのセットを実装する作業負荷は驚くべきものであり、コピーをコピーすることさえ非常に困難です。結局のところ、多くのコード依存関係が関係しています。そのため、作成者は、kubernetesによってすでに実装されているコードを再利用し、これに基づいてカスタムAPIオブジェクトを拡張できる方が興味深いと考えています。したがって、この記事のタイトルは巨人の肩の上にあり、kubernetesは巨人であり、参照に使用できる優れた設計とコード実装がたくさんあります。この記事のサンプルコードの作成者は、原則を詳細に説明していないことに注意してください。読者が特定の実装原則を理解する必要がある場合は、作成者の「kubernetesAPIオブジェクトタイプ定義の詳細な分析」を読むことができます。 、「kubernetesアプリケーションのetcd」および「kubernetesapiserverのストレージ実装の詳細な分析」。
この記事のコード依存関係は次のとおりです。
k8s.io/apimachinery v0.0.0-20191017185446-6e68a40eebf9
k8s.io/apiserver v0.0.0-20191018030144-550b75f0da71
成し遂げる
まず、プロジェクトをビルドします。このプロジェクトにcustomeapiという名前を付け、プロジェクトのルートディレクトリにcmdとpkgの2つのディレクトリを作成します。次に、カスタムAPIオブジェクトがあります。作成者はcustomeapi / pkg / api / test / v1パッケージでapiオブジェクトを定義します(読者は、kubernetesのapiが別のプロジェクトk8s.io/apiで定義されていることに注意する必要があります。この記事次のコードに示すように、デモンストレーションは独自のプロジェクトで簡単に定義できます)。
// 代码源自customeapi/pkg/api/test/v1/types.go
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// 定义自定义API对象Custom.
type Custom struct {
// 继承metav1.TypeMeta和metav1.ObjectMeta才实现了runtime.Object,这样Custom对象
// 的yaml的格式就像如下:
// kind: Custom
// apiVersion: test/v1
// metadata:
// labels:
// name: custom
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// 为了演示方便,Custom对象的规格和状态都定义为空类型,读者根据自己的业务进行设计
Spec CustomSpec
Status CustomStatus
}
type CustomSpec struct{}
type CustomStatus struct{}
// DeepCopyObject()是必须要实现的,这是runtime.Objec定义的接口,否则编译就会报错。读者需要注意,
// kubernetes的API对象的DeepCopyObject()函数是代码生成工具生成的,本文的示例是笔者自己写的。
func (in *Custom) DeepCopyObject() runtime.Object {
if in == nil {
return nil
}
out := new(Custom)
*out = *in
return out
}
var _ runtime.Object = &Custom{}
さて、カスタムオブジェクトが定義されました。コーデックにカスタムのタイプを認識させる必要があります。そうしないと、etcdに書き込む前にカスタムのシリアル化が失敗します。コードのこの部分は次のように実装されます。
// 代码源自customeapi/pkg/api/test/v1/register.go
package v1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// 定义自定义类型组名,因为是测试例子,所以笔者把组名定义为test,这样test/Custom才是类型全称
const GroupName = "test"
// 定义自定义类型的组名+版本,即test v1
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
// 程序初始化部分需要调用AddToScheme()来实现自定义类型的注册,具体的实现在addKnownTypes()
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// 把笔者上面定义的Custom添加到scheme中就完成了类型的注册,就是这么简单。读者需要注意,类型注册
// 其实是一个比较复杂的过程,kubernetes把这部分实现全部交给了scheme,把简单的接口留给了使用者。
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Custom{},
)
return nil
}
最後に、このオブジェクトをetcdに保存します。次のコードに示すように、作成者はcmd /main.goファイルからオブジェクトを実装します。
// 代码源自customeapi/cmd/main.go
package main
import (
"fmt"
"context"
testv1 "customapi/pkg/api/test/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
)
func main() {
// 构造scheme,然后调用刚刚实现的注册函数实现自定义API对象的注册。
scheme := runtime.NewScheme()
testv1.AddToScheme(scheme)
// 注册了自定义API对象,就可以构造codec工厂了,在通过codec工厂构造codec。所谓的codec就是
// 自定义API对象的序列化与反序列化的实现。
cf := serializer.NewCodecFactory(scheme)
codec := cf.LegacyCodec(testv1.SchemeGroupVersion)
// 有了codec就可以创建storagebackend.Config,他是构造storage.Interfaces的必要参数
config := storagebackend.NewDefaultConfig("", codec)
// 笔者在本机装了etcd,所以把etcd的地址填写为本机地址,这样方便测试
config.Transport.ServerList = append(config.Transport.ServerList, "127.0.0.1:2379")
// 创建storage.Interfaces对象,storage.Interfaces是apiserver对存储的抽象,这样我们
// 就可以像apiserver一样在etcd上操作自己定义的对象了。
storage, destroy, err := factory.Create(*config)
if nil != err {
fmt.Printf("%v\n", err)
}
// 构造Custom对象
custom := testv1.Custom{}
// 把Custom对象写入etcd
if err = storage.Create(context.Background(), "test", &custom, &custom, 0); nil != err {
fmt.Println(err)
}
// 把写入的Custom对象打印出来看看结果
if data, err := runtime.Encode(codec, &custom); nil == err {
fmt.Printf("%s\n", string(data))
}
// 必要的析构函数
destroy()
}
これまでのところ、作成者は、カスタムオブジェクトを格納するためにapiserverによって実装されたstorage.Interfaceインターフェイスを完成させました。コンパイルして実行した後の結果は次のとおりです。
I1018 11:10:21.782324 25634 client.go:357] parsed scheme: "endpoint"
I1018 11:10:21.782665 25634 endpoint.go:68] ccResolverWrapper: sending new addresses to cc: [{127.0.0.1:2379 0 <nil>}]
I1018 11:10:21.784459 25634 once.go:66] CPU time info is unavailable on non-linux or appengine environment.
I1018 11:10:21.787267 25634 client.go:357] parsed scheme: "endpoint"
I1018 11:10:21.787306 25634 endpoint.go:68] ccResolverWrapper: sending new addresses to cc: [{127.0.0.1:2379 0 <nil>}]
{"kind":"Custom","apiVersion":"test/v1","metadata":{"creationTimestamp":null},"Spec":{},"Status":{}}
次に、etcdctl --endpoints = http:// localhost:2379 get / testを使用すると、検証結果は次のようになります。
{"kind":"Custom","apiVersion":"test/v1","metadata":{"creationTimestamp":null},"Spec":{},"Status":{}}
あなたは終わった、あなたは驚いていますか?意外でしたか?少量のコードでkubernetesなどのAPIオブジェクトを操作するプログラムを作成できます。重要なのは、このオブジェクトタイプをカスタマイズして、デプロイされた独自のetcdに保存することです。
オブジェクトの取得と監視については、作成者が1つずつデモンストレーションするのではなく、読者が自分で試すことができます。
総括する
上記のコンテンツは、kubernetesの既製のコードを使用して、kubernetesのようなetcd上のオブジェクトを操作できるプログラムを実装できることを証明しています。ただし、使用できる量には、次のようないくつかの制限があります。
- オブジェクトタイプはmetav1.TypeMetaとmetav1.ObjectMetaを継承する必要があります。Metav1.TypeMetaは依然として非常に用途が広いですが、metav1.ObjectMetaはやや冗長であるか、十分でない場合があります。
- この例から、オブジェクトのシリアル化はjson形式です。オブジェクトがrpc(grpcなど)を介して他のシステムと対話する必要があり、オブジェクトがprotobufのシリアル化を必要とする場合はどうなりますか?
実際、この部分は、著者の「kubernetes APIオブジェクトタイプ定義の詳細な分析」と「kubernetesapiserverストレージ実装の詳細な分析」にあり、答えを見つけることができます。興味のある読者は、ぜひご覧ください。この瞬間、作者は巨大なkubernetesの肩に立っているような感覚を持っています。