5.3. Go ベースの Operator

5.3.1. Go ベースの Operator の Operator SDK の使用を開始する

Operator SDK によって提供されるツールおよびライブラリーを使用して Go ベースの Operator をセットアップし、実行することに関連した基本内容を示すには、Operator 開発者は Go ベースの Memcached の Operator のサンプル、分散キー/値のストアをビルドして、クラスターへデプロイすることができます。

5.3.1.1. 前提条件

5.3.1.2. Go ベースの Operator の作成およびデプロイ

Operator SDK を使用して Memcached の単純な Go ベースの Operator をビルドし、デプロイできます。

手順

  1. プロジェクトを作成します。

    1. プロジェクトディレクトリーを作成します。

      $ mkdir memcached-operator
    2. プロジェクトディレクトリーに移動します。

      $ cd memcached-operator
    3. operator-sdk init コマンドを実行してプロジェクトを初期化します。

      $ operator-sdk init \
          --domain=example.com \
          --repo=github.com/example-inc/memcached-operator

      このコマンドは、デフォルトで Go プラグインを使用します。

    4. Go ベースの Operator を OpenShift Container Platform で実行できるようにするには、config/manager/manager.yaml ファイルを編集し、以下の行を置き換えます。

      runAsUser: 65532

      以下の行に置き換えます。

      runAsNonRoot: true
      注記

      この手順は、Go ベースの Operator のみに必要な一時的な回避策です。詳細は、BZ#1914406 を参照してください。

  2. API を作成します。

    単純な Memcached API を作成します。

    $ operator-sdk create api \
        --resource=true \
        --controller=true \
        --group cache \
        --version v1 \
        --kind Memcached
  3. Operator イメージをビルドし、プッシュします。

    デフォルトの Makefile ターゲットを使用して Operator をビルドし、プッシュします。プッシュ先となるレジストリーを使用するイメージのプル仕様を使用して IMG を設定します。

    $ make docker-build docker-push IMG=<registry>/<user>/<image_name>:<tag>
  4. Operator を実行します。

    1. CRD をインストールします。

      $ make install
    2. プロジェクトをクラスターにデプロイします。IMG をプッシュしたイメージに設定します。

      $ make deploy IMG=<registry>/<user>/<image_name>:<tag>
  5. サンプルカスタムリソース (CR) を作成します。

    1. サンプル CR を作成します。

      $ oc apply -f config/samples/cache_v1_memcached.yaml \
          -n memcached-operator-system
    2. Operator を調整する CR を確認します。

      $ oc logs deployment.apps/memcached-operator-controller-manager \
          -c manager \
          -n memcached-operator-system
  6. クリーンアップします。

    以下のコマンドを実行して、この手順の一部として作成されたリソースをクリーンアップします。

    $ make undeploy

5.3.1.3. 次のステップ

5.3.2. Go ベースの Operator の Operator SDK チュートリアル

Operator 開発者は、Operator SDK での Go プログラミング言語のサポートを利用して、Go ベースの Memcached Operator のサンプルをビルドして、分散キー/値のストアを作成し、そのライフサイクルを管理することができます。

このプロセスは、Operator Framework の 2 つの重要な設定要素を使用して実行されます。

Operator SDK
operator-sdk CLI ツールおよび controller-runtime ライブラリー API
Operator Lifecycle Manager (OLM)
クラスター上の Operator のインストール、アップグレード、ロールベースのアクセス制御 (RBAC)
注記

このチュートリアルでは、Go ベースの Operator の Operator SDK の使用を開始する よりも詳細に説明します。

5.3.2.1. 前提条件

5.3.2.2. プロジェクトの作成

Operator SDK CLI を使用して memcached-operator というプロジェクトを作成します。

手順

  1. プロジェクトのディレクトリーを作成します。

    $ mkdir -p $HOME/projects/memcached-operator
  2. ディレクトリーに切り替えます。

    $ cd $HOME/projects/memcached-operator
  3. Go モジュールのサポートをアクティブにします。

    $ export GO111MODULE=on
  4. operator-sdk init コマンドを実行してプロジェクトを初期化します。

    $ operator-sdk init \
        --domain=example.com \
        --repo=github.com/example-inc/memcached-operator
    注記

    operator-sdk init コマンドは、デフォルトで Go プラグインを使用します。

    operator-sdk init コマンドは、Go モジュール と使用する go.mod ファイルを生成します。生成されるファイルには有効なモジュールパスが必要であるため、$GOPATH/src/ 外のプロジェクトを作成する場合は、--repo フラグが必要です。

  5. Go ベースの Operator を OpenShift Container Platform で実行できるようにするには、config/manager/manager.yaml ファイルを編集し、以下の行を置き換えます。

    runAsUser: 65532

    以下の行に置き換えます。

    runAsNonRoot: true
    注記

    この手順は、Go ベースの Operator のみに必要な一時的な回避策です。詳細は、BZ#1914406 を参照してください。

