検索

5.3. Go ベースの Operator

download PDF

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

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

重要

Operator プロジェクトの関連スキャフォールディングおよびテストツールなど、Red Hat がサポートするバージョンの Operator SDK CLI ツールは非推奨となり、Red Hat OpenShift Service on AWS の今後のリリースで削除される予定です。Red Hat は、現在のリリースライフサイクル中にこの機能のバグ修正とサポートを提供しますが、この機能は今後、機能拡張の提供はなく、Red Hat OpenShift Service on AWS リリースから削除されます。

新しい Operator プロジェクトを作成する場合、Red Hat がサポートするバージョンの Operator SDK は推奨されません。既存の Operator プロジェクトを使用する Operator 作成者は、Red Hat OpenShift Service on AWS 4 でリリースされるバージョンの Operator SDK CLI ツールを使用してプロジェクトを維持し、Red Hat OpenShift Service on AWS の新しいバージョンを対象とする Operator リリースを作成できます。

Operator プロジェクトの次の関連ベースイメージは 非推奨 ではありません。これらのベースイメージのランタイム機能と設定 API は、バグ修正と CVE への対応のために引き続きサポートされます。

  • Ansible ベースの Operator プロジェクトのベースイメージ
  • Helm ベースの Operator プロジェクトのベースイメージ

サポートされていない、コミュニティーによって管理されているバージョンの Operator SDK については、Operator SDK (Operator Framework) を参照してください。

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

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

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

5.3.1.1. 前提条件

  • Operator SDK CLI がインストールされている。
  • OpenShift CLI (oc) 4 以降がインストールされている。
  • Go 1.21 以降
  • dedicated-admin 権限を持つアカウントを使用して、oc で Red Hat OpenShift Service on AWS クラスターにログインしている。
  • クラスターがイメージをプルできるように、イメージをプッシュするリポジトリーを public として設定するか、イメージプルシークレットを設定している。

5.3.1.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.3.1.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"
plugins:
  manifests.sdk.operatorframework.io/v2: {}
  scorecard.sdk.operatorframework.io/v2: {}
  sdk.x-openshift.io/v1: {}
5.3.1.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.1.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.1.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.1.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.1.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.1.3.2.1. OpenAPI 検証

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

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

関連情報

5.3.1.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"
            ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
    
            cachev1 "github.com/example-inc/memcached-operator/api/v1"
    )
    
    // 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)
            log := ctrllog.FromContext(ctx)
            // Fetch the Memcached instance
            memcached := &cachev1.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 *cachev1.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(&cachev1.Memcached{}).
                    Owns(&appsv1.Deployment{}).
                    Complete(r)
    }

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

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

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

5.3.1.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.1.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.1.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.1.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.1.5. プロキシーサポートの有効化

Operator の作成者は、ネットワークプロキシーをサポートする Operator を開発できます。dedicated-admin ロールを持つ管理者は、Operator Lifecycle Manager (OLM) によって処理される環境変数のプロキシーサポートを設定します。Operator は以下の標準プロキシー変数の環境を検査し、値をオペランドに渡して、プロキシーされたクラスターをサポートする必要があります。

  • HTTP_PROXY
  • HTTPS_PROXY
  • NO_PROXY
注記

このチュートリアルでは、HTTP_PROXY を環境変数の例として使用します。

前提条件

  • クラスター全体の egress プロキシーが有効にされているクラスター。

手順

  1. controllers/memcached_controller.go ファイルを編集し、以下のパラメーターを追加します。

    1. operator-lib ライブラリーから proxy パッケージをインポートします。

      import (
        ...
         "github.com/operator-framework/operator-lib/proxy"
      )
    2. proxy.ReadProxyVarsFromEnv helper 関数を調整ループに、結果をオペランド環境に追加します。

      for i, container := range dep.Spec.Template.Spec.Containers {
      		dep.Spec.Template.Spec.Containers[i].Env = append(container.Env, proxy.ReadProxyVarsFromEnv()...)
      }
      ...
  2. 以下を config/manager/manager.yaml ファイルに追加して、Operator デプロイメントに環境変数を設定します。

    containers:
     - args:
       - --leader-elect
       - --leader-election-id=ansible-proxy-demo
       image: controller:latest
       name: manager
       env:
         - name: "HTTP_PROXY"
           value: "http_proxy_test"

