Edited at

KubernetesのCRD(Custom Resource Definition)とカスタムコントローラーの作成

More than 1 year has passed since last update.

CRDと、それを操作するカスタムコントローラーを一から作る個人メモです。


ここでの目標

1.以下のようなCRDリソースを作成できる

apiVersion: "workflow.com/v1beta"

kind: Workflow
metadata:
name: my-new-workflow
spec:
name: my-new-workflow-name
status:
name: init

2.作成後、status.nameがspec.nameと同じ値に更新されるようなカスタムコントローラーをDeploymentとして作る。


前提条件


  • kubernetes-1.9.6のKubernetesクラスタを準備する(Docker for Mac Edgeを使いました)

  • カスタムコントローラーはGo1.10で実装する


この記事でのソースコード

以下に、この記事作成のために作ったプロジェクトがあります。

https://github.com/Attsun1031/k8s-crd-sample


はじめに


CRDって?

Custom Resource Definitionです。DeploymentやPodといったものがリソースですが、ユーザー独自のリソースを定義することもでき、それがCRDです。

Kubernetesのリソースは内部のcontrollerを介して、リソースの状態の変化に応じて操作されます。例えばDeploymentが作成されたら、それを満たすReplicaSetやPodを作るなど。


カスタムコントローラーって?

カスタムコントローラーはユーザー独自に作成するコントローラーです。DeploymentなどデフォルトのリソースやCRDの変化に応じて何かアクションをとるということもできます。通常は、client-goなどを使ってプログラミングを行い、DeploymentとしてKubernetes上に展開するようです。

詳しくは公式ドキュメントの最初の方をみてください。


手順


CRDを作る

公式ドキュメントを参考にCRDを作ります。


yamlを作る

# crd.yaml

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: workflows.workflow.com
spec:
# group name to use for REST API: /apis/<group>/<version>
group: workflow.com
# version name to use for REST API: /apis/<group>/<version>
version: v1beta
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: workflows
# singular name to be used as an alias on the CLI and for display
singular: workflow
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: Workflow
# shortNames allow shorter string to match your resource on the CLI
shortNames:
- wf


applyする

$ kubectl apply -f crd.yaml


確認

$ kubectl get crd

NAME AGE
workflows.workflow.com 16m

$ kubectl get wf
No resources found.


カスタムコントローラー用のクライアントライブラリを自動生成する

公式のコード生成ツールを使って、CRDに紐づくクライアントライブラリを作成します。手取り足取りなドキュメントはなくハマりました。


code-generator他、必要なモジュールをインストールする

go get k8s.io/client-go/...

go get k8s.io/apimachinery/...
go get github.com/golang/glog
go get k8s.io/code-generator/...
cd ${カスタムコントローラーを作成するプロジェクトルート}
mkdir -p pkg/client
mkdir -p pkg/apis/workflow/v1beta


自動生成に必要なファイルを作成する


pkg/apis/workflow/register.go

package workflow

const (
GroupName = "workflow.com"
)


pkg/apis/workflow/v1beta/doc.go

// +k8s:deepcopy-gen=package

// Package v1beta is the v1beta version of the API.
// +groupName=workflow.com
package v1beta


pkg/apis/workflow/v1beta/types.go

package v1beta

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Workflow is a specification for a Workflow resource
type Workflow struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Status WorkflowStatus `json:"status"`
Spec WorkflowSpec `json:"spec"`
}

type WorkflowStatus struct {
Name string `json:"name"`
}

// WorkflowSpec is the spec for a Workflow resource
type WorkflowSpec struct {
Name string `json:"name"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// WorkflowList is a list of Workflow resources
type WorkflowList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`

Items []Workflow `json:"items"`
}


pkg/apis/workflow/v1beta/register.go

package v1beta

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/attsun1031/workflow/pkg/apis/workflow"
)

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

// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}

// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}

var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)

// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Workflow{},
&WorkflowList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}


自動生成を実行する

../../../k8s.io/code-generator/generate-groups.sh \

all \
github.com/attsun1031/k8s-crd-sample/pkg/client \
github.com/attsun1031/k8s-crd-sample/pkg/apis \
workflow:v1beta

これで、pkg/apis/workflow/v1beta/zz_generated.deepcopy.goというファイルと、pkg/client配下にinformerなどが一式作られているはずです。

$ ls -R pkg

apis client

pkg/apis:
workflow

pkg/apis/workflow:
register.go v1beta

pkg/apis/workflow/v1beta:
doc.go register.go types.go zz_generated.deepcopy.go

pkg/client:
clientset informers listers

pkg/client/clientset:
versioned

pkg/client/clientset/versioned:
clientset.go doc.go fake scheme typed

pkg/client/clientset/versioned/fake:
clientset_generated.go doc.go register.go

pkg/client/clientset/versioned/scheme:
doc.go register.go

pkg/client/clientset/versioned/typed:
workflow

