Kubebuilder でカスタム ポッドを作成する方法


この投稿では、 kubebuilder を使用して単純な Kubernetes コントローラーを実装します.

Kubebuilder には book がありますが、初心者には複雑すぎると思います.もっと簡単にできるようにしようと思います.ポッドを管理する単純なオペレーターを実装します.

Kubebuilder をインストールする



Kubebuilder は Go 1.17 をサポートしていないため、Go 1.16 をインストールする必要があります. Go のバージョンを管理するために goenv を使用することにしました.
  • 以前の Go バージョン
  • を削除
  • インストール goenv: Install goenv: https://github.com/syndbg/goenv/blob/master/INSTALL.md
  • インストール Go 1.16: $ goenv install 1.16.8
  • このバージョンを使用: $ goenv global 1.16.8
  • Kubebuilder をインストールします.

  • $ curl -L -o ~/.local/bin/kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
    $ chmod a+x ~/.local/bin/kubebuilder
    


    プロジェクトを初期化し、新しい API を作成する



    1 つのカスタム リソースを使用して新しいホワイト プロジェクトを作成するには、3 つの手順に従う必要があります.

    $ mkdir medium-kubebuilder-pod
    $ cd !$
    $ git init
    $ go mod init simplepod
    $ kubebuilder init --domain callepuzzle.com
    $ git add .
    $ git commit -m "init"
    $ kubebuilder create api --group medium --version v1alpha1 --kind SimplePod
    $ git add .
    $ git commit -m "create api"
    


    これで、プロジェクトに必要なすべての足場が揃いました.

    ここで make install を実行すると、kubebuilder は config/crd/bases の下にベース CRD と他のいくつかのファイルを生成するはずです. make run を実行すると、オペレーターをローカルで起動できるようになります.

    $ make install
    /home/cesar/projects/k8s-operator/medium-kubebuilder-pod/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
    go: creating new go.mod: module tmp
    Downloading sigs.k8s.io/kustomize/kustomize/[email protected]
    go get: added sigs.k8s.io/kustomize/kustomize/v3 v3.8.7
    /home/cesar/projects/k8s-operator/medium-kubebuilder-pod/bin/kustomize build config/crd | kubectl apply -f -
    customresourcedefinition.apiextensions.k8s.io/simplepods.medium.callepuzzle.com created$ kubectl get customresourcedefinitions.apiextensions.k8s.io simplepods.medium.callepuzzle.com 
    NAME                                CREATED AT
    simplepods.medium.callepuzzle.com   2021-10-13T15:47:52Z$ kubectl apply -f config/samples/medium_v1alpha1_simplepod.yaml$ kubectl get simplepods.medium.callepuzzle.com 
    NAME               AGE
    simplepod-sample   9s
    


    わかりました、「simplepod」リソースを作成できますが、何も実行せず、ロジックもありません.

    カスタム リソースを作成する


    api/v1alpha1/simplepod_types.go には、リソースの構造体が定義されています.

    $ git diff api/v1alpha1/simplepod_types.go config/samples/medium_v1alpha1_simplepod.yaml
    diff --git a/api/v1alpha1/simplepod_types.go b/api/v1alpha1/simplepod_types.go
    index f5f3fde..e4f0630 100644
    --- a/api/v1alpha1/simplepod_types.go
    +++ b/api/v1alpha1/simplepod_types.go
    @@ -29,7 +29,7 @@ type SimplePodSpec struct {
            // Important: Run "make" to regenerate code after modifying this file
    
            // Foo is an example field of SimplePod. Edit simplepod_types.go to remove/update
    -       Foo string `json:"foo,omitempty"`
    +       Command string `json:"command,omitempty"`
     }
    
     // SimplePodStatus defines the observed state of SimplePod
    diff --git a/config/samples/medium_v1alpha1_simplepod.yaml b/config/samples/medium_v1alpha1_simplepod.yaml
    index 671c617..dd8cda0 100644
    --- a/config/samples/medium_v1alpha1_simplepod.yaml
    +++ b/config/samples/medium_v1alpha1_simplepod.yaml
    @@ -4,4 +4,4 @@ metadata:
       name: simplepod-sample
     spec:
       # Add fields here
    -  foo: bar
    +  command: ls
    


    リソースの構造を変更するたびに、make install を実行してマニフェストを再生成する必要があります.

    次に、オペレーターのロジックを実装します. Pod オブジェクトを作成し、simplepod オブジェクトで指定されたコマンドを実行します.

    $ git diff controllers/simplepod_controller.go
    diff --git a/controllers/simplepod_controller.go b/controllers/simplepod_controller.go
    index 2f13668..89b63d0 100644
    --- a/controllers/simplepod_controller.go
    +++ b/controllers/simplepod_controller.go
    @@ -18,7 +18,10 @@ package controllers
    
     import (
            "context"
    +       "strings"
    
    +       corev1 "k8s.io/api/core/v1"
    +       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
            "k8s.io/apimachinery/pkg/runtime"
            ctrl "sigs.k8s.io/controller-runtime"
            "sigs.k8s.io/controller-runtime/pkg/client"
    @@ -36,6 +39,7 @@ type SimplePodReconciler struct {
     //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods,verbs=get;list;watch;create;update;patch;delete
     //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/status,verbs=get;update;patch
     //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/finalizers,verbs=update
    +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
    
     // Reconcile is part of the main kubernetes reconciliation loop which aims to
     // move the current state of the cluster closer to the desired state.
    @@ -47,16 +51,62 @@ type SimplePodReconciler struct {
     // For more details, check Reconcile and its Result here:
     // - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
     func (r *SimplePodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    -       _ = log.FromContext(ctx)
    +       log := log.FromContext(ctx)
    
    -       // your logic here
    +       var instance mediumv1alpha1.SimplePod
    +       errGet := r.Get(ctx, req.NamespacedName, &instance)
    +       if errGet != nil {
    +               log.Error(errGet, "Error getting instance")
    +               return ctrl.Result{}, client.IgnoreNotFound(errGet)
    +       }
    +
    +       pod := NewPod(&instance)
    +
    +       _, errCreate := ctrl.CreateOrUpdate(ctx, r.Client, pod, func() error {
    +               return ctrl.SetControllerReference(&instance, pod, r.Scheme)
    +       })
    +
    +       if errCreate != nil {
    +               log.Error(errCreate, "Error creating pod")
    +               return ctrl.Result{}, nil
    +       }
    +
    +       err := r.Status().Update(context.TODO(), &instance)
    +       if err != nil {
    +               return ctrl.Result{}, err
    +       }
    
            return ctrl.Result{}, nil
     }
    
    +func NewPod(pod *mediumv1alpha1.SimplePod) *corev1.Pod {
    +       labels := map[string]string{
    +               "app": pod.Name,
    +       }
    +
    +       return &corev1.Pod{
    +               ObjectMeta: metav1.ObjectMeta{
    +                       Name:      pod.Name,
    +                       Namespace: pod.Namespace,
    +                       Labels:    labels,
    +               },
    +               Spec: corev1.PodSpec{
    +                       Containers: []corev1.Container{
    +                               {
    +                                       Name:    "busybox",
    +                                       Image:   "busybox",
    +                                       Command: strings.Split(pod.Spec.Command, " "),
    +                               },
    +                       },
    +                       RestartPolicy: corev1.RestartPolicyOnFailure,
    +               },
    +       }
    +}
    +
     // SetupWithManager sets up the controller with the Manager.
     func (r *SimplePodReconciler) SetupWithManager(mgr ctrl.Manager) error {
            return ctrl.NewControllerManagedBy(mgr).
                    For(&mediumv1alpha1.SimplePod{}).
    +               Owns(&corev1.Pod{}).
                    Complete(r)
     }
    


    リソースの構造を変更するたびに、make install を実行してマニフェストを再生成する必要があります.

    次に、オペレーターのロジックを実装します. Pod オブジェクトを作成し、simplepod オブジェクトで指定されたコマンドを実行します.

    $ git diff controllers/simplepod_controller.go
    diff --git a/controllers/simplepod_controller.go b/controllers/simplepod_controller.go
    index 2f13668..89b63d0 100644
    --- a/controllers/simplepod_controller.go
    +++ b/controllers/simplepod_controller.go
    @@ -18,7 +18,10 @@ package controllers
    
     import (
            "context"
    +       "strings"
    
    +       corev1 "k8s.io/api/core/v1"
    +       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
            "k8s.io/apimachinery/pkg/runtime"
            ctrl "sigs.k8s.io/controller-runtime"
            "sigs.k8s.io/controller-runtime/pkg/client"
    @@ -36,6 +39,7 @@ type SimplePodReconciler struct {
     //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods,verbs=get;list;watch;create;update;patch;delete
     //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/status,verbs=get;update;patch
     //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/finalizers,verbs=update
    +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
    
     // Reconcile is part of the main kubernetes reconciliation loop which aims to
     // move the current state of the cluster closer to the desired state.
    @@ -47,16 +51,62 @@ type SimplePodReconciler struct {
     // For more details, check Reconcile and its Result here:
     // - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
     func (r *SimplePodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    -       _ = log.FromContext(ctx)
    +       log := log.FromContext(ctx)
    
    -       // your logic here
    +       var instance mediumv1alpha1.SimplePod
    +       errGet := r.Get(ctx, req.NamespacedName, &instance)
    +       if errGet != nil {
    +               log.Error(errGet, "Error getting instance")
    +               return ctrl.Result{}, client.IgnoreNotFound(errGet)
    +       }
    +
    +       pod := NewPod(&instance)
    +
    +       _, errCreate := ctrl.CreateOrUpdate(ctx, r.Client, pod, func() error {
    +               return ctrl.SetControllerReference(&instance, pod, r.Scheme)
    +       })
    +
    +       if errCreate != nil {
    +               log.Error(errCreate, "Error creating pod")
    +               return ctrl.Result{}, nil
    +       }
    +
    +       err := r.Status().Update(context.TODO(), &instance)
    +       if err != nil {
    +               return ctrl.Result{}, err
    +       }
    
            return ctrl.Result{}, nil
     }
    
    +func NewPod(pod *mediumv1alpha1.SimplePod) *corev1.Pod {
    +       labels := map[string]string{
    +               "app": pod.Name,
    +       }
    +
    +       return &corev1.Pod{
    +               ObjectMeta: metav1.ObjectMeta{
    +                       Name:      pod.Name,
    +                       Namespace: pod.Namespace,
    +                       Labels:    labels,
    +               },
    +               Spec: corev1.PodSpec{
    +                       Containers: []corev1.Container{
    +                               {
    +                                       Name:    "busybox",
    +                                       Image:   "busybox",
    +                                       Command: strings.Split(pod.Spec.Command, " "),
    +                               },
    +                       },
    +                       RestartPolicy: corev1.RestartPolicyOnFailure,
    +               },
    +       }
    +}
    +
     // SetupWithManager sets up the controller with the Manager.
     func (r *SimplePodReconciler) SetupWithManager(mgr ctrl.Manager) error {
            return ctrl.NewControllerManagedBy(mgr).
                    For(&mediumv1alpha1.SimplePod{}).
    +               Owns(&corev1.Pod{}).
                    Complete(r)
     }
    


    次に、変更をインストールしてオペレーターを実行します.

    $ go get k8s.io/api/core/[email protected]
    $ make install
    $ kubectl delete -f config/samples/medium_v1alpha1_simplepod.yaml
    $ kubectl apply -f config/samples/medium_v1alpha1_simplepod.yaml
    $ make run
    (in another terminal)
    $ kubectl get pod
    NAME               READY   STATUS      RESTARTS   AGE
    simplepod-sample   0/1     Completed   0          3s
    $ kubectl logs simplepod-sample 
    bin
    dev
    etc
    home
    proc
    root
    sys
    tmp
    usr
    var
    


    最終コード全体: https://github.com/jilgue/medium-kubebuilder-pod