LoginSignup
8
2

More than 1 year has passed since last update.

Kubernetes Operator初心者がOperatorを作成してみた (operator-sdk編)

Last updated at Posted at 2021-04-13

目的

Kubernetes Operatorは初心者にとってはとっつきにくいと思うので、自分の備忘録を残しておく。今回は、operator-sdkのTutorialを利用してOperatorへの理解を深める

内容

Go Operator Tutorial (operator-sdk)のmemcached-operatorを実装する. (memcached_controller.go)

memcached-operatorの内容:

  • Memcached というCustom Resource
    • spec.sizeでMemcachedのNodeの数を指定できる
    • status.nodesでNode情報をもつ
  • ControllerでReconcileLoopを実装
    • Memcachedインスタンスを取得
    • Deploymentがなければ作成
    • MemcachedのsizeでとDeploymentのreplicas数を同じにする
    • status.nodesにPod名を格納する

Prerequisite

  • Git
  • Go version 1.15
  • Docker version 17.03+.
  • kubectl (互換性のあるVersionのKubernetes clusterへ接続が必要)
  • (Docker registry (ローカルのみで開発し、ImageをPushしないのであれば docker-pushを適宜外し、ローカルのtag名にする))

参照: Installation Guide

実行環境

  • operator-sdk: v.1.5.0
  • go: 1.16.3
  • docker: 20.10.5
  • kubernetes (Docker for Mac): v1.19.7

ステップ

1. Projectの作成 (operator-sdk )

ディレクトリの作成

mkdir -p $HOME/projects/memcached-operator
cd $HOME/projects/memcached-operator

initializeする

operator-sdk init --domain example.com --repo github.com/example/memcached-operator

2. APIの作成 (operator-sdk)

KubernetesのAPI ResourceとControllerを作成

operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource --controller

確認 (Optional)

