Skip to content

필사 모드: Operator 上級編 — Finalizer・Admission Webhook・Status/Conditions 設計

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

はじめに

基本的な Reconcile ループを書いたことがある方なら、次は Operator を「デモ」から「本番」へ引き上げる番です。その境界線上にある 3 つのテーマが、Finalizer、Admission Webhook、そして Status/Conditions 設計です。

この 3 つが重要な理由はシンプルです。Reconcile ループは「望ましい状態への収束」に集中しますが、実際の運用ではそれ以外の問いが絶えず生じるからです。

- ユーザーが CR(Custom Resource)を削除したとき、外部に作成したクラウドリソース(S3 バケット、DNS レコード、外部 DB など)は誰が片付けるのでしょうか。(Finalizer)

- 不正な spec をそもそもクラスターに入れさせない、あるいはデフォルト値を埋めるにはどうすればよいでしょうか。(Admission Webhook)

- `kubectl get` でリソースの状態を一目で見せ、他のコントローラーや人間が「このリソースは準備できているか」を標準的に判断できるようにするにはどうすればよいでしょうか。(Status/Conditions)

本記事では、Kubebuilder が Kubernetes 1.36 / Go 1.26 をサポートし、controller-runtime が v0.24.x、controller-tools が v0.21.x である 2026 年基準で、各テーマを実践コードとともに説明します。また、かつてメトリクスエンドポイントの保護に使われていた kube-rbac-proxy が削除され、controller-runtime の `WithAuthenticationAndAuthorization` フィルターに置き換わった変更も反映しています。

まず全体像を 1 枚にまとめます。

+-------------------------------+

kubectl apply --> | Admission (mutating) | defaulting

| - デフォルト値を埋める |

+---------------+---------------+

|

v

+-------------------------------+

| Admission (validating) | validation

| - spec 検証・拒否 |

+---------------+---------------+

|

v

+-------------------------------+

| etcd 保存 (spec) |

+---------------+---------------+

|

v

+-------------------------------+

| Reconcile ループ | desired-state 収束

| - 外部リソース作成/更新 |

| - finalizer 追加 |

| - status/conditions 更新 |

+-------------------------------+

Finalizer で外部リソースをクリーンアップする

Finalizer とは何か

Finalizer は、オブジェクトの `metadata.finalizers` フィールドに入っている文字列のリストです。このリストが空でない限り、ユーザーが削除を要求しても API サーバーはオブジェクトを即座に消しません。代わりに `metadata.deletionTimestamp` を設定して「削除予定」の状態としてマークするだけです。ガベージコレクション(GC)は finalizer リストが完全に空になるまで待機します。

このメカニズムを使えば、コントローラーが「外部クリーンアップが完了した」と確信するまで、オブジェクトが実際に消えないように引き止めておけます。

ユーザー: kubectl delete myresource foo

|

v

+-------------------------------------------+

| API サーバー |

| finalizers は空か? |

+--------------------+----------------------+

| 空 | 空でない

v v

+----------------+ +-------------------------------+

| 即座に GC 削除 | | deletionTimestamp 設定 |

+----------------+ | (オブジェクトは残る、削除予定)|

+---------------+---------------+

|

v

+-------------------------------+

| Reconcile 再呼び出し |

| deletionTimestamp != 0 検出 |

| -> 外部リソースをクリーンアップ|

| -> finalizer を削除 |

+---------------+---------------+

|

v

+-------------------------------+

| finalizers 空 -> GC 削除 |

+-------------------------------+

肝心なのは「削除 = 即座に消える」ではなく、「削除 = deletionTimestamp が刻まれ、Reconcile がもう一度呼ばれる」という点です。この 1 回の追加呼び出しが、外部リソースをクリーンアップする最後の機会です。

Finalizer フローを Reconcile に実装する

controller-runtime は finalizer の追加・削除・確認を助けるヘルパーを提供します。`sigs.k8s.io/controller-runtime/pkg/controller/controllerutil` パッケージの `AddFinalizer`、`RemoveFinalizer`、`ContainsFinalizer` がそれです。

package controller

"context"

ctrl "sigs.k8s.io/controller-runtime"

"sigs.k8s.io/controller-runtime/pkg/client"

"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

cloudv1 "example.com/operator/api/v1"

)

const bucketFinalizer = "cloud.example.com/bucket-cleanup"

func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