5.3.2.2.1. PROJECT ファイル

operator-sdk init コマンドで生成されるファイルの 1 つに、Kubebuilder の PROJECT ファイルがあります。プロジェクトルートから実行される後続の operator-sdk コマンドおよび help 出力は、このファイルを読み取り、プロジェクトタイプが Go であることを認識しています。以下に例を示します。

domain: example.com
layout: go.kubebuilder.io/v3
projectName: memcached-operator
repo: github.com/example-inc/memcached-operator
version: 3-alpha
plugins:
  manifests.sdk.operatorframework.io/v2: {}
  scorecard.sdk.operatorframework.io/v2: {}
5.3.2.2.2. Manager について

Operator の主なプログラムは、Manager を初期化して実行する main.go ファイルです。Manager はすべてのカスタムリソース (CR) API 定義の Scheme を自動的に登録し、コントローラーおよび Webhook を設定して実行します。

Manager は、すべてのコントローラーがリソースの監視をする namespace を制限できます。

mgr, err := ctrl.NewManager(cfg, manager.Options{Namespace: namespace})

デフォルトで、Manager は Operator が実行される namespace を監視します。すべての namespace を確認するには、namespace オプションを空のままにすることができます。

mgr, err := ctrl.NewManager(cfg, manager.Options{Namespace: ""})

MultiNamespacedCacheBuilder 関数を使用して、特定の namespace セットを監視することもできます。

var namespaces []string 1
mgr, err := ctrl.NewManager(cfg, manager.Options{ 2
   NewCache: cache.MultiNamespacedCacheBuilder(namespaces),
})
1
namespace の一覧
2
Cmd 構造を作成し、共有依存関係を提供してコンポーネントを起動します。
5.3.2.2.3. 複数グループ API について

API およびコントローラーを作成する前に、Operator に複数の API グループが必要かどうかを検討してください。このチュートリアルでは、単一グループ API のデフォルトケースについて説明しますが、複数グループ API をサポートするようにプロジェクトのレイアウトを変更するには、以下のコマンドを実行します。

$ operator-sdk edit --multigroup=true

このコマンドにより、PROJECT ファイルが更新されます。このファイルは、以下の例のようになります。

domain: example.com
layout: go.kubebuilder.io/v3
multigroup: true
...

複数グループプロジェクトの場合、API Go タイプのファイルが apis/<group>/<version>/ ディレクトリーに作成され、コントローラーは controllers/<group>/ ディレクトリーに作成されます。続いて、Dockerfile が適宜更新されます。

追加リソース

5.3.2.3. API およびコントローラーの作成

Operator SDK CLI を使用してカスタムリソース定義 (CRD) API およびコントローラーを作成します。

手順

  1. 以下のコマンドを実行して、グループ cache、バージョン、v1、および種類 Memcached を指定して API を作成します。

    $ operator-sdk create api \
        --group=cache \
        --version=v1 \
        --kind=Memcached
  2. プロンプトが表示されたら y を入力し、リソースとコントローラーの両方を作成します。

    Create Resource [y/n]
    y
    Create Controller [y/n]
    y

    出力例

    Writing scaffold for you to edit...
    api/v1/memcached_types.go
    controllers/memcached_controller.go
    ...

このプロセスでは、api/v1/memcached_types.goMemcached リソース API が生成され、controllers/memcached_controller.go でコントローラーが生成されます。

5.3.2.3.1. API の定義

Memcached カスタムリソース (CR) の API を定義します。

手順

  1. api/v1/memcached_types.go で Go タイプの定義を変更し、以下の spec および status を追加します。

    // MemcachedSpec defines the desired state of Memcached
    type MemcachedSpec struct {
    	// +kubebuilder:validation:Minimum=0
    	// Size is the size of the memcached deployment
    	Size int32 `json:"size"`
    }
    
    // MemcachedStatus defines the observed state of Memcached
    type MemcachedStatus struct {
    	// Nodes are the names of the memcached pods
    	Nodes []string `json:"nodes"`
    }
  2. リソースタイプ用に生成されたコードを更新します。

    $ make generate
    ヒント

    *_types.go ファイルの変更後は、make generate コマンドを実行し、該当するリソースタイプ用に生成されたコードを更新する必要があります。

    上記の Makefile ターゲットは controller-gen ユーティリティーを呼び出して、api/v1/zz_generated.deepcopy.go ファイルを更新します。これにより、API Go タイプの定義は、すべての Kind タイプが実装する必要のある runtime.Object インターフェイスを実装します。