5.3.1.6. Operator の実行

Operator をビルドして実行するには、Operator SDK CLI を使用して Operator をバンドルし、Operator Lifecycle Manager (OLM) を使用してクラスターにデプロイします。

注記

Operator を Red Hat OpenShift Service on AWS ではなく OpenShift Container Platform クラスターにデプロイする場合は、次の 2 つのデプロイ方法をさらに使用できます。

  • クラスター外で Go プログラムとしてローカルに実行します。
  • クラスター上のデプロイメントとして実行します。
注記

Go ベースの Operator を、OLM を使用するバンドルとして実行する前に、サポート対象のイメージを使用するようにプロジェクトが更新されていることを確認してください。

関連情報

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

Operator Bundle Format は、Operator SDK および Operator Lifecycle Manager (OLM) のデフォルトパッケージ方法です。Operator SDK を使用して OLM に対して Operator を準備し、バンドルイメージとして Operator プロジェクトをビルドしてプッシュできます。

前提条件

  • 開発ワークステーションに Operator SDK CLI がインストールされている。
  • OpenShift CLI (oc) v4 以降がインストールされている。
  • Operator プロジェクトが Operator SDK を使用して初期化されている。
  • Operator が Go ベースの場合、Red Hat OpenShift Service on AWS で Operator を実行するために、サポート対象のイメージを使用するようにプロジェクトが更新されている。

手順

  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_IMG を設定します。

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

      $ docker push <registry>/<user>/<bundle_image_name>:<tag>
5.3.1.6.1.2. Operator Lifecycle Manager を使用した Operator のデプロイ

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

Operator Bundle Format は、Operator SDK および OLM のデフォルトパッケージ方法です。Operator SDK を使用して OLM でバンドルイメージを迅速に実行し、適切に実行されるようにできます。

前提条件

  • 開発ワークステーションに Operator SDK CLI がインストールされている。
  • Operator バンドルイメージがビルドされ、レジストリーにプッシュされている。
  • OLM が Kubernetes ベースのクラスターにインストールされている (apiextensions.k8s.io/v1 CRD を使用する場合は v1.16.0 以降、たとえば Red Hat OpenShift Service on AWS 4)
  • dedicated-admin 権限を持つアカウントを使用して oc でクラスターにログインしている。
  • Operator が Go ベースの場合、Red Hat OpenShift Service on AWS で Operator を実行するために、サポート対象のイメージを使用するようにプロジェクトが更新されている。

手順

  • 以下のコマンドを入力してクラスターで Operator を実行します。

    $ operator-sdk run bundle \1
        -n <namespace> \2
        <registry>/<user>/<bundle_image_name>:<tag> 3
    1
    run bundle コマンドは、有効なファイルベースのカタログを作成し、OLM を使用して Operator バンドルをクラスターにインストールします。
    2
    オプション: デフォルトで、このコマンドは ~/.kube/config ファイルの現在アクティブなプロジェクトに Operator をインストールします。-n フラグを追加して、インストールに異なる namespace スコープを設定できます。
    3
    イメージを指定しない場合、コマンドは quay.io/operator-framework/opm:latest をデフォルトのインデックスイメージとして使用します。イメージを指定した場合は、コマンドはバンドルイメージ自体をインデックスイメージとして使用します。
    重要

    Red Hat OpenShift Service on AWS 4.11 以降、run bundle コマンドは Operator カタログのファイルベースのカタログ形式をデフォルトでサポートしています。Operator カタログに関して、非推奨の SQLite データベース形式は引き続きサポートされますが、今後のリリースで削除される予定です。Operator の作成者はワークフローをファイルベースのカタログ形式に移行することが推奨されます。

    このコマンドにより、以下のアクションが行われます。

    • バンドルイメージをインジェクトしてインデックスイメージを作成します。インデックスイメージは不透明で一時的なものですが、バンドルを実稼働環境でカタログに追加する方法を正確に反映します。
    • 新規インデックスイメージを参照するカタログソースを作成します。これにより、OperatorHub が Operator を検出できるようになります。
    • OperatorGroupSubscriptionInstallPlan、および RBAC を含むその他の必要なリソースすべてを作成して、Operator をクラスターにデプロイします。

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

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. 次のコマンドを実行して CR を削除します。

    $ oc delete -f config/samples/cache_v1_memcached.yaml
  8. このチュートリアルの一環として作成したリソースをクリーンアップします。

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

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

      $ operator-sdk cleanup <project_name>

