[쿠버네티스 오퍼레이터] 2. 오퍼레이터 구현

안녕하세요! 펜타시큐리티 클라우드개발팀입니다.

지난 편에는 쿠버네티스 오퍼레이터와 커스텀 리소스에 대해 전반적인 내용을 소개해드렸는데요, 오늘은 오퍼레이터 프레임워크 (Operator SDK)를 이용한 쿠버네티스 오퍼레이터 구현에 대해 소개해드리려고 합니다!

  1. 오퍼레이터와 커스텀 리소스
  2. 오퍼레이터 구현
  3. 오퍼레이터 개발 모범 사례

Operator-SDK

Operator-SDK는 이름 그대로 오퍼레이터 개발을 편하게 만들어주는 SDK입니다. 리소스를 감시(watch)하는 구현, 리소스 변경 이벤트 발생 시 이를 저장하고 컨트롤 루프를 실행하는 구현 등을 내장하고 있어, 자체적으로 오퍼레이터 동작, 안정성, 성능 등을 위해 개발해야될 부분을 줄여줍니다.

하이레벨 API와 추상화를 제공해 로직을 직관적으로 작성할 수 있게 도우며, 기본적인 보일러플레이트 코드 생성(스케폴딩) 도구를 제공해 개발자는 핵심 운영 로직 작성에만 집중할 수 있습니다.

Memcached 오퍼레이터

Operator-SDK 공식 Go 기반 오퍼레이터 튜토리얼인 Memcached 오퍼레이터 구현을 살펴보면서 오퍼레이터가 어떻게 구현이 되는지 하나씩 설명해드리려고 합니다.

아래 Go 오퍼레이터 튜토리얼과 Memcached 오퍼레이터 코드를 참고하여 내용을 작성하였습니다.

오퍼레이터 프로젝트 살펴보기

먼저, operator-sdk init 명령어를 통해 프로젝트를 생성할 수 있습니다.

해당 명령어 실행 시, --domain pentasecurity.com 옵션은 이후 생성할 리소스들의 API 그룹이 <group name>.pentasecurity.com으로 설정되게 하고, --repo github.com/~~ 옵션은 소스코드 리포지토리 주소를 설정하기 위해 사용이 됩니다. 설정된 리포지토리 주소는 GOPATH 밖에서 생성된 프로젝트 내 파일들에서 정확한 모듈 경로를 찾을 때 사용이 됩니다.

이후, operator-sdk create명령어를 통해 커스텀 리소스와 컨트롤러를 생성할 수 있습니다.

아래에서 Memcached 커스텀 리소스와 컨트롤러를 생성할 때에는, API 그룹을 cache.pentasecurity.com , API 버전을 v1alpha1 , 리소스 종류를 Memcached이 되도록 설정하였습니다. 또한 --resource, --controller 옵션을 통해 각각 커스텀 리소스와 컨트롤러를 생성하도록 설정하였습니다.

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

그럼 api/v1alpha1/memcached_types.go 에 커스텀 리소스 관련 보일러플레이트 파일이, controllers/memcached_controller.go 에 컨트롤러 관련 보일러플레이트 파일이 생성됩니다.

*_types.go 파일이 변경되면 아래 명령어를 실행해, 변경 사항이 api/v1alpha1/zz_generated.deepcopy.goruntime.Object 관련 코드에 반영되도록 해야합니다.

$ make generate

또한 아래 명령어도 실행하여, 변경 사항이 config/crd/bases 경로에 위치한 CRD (Custom Resource Definition) 파일에 반영되도록 해아합니다.

$ make manifests

이제 Memcached 오퍼레이터 예제 코드들을 하나씩 살펴보겠습니다.

오퍼레이터 매니저 (Manager)

오퍼레이터 매니저는 매니저는 컨트롤러나 웹훅 (Webhook)을 실행시키고 관리합니다.

main.gomain 함수에서 Manager를 초기화하고 실행하는 코드를 확인할수 있습니다. 초기화 시 옵션으로 리소스 Kinds와 Go 타입 간 매핑을 제공하는 Scheme를 설정해주는 것을 확인할 수 있습니다.

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "86f835c3.example.com",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// speeds up voluntary leader transitions as the new leader don't have to wait
// LeaseDuration time first.
//
// In the default scaffold provided, the program ends immediately after
// the manager stops, so would be fine to enable this option. However,
// if you are doing or is intended to do any operation such as perform cleanups
// after the manager stops then its usage might be unsafe.
// LeaderElectionReleaseOnCancel: true,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}

그 아래를 보면 Memcached 컨트롤러를 설정해주는 부분이 있습니다. 쿠버네티스 API 통신에 사용되는 Client와 위에서 설정한 Scheme이 Memcached 컨트롤러에게 전달됩니다.

