본문 바로가기
golang

[golang] kubernetes Operator 만들기 Kubebuilder Controller-runtime 1 - Controller

by weq155 2023. 11. 14.
반응형
반응형

 

[golang] kubernetes Operator 만들기 Controller-runtime 1 - Controller

[golang] kubernetes Operator 만들기 Controller-runtime 2 - Overview

[golang] kubernetes Operator 만들기 Controller-runtime 3 - Manager

[golang] kubernetes Operator 만들기 Controller-runtime 4 - Builder

Introduction

kubernetes operator 개발을 시작하려면 kubebuilder나 operator-sdk에 대해 알아야 한다.

둘 다 kubernetes operator 개발에 도움을 주는 도구들이다.

go를 이용해서 kubernetes에 접근할 수 있는 라이브러리는 여러 가지가 있다.

 

가장 기초적인 client-go

오늘 다룰 Controller-runtime

Kubernetes Operator를 만드는데 가장 보편적으로 쓰이는 kubebuilderoperator-sdk

 

다 다른 라이브러리인 것 같지만 다 연관성이 있다.

나중에 설명하겠지만 kubebuilder에는 controller-runtime이 내장되어 있고,

operator-sdk에는 kubebuilder의 패키지가 쓰인다.

 

그래서 오늘은 기본이 되는 controller-runtime에 대해 알아보자

 

 

Getting Started

또한 controller-runtime이라는 go 문서를 보면 example controller를 시작할 수 있다!

이 예시는 컨트롤러가 reconciliation logic을 실행해서

ReplicaSet과 pod로 생성, 수정, 삭제 이벤트를 모니터링한다.

비교적 간단한 코드로 operator를 만들 수 있다.

package main

import (
 "context"
 "fmt"
 "os"

 appsv1 "k8s.io/api/apps/v1"
 corev1 "k8s.io/api/core/v1"

 ctrl "sigs.k8s.io/controller-runtime"
 "sigs.k8s.io/controller-runtime/pkg/client"

 // since we invoke tests with -ginkgo.junit-report we need to import ginkgo.
 _ "github.com/onsi/ginkgo/v2"
)

func main() {
 var log = ctrl.Log.WithName("builder-examples")

 manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{})
 if err != nil {
  log.Error(err, "could not create manager")
  os.Exit(1)
 }

 err = ctrl.
  NewControllerManagedBy(manager). // Create the Controller
  For(&appsv1.ReplicaSet{}).       // ReplicaSet is the Application API
  Owns(&corev1.Pod{}).             // ReplicaSet owns Pods created by it
  Complete(&ReplicaSetReconciler{Client: manager.GetClient()})
 if err != nil {
  log.Error(err, "could not create controller")
  os.Exit(1)
 }

 if err := manager.Start(ctrl.SetupSignalHandler()); err != nil {
  log.Error(err, "could not start manager")
  os.Exit(1)
 }
}

// ReplicaSetReconciler is a simple Controller example implementation.
type ReplicaSetReconciler struct {
 client.Client
}

// Implement the business logic:
// This function will be called when there is a change to a ReplicaSet or a Pod with an OwnerReference
// to a ReplicaSet.
//
// * Read the ReplicaSet
// * Read the Pods
// * Set a Label on the ReplicaSet with the Pod count.
func (a *ReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

 rs := &appsv1.ReplicaSet{}
 err := a.Get(ctx, req.NamespacedName, rs)
 if err != nil {
  return ctrl.Result{}, err
 }

 pods := &corev1.PodList{}
 err = a.List(ctx, pods, client.InNamespace(req.Namespace), client.MatchingLabels(rs.Spec.Template.Labels))
 if err != nil {
  return ctrl.Result{}, err
 }

 rs.Labels["pod-count"] = fmt.Sprintf("%v", len(pods.Items))
 err = a.Update(context.TODO(), rs)
 if err != nil {
  return ctrl.Result{}, err
 }

 return ctrl.Result{}, nil
}
Believe it or not, this short piece of code is all you need for an example controller.

main function
Let's take a look at the main function.