この時点でのOperatorを確認したい場合は以下のようにDeployができる (ステップごとにOperatorがどの機能ができてどの機能ができていないのかを確認するためなので、スキップも可)

  1. OPERATOR_IMGをセット

    docker_hub_name=nakamasato # 自分のDocker Hubのユーザ名を指定 (memcached-operatorというRepositoryは事前に作成しておく)
    export OPERATOR_IMG="$docker_hub_name/memcached-operator:v0.0.1-api-created"
    
  2. Dockerイメージを焼いてPushする

    make docker-build docker-push IMG=$OPERATOR_IMG
    
  3. OperatorをDeploy

    make deploy IMG=$OPERATOR_IMG
    
  4. OperatorのPodがRunningしていることを確認

    kubectl get pod -n memcached-operator-system
    NAME                                                     READY   STATUS    RESTARTS   AGE
    memcached-operator-controller-manager-865ffbbbcc-pdkxg   2/2     Running   0          76s
    
  5. Custom ResourceのMemcachedをデプロイ

    kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
    
  6. Controllerの実装をしていないので、Deploymentは作成されていない

    kubectl get deploy   
    No resources found in default namespace.
    
  7. Operator側のログを確認 (特に新しいMemcachedを作ったEventに対応するログはない)

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    2021-04-12T23:04:01.886Z    INFO    controller-runtime.metrics  metrics server is starting to listen    {"addr": "127.0.0.1:8080"}
    2021-04-12T23:04:01.887Z    INFO    setup   starting manager
    I0412 23:04:01.982492       1 leaderelection.go:243] attempting to acquire leader lease  memcached-operator-system/86f835c3.example.com...
    2021-04-12T23:04:02.078Z    INFO    controller-runtime.manager  starting metrics server {"path": "/metrics"}
    2021-04-12T23:04:02.282Z    DEBUG   controller-runtime.manager.events   Normal  {"object": {"kind":"ConfigMap","namespace":"memcached-operator-system","name":"86f835c3.example.com","uid":"dd829320-b9fb-4abc-9c18-cde6658daa96","apiVersion":"v1","resourceVersion":"2057764"}, "reason": "LeaderElection", "message": "memcached-operator-controller-manager-865ffbbbcc-pdkxg_4ecfda57-7d6a-42cc-bd5c-0cfa018a35f8 became leader"}
    2021-04-12T23:04:02.282Z    DEBUG   controller-runtime.manager.events   Normal  {"object": {"kind":"Lease","namespace":"memcached-operator-system","name":"86f835c3.example.com","uid":"2324fd70-20db-49a8-a975-98d71a158bd9","apiVersion":"coordination.k8s.io/v1","resourceVersion":"2057766"}, "reason": "LeaderElection", "message": "memcached-operator-controller-manager-865ffbbbcc-pdkxg_4ecfda57-7d6a-42cc-bd5c-0cfa018a35f8 became leader"}
    I0412 23:04:02.281814       1 leaderelection.go:253] successfully acquired lease memcached-operator-system/86f835c3.example.com
    2021-04-12T23:04:02.379Z    INFO    controller-runtime.manager.controller.memcached Starting EventSource    {"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "source": "kind source: /, Kind="}
    2021-04-12T23:04:02.479Z    INFO    controller-runtime.manager.controller.memcached Starting Controller {"reconciler group": "cache.example.com", "reconciler kind": "Memcached"}
    2021-04-12T23:04:02.479Z    INFO    controller-runtime.manager.controller.memcached Starting workers    {"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "worker count": 1}
    

ここまででできているもの:

タイプ 機能 実装
APIリソース MemcachedのAPI定義 (SpecのsizeとstatusのNodes) No
Controller Memcachedを取得 No
Controller MemcachedのDeploymentが存在していない場合に作成 No
Controller DeploymentのsizeをCustome Resource Memcachedのspecで指定された値と同じにする No
Controller Custom Resource Memcachedの statusを memcached のpod名で更新する No

3. APIを定義 (api/v1alpha1/memcached_types.go)

自動生成された api/v1alpha1/memcached_types.go を以下の様に変更する。

定義内容:

  • MemcachedSpec: MemcachedがデプロイされたときにいくつのPodsで動かすかを指定するためのsize
  • MemcachedStatus: Podの名前をMemcachedのNodesに文字列のリストとしてStatusにいれる
  • Memcached: 上記の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"`
}
// Memcached is the Schema for the memcacheds API
//+kubebuilder:subresource:status
type Memcached struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   MemcachedSpec   `json:"spec,omitempty"`
    Status MemcachedStatus `json:"status,omitempty"`
}

変更したあとには、これらを元に以下のコマンドで自動生成されたコードを更新する

make generate

API定義後にCRDのManifestファイルを以下のコマンドで生成

make manifests

確認 (Optional)