if err = (&controllers.MemcachedReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Memcached")
os.Exit(1)
}

그 외 매니저와 관련해 자세한 내용은 Go 튜토리얼 문서Kubebuilder 문서를 한번 읽어보시기 바랍니다.

Memcached 커스텀 리소스

Memcached 커스텀 리소스 관련 코드는 api/v1alpha1/memcached_types.go에 작성합니다.

해당 파일에서 Memcached 커스텀 리소스가 Memcached 구조체로 정의되어 있는 것을 확인할 수 있습니다.

필드로 metadata, spec, status가 보이네요. 각 필드는 커스텀 리소스 YAML 매니페스트에서 JSON 태그(json:"~~~")에 포함된 필드명과 필드 속성을 가지게 됩니다.

또 눈여겨볼 점은 +kubebuilder:subresource:status 마커 (marker)입니다. 이 마커를 추가하면 Status 서브 리소스를 활성화하게 됩니다.

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// +operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1,memcached-deployment}}
// Memcached is the Schema for the memcacheds API
type Memcached struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MemcachedSpec `json:"spec,omitempty"`
Status MemcachedStatus `json:"status,omitempty"`
}

여기서 서브 리소스가 무엇인지 간략하게 짚고 넘어가겠습니다. 커스텀 리소스에서 지원되는 서브 리소스 종류로는 /status/scale 이 있습니다. 서브 리소스는 커스텀 리소스에 대한 일부 정보를 가집니다. Status 서브 리소스는 커스텀 리소스의 상태 정보를, Scale 서브 리소스는 커스텀 리소스의 스케일링 관련 정보(Replicas 등)를 나타내게 됩니다. 서브 리소스는 리소스에 대해 더 세밀한 인가 (권한 설정)을 가능하게 하고, 상황에 따라 더 편리하고 효율적으로 리소스를 관리할 수 있게 합니다.

Memcached 구조체 안에 있는 Spec과 Status 필드의 구조체 타입도 살펴보겠습니다.

Spec에는 Size라는 Memcached 인스턴스 개수를 선언하는 필드가 존재하고, Status에는 실행되고 있는 Memcached 인스턴스 파드들의 이름을 저장하는 Nodes라는 필드가 존재하는 것을 확인할 수 있습니다.

// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Size defines the number of Memcached instances
// +operator-sdk:csv:customresourcedefinitions:type=spec
Size int32 `json:"size,omitempty"`

// Foo is an example field of Memcached. Edit memcached_types.go to remove/update
Foo string `json:"foo,omitempty"`
}

// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Nodes store the name of the pods which are running Memcached instances
// +operator-sdk:csv:customresourcedefinitions:type=status
Nodes []string `json:"nodes,omitempty"`
}

Memcached 컨트롤러

Memcached 컨트롤러 관련 코드는 controllers/memcached_controller.go에 작성합니다.

먼저 MemcachedReconciler라는 구조체가 선언되어 있는 것을 확인하실 수 있습니다. 해당 구조체 내 Client를 통해 쿠버네티스 API 서버 내에 있는 리소스 오브젝트를 읽거나 수정할 수 있습니다.

// MemcachedReconciler reconciles a Memcached object
type MemcachedReconciler struct {
client.Client
Scheme *runtime.Scheme
}

그리고 Reconcile이라는 함수에 Memcached 리소스를 관리하는 컨트롤 루프에 대한 코드가 작성됩니다.

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := ctrllog.FromContext(ctx)
...
return ctrl.Result{}, nil
}

Reconcile 함수에 있는 내용을 하나씩 살펴보겠습니다.

먼저, Memcached 오퍼레이터 내 Watcher에 의해 감지된 리소스 생성/변경/삭제 이벤트가 연관된 Memcached 오브젝트에 대한 정보로 변환되어Reconcile 함수의 req 전달인자로 전달됩니다. 리소스 Watcher에 대해서는 뒤쪽 SetupWithManager 함수를 소개할 때 다시 한번 말씀드리겠습니다.

아래 코드에서는 req에 포함된 Memcached 오브젝트의 네임스페이스와 이름 정보를 이용해 쿠버네티스 API 서버로부터 해당 리소스 정보를 Get 하고 있습니다.

리소스 삭제 시에도 Reconcile 함수가 호출되는데, 이 경우 리소스가 이미 삭제되어 쿠버네티스 API 서버에서 조회가 불가능할 것이므로 IsNotFound 에러가 반환될 것입니다. 이에 대한 처리도 해주고 있습니다.

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := ctrllog.FromContext(ctx)
// Fetch the Memcached instance
memcached := &cachev1alpha1.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
}