5.3.2.3.2. CRD マニフェストの生成

API が spec フィールドと status フィールドおよびカスタムリソース定義 (CRD) 検証マーカーで定義された後に、CRD マニフェストを生成できます。

手順

  • 以下のコマンドを実行し、CRD マニフェストを生成して更新します。

    $ make manifests

    この Makefile ターゲットは controller-gen ユーティリティーを呼び出し、config/crd/bases/cache.example.com_memcacheds.yaml ファイルに CRD マニフェストを生成します。

5.3.2.3.2.1. OpenAPI 検証

OpenAPIv3 スキーマは、マニフェストの生成時に spec.validation ブロックの CRD マニフェストに追加されます。この検証ブロックにより、Kubernetes が作成または更新時に Memcached CR のプロパティーを検証できます。

API の検証を設定するには、マーカーまたはアノテーションを使用できます。これらのマーカーには、+kubebuilder:validation 接頭辞が常にあります。

関連情報

5.3.2.4. コントローラーの実装

新規 API およびコントローラーの作成後に、コントローラーロジックを実装することができます。

手順

  • この例では、生成されたコントローラーファイル controllers/memcached_controller.go を以下の実装例に置き換えます。

    例5.1 memcached_controller.go の例

    /*
    Copyright 2020.
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    */
    
    package controllers
    
    import (
    	appsv1 "k8s.io/api/apps/v1"
    	corev1 "k8s.io/api/core/v1"
    	"k8s.io/apimachinery/pkg/api/errors"
    	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/apimachinery/pkg/types"
    	"reflect"
    
    	"context"
    
    	"github.com/go-logr/logr"
    	"k8s.io/apimachinery/pkg/runtime"
    	ctrl "sigs.k8s.io/controller-runtime"
    	"sigs.k8s.io/controller-runtime/pkg/client"
    
    	cachev1alpha1 "github.com/example/memcached-operator/api/v1alpha1"
    )
    
    // MemcachedReconciler reconciles a Memcached object
    type MemcachedReconciler struct {
    	client.Client
    	Log    logr.Logger
    	Scheme *runtime.Scheme
    }
    
    // +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
    // +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch
    // +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update
    // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
    // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;
    
    // Reconcile is part of the main kubernetes reconciliation loop which aims to
    // move the current state of the cluster closer to the desired state.
    // TODO(user): Modify the Reconcile function to compare the state specified by
    // the Memcached object against the actual cluster state, and then
    // perform operations to make the cluster state reflect the state specified by
    // the user.
    //
    // For more details, check Reconcile and its Result here:
    // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile
    func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    	log := r.Log.WithValues("memcached", req.NamespacedName)
    
    	// Fetch the Memcached instance
    	memcached := &cachev1alpha1.Memcached{}
    	err := r.Get(ctx, req.NamespacedName, memcached)
    	if err != nil {
    		if errors.IsNotFound(err) {
    			// Request object not found, could have been deleted after reconcile request.
    			// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
    			// Return and don't requeue
    			log.Info("Memcached resource not found. Ignoring since object must be deleted")
    			return ctrl.Result{}, nil
    		}
    		// Error reading the object - requeue the request.
    		log.Error(err, "Failed to get Memcached")
    		return ctrl.Result{}, err
    	}
    
    	// Check if the deployment already exists, if not create a new one
    	found := &appsv1.Deployment{}
    	err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)
    	if err != nil && errors.IsNotFound(err) {
    		// Define a new deployment
    		dep := r.deploymentForMemcached(memcached)
    		log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
    		err = r.Create(ctx, dep)
    		if err != nil {
    			log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
    			return ctrl.Result{}, err
    		}
    		// Deployment created successfully - return and requeue
    		return ctrl.Result{Requeue: true}, nil
    	} else if err != nil {
    		log.Error(err, "Failed to get Deployment")
    		return ctrl.Result{}, err
    	}
    
    	// Ensure the deployment size is the same as the spec
    	size := memcached.Spec.Size
    	if *found.Spec.Replicas != size {
    		found.Spec.Replicas = &size
    		err = r.Update(ctx, found)
    		if err != nil {
    			log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
    			return ctrl.Result{}, err
    		}
    		// Spec updated - return and requeue
    		return ctrl.Result{Requeue: true}, nil
    	}
    
    	// Update the Memcached status with the pod names
    	// List the pods for this memcached's deployment
    	podList := &corev1.PodList{}
    	listOpts := []client.ListOption{
    		client.InNamespace(memcached.Namespace),
    		client.MatchingLabels(labelsForMemcached(memcached.Name)),
    	}
    	if err = r.List(ctx, podList, listOpts...); err != nil {
    		log.Error(err, "Failed to list pods", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name)
    		return ctrl.Result{}, err
    	}
    	podNames := getPodNames(podList.Items)
    
    	// Update status.Nodes if needed
    	if !reflect.DeepEqual(podNames, memcached.Status.Nodes) {
    		memcached.Status.Nodes = podNames
    		err := r.Status().Update(ctx, memcached)
    		if err != nil {
    			log.Error(err, "Failed to update Memcached status")
    			return ctrl.Result{}, err
    		}
    	}
    
    	return ctrl.Result{}, nil
    }
    
    // deploymentForMemcached returns a memcached Deployment object
    func (r *MemcachedReconciler) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment {
    	ls := labelsForMemcached(m.Name)
    	replicas := m.Spec.Size
    
    	dep := &appsv1.Deployment{
    		ObjectMeta: metav1.ObjectMeta{
    			Name:      m.Name,
    			Namespace: m.Namespace,
    		},
    		Spec: appsv1.DeploymentSpec{
    			Replicas: &replicas,
    			Selector: &metav1.LabelSelector{
    				MatchLabels: ls,
    			},
    			Template: corev1.PodTemplateSpec{
    				ObjectMeta: metav1.ObjectMeta{
    					Labels: ls,
    				},
    				Spec: corev1.PodSpec{
    					Containers: []corev1.Container{{
    						Image:   "memcached:1.4.36-alpine",
    						Name:    "memcached",
    						Command: []string{"memcached", "-m=64", "-o", "modern", "-v"},
    						Ports: []corev1.ContainerPort{{
    							ContainerPort: 11211,
    							Name:          "memcached",
    						}},
    					}},
    				},
    			},
    		},
    	}
    	// Set Memcached instance as the owner and controller
    	ctrl.SetControllerReference(m, dep, r.Scheme)
    	return dep
    }
    
    // labelsForMemcached returns the labels for selecting the resources
    // belonging to the given memcached CR name.
    func labelsForMemcached(name string) map[string]string {
    	return map[string]string{"app": "memcached", "memcached_cr": name}
    }
    
    // getPodNames returns the pod names of the array of pods passed in
    func getPodNames(pods []corev1.Pod) []string {
    	var podNames []string
    	for _, pod := range pods {
    		podNames = append(podNames, pod.Name)
    	}
    	return podNames
    }
    
    // SetupWithManager sets up the controller with the Manager.
    func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
    	return ctrl.NewControllerManagedBy(mgr).
    		For(&cachev1alpha1.Memcached{}).
    		Owns(&appsv1.Deployment{}).
    		Complete(r)
    }

    コントローラーのサンプルは、それぞれの Memcached カスタムリソース (CR) について以下の調整 (reconciliation) ロジックを実行します。

    • Memcached デプロイメントを作成します (ない場合)。
    • デプロイメントのサイズが、Memcached CR 仕様で指定されたものと同じであることを確認します。
    • Memcached CR ステータスを memcached Pod の名前に置き換えます。