func main() {
 var log = ctrl.Log.WithName("builder-examples")

 manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{})
 if err != nil {
  log.Error(err, "could not create manager")
  os.Exit(1)
 }

 err = ctrl.
  NewControllerManagedBy(manager). // Create the Controller
  For(&appsv1.ReplicaSet{}).       // ReplicaSet is the Application API
  Owns(&corev1.Pod{}).             // ReplicaSet owns Pods created by it
  Complete(&ReplicaSetReconciler{Client: manager.GetClient()})
 if err != nil {
  log.Error(err, "could not create controller")
  os.Exit(1)
 }

 if err := manager.Start(ctrl.SetupSignalHandler()); err != nil {
  log.Error(err, "could not start manager")
  os.Exit(1)
 }
}

 

 

main function

func main() {
 var log = ctrl.Log.WithName("builder-examples")

 manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{})
 if err != nil {
  log.Error(err, "could not create manager")
  os.Exit(1)
 }

 err = ctrl.
  NewControllerManagedBy(manager). // Create the Controller
  For(&appsv1.ReplicaSet{}).       // ReplicaSet is the Application API
  Owns(&corev1.Pod{}).             // ReplicaSet owns Pods created by it
  Complete(&ReplicaSetReconciler{Client: manager.GetClient()})
 if err != nil {
  log.Error(err, "could not create controller")
  os.Exit(1)
 }

 if err := manager.Start(ctrl.SetupSignalHandler()); err != nil {
  log.Error(err, "could not start manager")
  os.Exit(1)
 }
}

 

main function에서는 세 가지 스텝이 있다.

manager와 NewManager 초기화

  1. 컨트롤러를 생성하고 NewControllerManagedBy로 매니저를 등록한다. For와 Own을 통해 트리거로 사용할 리소스를 생성한다. 마지막으로 ReplicaSetReconciler를 사용해서 완료한다.
  2. manager를 start

여기서 manager가 뭔지?, controller와 manager의 관계는 뭔지? 와 같은 여러 궁금증이 생길 것이다.

또한 For, Own에 대해서도 궁금할 것이다.

controller-runtime을 사용하게 된다면 이것들을 배우게 된다.

 

ReplicaSetReconciler

Reconciler는 말 그대로 reconciliation logic(조정 로직?)을 구현하는 것이다

type ReplicaSetReconciler struct {
 client.Client
}

 

 

ReplicaSetReconciler는 client.Client를 임베드하는 controller-runtime의 컴포넌트이다.

여기서는 목표하는 Kubernetes의 API 서버의 object나 Pod, ReplicaSet 가져오고 업데이트하는 용도로 사용된다.

 

Reconcile function은 operator에서 매우 중요한 기능이다

func (a *ReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

 rs := &appsv1.ReplicaSet{}
 err := a.Get(ctx, req.NamespacedName, rs)
 if err != nil {
  return ctrl.Result{}, err
 }

 pods := &corev1.PodList{}
 err = a.List(ctx, pods, client.InNamespace(req.Namespace), client.MatchingLabels(rs.Spec.Template.Labels))
 if err != nil {
  return ctrl.Result{}, err
 }

 rs.Labels["pod-count"] = fmt.Sprintf("%v", len(pods.Items))
 err = a.Update(context.TODO(), rs)
 if err != nil {
  return ctrl.Result{}, err
 }

 return ctrl.Result{}, nil
}

 

코드를 보면 매우 단순하게 구성된 걸 알 수 있다.

  1. 원하는 네임스페이스에 rs(replicaSet)을 가져오는 요청을 보냄
  2. 라벨이 일치하는 모든 파드를 찾음
  3. 찾은 파드의 개수를 pod-count 변수에 넣음
  4. rs를 업데이트

Reconcile function은 controller 구현에 필요한 코어 로직이다.

controller-runtime을 kubebuilder나 operator-sdk 중 하나를 이용해

custom controller를 개발할 수 있다.

 

 

Summary

controller-runtime을 사용해서 짧은 코드로 kubernetes operator를 만들어 보았다.

그 뒤에는 개발자가 reconciler에만 집중할 수 있게 도움을 주는 많은 컴포넌트들이 있음

컴포넌트들은 사용하기 편하지만, 컴포넌트들의 역할을 이해하는 것은 중요하다고 생각한다.

 

 

 

 

 

 

 

 

 

 

 

 

Reperence

* 본 글은  Masato Naka의 medium 게시글을 참조 & 번역함

https://nakamasato.medium.com/kubernetes-operator-series-1-controller-runtime-aa50d1d93c5c

 

Kubernetes Operator series 1 — controller-runtime

This is the first episode of a series about controller-runtime, which is a tool to make it easy to develop a custom controller.

nakamasato.medium.com

 

반응형