Memcached 오퍼레이터에서는 Memcached 파드 배포 및 관리를 디플로이먼트를 이용해 수행하고 있습니다.

디플로이먼트를 Get해서 가져오고, 만약 없으면 디플로이먼트를 생성하도록 하고 있습니다.

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

어떤 정보를 가진 디플로이먼트가 생성되는지 deploymentForMemcached 함수를 통해 한번 살펴보겠습니다.

  • 디플로이먼트 이름과 네임스페이스가 Memcached 오브젝트 이름과 네임스페이스와 같게 설정됩니다.
  • 디플로이먼트 Spec의 Replicas 값이 Memcached 오브젝트 Spec의 Size 값과 같게 설정됩니다. 생성된 Memcached 파드 개수가 Memcached 오브젝트에 선언된 상태와 같도록 하기 위함입니다.
  • 디플로이먼트와 디플로이먼트 하위 파드들 모두 labelsForMemcached 함수에서 반환된 레이블을 갖도록 설정됩니다. 나중에 파드 리스트 정보를 가져올 때 해당 레이블을 사용합니다.
  • SetControllerReference 함수를 통해 디플로이먼트의 Owner가 Memcached 오브젝트로 설정됩니다. 쿠버네티스 Owners와 Dependents에 대해서는 공식 문서를 한번 읽어보시기를 추천드립니다.
// 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{
// Ensure restrictive standard for the Pod.
// More info: <https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted>
SecurityContext: &corev1.PodSecurityContext{
RunAsNonRoot: &[]bool{true}[0],
// Please ensure that you can use SeccompProfile and do NOT use
// this field if your project must work on old Kubernetes
// versions < 1.19 or on vendors versions which
// do NOT support this field by default (i.e. Openshift < 4.11)
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
},
Containers: []corev1.Container{{
Image: "memcached:1.4.36-alpine",
Name: "memcached",
// Ensure restrictive context for the container
// More info: <https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted>
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: &[]bool{true}[0],
AllowPrivilegeEscalation: &[]bool{false}[0],
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
},
},
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}
}

이제 현재 상태가 Memcached 오브젝트에 선언된 상태 (Spec)가 되도록 조정하기 위한 작업을 수행합니다. 현재는 Memcached 오브젝트 Spec 내에 Size만 존재하니 이 부분에 대한 조정만 수행해주면 됩니다.

// 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
}
// Ask to requeue after 1 minute in order to give enough time for the
// pods be created on the cluster side and the operand be able
// to do the next update step accurately.
return ctrl.Result{RequeueAfter: time.Minute}, nil
}

조정 작업이 끝이 나면, 이제 조정 결과를 반영해 Memcached 오브젝트의 Status를 업데이트합니다. 아까 살펴봤던 디플로이먼트 하위 파드에 설정된 레이블을 이용해서 파드 리스트를 가져옵니다. 그리고 Status 내 PodNames 현재 값이 최신 값이 아닌 경우 업데이트합니다.

위에서 Size를 조정할 때 r.Update()를 썼던 것과 달리, PodNames을 업데이트할 때 r.Status().Update()를 사용하는데, 앞쪽 서브 리소스 부분에서 말씀드렸던 것처럼 Memcached 리소스의 Status 서브 리소스를 통해 Status를 업데이트하기 때문입니다.

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

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

작업 중 에러가 발생했거나, 잘 처리되었거나, 후속 처리가 필요할 수 있습니다. 각 상황에 맞는 적절한 결과 값을 반환해 처리를 종료하거나 후속처리하도록 할 수 있습니다.

Reconcile 함수에서 return이 사용되는 패턴은 크게 4가지입니다.

  • 에러가 발생할 경우: return ctrl.Result{}, err
  • 에러가 없고, 후속 처리를 위해 Requeue가 필요할 경우: return ctrl.Result{Requeue: true}, nil
  • 에러가 없고, 모든 처리가 마무리되었을 경우: return ctrl.Result{}, nil
  • 후속 처리를 위해 일정 시간 이후 Requeue가 필요할 경우: return ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())}, nil