この時点でのOperatorを確認したい場合は以下のようにDeployができる (ステップごとにOperatorがどの機能ができてどの機能ができていないのかを確認するためなので、スキップも可)

  1. OPERATOR_IMGをセット

    docker_hub_name=nakamasato # 自分のDocker Hubのユーザ名を指定 (memcached-operatorというRepositoryは事前に作成しておく)
    export OPERATOR_IMG="$docker_hub_name/memcached-operator:v0.0.1-api-defined"
    
  2. Dockerイメージを焼いてPushする

    make docker-build docker-push IMG=$OPERATOR_IMG
    
  3. OperatorをDeploy

    make deploy IMG=$OPERATOR_IMG
    
  4. OperatorのPodが新しいimageでRunningしていることを確認

    kubectl get pod -n memcached-operator-system      
    NAME                                                    READY   STATUS    RESTARTS   AGE
    memcached-operator-controller-manager-bcdb99b56-n4l5c   2/2     Running   0          24s
    

    イメージの確認: v0.0.1-api-definedとなってること

    kubectl get pod -n memcached-operator-system -o yaml | grep ' image:'
          image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0
          image: nakamasato/memcached-operator:v0.0.1-api-defined
          image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0
          image: nakamasato/memcached-operator:v0.0.1-api-defined
    
  5. Custom ResourceのMemcachedをデプロイ (一つ前の確認でApplyした場合は同じなので必要ない)

    kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
    
  6. Controllerの実装をしていないので、Deploymentは作成されていない

    kubectl get deploy   
    No resources found in default namespace.
    
  7. Operator側のログを確認 (特に新しいMemcachedを作ったEventに対応するログはない)

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    2021-04-12T23:21:10.637Z        INFO    controller-runtime.metrics      metrics server is starting to listen    {"addr": "127.0.0.1:8080"}
    2021-04-12T23:21:10.639Z        INFO    setup   starting manager
    I0412 23:21:10.640597       1 leaderelection.go:243] attempting to acquire leader lease  memcached-operator-system/86f835c3.example.com...
    2021-04-12T23:21:10.640Z        INFO    controller-runtime.manager      starting metrics server {"path": "/metrics"}
    I0412 23:21:38.610611       1 leaderelection.go:253] successfully acquired lease memcached-operator-system/86f835c3.example.com
    2021-04-12T23:21:38.610Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"ConfigMap","namespace":"memcached-operator-system","name":"86f835c3.example.com","uid":"dd829320-b9fb-4abc-9c18-cde6658daa96","apiVersion":"v1","resourceVersion":"2060588"}, "reason": "LeaderElection", "message": "memcached-operator-controller-manager-bcdb99b56-n4l5c_3302a23e-cab6-4527-a187-f19eeb9fedbb became leader"}
    2021-04-12T23:21:38.610Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"Lease","namespace":"memcached-operator-system","name":"86f835c3.example.com","uid":"2324fd70-20db-49a8-a975-98d71a158bd9","apiVersion":"coordination.k8s.io/v1","resourceVersion":"2060589"}, "reason": "LeaderElection", "message": "memcached-operator-controller-manager-bcdb99b56-n4l5c_3302a23e-cab6-4527-a187-f19eeb9fedbb became leader"}
    2021-04-12T23:21:38.611Z        INFO    controller-runtime.manager.controller.memcached Starting EventSource    {"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "source": "kind source: /, Kind="}
    2021-04-12T23:21:39.001Z        INFO    controller-runtime.manager.controller.memcached Starting Controller     {"reconciler group": "cache.example.com", "reconciler kind": "Memcached"}
    2021-04-12T23:21:39.001Z        INFO    controller-runtime.manager.controller.memcached Starting workers        {"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "worker count": 1}
    


ここまででできているもの:

タイプ 機能 実装
APIリソース MemcachedのAPI定義 (SpecのsizeとstatusのNodes) Yes
Controller Memcachedを取得 No
Controller MemcachedのDeploymentが存在していない場合に作成 No
Controller DeploymentのsizeをCustome Resource Memcachedのspecで指定された値と同じにする No
Controller Custom Resource Memcachedの statusを memcached のpod名で更新する No

4.1. コントローラーの実装 (MemcachedのInstanceを取得)

Reconcile に Memcached を取得するロジックを追加 (ログで自分で書いたコードを判別しやすくするため 1. Fetch the Memcached instance.をすべてのログメッセージに書いている)

controllers/memcached_controller.go
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := r.Log.WithValues("memcached", req.NamespacedName)

    // 1. Fetch the Memcached instance
    memcached := &cachev1alpha1.Memcached{}
    err := r.Get(ctx, req.NamespacedName, memcached)
    if err != nil {
        if errors.IsNotFound(err) {
            log.Info("1. Fetch the Memcached instance. Memcached resource not found. Ignoring since object must be deleted")
            return ctrl.Result{}, nil
        }
        // Error reading the object - requeue the request.
        log.Error(err, "1. Fetch the Memcached instance. Failed to get Mmecached")
        return ctrl.Result{}, err
    }
    log.Info("1. Fetch the Memcached instance. Memchached resource found", "memcached.Name", memcached.Name, "memcached.Namespace", memcached.Namespace)    

    return ctrl.Result{}, nil
}

確認 (Optional)

この時点でのOperatorを確認したい場合は以下のようにDeployができる (ステップごとにOperatorがどの機能ができてどの機能ができていないのかを確認するためなので、スキップも可)

  1. OPERATOR_IMGをセット

    docker_hub_name=nakamasato # 自分のDocker Hubのユーザ名を指定 (memcached-operatorというRepositoryは事前に作成しておく)
    export OPERATOR_IMG="$docker_hub_name/memcached-operator:v0.0.1-controller-fetch-memcached-instance"
    
  2. Dockerイメージを焼いてPushする

    make docker-build docker-push IMG=$OPERATOR_IMG
    
  3. OperatorをDeploy

    make deploy IMG=$OPERATOR_IMG
    
  4. OperatorのPodが新しいimageでRunningしていることを確認

    kubectl get pod -n memcached-operator-system      
    

イメージの確認: v0.0.1-controller-fetch-memcached-instanceとなってること

```
kubectl get pod -n memcached-operator-system -o yaml | grep ' image:'
```
  1. Custom ResourceのMemcachedをデプロイ (一つ前の確認でApplyした場合は同じなので必要ない)

    kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
    
  2. Controllerの実装をしていないので、Deploymentは作成されていない

    kubectl get deploy   
    No resources found in default namespace.
    
  3. Operator側のログを確認 (1. Fetch the Memcached instance.がログにかかれている!)

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    ...
    2021-04-12T23:35:02.751Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    


ここまででできているもの:

タイプ 機能 実装
APIリソース MemcachedのAPI定義 (SpecのsizeとstatusのNodes) Yes
Controller Memcachedを取得 Yes
Controller MemcachedのDeploymentが存在していない場合に作成 No
Controller DeploymentのsizeをCustome Resource Memcachedのspecで指定された値と同じにする No
Controller Custom Resource Memcachedの statusを memcached のpod名で更新する No

4.2. コントローラーの実装 (Deploymentをなければ作成)

  1. 必要なlibraryをImport
  2. Controllerに必要なRBACをMarkerにより追加
  3. NamespacedNameからDeploymentが存在しない場合 (IsNotFound)はdeployemntForMemcached関数でDeploymentを作成
  4. deployemntForMemcached 関数の定義 (DeploymentをMemcached用に作成する)
  5. labelsForMemcached関数の定義 (新規Deployment作成時につけるlabelを返す)
controllers/memcached_controllers.go
...

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" // 追加

    "context"

        ...
)

