[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
Overview
이번에는 controller-runtime에 대해 딥다이브 해볼 것이다.
controller-runtime에 중요한 부분인 Manager component에 대해 집중해서 진행해보고자 한다.
만약 이전 글을 읽지 않았다면 이전 글부터 보는 것이 이해에 도움 될 것이다.
Manager란 뭘까?
Package manager는 controller를 생성하는데 필요하며,
client나 cash, schemes와 같은 디펜던시를 제공한다.
또한 Controller는 반드시 Manager.Start로 시작해야한다.
다시 말해서 Manager의 주요 역할은 controller를 구성하고 라이프 사이클을 관리한다.
여기서 컨트롤러 관리에 의미에는 세 가지 측면이 있다.
- Controller의 라이프사이클을 관리하는 것은 컨트롤러의 시작과 중단 등록 등을 포함한다.
- Kubernetes API 서버나 클라이언트, 캐시, 이벤트 로더 등과 같이 controller에서 사용하는 리소스들에 대한 접근을 제공한다.
- leader election이라고 불리는 Controller의 고가용성을 관리한다.
Manager는 인터페이스이다.
// Manager initializes shared dependencies such as Caches and Clients, and provides them to Runnables.
// A Manager is required to create Controllers.
type Manager interface {
cluster.Cluster
Add(Runnable) error
Elected() <-chan struct{}
AddMetricsExtraHandler(path string, handler http.Handler) error
AddHealthzCheck(name string, check healthz.Checker) error
AddReadyzCheck(name string, check healthz.Checker) error
Start(ctx context.Context) error
GetWebhookServer() *webhook.Server
GetLogger() logr.Logger
GetControllerOptions() v1alpha1.ControllerConfigurationSpec
}
이 코드에서 중요하게 볼 부분은 cluster, Add(Runnable)과 Start(ctx context.Context) error 부분이다.
Manager를 어떻게 사용할까?
우리는 첫 번째 글에서 Manager를 NewManager를 통해 초기화하는 것을 진행했었다.
manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{})
그리고 마지막으로 Manager는 등록된 모든 controller를 실행한다.
manager.Start(ctrl.SetupSignalHandler())
그럼 Manager안에서 어떤 일이 일어나는지 살펴보자
NewManager
New function는 cluster라는 공유될 리소스(kubernetes API 서버, 캐시 등 이전글에서 다룸)를 구성하는
다른 컴포넌트를 구성한다.
그다음 New function은 Manager로 작동하는 instance인 controllerManager를 초기화한다.
manager/internal.go에서 controllerManager를 선언한 코드
var _ Runnable = &controllerManager{}
type controllerManager struct {
sync.Mutex
started bool
...
runnables *runnables
// cluster holds a variety of methods to interact with a cluster. Required.
cluster cluster.Cluster
...
}
전체 코드 중에 중요 필드
- runnables : *runnables는 등록된 controller set을 저장한다.
- cluster : cluster는 공유된 메서드들과 상호작용을 제공한다. (client, cash 등)
- leader election 관련 필드
- HA 구성
다음은 runnables에 대해 정확하게 알아보자
// runnables handles all the runnables for a manager by grouping them accordingly to their
// type (webhooks, caches etc.).
type runnables struct {
Webhooks *runnableGroup
Caches *runnableGroup
LeaderElection *runnableGroup
Others *runnableGroup
}
runnables는 네 가지 유형의 runnableGroup을 구성하는 struct이다.
runnables는 controller manager에 등록될 수 있는 여러 유형의 컴포넌트를 그룹화한다.
각 runnableGroup은 같은 유형의 컴포넌트 목록을 구성하고 추가, 삭제 실행 등을 제공한다.
다른 그룹을 사용해 컴포넌트의 실행 순서를 확인하고 다른 유형의 컴포넌트를 개별적으로 관리한다.
- WebHook : 컨트롤러에 정의된 웹훅서버의 그룹
- Caches : cluster가 이 그룹에 추가되며 GetCache() 메서드로 실행 가능한 그룹
- LeaderElection : NeedLeaderElection() 메소드로 실행 가능한 그룹
- Others : Controller를 등록할 때 컨트롤러는 이 그룹에 추가됨
Add function을 이용한 runnables 구성
func (r *runnables) Add(fn Runnable) error {
switch runnable := fn.(type) {
case hasCache:
return r.Caches.Add(fn, func(ctx context.Context) bool {
return runnable.GetCache().WaitForCacheSync(ctx)
})
case *webhook.Server:
return r.Webhooks.Add(fn, nil)
case LeaderElectionRunnable:
if !runnable.NeedLeaderElection() {
return r.Others.Add(fn, nil)
}
return r.LeaderElection.Add(fn, nil)
default:
return r.LeaderElection.Add(fn, nil)
}
}
Add function은 Runnables를 가져와 Runnables의 유형에 따라 해당 그룹에 추가합니다.
hasCache와 LeaderElectionRunnable 인터페이스는 컴포넌트를 각각 cashes 나 LeaderElection 그룹에 추가할지를
결정하는 역할이다,
controllerManager가 직접 다른 Runnable 인터페이스를 구현하기도 한다.
var _ Runnable = &controllerManager{}
Runnable은 아주 간단한 인터페이스이며, Context만을 인수로 갖는 Start function을 가진다.
실행 or 에러 반환
// Runnable allows a component to be started.
// It's very important that Start blocks until
// it's done running.
type Runnable interface {
// Start starts running the component. The component will stop running
// when the context is closed. Start blocks until the context is closed or
// an error occurs.
Start(context.Context) error
}
NewControllerManagedBy
builder 패키지 안에 있는 NewControllerManagedBy 함수는 여기서 확인할 수 있다.
https://pkg.go.dev/sigs.k8s.io/controller-runtime#pkg-variables
// NewControllerManagedBy returns a new controller builder that will be started by the provided Manager.
NewControllerManagedBy = builder.ControllerManagedBy
다음에 builder를 조사한 글에서 다시 다뤄 보도록 하고 빌더가 어떻게 구성되는지를 보자
// Builder builds a Controller.
type Builder struct {
forInput ForInput
ownsInput []OwnsInput
watchesInput []WatchesInput
mgr manager.Manager
globalPredicates []predicate.Predicate
ctrl controller.Controller
ctrlOptions controller.Options
name string
}
ContrllerManagedBy는 mgr이라는 특정한 manager 필드로 구성됨
// ControllerManagedBy returns a new controller builder that will be started by the provided Manager.
func ControllerManagedBy(m manager.Manager) *Builder {
return &Builder{mgr: m}
}
Complete function에서는 Build function을 호출하고 내부적으로 doController와 doWatch를 호출함
doController에서는 관리자의 공유 리소스를 사용하여 컨트롤러를 초기화하고 doWatch는 컨트롤러를 시작한다.
NewControllerManagedBy는 각 컨트롤러가 Manager에 컨트롤러를 등록할 때 사용함
Start()
마지막으로 우리는 manager를 start 한다. Manager 인터페이스는 Start(context.Context) error 구문을
무조건 가지고 있어야 한다.
type Manager interface {
...
Start(ctx context.Context) error
..
}
controllerManager는 구현된 Manager 인터페이스이다.
Start() function은 주로 아래의 과정을 따른다.
- runnables에 cluster를 추가 (cluster 또한 runnables.Cache에 추가됨)
- Start runnables.Webhooks, runnables.Caches, runnables.Others, and runnables.LeaderElection.
// (1) Add the cluster runnable.
if err := cm.add(cm.cluster); err != nil {
...
// (2) First start any webhook servers
if err := cm.runnables.Webhooks.Start(cm.internalCtx); err != nil {
...
// (3) Start and wait for caches.
if err := cm.runnables.Caches.Start(cm.internalCtx); err != nil {
...
// (4) Start the non-leaderelection Runnables after the cache has synced.
if err := cm.runnables.Others.Start(cm.internalCtx); err != nil {
// (5) Start the leader election and all required runnables.
if err := cm.startLeaderElection(ctx); err != nil {
...
if err := cm.startLeaderElectionRunnables(); err != nil {
...
Start()는 manager에 등록된 모든 runnables를 시작한다.
Summary
- Manager는 controller의 등록, 시작, 중단을 구성하고 수명주기를 관리함
- controllerManager는 Manager 인터페이스의 구현체이며, cluster와 runnables를 가진다.
- cluster는 Kubernetes API server, cache, schema과 같은 공유 리소스를 가지며 Manager.New function으로 초기화된다.
- runnables는 Webhooks, Caches, LeaderElection, and Others 네 가지 유형으로 구성됨
- Controller에 Manager를 바인딩하려면 builder가 필요하다.
- NewControllerManagedBy는 Manager를 builder에 구성하는 데 사용되며, 빌드 시 Manager에게 새로운 controller가 등록됨 (new controller는 runnables.Others에 등록됨)
Reperence
* 본 글은 Masato Naka의 medium 게시글을 참조 & 번역함