Go with kubebuilder에서kubernetes 컨트롤러 작성

본고에서 우리는 kubebuilder을 사용하여 간단한kubernetes 컨트롤러를 실현할 것이다

kubebuilder 설치


설치 지침은 kubebuilder installation docs에서 확인할 수 있습니다.

kustomize 설치


kubebuilder는 내부적으로kustomize에 의존하기 때문에 instructions here에서 설치해야 합니다
이제 저희가 컨트롤러를 구축할 준비가 다 됐어요.

프로젝트 시작


나는 이미 빈 고 프로젝트를 초기화했다
go mod init cnat
다음은 컨트롤러 프로젝트를 초기화합니다
kubebuilder init --domain ishankhare.dev
현재 우리는 kubebuilder가 우리의 프로젝트에 필요한 모든 비계를 설치하도록 요구할 것이다
kubebuilder create api --group cnat --version v1alpha1 --kind At
Kubebuilder는 ResourceController 디렉터리를 만드는지 물어볼 것입니다. 우리는 각각 y을 만들 수 있습니다. 왜냐하면 우리는 이 디렉터리를 만들고 싶기 때문입니다.
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing scaffold for you to edit...

비계를 시험적으로 설치하다.


만약 우리가 지금 make install을 실행한다면,kubebuilder는 CRDs에서 config/crd/bases과 다른 파일을 생성할 것입니다.
현재 make run을 실행하면 로컬에서 이 조작부호를 시작할 수 있습니다.이것은 로컬 테스트에 매우 도움이 될 뿐만 아니라, 우리는 조작 코드의 업무 논리를 실현하고 디버깅할 것이다.실시간 로그는 우리로 하여금 우리의 코드 라이브러리 변경이 조작원의 상황을 어떻게 반영하는지 이해하게 할 것이다.

상태 하위 리소스 사용