...

//+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) {
    log := r.Log.WithValues("memcached", req.NamespacedName)

    // 1. Fetch the Memcached instance
        // ...前節で実装済み

    // 2. 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("2. Check if the deployment already exists, if not create a new one. Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
        err = r.Create(ctx, dep)
        if err != nil {
            log.Error(err, "2. Check if the deployment already exists, if not create a new one. 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, "2. Check if the deployment already exists, if not create a new one. Failed to get Deployment")
        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}
}

...

SetupWithManagerで、どのリソースをWatchするかを設定している。今回作成した Memcached 以外にも管理したいDeploymentをOwnsで管理できるようにする。

memcached_controller.go
// 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)
}

確認 (Optional)

この時点でのOperatorを確認したい場合は以下のようにDeployができる (ステップごとにOperatorがどの機能ができてどの機能ができていないのかを確認するためなので、スキップも可)

  1. OPERATOR_IMGをセット

    docker_hub_name=nakamasato # 自分のDocker Hubのユーザ名を指定 (memcached-operatorというRepositoryは事前に作成しておく)
    export OPERATOR_IMG="$docker_hub_name/memcached-operator:v0.0.1-controller-create-memcached-deployment"
    
  2. Dockerイメージを焼いてPushする

    make docker-build docker-push IMG=$OPERATOR_IMG
    

    以下のエラーがでたら 指示通りにgo get k8s.io/api/core/v1@v0.19.2を実行する

     go: github.com/example/memcached-operator/controllers: package k8s.io/api/core/v1 imported from implicitly required module; to add missing requirements, run:
            go get k8s.io/api/core/v1@v0.19.2
    
  3. OperatorをDeploy

    make deploy IMG=$OPERATOR_IMG
    
  4. OperatorのPodが新しいimageでRunningしていることを確認

    kubectl get pod -n memcached-operator-system      
    