次のサブセクションでは、実装例のコントローラーがリソースを監視する方法と reconcile ループがトリガーされる方法を説明しています。これらのサブセクションを省略し、直接 Operator の実行 に進むことができます。

5.3.2.4.1. コントローラーによって監視されるリソース

controllers/memcached_controller.goSetupWithManager() 関数は、CR およびコントローラーによって所有され、管理される他のリソースを監視するようにコントローラーがビルドされる方法を指定します。

import (
	...
	appsv1 "k8s.io/api/apps/v1"
	...
)

func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&cachev1.Memcached{}).
		Owns(&appsv1.Deployment{}).
		Complete(r)
}

NewControllerManagedBy() は、さまざまなコントローラー設定を可能にするコントローラービルダーを提供します。

For(&cachev1.Memcached{}) は、監視するプライマリーリソースとして Memcached タイプを指定します。Memcached タイプのそれぞれの Add、Update、または Delete イベントの場合、reconcile ループに Memcached オブジェクトの (namespace および name キーから成る) reconcile Request 引数が送られます。

Owns(&appsv1.Deployment{}) は、監視するセカンダリーリソースとして Deployment タイプを指定します。Add、Update、または Delete イベントの各 Deployment タイプの場合、イベントハンドラーは各イベントを、デプロイメントのオーナーの reconcile request にマップします。この場合、デプロイメントが作成された Memcached オブジェクトがオーナーです。

