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で実装する
この記事でのソースコード
以下に、この記事作成のために作ったプロジェクトがあります。
はじめに
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
カスタムコントローラーの仕組みについて解説している記事