イメージの確認: v0.0.1-controller-create-memcached-deploymentとなってること

```
kubectl get pod -n memcached-operator-system -o yaml | grep ' image:'
```
  1. Custom ResourceのMemcachedをデプロイ

    Fieldが変わっているので一度削除してからApplyし直す

    kubectl delete -f config/samples/cache_v1alpha1_memcached.yaml
    

    以下のようにSpecをsize: 3に変更

    config/samples/cache_v1alpha1_memcached.yaml
    apiVersion: cache.example.com/v1alpha1
    kind: Memcached
    metadata:
      name: memcached-sample
    spec:
      size: 3
    
    kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
    
  2. ControllerでDeploymentを作成する部分を実装したのでDeploymentができている

    kubectl get deploy   
    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    memcached-sample   3/3     3            3           4s
    

    しかしspecのサイズを替えても、Deploymentには反映しない。 (次の節で実装) (試したい場合は、kubectl patch memcached memcached-sample -p '{"spec":{"size": 5}}' --type=merge をアプライしてから、 kubectl get deployで数が変わらないことを確認)

  3. Operator側のログを確認 (2. Check if the deployment already exists, if not create a new one. Creating a new Deploymentがログにかかれている!)

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    ...
    2021-04-13T00:16:21.687Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    2021-04-13T00:16:21.982Z        INFO    controllers.Memcached   2. Check if the deployment already exists, if not create a new one. Creating a new Deployment   {"memcached": "default/memcached-sample", "Deployment.Namespace": "default", "Deployment.Name": "memcached-sample"}
    2021-04-13T00:16:22.006Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    


ここまででできているもの:

タイプ 機能 実装
APIリソース MemcachedのAPI定義 (SpecのsizeとstatusのNodes) Yes
Controller Memcachedを取得 Yes
Controller MemcachedのDeploymentが存在していない場合に作成 Yes
Controller DeploymentのsizeをCustome Resource Memcachedのspecで指定された値と同じにする No
Controller Custom Resource Memcachedの statusを memcached のpod名で更新する No

4.3. コントローラーの実装 (Deployment sizeとMemcached sizeを同じ値に保つ)

controllers/memcached_controllers.go
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // 1. Fetch the Memcached instance
    // 実装済み
    // 2. Check if the deployment already exists, if not create a new one
    // 実装済み
    // 3. 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, "3. Ensure the deployment size is the same as the spec. Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
            return ctrl.Result{}, err
        }
        // Spec updated - return and requeue
        log.Info("3. Ensure the deployment size is the same as the spec. Update deployment size", "Deployment.Spec.Replicas", size)
        return ctrl.Result{Requeue: true}, nil
    }

確認 (Optional)

この時点でのOperatorを確認したい場合は以下のようにDeployができる (ステップごとにOperatorがどの機能ができてどの機能ができていないのかを確認するためなので、スキップも可)

  1. OPERATOR_IMGをセット

    docker_hub_name=nakamasato # 自分のDocker Hubのユーザ名を指定 (memcached-operatorというRepositoryは事前に作成しておく)
    export OPERATOR_IMG="$docker_hub_name/memcached-operator:v0.0.1-controller-sync-deployment-size"
    
  2. Dockerイメージを焼いてPushする

    make docker-build docker-push IMG=$OPERATOR_IMG
    
  3. OperatorをDeploy

    make deploy IMG=$OPERATOR_IMG
    
  4. OperatorのPodが新しいimageでRunningしていることを確認

    kubectl get pod -n memcached-operator-system      
    