5.3.1.8. 関連情報

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

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

重要

Operator プロジェクトの関連スキャフォールディングおよびテストツールなど、Red Hat がサポートするバージョンの Operator SDK CLI ツールは非推奨となり、Red Hat OpenShift Service on AWS の今後のリリースで削除される予定です。Red Hat は、現在のリリースライフサイクル中にこの機能のバグ修正とサポートを提供しますが、この機能は今後、機能拡張の提供はなく、Red Hat OpenShift Service on AWS リリースから削除されます。

新しい Operator プロジェクトを作成する場合、Red Hat がサポートするバージョンの Operator SDK は推奨されません。既存の Operator プロジェクトを使用する Operator 作成者は、Red Hat OpenShift Service on AWS 4 でリリースされるバージョンの Operator SDK CLI ツールを使用してプロジェクトを維持し、Red Hat OpenShift Service on AWS の新しいバージョンを対象とする Operator リリースを作成できます。

Operator プロジェクトの次の関連ベースイメージは 非推奨 ではありません。これらのベースイメージのランタイム機能と設定 API は、バグ修正と CVE への対応のために引き続きサポートされます。

  • Ansible ベースの Operator プロジェクトのベースイメージ
  • Helm ベースの Operator プロジェクトのベースイメージ

サポートされていない、コミュニティーによって管理されているバージョンの Operator SDK については、Operator SDK (Operator Framework) を参照してください。

5.3.2.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 マニフェスト。

5.3.3. 新しい Operator SDK バージョンの Go ベースの Operator プロジェクトの更新

Red Hat OpenShift Service on AWS 4 は Operator SDK 1.31.0 をサポートしています。ワークステーションに 1.28.0 CLI がすでにインストールされている場合は、最新バージョンをインストール して CLI を 1.31.0 に更新できます。

重要

Operator プロジェクトの関連スキャフォールディングおよびテストツールなど、Red Hat がサポートするバージョンの Operator SDK CLI ツールは非推奨となり、Red Hat OpenShift Service on AWS の今後のリリースで削除される予定です。Red Hat は、現在のリリースライフサイクル中にこの機能のバグ修正とサポートを提供しますが、この機能は今後、機能拡張の提供はなく、Red Hat OpenShift Service on AWS リリースから削除されます。

新しい Operator プロジェクトを作成する場合、Red Hat がサポートするバージョンの Operator SDK は推奨されません。既存の Operator プロジェクトを使用する Operator 作成者は、Red Hat OpenShift Service on AWS 4 でリリースされるバージョンの Operator SDK CLI ツールを使用してプロジェクトを維持し、Red Hat OpenShift Service on AWS の新しいバージョンを対象とする Operator リリースを作成できます。

Operator プロジェクトの次の関連ベースイメージは 非推奨 ではありません。これらのベースイメージのランタイム機能と設定 API は、バグ修正と CVE への対応のために引き続きサポートされます。

  • Ansible ベースの Operator プロジェクトのベースイメージ
  • Helm ベースの Operator プロジェクトのベースイメージ

サポートされていない、コミュニティーによって管理されているバージョンの Operator SDK については、Operator SDK (Operator Framework) を参照してください。