log := ctrl.LoggerFrom(ctx)

var bucket cloudv1.Bucket

if err := r.Get(ctx, req.NamespacedName, &bucket); err != nil {

// NotFound はすでに削除済みなので無視する。

return ctrl.Result{}, client.IgnoreNotFound(err)

}

// 1) 削除要求かどうかを判定する。

if !bucket.ObjectMeta.DeletionTimestamp.IsZero() {

// 削除進行中。finalizer がまだ自分のものならクリーンアップする。

if controllerutil.ContainsFinalizer(&bucket, bucketFinalizer) {

if err := r.cleanupExternalBucket(ctx, &bucket); err != nil {

// クリーンアップ失敗時は finalizer を残してリトライさせる。

log.Error(err, "外部バケットのクリーンアップに失敗")

return ctrl.Result{}, err

}

// クリーンアップ成功。finalizer を削除 -> GC がオブジェクトを消す。

controllerutil.RemoveFinalizer(&bucket, bucketFinalizer)

if err := r.Update(ctx, &bucket); err != nil {

return ctrl.Result{}, err

}

}

// finalizer がなければやることはない。

return ctrl.Result{}, nil

}

// 2) 通常の作成/更新パス: finalizer がなければ先に追加する。

if !controllerutil.ContainsFinalizer(&bucket, bucketFinalizer) {

controllerutil.AddFinalizer(&bucket, bucketFinalizer)

if err := r.Update(ctx, &bucket); err != nil {

return ctrl.Result{}, err

}

// Update が新しいリソースバージョンを作ったので即座に返し、

// 次の Reconcile で本作業を続ける。

return ctrl.Result{}, nil

}

// 3) 実際の外部リソース収束ロジック。

if err := r.reconcileExternalBucket(ctx, &bucket); err != nil {

return ctrl.Result{}, err

}

return ctrl.Result{}, nil

}

上記コードの中心となるパターンをまとめると次のとおりです。

1. **`DeletionTimestamp.IsZero()`** で削除中かどうかを最初に確認します。

2. 削除中なら外部リソースをクリーンアップし、成功した後にのみ finalizer を削除します。クリーンアップに失敗したらエラーを返してリトライさせ、finalizer はそのまま残します。

3. 通常パスでは本格的な収束ロジックの前に finalizer を追加します。外部リソースを作る前に finalizer が先に付いていないと、途中で削除要求が来てもクリーンアップの機会を失います。

外部クリーンアップ関数と冪等性

`cleanupExternalBucket` は冪等(idempotent)でなければなりません。Reconcile は同じオブジェクトに対して何度も呼ばれうるため、すでに消えたバケットを再度消そうとする試みがエラーを投げないように処理する必要があります。

func (r *BucketReconciler) cleanupExternalBucket(ctx context.Context, bucket *cloudv1.Bucket) error {

name := bucket.Status.ExternalName

if name == "" {

// まだ外部リソースを作ったことがない -> 片付けるものはない。

return nil

}

err := r.CloudAPI.DeleteBucket(ctx, name)

if isNotFound(err) {

// すでに存在しない -> 成功とみなす(冪等性)。

return nil

}

return err

}

`Status.ExternalName` が空であるか、外部 API が「すでに存在しない」を返したら成功として扱います。こうすることでクリーンアップ処理を安全に繰り返せます。

Admission Webhook: Defaulting と Validation

Webhook の種類

Admission Webhook は、オブジェクトが etcd に保存される前に割り込むフックです。2 種類あります。

| 種類 | 目的 | オブジェクトを変更できるか | 代表的な用途 |

| --- | --- | --- | --- |

| Mutating | オブジェクト変形 | 可能 | デフォルト値の充填、ラベル注入 |

| Validating | オブジェクト検証 | 不可 | spec の拒否、ポリシー強制 |

順序は常に Mutating が先、Validating が後です。つまりデフォルト値を埋めた後に検証するため、検証ロジックは常に完成したオブジェクトを見ると仮定できます。

Kubebuilder で Webhook を生成する

Kubebuilder は webhook のスキャフォールディングをコマンド 1 行で提供します。以下は defaulting(mutating)と validation(validating)の両方を有効にする例です。

kubebuilder create webhook \

--group cloud \

--version v1 \

--kind Bucket \

--defaulting \

--programmatic-validation

