[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를 만드는데 가장 보편적으로 쓰이는 kubebuilder와 operator-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 초기화
- 컨트롤러를 생성하고 NewControllerManagedBy로 매니저를 등록한다. For와 Own을 통해 트리거로 사용할 리소스를 생성한다. 마지막으로 ReplicaSetReconciler를 사용해서 완료한다.
- 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
}
코드를 보면 매우 단순하게 구성된 걸 알 수 있다.
- 원하는 네임스페이스에 rs(replicaSet)을 가져오는 요청을 보냄
- 라벨이 일치하는 모든 파드를 찾음
- 찾은 파드의 개수를 pod-count 변수에 넣음
- 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
'golang' 카테고리의 다른 글
[golang] kubernetes Operator 만들기 Kubebuilder Controller-runtime 3 - Manager (1) | 2023.11.14 |
---|---|
[golang] kubernetes Operator 만들기 Kubebuilder Controller-runtime 2 - Overview (1) | 2023.11.14 |
[golang] go kubernetes client-go를 이용한 쿠버네티스 관리 (0) | 2023.11.14 |
[golang] Golang pointer, routine 포인터, 고루틴 (2) | 2023.11.13 |
[golang] Golang 기초 (2) | 2023.11.13 |