파일 api/v1alpha1/at_types.goAt 구조 위에 다음과 같은 주석을 추가합니다.
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// At is the Schema for the ats API
type At struct {
.
.
.
이것은 우리로 하여금 specstatus 필드 사이에서 특권 분리를 사용할 수 있게 할 것이다.

배경.


우리가 이곳에서 무엇을 세울 것인지에 대한 배경 지식들우리의 컨트롤러는 기본적으로'Cloud Native At'(약칭 cnat)-Unix at 명령의 클라우드 본체 버전이다.Dell은 다음과 같은 두 가지 주요 항목을 포함하는 CRD 목록을 제공합니다.
  • 명령 실행
  • 명령 실행 시간
  • 우리의 컨트롤러는 기본적으로 적당한 시간을 기다리고 관찰한 후에 Pod을 생성하고 이 Pod에서 주어진 명령을 실행합니다.이 모든 내용은 StatusCRD으로 계속 업데이트되며, kubectl을 통해 확인할 수 있습니다.
    시작해보도록 하겠습니다.

    CRD 아웃라인


    이 섹션에서는 CRD에 대해 살펴보겠습니다.상술한 두 가지를 감안하여 우리는 기본적으로 그것을 다음과 같이 표시할 수 있다.
    apiVersion: cnat.ishankhare.dev/v1alpha1
    kind: At
    metadata:
      name: at-sample
    spec:
      schedule: "2020-11-16T10:12:00Z"
      command: "echo hello world!"
    
    이러한 정보는 우리 컨트롤러로 하여금 '언제' 와 '무엇을 실행할 것' 을 결정하게 하기에 충분하다.

    CRD 정의


    이제 우리는 CRD에 대한 우리의 기대를 알고 그것을 실현합시다.파일 api/v1alpha1/at_types.go으로 이동하여 다음 필드를 코드에 추가합니다.
    const (
        PhasePending = "PENDING"
        PhaseRunning = "RUNNING"
        PhaseDone    = "DONE"
    )
    
    또한 기존 AtSpecAtStatus의 구조를 다음과 같이 편집했습니다.
    type AtSpec struct {
      Schedule string `json:"schedule,omitempty"`
      Command  string `json:"command,omitempty"`
    }
    
    type AtStatus struct {
      Phase string `json:"phase,omitempty"`
    }
    
    저희가 뛰어다니면서 저장할게요.
    $ make manifests
    go: creating new go.mod: module tmp
    go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.2.5
    /Users/ishankhare/godev/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
    
    $ make install    # this will install the generated CRD into k8s
    
    우리는 지금 CustomResourceDefinition에서 우리를 위해 생성된 완전한 config/crd/bases/cnat.ishankhare.dev_ats.yaml을 볼 수 있을 것이다.CRD을 처음 접한 사람들에게는 쿠베르네트스가 kind: At의 의미를 확인하는 데 도움을 줄 것이다.Deployments, Pods 등 내장된 자원 유형과 달리 우리는 CustomResourceDefinition을 통해kubernetes에 사용자 정의 유형을 정의해야 한다.At 리소스를 만들어 보겠습니다.
    cat >sample-at.yaml<<EOF
    apiVersion: cnat.ishankhare.dev/v1alpha1
    kind: At
    metadata:
      name: at-sample
    spec:
      schedule: "2020-11-16T10:12:00Z"
      command: "echo hello world!"
    EOF
    
    이전에 표시된 CRD에 대해 현재 kubectl apply -f을 실행하는 경우
    kubectl apply -f sample-at.yaml
    kubectl get at
    NAME        AGE
    at-sample   124m
    
    그래서 현재kubernetes에서 사용자 정의 형식을 식별했지만, 어떻게 처리해야 할지 모르겠습니다.컨트롤러의 논리적 부분은 여전히 부족하지만, 우리는 그것을 둘러싸고 논리를 쓰기 시작할 준비가 되어 있다.

    컨트롤러 논리 구현


    이제 우리의 조작부호를 위해 논리를 실현하는 흥미로운 부분을 살펴보자.이것은 기본적으로 kubernetes에서 우리가 생성한 CRD를 어떻게 처리하는지 알려줄 것입니다.쿠베르네트를 마술로 만드는 것 같다고 말하는 사람은 드물다.
    대부분의 비계는 kubebuilder가 main.gocontrollers/에서 만들어졌습니다.controllers/at_controller.go 파일에 우리는 함수 Reconcile이 있다.이것이 조화 순환의 주체라고 가정하자.
    작은 리셋기 - 컨트롤러는 기본적으로 상기 대상에서 주기적인 순환을 실행하고 이러한 대상의 어떠한 변화도 관찰한다.변화가 발생하면 순환체를 촉발하고 관련 논리를 집행한다.이 함수 Reconcile은 바로 논리 부분으로 우리의 모든 컨트롤러 논리의 입구점이다.
    이를 중심으로 기본 구조를 추가하겠습니다.
    func (r *AtReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
        reqLogger := r.Log.WithValues("at", req.NamespacedName)
        reqLogger.Info("=== Reconciling At")
    
        instance := &cnatv1alpha1.At{}
        err := r.Get(context.TODO(), req.NamespacedName, instance)
        if err != nil {
            if errors.IsNotFound(err) {
                // object not found, could have been deleted after
                // reconcile request, hence don't requeue
                return ctrl.Result{}, nil
            }
    
            // error reading the object, requeue the request
            return ctrl.Result{}, err
        }
    
        // if no phase set, default to Pending
        if instance.Status.Phase == "" {
            instance.Status.Phase = cnatv1alpha1.PhasePending
        }
    
        // state transition PENDING -> RUNNING -> DONE
    
    
        return ctrl.Result{}, nil
    }
    
    여기에서 우리는 기본적으로 기록기와 At 대상의 빈 실례를 만들었습니다. 이것은 사용자가 기존 At 대상의kubernetes를 조회하고 상태를 읽거나 쓸 수 있도록 합니다. 이 함수는 대장 논리의 결과와 오류를 되돌려야 합니다.ctrl.Result 대상은 즉시 Requeue(bool) 또는 RequeueAftertime.Duration을 대조할 수 있다. 마찬가지로 이것들은 모두 선택할 수 있다.
    이제 중요한 부분, 상태도를 살펴보자.다음 그림은 새로운 At을 생성할 때의 기본 상태를 보여줍니다.

    이제 함수에서 이 논리를 실현하기 시작합시다.
    ...
    // state transition PENDING -> RUNNING -> DONE
    switch instance.Status.Phase {
        case cnatv1alpha1.PhasePending:
            reqLogger.Info("Phase: PENDING")
    
            diff, err := schedule.TimeUntilSchedule(instance.Spec.Schedule)
            if err != nil {
                reqLogger.Error(err, "Schedule parsing failure")
    
                return ctrl.Result{}, err
            }
    
            reqLogger.Info("Schedule parsing done", "Result", fmt.Sprintf("%v", diff))
    
            if diff > 0 {
                // not yet time to execute, wait until scheduled time
                return ctrl.Result{RequeueAfter: diff * time.Second}, nil
            }
    
            reqLogger.Info("It's time!", "Ready to execute", instance.Spec.Command)
            // change state
            instance.Status.Phase = cnatv1alpha1.PhaseRunning
    
    schedule.TimeUntilSchedule 함수의 실현은 다음과 같이 매우 간단하다.
    package schedule
    
    import "time"
    
    func TimeUntilSchedule(schedule string) (time.Duration, error) {
        now := time.Now().UTC()
        layout := "2006-01-02T15:04:05Z"
        scheduledTime, err := time.Parse(layout, schedule)
        if err != nil {
            return time.Duration(0), err
        }
    
        return scheduledTime.Sub(now), nil
    }
    
    case 주체 중 다음 switchRUNNING 단계다.이것은 가장 복잡한 것이다. 그것을 더욱 잘 설명하기 위해서 나는 어디에나 주석을 추가했다.
    case cnatv1alpha1.PhaseRunning:
            reqLogger.Info("Phase: RUNNING")
    
            pod := spawn.NewPodForCR(instance)
            err := ctrl.SetControllerReference(instance, pod, r.Scheme)
            if err != nil {
                // requeue with error
                return ctrl.Result{}, err
            }
    
            query := &corev1.Pod{}
            // try to see if the pod already exists
            err = r.Get(context.TODO(), req.NamespacedName, query)
            if err != nil && errors.IsNotFound(err) {
                // does not exist, create a pod
                err = r.Create(context.TODO(), pod)
                if err != nil {
                    return ctrl.Result{}, err
                }
    
                // Successfully created a Pod
                reqLogger.Info("Pod Created successfully", "name", pod.Name)
                return ctrl.Result{}, nil
            } else if err != nil {
                // requeue with err
                reqLogger.Error(err, "cannot create pod")
                return ctrl.Result{}, err
            } else if query.Status.Phase == corev1.PodFailed ||
                query.Status.Phase == corev1.PodSucceeded {
                // pod already finished or errored out`
                reqLogger.Info("Container terminated", "reason", query.Status.Reason,
                    "message", query.Status.Message)
                instance.Status.Phase = cnatv1alpha1.PhaseDone
            } else {
                // don't requeue, it will happen automatically when
                // pod status changes
                return ctrl.Result{}, nil
            }
    
    우리는 ctrl.SetControllerReference을 설정했는데, 기본적으로kubernetes가 실행될 때 만들어진 Pod은 이 At의 실례가 가지고 있음을 알려 준다.컨트롤러가 만든 자원을 볼 때 편리할 것입니다.
    우리는 여기서 또 다른 외부 함수 spawn.NewPodForCR을 사용했는데 주로 우리의 Pod 맞춤형 자원 규범을 위해 새로운 At을 생성하는 것을 책임진다
    package spawn
    
    import (
        cnatv1alpha1 "cnat/api/v1alpha1"
        "strings"
    
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    )
    
    func NewPodForCR(cr *cnatv1alpha1.At) *corev1.Pod {
        labels := map[string]string{
            "app": cr.Name,
        }
    
        return &corev1.Pod{
            ObjectMeta: metav1.ObjectMeta{
                Name:      cr.Name,
                Namespace: cr.Namespace,
                Labels:    labels,
            },
            Spec: corev1.PodSpec{
                Containers: []corev1.Container{
                    {
                        Name:    "busybox",
                        Image:   "busybox",
                        Command: strings.Split(cr.Spec.Command, " "),
                    },
                },
                RestartPolicy: corev1.RestartPolicyOnFailure,
            },
        }
    }
    
    코드의 마지막 처리 DONE 위상과 defaultswitch 사례.스위치 외부에서 status 서브 리소스를 업데이트하는 작업도 자주 수행됩니다.
    case cnatv1alpha1.PhaseDone:
            reqLogger.Info("Phase: DONE")
            // reconcile without requeuing
            return ctrl.Result{}, nil
        default:
            reqLogger.Info("NOP")
            return ctrl.Result{}, nil
        }
    
        // update status
        err = r.Status().Update(context.TODO(), instance)
        if err != nil {
            return ctrl.Result{}, err
        }
    
        return ctrl.Result{}, nil
    
    마지막으로 우리는 SetControllerReference을 이용하여 다음과 같은 내용을 같은 파일의 SetupWithManager 함수에 추가했다.
    err := ctrl.NewControllerManagedBy(mgr).
            For(&cnatv1alpha1.At{}).
            Owns(&corev1.Pod{}).
            Complete(r)
    
        if err != nil {
            return err
        }
    
        return nil
    
    Owns(&corev1.Pod{})을 지정하는 부분은 컨트롤러 관리자에게 이 컨트롤러가 만든 POD도 변경 사항을 감시해야 한다고 알려준다.
    전체 최종 코드 구현을 보려면thisgithub repo-ishankhare07/kubebuilder-controller을 보십시오.
    현재, 우리는 컨트롤러의 실현을 완성했다. 우리는 make run을 사용하여 현재kube 상하문에 있는 그룹에서 그것을 실행하고 테스트할 수 있다
    go: creating new go.mod: module tmp
    go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.2.5
    /Users/ishankhare/godev/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
    go fmt ./...
    go vet ./...
    /Users/ishankhare/godev/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
    go run ./main.go
    2020-11-18T05:05:45.531+0530    INFO    controller-runtime.metrics      metrics server is starting to listen    {"addr": ":8080"}
    2020-11-18T05:05:45.531+0530    INFO    setup   starting manager
    2020-11-18T05:05:45.531+0530    INFO    controller-runtime.manager      starting metrics server {"path": "/metrics"}
    2020-11-18T05:05:45.531+0530    INFO    controller-runtime.controller   Starting EventSource    {"controller": "at", "source": "kind source: /, Kind="}
    2020-11-18T05:05:45.732+0530    INFO    controller-runtime.controller   Starting EventSource    {"controller": "at", "source": "kind source: /, Kind="}
    2020-11-18T05:05:46.232+0530    INFO    controller-runtime.controller   Starting Controller     {"controller": "at"}
    2020-11-18T05:05:46.232+0530    INFO    controller-runtime.controller   Starting workers        {"controller": "at", "worker count": 1}
    2020-11-18T05:05:46.232+0530    INFO    controllers.At  === Reconciling At      {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.232+0530    INFO    controllers.At  Phase: PENDING  {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.232+0530    INFO    controllers.At  Schedule parsing done   {"at": "cnat/at-sample", "Result": "-14053h23m46.232658s"}
    2020-11-18T05:05:46.232+0530    INFO    controllers.At  It's time!      {"at": "cnat/at-sample", "Ready to execute": "echo YAY!"}
    2020-11-18T05:05:46.436+0530    DEBUG   controller-runtime.controller   Successfully Reconciled {"controller": "at", "request": "cnat/at-sample"}
    2020-11-18T05:05:46.443+0530    INFO    controllers.At  === Reconciling At      {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.443+0530    INFO    controllers.At  Phase: RUNNING  {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.718+0530    INFO    controllers.At  Pod Created successfully        {"at": "cnat/at-sample", "name": "at-sample"}
    2020-11-18T05:05:46.718+0530    DEBUG   controller-runtime.controller   Successfully Reconciled {"controller": "at", "request": "cnat/at-sample"}
    2020-11-18T05:05:46.722+0530    INFO    controllers.At  === Reconciling At      {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.722+0530    INFO    controllers.At  Phase: RUNNING  {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.722+0530    DEBUG   controller-runtime.controller   Successfully Reconciled {"controller": "at", "request": "cnat/at-sample"}
    2020-11-18T05:05:46.778+0530    INFO    controllers.At  === Reconciling At      {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.778+0530    INFO    controllers.At  Phase: RUNNING  {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.778+0530    DEBUG   controller-runtime.controller   Successfully Reconciled {"controller": "at", "request": "cnat/at-sample"}
    2020-11-18T05:05:46.846+0530    INFO    controllers.At  === Reconciling At      {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.847+0530    INFO    controllers.At  Phase: RUNNING  {"at": "cnat/at-sample"}
    2020-11-18T05:05:46.847+0530    DEBUG   controller-runtime.controller   Successfully Reconciled {"controller": "at", "request": "cnat/at-sample"}
    2020-11-18T05:05:49.831+0530    INFO    controllers.At  === Reconciling At      {"at": "cnat/at-sample"}
    2020-11-18T05:05:49.831+0530    INFO    controllers.At  Phase: RUNNING  {"at": "cnat/at-sample"}
    2020-11-18T05:05:49.831+0530    INFO    controllers.At  Container terminated    {"at": "cnat/at-sample", "reason": "", "message": ""}
    2020-11-18T05:05:50.054+0530    DEBUG   controller-runtime.controller   Successfully Reconciled {"controller": "at", "request": "cnat/at-sample"}
    2020-11-18T05:05:50.056+0530    INFO    controllers.At  === Reconciling At      {"at": "cnat/at-sample"}
    2020-11-18T05:05:50.056+0530    INFO    controllers.At  Phase: DONE     {"at": "cnat/at-sample"}
    2020-11-18T05:05:50.056+0530    DEBUG   controller-runtime.controller   Successfully Reconciled {"controller": "at", "request": "cnat/at-sample"}
    
    실행할 수 있는 출력 명령을 보려는 경우
    $ kubectl logs -f at-sample
    hello world!
    
    이 글은 먼저 나의 개인 블로그 ishankhare.dev에 발표되었다

    좋은 웹페이지 즐겨찾기