ただし、既存の Operator プロジェクトが Operator SDK 1.31.0 との互換性を維持するには、1.28.0 以降に導入された関連する重大な変更に対し、更新手順を実行する必要があります。アップグレードの手順は、以前は 1.28.0 で作成または維持されている Operator プロジェクトのいずれかで手動で実行する必要があります。

5.3.3.1. Operator SDK 1.31.0 の Go ベースの Operator プロジェクトの更新

次の手順では、1.31.0 との互換性を確保するため、既存の Go ベースの Operator プロジェクトを更新します。

前提条件

  • Operator SDK 1.31.0 がインストールされている
  • Operator SDK 1.28.0 で作成または保守されている Operator プロジェクト

手順

  1. 次の例に示すように、Operator プロジェクトの Makefile を編集して Operator SDK バージョンを 1.31.0 に更新します。

    例 makefile

    # Set the Operator SDK version to use. By default, what is installed on the system is used.
    # This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit.
    OPERATOR_SDK_VERSION ?= v1.36.1 1

    1
    バージョンを 1.28.0 から 1.31.0 に変更します。
  2. go/v4 プラグインは安定し、Go ベースの Operator のスキャフォールディング時に使用されるデフォルトバージョンです。Golang v2 および v3 プラグインから新しい Golang v4 プラグインへの移行により、大きな変更が導入されました。この移行は、Golang 開発の進化度を反映して、プロジェクトの機能と互換性を強化するように設計されています。

    これらの変更の背後にある理由の詳細については、Kubebuilder ドキュメントの go/v3 vs go/v4 を参照してください。

    v4 プラグイン形式への移行プロセスと詳細な移行手順は、Kubebuilder ドキュメントの ファイルを手動で更新して、移行 from go/v3 to go/v4 を参照してください。

  3. kustomize/v2 プラグインは安定版になり、go/v4ansible/v1helm/v1、および hybrid/v1-alpha プラグインを使用する場合にプラグインチェーンで使用されるデフォルトのバージョンになりました。このデフォルトのスキャフォールディングの詳細については、Kubebuilder ドキュメントの Kustomize v2 を参照してください。
  4. Operator プロジェクトがマルチプラットフォームまたはマルチアーキテクチャー、ビルドを使用する場合、既存の docker-buildx ターゲットをプロジェクトの Makefile の以下の定義に置き換えます。

    例 makefile

    docker-buildx:
    ## Build and push the Docker image for the manager for multi-platform support
    	- docker buildx create --name project-v3-builder
    	docker buildx use project-v3-builder
    	- docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile .
    	- docker buildx rm project-v3-builder

  5. 1.29 を使用するには、Operator プロジェクトの Kubernetes バージョンをアップグレードする必要があります。プロジェクト構造、Makefile、および go.mod ファイルで以下の変更を加える必要があります。

    重要

    go/v3 プラグインは Kubebuilder で非推奨であるため、Operator SDK も今後のリリースで go/v4 に移行しています。

    1. go.mod ファイルを更新して、依存関係をアップグレードします。

      k8s.io/api v0.29.2
      k8s.io/apimachinery v0.29.2
      k8s.io/client-go v0.29.2
      sigs.k8s.io/controller-runtime v0.17.3
    2. 次のコマンドを実行して、アップグレードされた依存関係をダウンロードします。

      $ go mod tidy
    3. プロジェクトは、kube-rbac-proxy バージョン 0.16.0 でスキャフォールディングされるようになりました。以下の変更を加えて、スキャフォールディングされた config/default/manager_auth_proxy_patch.yaml ファイルで kube-rbac-proxy のバージョンを変更します。

      - gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0
      + gcr.io/kubebuilder/kube-rbac-proxy:v0.16.0
    4. Kustomize で構築されたすべてのリソースを含むファイルを生成できるようになりました。このファイルは、依存関係なしでこのプロジェクトをインストールするために必要です。以下の変更を加えて、Makefile を更新します。

      + .PHONY: build-installer
      +   build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
      +   	mkdir -p dist
      +   	cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
      +   	$(KUSTOMIZE) build config/default > dist/install.yaml
    5. 次の変更を加えて、Makefile の ENVTEST_K8S_VERSION 変数を更新します。

      - ENVTEST_K8S_VERSION = 1.28.3
      + ENVTEST_K8S_VERSION = 1.29.0
    6. Makefile から以下のセクションを削除します。

      - GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
      - GOLANGCI_LINT_VERSION ?= v1.54.2
      - golangci-lint:
      - 	@[ -f $(GOLANGCI_LINT) ] || { \
      - 	set -e ;\
      - 	curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\
      - 	}
    7. 以下の変更を加えて、Makefile を更新します。

      例5.2 Makefile の変更

      - ## Tool Binaries
      - KUBECTL ?= kubectl
      - KUSTOMIZE ?= $(LOCALBIN)/kustomize
      - CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
      - ENVTEST ?= $(LOCALBIN)/setup-envtest
      -
      - ## Tool Versions
      - KUSTOMIZE_VERSION ?= v5.2.1
      - CONTROLLER_TOOLS_VERSION ?= v0.13.0
      -
      - .PHONY: kustomize
      - kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading.
      - $(KUSTOMIZE): $(LOCALBIN)
      -   @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \
      -   echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \
      -   rm -rf $(LOCALBIN)/kustomize; \
      -   fi
      -   test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION)
      -
      - .PHONY: controller-gen
      - controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten.
      - $(CONTROLLER_GEN): $(LOCALBIN)
      -   test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \
      -   GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
      -
      - .PHONY: envtest
      - envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
      - $(ENVTEST): $(LOCALBIN)
      -   test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
      + ## Tool Binaries
      + KUBECTL ?= kubectl
      + KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION)
      + CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION)
      + ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION)
      + GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION)
      +
      + ## Tool Versions
      + KUSTOMIZE_VERSION ?= v5.3.0
      + CONTROLLER_TOOLS_VERSION ?= v0.14.0
      + ENVTEST_VERSION ?= release-0.17
      + GOLANGCI_LINT_VERSION ?= v1.57.2
      +
      + .PHONY: kustomize
      + kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
      + $(KUSTOMIZE): $(LOCALBIN)
      + 	$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))
      +
      + .PHONY: controller-gen
      + controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
      + $(CONTROLLER_GEN): $(LOCALBIN)
      + 	$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
      +
      + .PHONY: envtest
      + envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
      + $(ENVTEST): $(LOCALBIN)
      + 	$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
      +
      + .PHONY: golangci-lint
      + golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
      + $(GOLANGCI_LINT): $(LOCALBIN)
      + 	$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION})
      +
      + # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
      + # $1 - target path with name of binary (ideally with version)
      + # $2 - package url which can be installed
      + # $3 - specific version of package
      + define go-install-tool
      + @[ -f $(1) ] || { \
      + set -e; \
      + package=$(2)@$(3) ;\
      + echo "Downloading $${package}" ;\
      + GOBIN=$(LOCALBIN) go install $${package} ;\
      + mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\
      + }
      + endef

5.3.3.2. 関連情報

Red Hat logoGithubRedditYoutubeTwitter

詳細情報

試用、購入および販売

コミュニティー

Red Hat ドキュメントについて

Red Hat をお使いのお客様が、信頼できるコンテンツが含まれている製品やサービスを活用することで、イノベーションを行い、目標を達成できるようにします。

多様性を受け入れるオープンソースの強化

Red Hat では、コード、ドキュメント、Web プロパティーにおける配慮に欠ける用語の置き換えに取り組んでいます。このような変更は、段階的に実施される予定です。詳細情報: Red Hat ブログ.

会社概要

Red Hat は、企業がコアとなるデータセンターからネットワークエッジに至るまで、各種プラットフォームや環境全体で作業を簡素化できるように、強化されたソリューションを提供しています。

© 2024 Red Hat, Inc.