5.3.2.4.2. コントローラーの設定

多くの他の便利な設定を使用すると、コントローラーを初期化できます。以下に例を示します。

  • MaxConcurrentReconciles オプションを使用して、コントローラーの同時調整の最大数を設定します。デフォルトは 1 です。

    func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
        return ctrl.NewControllerManagedBy(mgr).
            For(&cachev1.Memcached{}).
            Owns(&appsv1.Deployment{}).
            WithOptions(controller.Options{
                MaxConcurrentReconciles: 2,
            }).
            Complete(r)
    }
  • 述語を使用した監視イベントをフィルターリングします。
  • EventHandler のタイプを選択し、監視イベントが reconcile ループの reconcile request に変換する方法を変更します。プライマリーリソースおよびセカンダリーリソースよりも複雑な Operator 関係の場合は、EnqueueRequestsFromMapFunc ハンドラーを使用して、監視イベントを任意の reconcile request のセットに変換することができます。

これらの設定およびその他の設定に関する詳細は、アップストリームの Builder および Controller の GoDocs を参照してください。

5.3.2.4.3. reconcile ループ

すべてのコントローラーには、reconcile ループを実装する Reconcile() メソッドのある reconciler オブジェクトがあります。この reconcile ループには、キャッシュからプライマリーリソースオブジェクトの Memcached を検索するために使用される namespace および name キーである Request 引数が渡されます。

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

	cachev1 "github.com/example-inc/memcached-operator/api/v1"
	...
)

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  // Lookup the Memcached instance for this reconcile request
  memcached := &cachev1.Memcached{}
  err := r.Get(ctx, req.NamespacedName, memcached)
  ...
}

返り値、結果、およびエラーに基づいて、Request は再度キューに入れられ、reconcile ループが再びトリガーされる可能性があります。

// Reconcile successful - don't requeue
return ctrl.Result{}, nil
// Reconcile failed due to error - requeue
return ctrl.Result{}, err
// Requeue for any reason other than an error
return ctrl.Result{Requeue: true}, nil

Result.RequeueAfter を設定して、猶予期間後にも要求を再びキューに入れることができます。

import "time"

// Reconcile for any reason other than an error after 5 seconds
return ctrl.Result{RequeueAfter: time.Second*5}, nil
注記

RequeueAfter を定期的な CR の調整に設定している Result を返すことができます。

reconciler、クライアント、およびリソースイベントとの対話に関する詳細は、Controller Runtime Client API のドキュメントを参照してください。

5.3.2.4.4. パーミッションおよび RBAC マニフェスト

コントローラーには、管理しているリソースと対話するために特定の RBAC パーミッションが必要です。これらは、以下のような RBAC マーカーを使用して指定されます。

// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;

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

config/rbac/role.yamlClusterRole オブジェクトマニフェストは、make manifests コマンドが実行されるたびに controller-gen ユーティリティーを使用して、以前のマーカーから生成されます。

5.3.2.5. Operator の実行

Operator SDK CLI を使用して Operator をビルドし、実行する方法は 3 つあります。

  • クラスター外で Go プログラムとしてローカルに実行します。
  • クラスター上のデプロイメントとして実行します。
  • Operator をバンドルし、Operator Lifecycle Manager (OLM) を使用してクラスター上にデプロイします。
注記

Go ベースの Operator を OpenShift Container Platform でのデプロイメントとして、または OLM を使用するバンドルとして実行する前に、プロジェクトがサポートされているイメージを使用するように更新されていることを確認します。

5.3.2.5.1. クラスター外でローカルに実行する。

Operator プロジェクトをクラスター外の Go プログラムとして実行できます。これは、デプロイメントとテストを迅速化するという開発目的において便利です。

