※本記事は予約投稿です
こんにちは、虎の穴ラボのY.N.です。
虎の穴ラボ Advent Calendar 2021 - Qiita 11日目の記事です。
10日目はH.Kさんによる KotlinでRuby/Railsっぽい便利なプロパティ(メソッド)を使えるようにしてみる! でした。
12日目はA.M.さんが リモートワークでの働き方 という記事を書く予定です。
はじめに
Kubernetesを使っていると、cert-managerやIstioなどの便利なリソースを使うことが多いと思います。
しかし、これらはKubernetesの標準装備というわけではなく、カスタムリソース(以下、CR)と呼ばれ、名前の通りカスタマイズされたリソースなのです。
もちろん、「カスタムリソース」と呼ばれているくらいなので、任意の動きをするリソースを自作することもでき、
ご自身のKubernetesクラスタに乗せて使用することができます。
そこで、この記事では、KubernetesのCRを実装してみようということで、
KubebuilderというツールのTutorialを実践していく記事になります。
目次
- はじめに
- 目次
- 前提知識
- 実行環境
- 必要なもの
- Kubebuilderとは
- 環境構築
- 実践 kubebuilder tutorial
- 作業ディレクトリの準備(1. Tutorial: Building Cronjob)
- main.goのコーディング(1.2 Every journey needs a start, every program a main)
- APIの作成(1.4. Adding a new API)
- 構造体の実装 (1.5. Designing an API)
- Controllerの実装(1.6. What's in a controller? ~ 1.7. Implementing a controller)
- 1: Load the CronJob by name
- 2: List all active jobs, and update the status
- 3: Clean up old jobs according to the history limit
- 4: Check if we’re suspended
- 5: Get the next scheduled run
- 6: Run a new job if it’s on schedule, not past the deadline, and not blocked by our concurrency policy
- 7: Requeue when we either see a running job or it’s time for the next scheduled run
- SetupWithManagerの実装
- Webhookの実装(1.8. Implementing defaulting/validating webhooks)
- マニフェストの作成 〜 実行(1.9. Running and deploying the controller)
- イメージの作成(1.9. Running and deploying the controller)
- cert-manager, webhookのデプロイ(1.9.1. Deploying the cert manager ~ 1.9.2. Deploying webhooks)
- テスト(1.10. Writing tests)
- おわりに
- 書籍の紹介
- P.S.
前提知識
本記事は、Kubernetesのコアな部分について実践する記事となっているため、
下記の知識があるとより理解が深まるような内容となっております。
- Kubernetes関連知識
- Kubernetesマニフェストの書き方
- Kubernetesの標準リソースについての知識
- Controller
- RBAC
- Admission Webhook
- Go言語の基礎文法
実行環境
本記事はMacPCで行うことを前提としています。
私が実際に動作を確認した環境は下記のとおりです。
- macOS Catalina 10.15
- bash 5.1.12
- go 1.17.3
- make 3.81
- docker 20.10.8
- kubernetes v1.22
- kubebuilder 3.2.0
- kind v0.11.1 go 1.16.4
- kubectl client 1.21, server 1.16
- kustomize v4.0.4
必要なもの
Kubebuilder Tutorial の実施には以下の物が必要となります。
本記事では、これらの環境構築については記載していないため、必要に応じて準備していただけますと幸いです。
- Go言語実行環境
- Docker
- kind
- kubectl
- kustomize
- make
Kubebuilderとは
Kubebuilderは、カスタムリソース定義(以下、CRD)を使用してKubernetesAPIを構築するためのフレームワークです。(README.mdより抜粋)
Kubebuilderを使用することで、CRを実装するのに必要なテンプレートが用意されます。
また、KubebuilderはCRの実装に注力できるように、マニフェストの作成やデプロイなどはMakefileを使用しての簡略化が実現されているなど、実装初心者でも使いやすいツールです。
環境構築
KubebuilderのQuick Startに従ってKubebuilderのバイナリを準備する必要があります。
$ curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH) $ chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
バイナリを取得できたら実行できるかを確認してください。
$ kubebuilder version
こんな感じで出てきたらOKです。
Version: main.version{KubeBuilderVersion:"3.2.0", KubernetesVendor:"1.22.1", GitCommit:"b7a730c84495122a14a0faff95e9e9615fffbfc5", BuildDate:"2021-10-29T18:32:16Z", GoOs:"darwin", GoArch:"amd64"}
実践 kubebuilder tutorial
今回実施していくのは、Kubebuilder Tutorialの 1. Tutorial: Building Cronjobです。
このTutorialでは、CronJobのCRを作成し、テストまでを行います。
作業ディレクトリの準備(1. Tutorial: Building Cronjob)
このページでは、作業ディレクトリ(プロジェクト)の準備を行います。
$ mkdir project $ cd project $ kubebuilder init --domain tutorial.kubebuilder.io --repo tutorial.kubebuilder.io/project
このコマンドを実行すると、projectディレクトリができ、テンプレートファイル群が出来上がります。
main.goのコーディング(1.2 Every journey needs a start, every program a main)
ここで、projectディレクトリ内に作成されたmain.goのコーディングを行います。
main.goはCRのコントローラやWebhookを実行するマネージャとして機能します。
完成するソースコードは下記のようになります。
ソースコードを見ると、気になるコメントがありますが、これはマーカーコメントと呼ばれるコメントです。
kubebuilderはこのマーカーコメントを解析してkubernetes マニフェストを作成するので、これらのコメントは内容に従って必ず記載してください。
//+kubebuilder:scaffold:imports
APIの作成(1.4. Adding a new API)
さて、次にCronJobのAPIを作成します
次のコマンドでapiのテンプレートを作成します。
$ kubebuilder create api --group batch --version v1 --kind CronJob
Create Resouce と Create Controllerと聞かれるので y で先に進みます。
すると、ディレクトリ内にapiディレクトリとcontrollersディレクトリが作成されます。
apiディレクトリの中にはv1ディレクトリがあり、cronjob_types.goというファイルが出来上がっていると思います。
また、controllersディレクトリの中にはcronjob_controller.goというファイルが出来上がっていると思います。
これらのファイルについてもmain.goと同様に次以降の章で実装を行っていきます。
構造体の実装 (1.5. Designing an API)
1.5. Designing an API ではcronjob_types.goのコーディングを行っていきます。
最終的なコードは下記のとおりです。
cronjob_types.goはテンプレートとして、CronJobSpecとCronJobStatus、CronJob、CronJobListという構造体を持っています。
CronJobSpecにはKubernetes マニフェストに記述するspecの内容について記載しています。
下記のようにマーカーコメントでvalidationやoptionalの設定を行うことができます。
この場合、Kubernetes マニフェストに記述するspec.startingDeadlineSecondsは、最低値が0でオプションとするコードになります。
//+kubebuilder:validation:Minimum=0 // +optional StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty"`
CronJobStatusはkubectl describe等でリソースの状態を見ようとした際に確認できる内容を記載します。
基本的な書き方はCronJobSpecと同様です。
CronJobはルートオブジェクトになります。
ここの記載は基本的にテンプレート通りで問題ありません。
SpecにCronJobSpecを、StatusにCronJobStatusを使用するように書かれています。
Controllerの実装(1.6. What's in a controller? ~ 1.7. Implementing a controller)
kubebuilderによって作成されたcontrollersディレクトリの中身を見てみると、cronjob_controller.goがあります。
これがCronJobのコントローラ、メインロジックになります。
Controllerテンプレートには、あらかじめReconciler構造体とReconcile関数、SetUpWithManager関数が定義されています。
最終的なコードは下記のとおりです。
CronJobReconciler構造体は下記のように実装します。
type CronJobReconciler struct { client.Client Scheme *runtime.Scheme Clock }
Controllerが関連するオブジェクトを操作するため、いくつかのRBAC権限が必要です。
RBAC用のマーカーコメントを記述することで実行に必要なRBAC権限を記載します。
下記のように記載することで、Controllerが、resourcesで指定されたリソースに対して、verbsで指定した操作を行うことができる権限を与えることができます。
//+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch //+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update //+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get
そして、次はメインロジックとなるReconcile関数の実装です。(1.7. Implementing a controller)
1: Load the CronJob by name
r.Getで、req.NamespacedNameで指定されたNamespaceとNameを持つCronJobオブジェクトを取得します。
オブジェクトが取得できなかった場合はclient.IgnoreNotFound(err)をreturnすることでNotFoundエラーをスキップしています。
これをスキップしないとRequeueして再度Reconcileを実行した場合、NotFoundエラーが出続けてしまうためです。
var cronJob batchv1.CronJob if err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil { log.Error(err, "unable to fetch CronJob") return ctrl.Result{}, client.IgnoreNotFound(err) }
2: List all active jobs, and update the status
r.ListでJobのリストを取得します。
var childJobs kbatch.JobList if err := r.List(ctx, &childJobs, client.InNamespace(req.Namespace), client.MatchingFields{jobOwnerKey: req.Name}); err != nil { log.Error(err, "unable to list child Jobs") return ctrl.Result{}, err }
Jobのリストを取得したら、状態を確認し、Active、Failed、Successfulに振り分けを行います。
振り分けたJobのリストは後のロジックで使用されます。
for i, job := range childJobs.Items { _, finishedType := isJobFinished(&job) switch finishedType { case "": activeJobs = append(activeJobs, &childJobs.Items[i]) case kbatch.JobFailed: failedJobs = append(failedJobs, &childJobs.Items[i]) case kbatch.JobComplete: successfulJobs = append(successfulJobs, &childJobs.Items[i]) } ~~~ }
このロジックの最後で、activeなCronJobのStatusを更新しています。
cronJob.Status.Active = nil for _, activeJob := range activeJobs { jobRef, err := ref.GetReference(r.Scheme, activeJob) if err != nil { log.Error(err, "unable to make reference to active job", "job", activeJob) continue } cronJob.Status.Active = append(cronJob.Status.Active, *jobRef) } if err := r.Status().Update(ctx, &cronJob); err != nil { log.Error(err, "unable to update CronJob status") return ctrl.Result{}, err }
3: Clean up old jobs according to the history limit
終了しているジョブ(failedJobs、successfulJobs)について、履歴上限を超えたものを削除します。
if cronJob.Spec.FailedJobsHistoryLimit != nil { sort.Slice(failedJobs, func(i, j int) bool { if failedJobs[i].Status.StartTime == nil { return failedJobs[j].Status.StartTime != nil } return failedJobs[i].Status.StartTime.Before(failedJobs[j].Status.StartTime) }) for i, job := range failedJobs { if int32(i) >= int32(len(failedJobs))-*cronJob.Spec.FailedJobsHistoryLimit { break } if err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil { log.Error(err, "unable to delete old failed job", "job", job) } else { log.V(0).Info("deleted old failed job", "job", job) } } } if cronJob.Spec.SuccessfulJobsHistoryLimit != nil { sort.Slice(successfulJobs, func(i, j int) bool { if successfulJobs[i].Status.StartTime != nil { return successfulJobs[j].Status.StartTime != nil } return successfulJobs[i].Status.StartTime.Before(successfulJobs[j].Status.StartTime) }) for i, job := range successfulJobs { if int32(i) >= int32(len(successfulJobs))-*cronJob.Spec.SuccessfulJobsHistoryLimit { break } if err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); (err) != nil { log.Error(err, "unable to delete old sucessful job", "job", job) } else { log.V(0).Info("deleted old successful job", "job", job) } } }
4: Check if we’re suspended
オブジェクトが一時停止している場合、ジョブを実行せず停止します。
if cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend { log.V(1).Info("cronjob suspended, skipping") return ctrl.Result{}, nil }
5: Get the next scheduled run
次にスケジュールされているJobを取得します。
missedRun, nextRun, err := getNextSchedule(&cronJob, r.Now()) if err != nil { log.Error(err, "unable to figure out CronJobschedule") return ctrl.Result{}, nil }
6: Run a new job if it’s on schedule, not past the deadline, and not blocked by our concurrency policy
ジョブが実行できる状態であれば実際にジョブを実行します。
// 実行されていないジョブがあるか if missedRun.IsZero() { log.V(1).Info("no upcoming scheduled times, sleeping until next") return scheduledResult, nil } // 予定開始時刻のデッドラインを過ぎていないか log = log.WithValues("current run", missedRun) tooLate := false if cronJob.Spec.StartingDeadlineSeconds != nil { tooLate = missedRun.Add(time.Duration(*cronJob.Spec.StartingDeadlineSeconds) * time.Second).Before(r.Now()) } if tooLate { log.V(1).Info("missed starting deadline for last run, sleeping till next") return scheduledResult, nil } // 同時実行ポリシーがForbidConcurrent(同時実行禁止)でactiveなJobがあるか if cronJob.Spec.ConcurrencyPolicy == batchv1.ForbidConcurrent && len(activeJobs) > 0 { log.V(1).Info("concurrency policy blocks concurrent runs, skipping", "num active", len(activeJobs)) return scheduledResult, nil } // 同時実行ポリシーでReplaceCOncurrent(置き換え)が指定されている場合はactiveなJobを置き換えるために削除する if cronJob.Spec.ConcurrencyPolicy == batchv1.ReplaceConcurrent { for _, activeJob := range activeJobs { if err := r.Delete(ctx, activeJob, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil { log.Error(err, "unable to delete active job", "job", activeJob) return ctrl.Result{}, err } } }
7: Requeue when we either see a running job or it’s time for the next scheduled run
最後に、結果をreturnします。
// scheduledResult := ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())} return scheduledResult, nil
SetupWithManagerの実装
SetupWithManagerではControllerが監視するリソースの種別をctrl.NewControllerManagedByで指定しています。
ForにはControllerが管理するCRを指定するため、ここではCronJobを指定します。
OwnsにはForで指定したリソース(CronJob)がkbatch.Jobのリソースを作成するため、kbatch.Jobを指定します。
これによってCronJobまたはCronJob Controllerが作成したkbatch.JobのEventを察知してReconcile処理が呼び出されるようになります。
func (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error { ... return ctrl.NewControllerManagedBy(mgr). For(&batchv1.CronJob{}). Owns(&kbatch.Job{}). Complete(r) }
Webhookの実装(1.8. Implementing defaulting/validating webhooks)
次は、Admission Webhookを実装する場合の内容になります。
まずは、kubebuilderの機能で Admission Webhookのテンプレートを作成します。
--defaulting オプションで Defaulting Webhook用のコード、
--programmatic-validation オプションで Validating Webhook用のコードを自動生成してくれます。
$ kubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation
これを実行すると、api/v1ディレクトリの直下にcronjob_webhook.goが作成されます。
本来、SetupWebhookWithManagerの呼び出しがmain.goに追記されますが、本記事ではすでに完成形のコードを記載しているため、変化がないように見えている点だけ注意してください。
また、テスト時にWebhookを無効化して実行するため、環境変数で無効化できるようにしています。
if os.Getenv("ENABLE_WEBHOOKS") != "false" { if err = (&batchv1.CronJob{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "CronJob") os.Exit(1) } }
cronjob_webhook.goの最終的なコードは下記のとおりです。
Webhookテンプレートには、SetupWebhookWithManager、Default、ValidateCreate、ValidateUpdate、ValidateDeleteが予め定義されています。
SetupWebhookWithManagerはこのままで大丈夫ですが、他の4つについては実装する必要があります。
Default関数(Mutating Admission Webhook)の実装
Default関数はSpecに値が設定されていないものについて、デフォルト値を設定するようにします。
func (r *CronJob) Default() { cronjoblog.Info("default", "name", r.Name) // TODO(user): fill in your defaulting logic. if r.Spec.ConcurrencyPolicy == "" { r.Spec.ConcurrencyPolicy = AllowConcurrent } if r.Spec.Suspend == nil { r.Spec.Suspend = new(bool) } if r.Spec.SuccessfulJobsHistoryLimit == nil { r.Spec.SuccessfulJobsHistoryLimit = new(int32) *r.Spec.SuccessfulJobsHistoryLimit = 3 } if r.Spec.FailedJobsHistoryLimit == nil { r.Spec.FailedJobsHistoryLimit = new(int32) *r.Spec.FailedJobsHistoryLimit = 1 } }
ValidateXXX関数(Validating Admission Webhook)の実装
Kubebuilder TutorialのValidateCreate, ValidateUpdate関数は、リソースのcreate、update時に同じvalidationが効くようにするため、validateCronJobという関数を作成し、それを呼び出すようにしています。
func (r *CronJob) ValidateCreate() error { cronjoblog.Info("validate create", "name", r.Name) return r.validateCronJob() } func (r *CronJob) ValidateUpdate(old runtime.Object) error { cronjoblog.Info("validate update", "name", r.Name) return r.validateCronJob() }
ValidateDelete関数は今回削除時のvalidationが不要なため何もしないようにしています。
func (r *CronJob) ValidateDelete() error { cronjoblog.Info("validate delete", "name", r.Name) return nil }
validateCronJobではnameとspec.scheduleの内容をvalidationするロジックを記述しています。
マニフェストの作成 〜 実行(1.9. Running and deploying the controller)
マニフェストの作成
kubebuilderではmakefileが用意されており、makeコマンドを使用することでコードの内容からCR、CRDのmanifestを作成してくれます。
$ make manifests
作成したmanifestをclusterに適用します
※適用にはkubectl applyを使用しているので、kubectl config current-contextでどのclusterを使用しているか確認してください。
本記事ではkindで作成したclusterを使用しています。
$ make install
make installの結果が下記のようになっていれば、CronJobのCRDが作成されているので成功となります。
/Users/xxxx/workspace/kubebuilder/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases /Users/xxxx/workspace/kubebuilder/bin/kustomize build config/crd | kubectl apply -f - customresourcedefinition.apiextensions.k8s.io/cronjobs.batch.tutorial.kubebuilder.io created
kubectl get crdsで確認してみると、作成したCRDが適用されているのがわかります。
$ kubectl get crds | grep cronjob
cronjobs.batch.tutorial.kubebuilder.io 2021-12-08T04:28:05Z
CRDが適用できたのでいよいよ実行していきます。
この際ローカルでのテストになるためWebhookは無効化して実行します。
$ make run ENABLE_WEBHOOKS=false ... 2021-12-08T13:40:11.294+0900 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"} 2021-12-08T13:40:11.295+0900 INFO setup starting manager 2021-12-08T13:40:11.396+0900 INFO starting metrics server {"path": "/metrics"} 2021-12-08T13:40:11.396+0900 INFO controller.cronjob Starting EventSource {"reconciler group": "batch.tutorial.kubebuilder.io", "reconciler kind": "CronJob", "source": "kind source: /, Kind="} 2021-12-08T13:40:11.396+0900 INFO controller.cronjob Starting EventSource {"reconciler group": "batch.tutorial.kubebuilder.io", "reconciler kind": "CronJob", "source": "kind source: /, Kind="} 2021-12-08T13:40:11.396+0900 INFO controller.cronjob Starting Controller {"reconciler group": "batch.tutorial.kubebuilder.io", "reconciler kind": "CronJob"} 2021-12-08T13:40:11.497+0900 INFO controller.cronjob Starting workers {"reconciler group": "batch.tutorial.kubebuilder.io", "reconciler kind": "CronJob", "worker count": 1}
コントローラが起動しました。
次に、CronJobリソースを作成します。config/samples/batch_v1_cronjob.yaml
を編集しましょう。
apiVersion: batch.tutorial.kubebuilder.io/v1 kind: CronJob metadata: name: cronjob-sample spec: # TODO(user): Add fields here schedule: "*/1 * * * *" startingDeadlineSeconds: 60 concurrencyPolicy: Allow jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the kubernetes cluster restartPolicy: OnFailure
これは、1分ごとに日付と「Hello from the kubernetes cluster」をechoするだけのCronJobです。
新しくターミナルを開き、これを適用します。
$ kubectl apply -f config/samples/batch_v1_cronjob.yaml
cronjob.batch.tutorial.kubebuilder.io/cronjob-sample created
リソースが作成されているか確認してみましょう。
CronJobリソースの確認
$ kubectl get cronjob.batch.tutorial.kubebuilder.io NAME AGE cronjob-sample 33s $ kubectl get cronjob.batch.tutorial.kubebuilder.io -o yaml apiVersion: v1 items: - apiVersion: batch.tutorial.kubebuilder.io/v1 kind: CronJob metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"batch.tutorial.kubebuilder.io/v1","kind":"CronJob","metadata":{"annotations":{},"name":"cronjob-sample","namespace":"default"},"spec":{"concurrencyPolicy":"Allow","jobTemplate":{"spec":{"template":{"spec":{"containers":[{"args":["/bin/sh","-c","date; echo Hello from the kubernetes cluster"],"image":"busybox","name":"hello"}],"restartPolicy":"OnFailure"}}}},"schedule":"*/1 * * * *","startingDeadlineSeconds":60}} creationTimestamp: "2021-12-08T04:47:18Z" generation: 1 managedFields: - apiVersion: batch.tutorial.kubebuilder.io/v1 fieldsType: FieldsV1 fieldsV1: f:metadata: f:annotations: .: {} f:kubectl.kubernetes.io/last-applied-configuration: {} f:spec: .: {} f:concurrencyPolicy: {} f:jobTemplate: .: {} f:spec: .: {} f:template: .: {} f:spec: .: {} f:restartPolicy: {} f:schedule: {} f:startingDeadlineSeconds: {} manager: kubectl-client-side-apply operation: Update time: "2021-12-08T04:47:18Z" - apiVersion: batch.tutorial.kubebuilder.io/v1 fieldsType: FieldsV1 fieldsV1: f:spec: f:jobTemplate: f:metadata: {} f:spec: f:template: f:metadata: {} f:spec: f:containers: {} f:status: {} manager: main operation: Update time: "2021-12-08T04:47:18Z" name: cronjob-sample namespace: default resourceVersion: "21295136" selfLink: /apis/batch.tutorial.kubebuilder.io/v1/namespaces/default/cronjobs/cronjob-sample uid: 6363c17a-9d46-4c3d-8624-0dda1a071195 spec: concurrencyPolicy: Allow jobTemplate: spec: template: spec: containers: - args: - /bin/sh - -c - date; echo Hello from the kubernetes cluster image: busybox name: hello restartPolicy: OnFailure schedule: '*/1 * * * *' startingDeadlineSeconds: 60 status: {} kind: List metadata: resourceVersion: "" selfLink: ""
Jobリソースの確認
$ kubectl get job NAME COMPLETIONS DURATION AGE cronjob-sample-1638989280 1/1 8s 3m7s cronjob-sample-1638989340 1/1 4s 2m7s cronjob-sample-1638989400 1/1 4s 67s cronjob-sample-1638989460 1/1 3s 7s
動作確認が取れたのでCtrl + Cでmake runをしているプロセスを停止、
適用済みのCronJobリソースを削除します。
$ kubectl delete -f config/samples/batch_v1_cronjob.yaml
イメージの作成(1.9. Running and deploying the controller)
DockerHubに作成したControllerのイメージをデプロイします。 ※<some-registry>, <project-name>, tagは適宜ご自身の環境に合わせて書き換えてください。
$ make docker-build docker-push IMG=<some-registry>/<project-name>:tag $ make deploy IMG=<some-registry>/<project-name>:tag
cert-manager, webhookのデプロイ(1.9.1. Deploying the cert manager ~ 1.9.2. Deploying webhooks)
テストにあたって、webhook serverの証明書発行に使用しているcert-managerをclusterにインストールする必要があります。
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.0/cert-manager.yaml
cert-managerのインストールができているか確認します。
下記のように出ていればインストール成功です。
$ kubectl get pods -n cert-manager NAME READY STATUS RESTARTS AGE cert-manager-77fd97f598-khfwn 1/1 Running 0 91s cert-manager-cainjector-7974c84449-zmh9n 1/1 Running 0 91s cert-manager-webhook-5f4b965fbd-6bqlg 1/1 Running 0 91s
続けて、マニフェスト内のcert-managerとwebhookを適用する部分がデフォルトではコメントアウトされているので、このコメントを外します。
これをデプロイします。
$ make deploy IMG=<some-registry>/<project-name>:tag
証明書が発行されるまで待ちます(おおよそ1分程度)
下記のように出たら証明書が発行されています
$ kubectl get certificate -A
NAMESPACE NAME READY SECRET AGE
projects-system projects-serving-cert True webhook-server-cert 16m
再度、CronJobを作成して動作を確認するとmake runを実行していない状態でもJobが動いているのを確認できます。
$ kubectl apply -f config/samples/batch_v1_cronjob.yaml
cronjob.batch.tutorial.kubebuilder.io/cronjob-sample created
$ kubectl get cronjob.batch.tutorial.kubebuilder.io NAME AGE cronjob-sample 7m22s FVFD40TYP3YV:kubebuilder 1080$ kubectl get job NAME COMPLETIONS DURATION AGE cronjob-sample-1638993360 1/1 3s 2m6s cronjob-sample-1638993420 1/1 4s 69s cronjob-sample-1638993480 1/1 3s 12s
テスト(1.10. Writing tests)
最後に、カスタムコントローラのテストについて実施していきます。
controllersディレクトリ内に suite_test.go
というファイルが作成されています。
これは、main.goと同じような流れでテスト用コントローラのセットアップを行うテストプログラムです。
そのため、これだけだとカスタムコントローラのテストはできないので、<カスタムコントローラ名>_test.goというファイルを作成する必要があります。
実際のコードは下記のようになります。
テストの実施は下記のコマンドを controllersディレクトリ
で実行します。
$ go test ./...
が、これではなんと動きません。。。
Tutorialには記載されていませんが、go testでテストを行う場合、ローカル環境に下記のバイナリが必要になります。
- kube-apiserver
- kubectl
- etcd
kubernetesのビルド
というわけで、kube-apiserver、kubectlのバイナリを作成するため、kubernetesのビルドを行っていきます。
kubernetesはREADME.mdに従ってビルドを行えば問題なく完了できます。
ビルドにはそれなりに時間がかかります。
また、kubernetesのビルドにはversion 4.2以上のbashが必要ですので注意してください。
$ export GOPATH=$(go env GOPATH) $ mkdir -p $GOPATH/src/k8s.io $ cd $GOPATH/src/k8s.io $ git clone https://github.com/kubernetes/kubernetes $ cd kubernetes $ bash ※zsh等のシェルを使っている方のみ $ make
完了すると _output/bin
にビルド成果物が出来上がります。
下記の手順に従ってファイルをコピーしてください。
$ sudo mkdir -p /usr/local/kubebuilder/bin $ sudo cp _output/bin/kube-apiserver /usr/local/kubebuilder/bin $ sudo cp _output/bin/kubectl /usr/local/kubebuilder/bin
etcdのビルド
etcdのバイナリについてもetcdの公式ページに従ってビルドしていきます。
$ git clone https://github.com/coreos/etcd.git
$ cd etcd
$ ./build
完了すると /bin
にビルド成果物が出来上がります。
下記の手順に従ってファイルをコピーしてください。
$ sudo cp bin/etcd /usr/local/kubebuilder/bin
さて、改めてテストを実行します。
$ go test ./... ok tutorial.kubebuilder.io/project/controllers 21.423s
OKが出たのでテストが通っていることが確認できました。
おわりに
さて、ここまでkubebuilder tutorialを実践してきました。
CRの実装は非常に複雑で、概念も難しいものが多かったですが、なんとかこうして実装することができました。
リソースがどう制御されているのか、Reconcilerがどうリソースに働きかけるのか、そんなところが少しでもこの記事を読んで理解できていたなら幸いです。
非常に長い記事になってしまいましたが、ここまで読んでいただきありがとうございました。
書籍の紹介
最後に、KubernetesのCRを作成するために参考させていただきました書籍を紹介させていただきます。
実践入門 Kubernetes カスタムコントローラーへの道
です。
本記事はKubebuilder Tutorialの内容について記載していますが、この書籍の中でもkubebuilderを使用したカスタムコントローラの実装について細かく解説をしてくれています。
また、Kubernetes公式が出しているカスタムコントローラー実装練習用のリポジトリ「sample-controller」の内容をなぞりながらカスタムコントローラ実装についてわかりやすく書かれている本だと思いますので、ぜひおすすめの本です。
P.S.
採用情報
■募集職種
yumenosora.co.jp
カジュアル面談も随時開催中です
■お申し込みはこちら!
news.toranoana.jp
■ToraLab.fmスタートしました!
メンバーによるPodcastを配信中!
是非スキマ時間に聞いて頂けると嬉しいです。
anchor.fm
■Twitterもフォローしてくださいね!
ツイッターでも随時情報発信をしています
twitter.com