イメージの確認: v0.0.1-controller-sync-deployment-sizeとなってること

```
kubectl get pod -n memcached-operator-system -o yaml | grep ' image:'
```
  1. Custom ResourceのMemcachedをデプロイ

    以下のようにSpecをsize: 3 -> size: 2に変更

    config/samples/cache_v1alpha1_memcached.yaml
    apiVersion: cache.example.com/v1alpha1
    kind: Memcached
    metadata:
      name: memcached-sample
    spec:
      size: 2
    
    kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
    
  2. ControllerでDeploymentを作成する部分を実装したReplicaが2に変更されている!

    kubectl get deploy   
    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    memcached-sample   2/2     2            2           4s
    
  3. Operator側のログを確認 (3. Ensure the deployment size is the same as the spec.がログにかかれている!)

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    ...
    2021-04-13T00:42:26.366Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    2021-04-13T00:42:26.377Z        INFO    controllers.Memcached   3. Ensure the deployment size is the same as the spec. Update deployment size   {"memcached": "default/memcached-sample", "Deployment.Spec.Replicas": 2}
    2021-04-13T00:42:26.383Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    


ここまででできているもの:

タイプ 機能 実装
APIリソース MemcachedのAPI定義 (SpecのsizeとstatusのNodes) Yes
Controller Memcachedを取得 Yes
Controller MemcachedのDeploymentが存在していない場合に作成 Yes
Controller DeploymentのsizeをCustome Resource Memcachedのspecで指定された値と同じにする Yes
Controller Custom Resource Memcachedの statusを memcached のpod名で更新する No

4.4. コントローラーの実装 (Memcached statusを Pod名で更新)

  • reflectをimportに追加
  • ロジック追加: podListを取得→getPodNamesからPod名のarrayを取得→memcached.Status.Nodesに反映させる
  • getPodNames関数を定義: arrayにPod名を入れて返す
controllers/memcached_controllers.go

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"

    cachev1alpha1 "github.com/example/memcached-operator/api/v1alpha1"
    "github.com/go-logr/logr"
    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
)
...

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := r.Log.WithValues("memcached", req.NamespacedName)

    // 1. Fetch the Memcached instance
        // 実装済み
    // 2. Check if the deployment already exists, if not create a new one
        // 実装済み
    // 3. Ensure the deployment size is the same as the spec
        // 実装済み

    // 4. 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, "4. Update the Memcached status with the pod names. Failed to list pods", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name)
        return ctrl.Result{}, err
    }
    podNames := getPodNames(podList.Items)
    log.Info("4. Update the Memcached status with the pod names. Pod list", "podNames", podNames)

    // 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, "4. Update the Memcached status with the pod names. Failed to update Memcached status")
            return ctrl.Result{}, err
        }
    }
    log.Info("4. Update the Memcached status with the pod names. Update memcached.Status", "memcached.Status.Nodes", memcached.Status.Nodes)

    return ctrl.Result{}, nil
}

...

// 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
}

確認 (Optional)

この時点でのOperatorを確認したい場合は以下のようにDeployができる (ステップごとにOperatorがどの機能ができてどの機能ができていないのかを確認するためなので、スキップも可)

  1. OPERATOR_IMGをセット

    docker_hub_name=nakamasato # 自分のDocker Hubのユーザ名を指定 (memcached-operatorというRepositoryは事前に作成しておく)
    export OPERATOR_IMG="$docker_hub_name/memcached-operator:v0.0.1-controller-update-memcached-status"
    
  2. Dockerイメージを焼いてPushする

    make docker-build docker-push IMG=$OPERATOR_IMG
    
  3. OperatorをDeploy

    make deploy IMG=$OPERATOR_IMG
    
  4. OperatorのPodが新しいimageでRunningしていることを確認

    kubectl get pod -n memcached-operator-system      
    