pkg/client/clientset/versioned/typed/workflow:
v1beta

pkg/client/clientset/versioned/typed/workflow/v1beta:
doc.go fake generated_expansion.go workflow.go workflow_client.go

pkg/client/clientset/versioned/typed/workflow/v1beta/fake:
doc.go fake_workflow.go fake_workflow_client.go

pkg/client/informers:
externalversions

pkg/client/informers/externalversions:
factory.go generic.go internalinterfaces workflow

pkg/client/informers/externalversions/internalinterfaces:
factory_interfaces.go

pkg/client/informers/externalversions/workflow:
interface.go v1beta

pkg/client/informers/externalversions/workflow/v1beta:
interface.go workflow.go

pkg/client/listers:
workflow

pkg/client/listers/workflow:
v1beta

pkg/client/listers/workflow/v1beta:
expansion_generated.go workflow.go


カスタムコントローラーを作る

公式が提供しているサンプルコントローラープロジェクトを参考に適当に作ってみます。

client-goと、生成したクライアントライブラリを使ってプログラミングします(基本的には本家のサンプルプロジェクトのコピペでいけます)。本家サンプルをさらにシンプルにしたものgithubにあげています。controller-main.go, pkg/controller.goをご参照ください。


カスタムコントローラーのDockerイメージを作成する

さきほどのgoプログラムをコンパイルしたうえで、Dockerイメージを作ってpushします。イメージ用のレポジトリは適当にdockerhubとかで用意してください。

# Dockerfile

FROM alpine:3.7
COPY controller-main /app/controller-main
ENTRYPOINT ["/app/controller-main"]

# compile go code

$ GOOS=linux GOARCH=amd64 go build controller-main.go

# build image
REPONAME="your-docker-repo-name"
$ docker build . -t ${REPONAME}/sample-controller-main

# push image
$ docker push ${REPONAME}/sample-controller-main:latest


新しいCRDリソースを作成する

# my-new-workflow.yaml

apiVersion: "workflow.com/v1beta"
kind: Workflow
metadata:
name: my-new-workflow
spec:
name: my-new-workflow-name
status:
name: init

$ kubectl apply -f my-new-workflow.yaml

$ kubectl describe wf
Name: my-new-workflow
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"workflow.com/v1beta","kind":"Workflow","metadata":{"annotations":{},"name":"my-new-workflow","namespace":"default"},"spec":{"name":"my-n...
API Version: workflow.com/v1beta
Kind: Workflow
Metadata:
Cluster Name:
Creation Timestamp: 2018-05-06T05:01:10Z
Resource Version: 71528
Self Link: /apis/workflow.com/v1beta/namespaces/default/workflows/my-new-workflow
UID: 7e050fa3-50ea-11e8-ac12-025000000001
Spec:
Name: my-new-workflow-name
Status:
Name: init
Events: <none>


カスタムコントローラーをデプロイする

先ほどのDockerイメージを使ったDeployment YAMLを作成して、applyします。

# deploy-controller-main.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-main
spec:
selector:
matchLabels:
app: controller-main
replicas: 1
template:
metadata:
labels:
app: controller-main
spec:
containers:
- name: controller-main
image: attsun/sample-controller-main:latest


完成!

デプロイ後、先ほど作ったWorkfkowを確認すると、status.nameの値が書き換えられています。

$ kubectl describe wf

Name: my-new-workflow
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"workflow.com/v1beta","kind":"Workflow","metadata":{"annotations":{},"name":"my-new-workflow","namespace":"default"},"spec":{"name":"my-n...
API Version: workflow.com/v1beta
Kind: Workflow
Metadata:
Cluster Name:
Creation Timestamp: 2018-05-06T05:01:10Z
Generation: 0
Resource Version: 71615
Self Link: /apis/workflow.com/v1beta/namespaces/default/workflows/my-new-workflow
UID: 7e050fa3-50ea-11e8-ac12-025000000001
Spec:
Name: my-new-workflow-name
Status:
Name: my-new-workflow-name
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Synced 7s workflow-controller Workflow synced successfully
Normal Synced 7s workflow-controller Workflow synced successfully


まとめ

以上です。カスタムコントローラーの作成については、


  • 全体的に情報が分散している

  • サンプルコードを読みつつ、ある程度client-goの各モジュールを自力で理解する必要がある

  • コードジェネレーションの部分の詳細なガイドがない

という点から、少しハードルが高い感じです。

しかし、カスタムコントローラーの仕組み自体は、kubernetes上でサードパーティアプリケーションを展開する上で非常に簡単かつ無用な複雑性をもちこまないので、広まっていってほしいと思います。


参考


https://github.com/kubernetes/sample-controller

本家のサンプルコントローラープロジェクト


https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/

コード自動生成部分について解説している記事


https://medium.com/@cloudark/kubernetes-custom-controllers-b6c7d0668fdf

カスタムコントローラーの仕組みについて解説している記事