手順

  • 以下のコマンドを実行して、~/.kube/config ファイルに設定されたクラスターにカスタムリソース定義 (CRD) をインストールし、Operator をローカルで実行します。

    $ make install run

    出力例

    ...
    2021-01-10T21:09:29.016-0700	INFO	controller-runtime.metrics	metrics server is starting to listen	{"addr": ":8080"}
    2021-01-10T21:09:29.017-0700	INFO	setup	starting manager
    2021-01-10T21:09:29.017-0700	INFO	controller-runtime.manager	starting metrics server	{"path": "/metrics"}
    2021-01-10T21:09:29.018-0700	INFO	controller-runtime.manager.controller.memcached	Starting EventSource	{"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "source": "kind source: /, Kind="}
    2021-01-10T21:09:29.218-0700	INFO	controller-runtime.manager.controller.memcached	Starting Controller	{"reconciler group": "cache.example.com", "reconciler kind": "Memcached"}
    2021-01-10T21:09:29.218-0700	INFO	controller-runtime.manager.controller.memcached	Starting workers	{"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "worker count": 1}

5.3.2.5.2. サポートされるイメージを使用する Operator の準備

Go ベースの Operator を OpenShift Container Platform で実行する前に、サポートされるイメージを使用するようにプロジェクトを更新します。

手順

  1. プロジェクトの root レベルの Dockerfile をサポートされるイメージを使用するように更新します。以下のデフォルトのランナーイメージの参照を変更します。

    FROM gcr.io/distroless/static:nonroot

    以下のように変更してください。

    FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
  2. Go プロジェクトのバージョンによっては、 Dockerfile に USER 65532:65532 または USER nonroot:nonroot ディレクティブが含まれる可能性があります。いずれの場合も、サポートされるランナーイメージでは必要ないため、その行を削除します。
  3. config/default/manager_auth_proxy_patch.yaml ファイルで、以下の image の値を変更します。

    gcr.io/kubebuilder/kube-rbac-proxy:<tag>

    サポートされるイメージを使用するには、以下へ変更します。

    registry.redhat.io/openshift4/ose-kube-rbac-proxy:v4.7
5.3.2.5.3. クラスター上でのデプロイメントとしての実行

Operator プロジェクトは、クラスター上でのデプロイメントとして実行することができます。

前提条件

  • プロジェクトを更新してサポートされるイメージを使用することで、OpenShift Container Platform で実行する Go ベースの Operator が準備済みである。

手順

  1. 以下の make コマンドを実行して Operator イメージをビルドし、プッシュします。以下の手順の IMG 引数を変更して、アクセス可能なリポジトリーを参照します。Quay.io などのリポジトリーサイトにコンテナーを保存するためのアカウントを取得できます。

    1. イメージをビルドします。

      $ make docker-build IMG=<registry>/<user>/<image_name>:<tag>
      注記

      Operator の SDK によって生成される Dockerfile は、go build について GOARCH=amd64 を明示的に参照します。これは、AMD64 アーキテクチャー以外の場合は GOARCH=$TARGETARCH に修正できます。Docker は、-platform で指定された値に環境変数を自動的に設定します。Buildah では、そのために -build-arg を使用する必要があります。詳細は、Multiple Architectures を参照してください。

    2. イメージをリポジトリーにプッシュします。

      $ make docker-push IMG=<registry>/<user>/<image_name>:<tag>
      注記

      両方のコマンドのイメージの名前とタグ (例: IMG=<registry>/<user>/<image_name>:<tag>) を Makefile に設定することもできます。IMG ?= controller:latest の値を変更して、デフォルトのイメージ名を設定します。

  2. 以下のコマンドを実行して Operator をデプロイします。

    $ make deploy IMG=<registry>/<user>/<image_name>:<tag>

    デフォルトで、このコマンドは <project_name>-system の形式で Operator プロジェクトの名前で namespace を作成し、デプロイメントに使用します。このコマンドは、config/rbac から RBAC マニフェストもインストールします。

  3. Operator が実行されていることを確認します。

    $ oc get deployment -n <project_name>-system

    出力例

    NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
    <project_name>-controller-manager       1/1     1            1           8m

5.3.2.5.4. Operator のバンドルおよび Operator Lifecycle Manager を使用したデプロイ

Operator Lifecycle Manager (OLM) は、Kubernetes クラスターで Operator (およびそれらの関連サービス) をインストールし、更新し、通常はライフサイクルを管理するのに役立ちます。OLM はデフォルトで OpenShift Container Platform にインストールされ、Kubernetes 拡張として実行されるため、追加のツールなしにすべての Operator のライフサイクル管理機能に Web コンソールおよび OpenShift CLI (oc) を使用できます。

Operator Bundle Format は、Operator SDK および OLM のデフォルトパッケージ方法です。Operator SDK を使用して OLM に対して Operator を準備し、OLM を使用してバンドルイメージをビルド、プッシュ、検証、および実行することができます。

前提条件

  • 開発ワークステーションに Operator SDK CLI がインストールされていること。
  • OpenShift CLI (oc) v4.7+ がインストールされていること。
  • (OpenShift Container Platform 4.7 など、apiextensions.k8s.io/v1 CRD を使用する場合は v1.16.0 以降の) Kubernetes ベースのクラスターに Operator Lifecycle Manager (OLM) がインストールされていること。
  • cluster-admin パーミッションのあるアカウントを使用して oc でクラスターへログインしていること。
  • Operator プロジェクトが Operator SDK を使用して初期化されていること。
  • Operator が Go ベースの場合、プロジェクトは OpenShift Container Platform で実行するためのサポートされるイメージを使用するように更新されている必要がある。

手順

  1. 以下の make コマンドを Operator プロジェクトディレクトリーで実行し、Operator イメージをビルドし、プッシュします。以下の手順の IMG 引数を変更して、アクセス可能なリポジトリーを参照します。Quay.io などのリポジトリーサイトにコンテナーを保存するためのアカウントを取得できます。

    1. イメージをビルドします。

      $ make docker-build IMG=<registry>/<user>/<operator_image_name>:<tag>
      注記

      Operator の SDK によって生成される Dockerfile は、go build について GOARCH=amd64 を明示的に参照します。これは、AMD64 アーキテクチャー以外の場合は GOARCH=$TARGETARCH に修正できます。Docker は、-platform で指定された値に環境変数を自動的に設定します。Buildah では、そのために -build-arg を使用する必要があります。詳細は、Multiple Architectures を参照してください。

    2. イメージをリポジトリーにプッシュします。

      $ make docker-push IMG=<registry>/<user>/<operator_image_name>:<tag>
  2. Operator SDK generate bundle および bundle validate のサブコマンドを含む複数のコマンドを呼び出す make bundle コマンドを実行し、Operator バンドルマニフェストを作成します。

    $ make bundle IMG=<registry>/<user>/<operator_image_name>:<tag>

    Operator のバンドルマニフェストは、アプリケーションを表示し、作成し、管理する方法を説明します。make bundle コマンドは、以下のファイルおよびディレクトリーを Operator プロジェクトに作成します。

    • ClusterServiceVersion オブジェクトを含む bundle/manifests という名前のバンドルマニフェストディレクトリー
    • bundle/metadata という名前のバンドルメタデータディレクトリー
    • config/crd ディレクトリー内のすべてのカスタムリソース定義 (CRD)
    • Dockerfile bundle.Dockerfile

    続いて、これらのファイルは operator-sdk bundle validate を使用して自動的に検証され、ディスク上のバンドル表現が正しいことを確認します。

  3. 以下のコマンドを実行し、バンドルイメージをビルドしてプッシュします。OLM は、1 つ以上のバンドルイメージを参照するインデックスイメージを使用して Operator バンドルを使用します。

    1. バンドルイメージをビルドします。イメージをプッシュしようとするレジストリー、ユーザー namespace、およびイメージタグの詳細で BUNDLE_IMAGE を設定します。

      $ make bundle-build BUNDLE_IMG=<registry>/<user>/<bundle_image_name>:<tag>
    2. バンドルイメージをプッシュします。

      $ docker push <registry>/<user>/<bundle_image_name>:<tag>
  4. 以下の Operator SDK コマンドを使用して、クラスターで OLM のステータスを確認します。

    $ operator-sdk olm status \
        --olm-namespace=openshift-operator-lifecycle-manager
  5. Operator SDK で OLM 統合を使用して、クラスターで Operator を実行します。

    $ operator-sdk run bundle \
        [-n <namespace>] \1
        <registry>/<user>/<bundle_image_name>:<tag>
    1
    デフォルトで、このコマンドは ~/.kube/config ファイルの現在アクティブなプロジェクトに Operator をインストールします。-n フラグを追加して、インストールに異なる namespace スコープを設定できます。

    このコマンドは、以下の操作を実行します。

    • バンドルイメージをインジェクトしてインデックスイメージを作成します。
    • 新規インデックスイメージを参照するカタログソースを作成します。これにより、OperatorHub が Operator を検出できるようになります。
    • Operator グループ、サブスクリプション、インストール計画、および RBAC を含むその他の必要なオブジェクトすべてを作成して、Operator をクラスターにデプロイします。

5.3.2.6. カスタムリソースの作成

Operator のインストール後に、Operator によってクラスターに提供されるカスタムリソース (CR) を作成して、これをテストできます。

前提条件

  • クラスターにインストールされている Memcached CR を提供する Memcached Operator の例

手順

  1. Operator がインストールされている namespace へ変更します。たとえば、make deploy コマンドを使用して Operator をデプロイした場合は、以下のようになります。

    $ oc project memcached-operator-system
  2. config/samples/cache_v1_memcached.yamlMemcached CR マニフェストのサンプルを編集し、以下の仕様が含まれるようにします。

    apiVersion: cache.example.com/v1
    kind: Memcached
    metadata:
      name: memcached-sample
    ...
    spec:
    ...
      size: 3
  3. CR を作成します。

    $ oc apply -f config/samples/cache_v1_memcached.yaml
  4. Memcached Operator が、正しいサイズで CR サンプルのデプロイメントを作成することを確認します。

    $ oc get deployments

    出力例

    NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
    memcached-operator-controller-manager   1/1     1            1           8m
    memcached-sample                        3/3     3            3           1m

  5. ステータスが Memcached Pod 名で更新されていることを確認するために、Pod および CR ステータスを確認します。

    1. Pod を確認します。

      $ oc get pods

      出力例

      NAME                                  READY     STATUS    RESTARTS   AGE
      memcached-sample-6fd7c98d8-7dqdr      1/1       Running   0          1m
      memcached-sample-6fd7c98d8-g5k7v      1/1       Running   0          1m
      memcached-sample-6fd7c98d8-m7vn7      1/1       Running   0          1m

    2. CR ステータスを確認します。

      $ oc get memcached/memcached-sample -o yaml

      出力例

      apiVersion: cache.example.com/v1
      kind: Memcached
      metadata:
      ...
        name: memcached-sample
      ...
      spec:
        size: 3
      status:
        nodes:
        - memcached-sample-6fd7c98d8-7dqdr
        - memcached-sample-6fd7c98d8-g5k7v
        - memcached-sample-6fd7c98d8-m7vn7

  6. デプロイメントサイズを更新します。

    1. config/samples/cache_v1_memcached.yaml ファイルを更新し、Memcached CR の spec.size フィールドを 3 から 5 に変更します。

      $ oc patch memcached memcached-sample \
          -p '{"spec":{"size": 5}}' \
          --type=merge
    2. Operator がデプロイメントサイズを変更することを確認します。

      $ oc get deployments

      出力例

      NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
      memcached-operator-controller-manager   1/1     1            1           10m
      memcached-sample                        5/5     5            5           3m

  7. このチュートリアルの一環として作成したリソースをクリーンアップします。

    • Operator のテストに make deploy コマンドを使用した場合は、以下のコマンドを実行します。

      $ make undeploy
    • Operator のテストに operator-sdk run bundle コマンドを使用した場合は、以下のコマンドを実行します。

      $ operator-sdk cleanup <project_name>

5.3.2.7. 関連情報

5.3.3. Go ベースの Operator のプロジェクトレイアウト

operator-sdk CLI は、各 Operator プロジェクトに多数のパッケージおよびファイルを生成、または スキャフォールディング することができます。

5.3.3.1. Go ベースのプロジェクトレイアウト

operator-sdk init コマンドを使用して生成される Go ベースの Operator プロジェクト (デフォルトタイプ) には、以下のディレクトリーおよびファイルが含まれます。

ファイルまたはディレクトリー目的

main.go

Operator のメインプログラム。これは、apis/ ディレクトリーのすべてのカスタムリソース定義 (CRD) を登録する新規のマネージャーをインスタンス化し、controllers/ ディレクトリーのすべてのコントローラーを起動します。

apis/

CRD の API を定義するディレクトリーツリー。apis/<version>/<kind>_types.go ファイルを編集して各リソースタイプの API を定義し、それらのパッケージをコントローラーにインポートして、これらのリソースタイプを監視する必要があります。

controllers/

コントローラーの実装。controller/<kind>_controller.go ファイルを編集し、指定された kind のリソースタイプを処理するためのコントローラーの reconcile ロジックを定義します。

config/

クラスターにコントローラーをデプロイするために使用される Kubernetes マニフェスト (CRD、RBAC、および証明書を含む)。

Makefile

コントローラーのビルドおよびデプロイに使用するターゲット。

Dockerfile

コンテナーエンジンが Operator をビルドするために使用する手順。

manifests/

CRD の登録、RBAC のセットアップ、およびデプロイメントとして Operator のデプロイをする Kubernetes マニフェスト。