イメージの確認: v0.0.1-controller-update-memcached-statusとなってること

```
kubectl get pod -n memcached-operator-system -o yaml | grep ' image:'
```
  1. Custom ResourceのMemcachedをデプロイ (前節でDeployしてあれば変更なし)

    kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
    
  2. Memcachedのstatus.nodesがpod名になっていることを確認

    kubectl get memcached memcached-sample -o jsonpath='{.status}'
    {"nodes":["memcached-sample-6c765df685-5b9dm","memcached-sample-6c765df685-z5zgn"]}
    
    kubectl get pod                                                      
    NAME                                READY   STATUS    RESTARTS   AGE
    memcached-sample-6c765df685-5b9dm   1/1     Running   0          37m
    memcached-sample-6c765df685-z5zgn   1/1     Running   0          37m
    
  3. Operator側のログを確認 (``がログにかかれている!)

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    ...
    2021-04-13T00:57:17.594Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    2021-04-13T00:57:17.660Z        INFO    controllers.Memcached   4. Update the Memcached status with the pod names. Pod list     {"memcached": "default/memcached-sample", "podNames": ["memcached-sample-6c765df685-5b9dm", "memcached-sample-6c765df685-z5zgn"]}
    2021-04-13T00:57:17.660Z        INFO    controllers.Memcached   4. Update the Memcached status with the pod names. Update memcached.Status      {"memcached": "default/memcached-sample", "memcached.Status.Nodes": ["memcached-sample-6c765df685-5b9dm", "memcached-sample-6c765df685-z5zgn"]}
    


ここまででできているもの:

タイプ 機能 実装
APIリソース MemcachedのAPI定義 (SpecのsizeとstatusのNodes) Yes
Controller Memcachedを取得 Yes
Controller MemcachedのDeploymentが存在していない場合に作成 Yes
Controller DeploymentのsizeをCustome Resource Memcachedのspecで指定された値と同じにする Yes
Controller Custom Resource Memcachedの statusを memcached のpod名で更新する Yes