컨트롤러 파일에 Reconcile 함수 외에 SetupWithManager라는 함수도 존재하는데, 이 함수 내 NewControllerManagedBy 함수를 통해 컨트롤러에 대해 여러 설정을 해줄 수 있습니다.

  • For(): Watcher에 의해 감시될 주요 리소스를 지정합니다. 여기서는 Memcached 커스텀 리소스로 설정되었습니다. 생성, 변경, 삭제 이벤트가 발생한 Memcached 오브젝트 정보가 Reconcile 함수의 Request 전달인자로 전달됩니다. For()는 한번만 사용될 수 있습니다.
  • Owns(): Watcher에 의해 감시될 하위 리소스를 지정합니다. 여기서는 디플로이먼트가 설정되었습니다. 디플로이먼트 오브젝트에 대해 생성, 변경, 삭제 이벤트가 발생할 경우, 해당 오브젝트의 Owner인 Memcached 오브젝트 정보가 Reconcile 함수의 Request 전달인자로 전달됩니다. Owns()는 여러 번 사용될 수 있습니다.
  • Watches(): ForOwns와 달리 EnqueueRequestsFromMapFunc와 같은 핸들러를 인자로 전달해 임의의 감시 로직을 설정해줄 수 있습니다. Watches()는 여러 번 사용될 수 있습니다.
  • 그 외 동시 처리를 위한 Reconcile 개수 설정, Predicates를 이용한 이벤트 필터링 등 나머지 설정은 튜토리얼 공식 문서를 참조하시기 바랍니다.
// 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)
}

현재의 Memcached 오퍼레이터 예제 코드에는 없는 내용이지만, EnqueueRequestsFromMapFunc함수에 대해 좀 더 자세히 말씀드리기 위해 하나의 가상 사용 사례를 보여드리겠습니다.

만약 Memcached 파드가 변경될 경우, Memcached Status 내 PodNames을 업데이트해주기 위해 Memcached 컨트롤러가 호출되어야한다면 어떻게 해야할까요?

이 경우, Memcached 컨트롤러의 주요 리소스는 이미 Memcached 오브젝트로 설정되어 있어 For()를 사용할 수 없고, Memcached 파드의 Owner는 Memcached 오브젝트가 아니기 때문에 Owns()를 사용할 수 없습니다.

이 때 Watches()EnqueueRequestsFromMapFunc 와 함께 사용할 수 있습니다.

// 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{}).
Watches(&source.Kind{Type: &corev1.Pod{}}, handler.EnqueueRequestsFromMapFunc(r.PodReconciler)).
Complete(r)
}

func (r *MemcachedReconciler) PodReconciler(obj client.Object) (requests []ctrl.Request) {
memcachedName := obj.Labels["memcached_cr"]
memcachedNamespace := obj.GetNamespace()

requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: memcachedNamespace,
Name: memcachedName,
},
})

return requests
}

RBAC 설정

Reconcile 함수 위쪽에 +kubebuilder:rbac으로 시작하는 RBAC 마커를 확인할 수 있습니다. 컨트롤러가 동작 시 사용하는 쿠버네티스 API에 대해 필요한 권한을 RBAC 마커를 통해 설정해줄 수 있습니다.

현재 Reconcile 함수 내에서는 Memcached, 디플로이먼트, 파드 총 3가지 리소스에 대한 API를 사용하므로, 각각에 대해 적절한 권한을 설정해주고 있습니다.

//+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;watch

// 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.12.1/pkg/reconcile>
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

RBAC 마커를 설정한 이후에는 아래 명령어를 실행해 권한 설정 내용이 config/rbac/role.yaml에 반영되도록 해아합니다.

make manifests

마침

추가적으로 Operator-SDK 구조나 오퍼레이터 구현에 대해 정말 잘 정리된 글들을 소개해드리니, 시간되실 때 한번 읽어보시면 좋을 것 같습니다.

참고로 Kubebuilder는 Operator-SDK의 기반이 되는 도구로, Operator-SDK 문서에 없는 내용이 Kubebuilder 문서에 잘 소개되어 있는 경우가 많습니다. 따라서, Operator-SDK를 사용하실 때, Kubebuilder 문서를 참고하시면 큰 도움이 됩니다.

다음 3편에서는 저희가 오퍼레이터를 개발할 때 맞닿뜨렸던 문제와 이를 해결해나갔던 경험을 바탕으로 정리한 오퍼레이터 개발 모범 사례에 대해 말씀드리려고 합니다.

오퍼레이터 시리즈 바로가기

  1. 오퍼레이터와 커스텀 리소스
  2. 오퍼레이터 구현
  3. 오퍼레이터 개발 모범 사례

참고 자료

[1]https://sdk.operatorframework.io/

[2]https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/

[3]https://github.com/operator-framework/operator-sdk/tree/latest/testdata/go/v3\

[4]https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#status-subresource

[5]https://book.kubebuilder.io/cronjob-tutorial/empty-main.html

--

--

펜타시큐리티 보안기술연구소
PentaSecurity Labs

펜타시큐리티 보안 기술 연구소 사람들의 생활과 기술 연구 및 각종 활동에 관한 이야기를 담은 블로그