このコマンドは `internal/webhook/v1/bucket_webhook.go` のようなファイルと、登録のためのマーカー、そして `config/webhook/` 配下のマニフェストパッチを生成します。

Defaulting (Mutating) の実装

最新の controller-runtime の webhook API は `admission.CustomDefaulter` インターフェースを使います。`Default` メソッドの中でオブジェクトにデフォルト値を埋めます。

package webhookv1

"context"

"fmt"

"k8s.io/apimachinery/pkg/runtime"

ctrl "sigs.k8s.io/controller-runtime"

logf "sigs.k8s.io/controller-runtime/pkg/log"

"sigs.k8s.io/controller-runtime/pkg/webhook"

"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

cloudv1 "example.com/operator/api/v1"

)

// +kubebuilder:webhook:path=/mutate-cloud-example-com-v1-bucket,mutating=true,failurePolicy=fail,sideEffects=None,groups=cloud.example.com,resources=buckets,verbs=create;update,versions=v1,name=mbucket-v1.kb.io,admissionReviewVersions=v1

type BucketCustomDefaulter struct {

DefaultRegion string

}

var _ admission.CustomDefaulter = &BucketCustomDefaulter{}

func (d *BucketCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {

bucket, ok := obj.(*cloudv1.Bucket)

if !ok {

return fmt.Errorf("expected a Bucket object but got %T", obj)

}

log := logf.FromContext(ctx)

// region が空ならデフォルト値を埋める。

if bucket.Spec.Region == "" {

bucket.Spec.Region = d.DefaultRegion

log.Info("defaulting region", "region", d.DefaultRegion)

}

// versioning が明示されていなければ安全にオンにする。

if bucket.Spec.Versioning == nil {

enabled := true

bucket.Spec.Versioning = &enabled

}

return nil

}

Validation (Validating) の実装

検証は `admission.CustomValidator` インターフェースを実装します。作成・更新・削除それぞれのメソッドを持ち、拒否するにはエラーを返します。

// +kubebuilder:webhook:path=/validate-cloud-example-com-v1-bucket,mutating=false,failurePolicy=fail,sideEffects=None,groups=cloud.example.com,resources=buckets,verbs=create;update,versions=v1,name=vbucket-v1.kb.io,admissionReviewVersions=v1

type BucketCustomValidator struct{}

var _ admission.CustomValidator = &BucketCustomValidator{}

func (v *BucketCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {

bucket, ok := obj.(*cloudv1.Bucket)

if !ok {

return nil, fmt.Errorf("expected a Bucket object but got %T", obj)

}

return v.validate(bucket)

}

func (v *BucketCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {

oldBucket := oldObj.(*cloudv1.Bucket)

newBucket := newObj.(*cloudv1.Bucket)

// region は不変フィールドとして強制する。

if oldBucket.Spec.Region != newBucket.Spec.Region {

return nil, fmt.Errorf("spec.region is immutable")

}

return v.validate(newBucket)

}

func (v *BucketCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {

return nil, nil

}

func (v *BucketCustomValidator) validate(bucket *cloudv1.Bucket) (admission.Warnings, error) {

var warnings admission.Warnings

if bucket.Spec.Region == "" {

return nil, fmt.Errorf("spec.region must not be empty")

}

allowed := map[string]bool{"ap-northeast-2": true, "us-east-1": true}

if !allowed[bucket.Spec.Region] {

return nil, fmt.Errorf("spec.region %q is not allowed", bucket.Spec.Region)

}

if bucket.Spec.RetentionDays > 365 {

warnings = append(warnings, "retentionDays が 365 を超えています。コストに注意してください。")

}

return warnings, nil

}

`admission.Warnings` を返すと、拒否せずにユーザーへ警告を表示できます。ポリシー上ブロックすべきならエラーを、勧告のみなら warning を使います。

Webhook の登録

webhook はマネージャーに登録される必要があります。最新の Kubebuilder スタイルでは、`SetupWebhookWithManager` 関数を通じてビルダーで接続します。

func SetupBucketWebhookWithManager(mgr ctrl.Manager) error {

return ctrl.NewWebhookManagedBy(mgr).

For(&cloudv1.Bucket{}).

WithDefaulter(&BucketCustomDefaulter{DefaultRegion: "ap-northeast-2"}).

WithValidator(&BucketCustomValidator{}).

Complete()

}

cert-manager で証明書を管理する

Admission Webhook は HTTPS で呼ばれるため、API サーバーが信頼する TLS 証明書が必要です。手動での管理は煩雑なので、cert-manager を使うのが標準です。Kubebuilder は `config/certmanager/` 配下に関連マニフェストを生成してくれます。

apiVersion: cert-manager.io/v1

kind: Issuer

metadata:

name: selfsigned-issuer

namespace: system

spec:

selfSigned: {}

apiVersion: cert-manager.io/v1

kind: Certificate

metadata:

name: serving-cert

namespace: system

spec:

dnsNames:

- webhook-service.system.svc

- webhook-service.system.svc.cluster.local

issuerRef:

kind: Issuer

name: selfsigned-issuer

secretName: webhook-server-cert

そして webhook 設定で `cert-manager.io/inject-ca-from` アノテーションにより CA バンドルを自動注入します。

apiVersion: admissionregistration.k8s.io/v1

kind: ValidatingWebhookConfiguration

metadata:

name: bucket-validating-webhook

annotations:

cert-manager.io/inject-ca-from: system/serving-cert

webhooks:

- name: vbucket-v1.kb.io

failurePolicy: Fail

sideEffects: None

admissionReviewVersions: ["v1"]

clientConfig:

service:

name: webhook-service

namespace: system

path: /validate-cloud-example-com-v1-bucket

rules:

- apiGroups: ["cloud.example.com"]

apiVersions: ["v1"]

operations: ["CREATE", "UPDATE"]

resources: ["buckets"]

Status サブリソースと Conditions 標準

Status サブリソースを使う理由

`status` をサブリソースとして宣言すると、spec と status が別々のエンドポイントで管理されます。こうすると、コントローラーが status だけを更新するときに spec の `resourceVersion` 競合を避けられ、ユーザーの spec 編集とコントローラーの status 更新が互いを上書きしません。

CRD の型にマーカーを追加します。

// +kubebuilder:object:root=true

// +kubebuilder:subresource:status

type Bucket struct {

metav1.TypeMeta `json:",inline"`

metav1.ObjectMeta `json:"metadata,omitempty"`

Spec BucketSpec `json:"spec,omitempty"`

Status BucketStatus `json:"status,omitempty"`

}

Conditions の標準構造

Kubernetes エコシステムは、status に `conditions` 配列を置くことを標準的な慣習としています。各 Condition は次のフィールドを持ちます。

| フィールド | 意味 |

| --- | --- |

| Type | 条件名(例: Ready, Progressing) |

| Status | True / False / Unknown |

| Reason | 機械が読む短い理由コード(CamelCase) |

| Message | 人間が読む説明 |

| LastTransitionTime | 状態が最後に変わった時刻 |

| ObservedGeneration | この条件が反映した spec 世代 |

`metav1.Condition` 型をそのまま使えます。

type BucketStatus struct {

// ObservedGeneration はコントローラーが最後に処理した spec 世代。

ObservedGeneration int64 `json:"observedGeneration,omitempty"`

// ExternalName は実際に作成された外部バケット名。

ExternalName string `json:"externalName,omitempty"`

// +patchMergeKey=type

// +patchStrategy=merge

// +listType=map

// +listMapKey=type

Conditions []metav1.Condition `json:"conditions,omitempty"`

}

meta.SetStatusCondition の使用

`k8s.io/apimachinery/pkg/api/meta` パッケージの `SetStatusCondition` は、同じ Type の Condition がすでにあれば更新し、なければ追加します。Status が変わったときだけ `LastTransitionTime` を更新する点が肝心です。

"k8s.io/apimachinery/pkg/api/meta"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

)

func (r *BucketReconciler) markReady(ctx context.Context, bucket *cloudv1.Bucket, ready bool, reason, msg string) error {

status := metav1.ConditionFalse

if ready {

status = metav1.ConditionTrue

}

meta.SetStatusCondition(&bucket.Status.Conditions, metav1.Condition{

Type: "Ready",

Status: status,

Reason: reason,

Message: msg,

ObservedGeneration: bucket.Generation,

})

// observedGeneration も合わせて更新し「この世代を処理した」と記録する。

bucket.Status.ObservedGeneration = bucket.Generation

// 必ず Status().Update() を使い status サブリソースだけを更新する。

return r.Status().Update(ctx, bucket)

}

`r.Update()` ではなく `r.Status().Update()` を使う点に注意してください。前者は spec を、後者は status サブリソースを更新します。

Reconcile で Conditions を埋める

func (r *BucketReconciler) reconcileExternalBucket(ctx context.Context, bucket *cloudv1.Bucket) error {

// 進行中の状態を記録する。

if err := r.markCondition(ctx, bucket, "Progressing", metav1.ConditionTrue,

"Creating", "外部バケット作成中"); err != nil {

return err

}

name, err := r.CloudAPI.EnsureBucket(ctx, bucket.Spec.Region)

if err != nil {

_ = r.markCondition(ctx, bucket, "Ready", metav1.ConditionFalse,

"CreateFailed", err.Error())

return err

}

bucket.Status.ExternalName = name

return r.markCondition(ctx, bucket, "Ready", metav1.ConditionTrue,

"Created", "外部バケット準備完了")

}

additionalPrinterColumns で kubectl 出力を整える

`kubectl get` で status を一目で見せるには、printer column のマーカーを追加します。controller-tools が CRD に `additionalPrinterColumns` を生成してくれます。

// +kubebuilder:printcolumn:name="Region",type=string,JSONPath=`.spec.region`

// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`

// +kubebuilder:printcolumn:name="External",type=string,JSONPath=`.status.externalName`

// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

type Bucket struct {

// ...

}

生成された CRD には次のような YAML が入ります。

additionalPrinterColumns:

- name: Region

type: string

jsonPath: .spec.region

- name: Ready

type: string

jsonPath: .status.conditions[?(@.type=="Ready")].status

- name: External

type: string

jsonPath: .status.externalName

- name: Age

type: date

jsonPath: .metadata.creationTimestamp

これでユーザーは次のように状態を 1 行で確認できます。

NAME REGION READY EXTERNAL AGE

my-data ap-northeast-2 True my-data-9af31 12m

よくある落とし穴

Finalizer 漏れによるリソースリーク

外部リソースを作る前に finalizer を追加しないと、素早い削除要求の際に Reconcile が「クリーンアップの機会」を得られないままオブジェクトが GC されます。その結果、クラウドに孤立リソースが残り、コストが漏れ出します。必ず外部リソースの作成前に finalizer を先に付けてください。

failurePolicy によるクラスター麻痺

Validating Webhook の `failurePolicy: Fail` は、webhook サーバーが応答できないと該当リソースのすべての作成・更新を拒否します。webhook が自身が管理するリソースに依存していたり、webhook Pod が落ちたりすると、クラスターの一部 API が麻痺する可能性があります。

| failurePolicy | webhook 障害時の動作 | 適する場合 |

| --- | --- | --- |

| Fail | 要求を拒否 | セキュリティ・ポリシー強制が重要なとき |

| Ignore | 要求を通過 | 可用性がより重要なとき |

コアなシステム名前空間は `namespaceSelector` で webhook 対象から除外し、webhook 障害がクラスターのブートストラップを妨げないようにするのが安全です。

Webhook タイムアウト

webhook はデフォルトのタイムアウト(`timeoutSeconds`)が短いです。外部 API 呼び出しのような遅い作業を webhook の中で行うと、タイムアウトで要求が拒否されることがあります。webhook は高速なインメモリ検証・デフォルト処理だけに使い、重い作業は Reconcile に回してください。

処理順序の混同

Mutating は常に Validating より先に実行されます。したがって Validating ではデフォルト値がすでに埋まっていると仮定できます。逆に Mutating であるフィールドを埋め忘れると、Validating が「空フィールド」を理由に拒否することがあるので、2 つのフックの責務を明確に分けてください。

Status 更新の競合

status を `r.Update()` で更新すると、spec と競合したり、サブリソース設定が無視されたりすることがあります。常に `r.Status().Update()` を使い、`ObservedGeneration` を合わせて更新して「どの世代を処理したか」を明確に記録してください。

テスト

Finalizer のテスト

envtest を使うと実際の API サーバーを立てて finalizer フローを検証できます。オブジェクトを作り、削除要求を送った後、外部クリーンアップが呼ばれて finalizer が削除されるかを確認します。

. "github.com/onsi/ginkgo/v2"

. "github.com/onsi/gomega"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"sigs.k8s.io/controller-runtime/pkg/client"

)

var _ = Describe("Bucket finalizer", func() {

It("削除時に外部リソースをクリーンアップする", func() {

bucket := &cloudv1.Bucket{

ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},

Spec: cloudv1.BucketSpec{Region: "ap-northeast-2"},

}

Expect(k8sClient.Create(ctx, bucket)).To(Succeed())

// finalizer が追加されるまで待つ。

Eventually(func() bool {

_ = k8sClient.Get(ctx, client.ObjectKeyFromObject(bucket), bucket)

return len(bucket.Finalizers) > 0

}).Should(BeTrue())

// 削除要求。

Expect(k8sClient.Delete(ctx, bucket)).To(Succeed())

// 最終的にオブジェクトが消えなければならない。

Eventually(func() bool {

err := k8sClient.Get(ctx, client.ObjectKeyFromObject(bucket), bucket)

return client.IgnoreNotFound(err) == nil && err != nil

}).Should(BeTrue())

// モックのクラウド API で削除が呼ばれたかを確認する。

Expect(fakeCloud.DeletedBuckets()).To(ContainElement("test"))

})

})

Webhook のテスト

webhook テストは、defaulter と validator を直接呼び出す単体テストで十分にカバーできます。envtest に webhook サーバーを付けて統合テストも可能です。

var _ = Describe("Bucket validator", func() {

It("許可されていない region を拒否する", func() {

v := &BucketCustomValidator{}

bucket := &cloudv1.Bucket{Spec: cloudv1.BucketSpec{Region: "mars-1"}}

_, err := v.ValidateCreate(ctx, bucket)

Expect(err).To(HaveOccurred())

})

It("デフォルト region を埋める", func() {

d := &BucketCustomDefaulter{DefaultRegion: "ap-northeast-2"}

bucket := &cloudv1.Bucket{}

Expect(d.Default(ctx, bucket)).To(Succeed())

Expect(bucket.Spec.Region).To(Equal("ap-northeast-2"))

})

})

運用チェックリスト

| 項目 | 確認 |

| --- | --- |

| 外部リソース作成前に finalizer 追加 | 必須 |

| クリーンアップ関数の冪等性 | 必須 |

| Mutating は高速なデフォルト処理のみ | 推奨 |

| Validating で不変フィールドを強制 | 推奨 |

| failurePolicy と namespaceSelector の検討 | 必須 |

| status は Status().Update() のみで更新 | 必須 |

| ObservedGeneration の記録 | 推奨 |

| printer columns で可視性を確保 | 推奨 |

| cert-manager で webhook 証明書を自動化 | 推奨 |

おわりに

Finalizer、Admission Webhook、Status/Conditions は、Operator を「動くデモ」から「運用できるコントローラー」へ引き上げる 3 本の柱です。それぞれをまとめると次のとおりです。

- **Finalizer** は削除に割り込み、外部リソースを安全にクリーンアップする最後の機会を保証します。外部リソースを作る前に付け、クリーンアップに成功した後にのみ削除してください。

- **Webhook** は不正な spec をクラスター進入段階でブロックし、デフォルト値を埋めます。Mutating は高速に、Validating は厳格に、そして failurePolicy と timeout を慎重に設定してください。

- **Status/Conditions** はリソースの状態を標準的な方法で公開し、人間と他のコントローラーが同じ方法で「準備完了」を判断できるようにします。`meta.SetStatusCondition` と `ObservedGeneration` を一貫して使ってください。

この 3 つを冪等性という共通原則の上で実装すれば、リトライと並行性にも揺らがない堅牢な Operator を作れます。

References

- [Kubebuilder Book](https://kubebuilder.io/book/)

- [Operator SDK Documentation](https://sdk.operatorframework.io/)

- [controller-runtime GoDoc](https://pkg.go.dev/sigs.k8s.io/controller-runtime)

- [Kubernetes: Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)

- [kubernetes-sigs/kubebuilder (GitHub)](https://github.com/kubernetes-sigs/kubebuilder)

- [kubernetes-sigs/controller-runtime (GitHub)](https://github.com/kubernetes-sigs/controller-runtime)

- [Kubernetes: Dynamic Admission Control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)

- [cert-manager Documentation](https://cert-manager.io/docs/)

- [Kubernetes: Finalizers](https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/)

현재 단락 (1/438)

基本的な Reconcile ループを書いたことがある方なら、次は Operator を「デモ」から「本番」へ引き上げる番です。その境界線上にある 3 つのテーマが、Finalizer、Admissi...

작성 글자: 0원문 글자: 17,286작성 단락: 0/438