5. 確認

  1. もしもMemcachedを前節でDeployしていれば削除。

    kubectl delete -f config/samples/cache_v1alpha1_memcached.yaml
    
  2. memcached-operatorのデプロイ  -> operatorは memcached-operator-system というNamespaceにデプロイされる

    export OPERATOR_IMG="nakamasato/memcached-operator:v0.0.1"
    make deploy IMG=$OPERATOR_IMG
    
  3. Memcached (CR)を作成

    kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
    

    ControllerのLogから以下が見て取れる:
       1. Memcachedの取得
       2. Deploymentの作成
       3. MemcachedStatusの更新

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    
    2021-11-12T00:47:04.417Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    2021-11-12T00:47:04.417Z        INFO    controllers.Memcached   2. Check if the deployment already exists, if not create a new one. Creating a new Deployment   {"memcached": "default/memcached-sample", "Deployment.Namespace": "default", "Deployment.Name": "memcached-sample"}
    2021-11-12T00:47:04.422Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    2021-11-12T00:47:04.523Z        INFO    controllers.Memcached   4. Update the Memcached status with the pod names. Pod list     {"memcached": "default/memcached-sample", "podNames": ["memcached-sample-6c765df685-n7ml4", "memcached-sample-6c765df685-74l72"]}
    2021-11-12T00:47:04.529Z        INFO    controllers.Memcached   4. Update the Memcached status with the pod names. Update memcached.Status      {"memcached": "default/memcached-sample", "memcached.Status.Nodes": ["memcached-sample-6c765df685-n7ml4", "memcached-sample-6c765df685-74l72"]}
    
  4. Memcached のサイズ変更

    kubectl patch memcached memcached-sample -p '{"spec":{"size": 5}}' --type=merge
    

    ログを確認:
       1. Memcachedの取得
       2. DeploymentのReplica更新
       3. MemcachedStatusの更新

    kubectl logs $(kubectl get po -n memcached-operator-system | grep memcached-operator-controller-manager | awk '{print $1}') -c manager -n memcached-operator-system -f
    
    2021-11-12T00:48:44.764Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    2021-11-12T00:48:44.772Z        INFO    controllers.Memcached   3. Ensure the deployment size is the same as the spec. Update deployment size   {"memcached": "default/memcached-sample", "Deployment.Spec.Replicas": 5}
    2021-11-12T00:48:44.772Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memchached resource found      {"memcached": "default/memcached-sample", "memcached.Name": "memcached-sample", "memcached.Namespace": "default"}
    2021-11-12T00:48:44.772Z        INFO    controllers.Memcached   4. Update the Memcached status with the pod names. Pod list     {"memcached": "default/memcached-sample", "podNames": ["memcached-sample-6c765df685-n7ml4", "memcached-sample-6c765df685-74l72"]}
    ...
    2021-11-12T00:48:45.987Z        INFO    controllers.Memcached   4. Update the Memcached status with the pod names. Pod list     {"memcached": "default/memcached-sample", "podNames": ["memcached-sample-6c765df685-n7ml4", "memcached-sample-6c765df685-74l72", "memcached-sample-6c765df685-g9pj5", "memcached-sample-6c765df685-9wjrv", "memcached-sample-6c765df685-nbbmk"]}
    2021-11-12T00:48:45.987Z        INFO    controllers.Memcached   4. Update the Memcached status with the pod names. Update memcached.Status      {"memcached": "default/memcached-sample", "memcached.Status.Nodes": ["memcached-sample-6c765df685-n7ml4", "memcached-sample-6c765df685-74l72", "memcached-sample-6c765df685-g9pj5", "memcached-sample-6c765df685-9wjrv", "memcached-sample-6c765df685-nbbmk"]}
    

    3. Ensure the deployment size is the same as the spec. Update deployment sizeで、Deploymentの数が更新されていることがわかる
    さらに、4. Update the Memcached status with the pod names. Update memcached.Status {"memcached": "default/memcached-sample", "memcached.Status.Nodes": ["memcached-sample-6c765df685-n7ml4", "memcached-sample-6c765df685-74l72", "memcached-sample-6c765df685-g9pj5", "memcached-sample-6c765df685-9wjrv", "memcached-sample-6c765df685-nbbmk"]} で Status.Nodesにも反映されていることがわかる

    kubectl get po 
    NAME                                READY   STATUS    RESTARTS   AGE
    memcached-sample-6c765df685-74l72   1/1     Running   0          4m36s
    memcached-sample-6c765df685-9wjrv   1/1     Running   0          2m56s
    memcached-sample-6c765df685-g9pj5   1/1     Running   0          2m56s
    memcached-sample-6c765df685-n7ml4   1/1     Running   0          4m36s
    memcached-sample-6c765df685-nbbmk   1/1     Running   0          2m56s
    
    kubectl get memcached memcached-sample -o jsonpath='{.status}'
    {"nodes":["memcached-sample-6c765df685-n7ml4","memcached-sample-6c765df685-74l72","memcached-sample-6c765df685-g9pj5","memcached-sample-6c765df685-9wjrv","memcached-sample-6c765df685-nbbmk"]}
    
  5. Memcachedの削除

    kubectl delete -f config/samples/cache_v1alpha1_memcached.yaml
    

    ログを確認すると削除された

    2021-11-12T00:53:22.424Z        INFO    controllers.Memcached   1. Fetch the Memcached instance. Memcached resource not found. Ignoring since object must be deleted    {"memcached": "default/memcached-sample"}
    

6. operatorの削除

make undeploy

まとめ

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2