Go with kubebuilder에서kubernetes 컨트롤러 작성
17941 단어 operatorskubernetesgocontrollers
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는 Resource
과 Controller
디렉터리를 만드는지 물어볼 것입니다. 우리는 각각 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.go
에 At
구조 위에 다음과 같은 주석을 추가합니다.// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// At is the Schema for the ats API
type At struct {
.
.
.
이것은 우리로 하여금 spec
과 status
필드 사이에서 특권 분리를 사용할 수 있게 할 것이다.배경.
우리가 이곳에서 무엇을 세울 것인지에 대한 배경 지식들우리의 컨트롤러는 기본적으로'Cloud Native At'(약칭 cnat)-Unix
at
명령의 클라우드 본체 버전이다.Dell은 다음과 같은 두 가지 주요 항목을 포함하는 CRD
목록을 제공합니다.Pod
을 생성하고 이 Pod
에서 주어진 명령을 실행합니다.이 모든 내용은 Status
의 CRD
으로 계속 업데이트되며, 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"
)
또한 기존 AtSpec
과 AtStatus
의 구조를 다음과 같이 편집했습니다.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.go
과 controllers/
에서 만들어졌습니다.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) 또는 RequeueAfter
과 time.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
주체 중 다음 switch
은 RUNNING
단계다.이것은 가장 복잡한 것이다. 그것을 더욱 잘 설명하기 위해서 나는 어디에나 주석을 추가했다.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
위상과 default
의 switch
사례.스위치 외부에서 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에 발표되었다
Reference
이 문제에 관하여(Go with kubebuilder에서kubernetes 컨트롤러 작성), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ishankhare07/writing-a-simple-kubernetes-controller-in-go-with-kubebuilder-ib8텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)