[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
[golang] kubernetes Operator 만들기 Controller-runtime 5 - Reconciler
[golang] kubernetes Operator 만들기 Controller-runtime 6 - Controller
Overview
이전에 두 게시글에서 우리는 controller-runtime의 Manager와 builder 컴포넌트에 대해 알아보았다.
이번에는 Reconciler 컴포넌트에서 알아보자
Reconciler란?
Reconciler는 controller-runtime에서 캡슐화된 Reconciliation 로직을 가지고 있는 중요한 컴포넌트이다.
개발자는 실제 Reconciler로 동작하는 프로세스를 구현해야한다.
하지만 이전 글에서 알 수 있듯이 Reconciler만으로는 O
perator를 동작할 수 없다.
Reconciler는 Controller의 한 부분으로 동작하게 된다.
Controller는 특정한 오브젝트에 대해 Rconciler를 호출 할 것인지 결정한다.
Builder 컴포넌트를 통해 Reconciler는 Controller와 결합되며 관계를 갖게 된다.
아래의 예시에서 Controller는 Kubernetes Object의 지정된 리소스의 변경을 감지한다.
이벤트가 발생했을 때, 해당 이벤트를 queue에 넣고 Reconciler 함수를 호출한다.
Reconciler 인터페이스는 Reconcile이라는 하나의 메소드를 호출한다.
type Reconciler interface {
// Reconcile performs a full reconciliation for the object referred to by the Request.
// The Controller will requeue the Request to be processed again if an error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
Reconcile(context.Context, Request) (Result, error)
}
Reconciler는 직관적인 인터페이스로 Reconcile에서 구현된 메소드가 Reconciler의 타입이 된다.
다음으로는 Request와 Result를 정의하는 방법을 확인해보자
Request struct 정의
type Result struct {
// Requeue tells the Controller to requeue the reconcile key. Defaults to false.
Requeue bool
// RequeueAfter if greater than 0, tells the Controller to requeue the reconcile key after the Duration.
// Implies that Requeue is true, there is no need to set Requeue to true at the same time as RequeueAfter.
RequeueAfter time.Duration
}
제공된 코멘트를 바탕으로 Reconciler의 대상 객체는 namespace의 name인걸 알 수 있다.
Result struct 정의
type Result struct {
// Requeue tells the Controller to requeue the reconcile key. Defaults to false.
Requeue bool
// RequeueAfter if greater than 0, tells the Controller to requeue the reconcile key after the Duration.
// Implies that Requeue is true, there is no need to set Requeue to true at the same time as RequeueAfter.
RequeueAfter time.Duration
}
Result struct는 2개의 필드를 포함한다.
Requeue 필드는 Controller가 추가 프로세스를 위해 queue에 작업을 다시 넣을 것인지를 결정한다.
기본값은 false로 설정되어 있다.
RequeueAfter 필드는 0보다 큰 값으로 설정된 경우 reconcile key를 지정된 Duration 이후에 Controller에 집어넣는다.
RequeueAfter를 지정하려면 Requeue가 true로 지정되어야 함을 유의하자
Reconciler를 구현하는 방법
Reconciler를 구현하는 방법은 2가지가 있다.
- With your own struct (e.g. reconciler) with Reconcile function
- With reconcile.Func
각 방법에 대해 자세하게 알아보자
With your own struct (e.g. reconciler) with Reconcile function
Own struct와 Reconcile function을 사용해서 Reconciler를 구성하는 방법은 아래의 절차를 따른다.
- reconciler에 제공할 struct를 정의한다. struct는 필요에따라 추가 필드나 디펜던시를 가질 수 있다.
- Reconcile function에서 조정이 필요한 객체의 이름과 네임스페이스가 포함된 객체와 컨텍스트에 엑세스 할 수 있다.
// ReplicaSetReconciler is a simple Controller example implementation.
type ReplicaSetReconciler struct {
client.Client
}
func (r *ReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
rs := &appsv1.ReplicaSet{}
err := r.Get(ctx, req.NamespacedName, rs)
if err != nil {
return ctrl.Result{}, err
}
...
return ctrl.Result{}, nil
}
이 예시에서는 ReplicaSetReconciler struct는 ReplicaSet 객체에 대한 Reconcile function을 수행하기 위해 구현되었다.
Get 메소드는 제공된 Namespace Name을 바탕으로 ReplicaSet 개체를 검색하는데 사용된다.
With reconcile.Func
다른 방법으로 Reconciler를 구현하는 방법은 reconcile package에서 제공하는 reconile.Func를 사용하는 것이다. 이 방법은 구현 프로세스를 단순화 하는 방법이다.
아래의 예시를 참고하자
reconcile.Func(func(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
// Implement your reconciliation logic here
// You can read and write objects, perform desired actions, and return the result
return reconcile.Result{}, nil
})
reconcile.Func 정의
// Func is a function that implements the reconcile interface.
type Func func(context.Context, Request) (Result, error)
var _ Reconciler = Func(nil)
// Reconcile implements Reconciler.
func (r Func) Reconcile(ctx context.Context, o Request) (Result, error) { return r(ctx, o) }
reconcile.Func은 context.Context의 타입의 ctx, reconcile.Request 타입의 req 두가지 파라미터를 가진다. function 내에서 제공된 파라미터를 사용해서 로직을 구현할 수 있다.
reconcile.Func은 구현된 Reconciler 인터페이스이다.
이 방법을 사용하면 custom 로직을 reconcile.Func에 직접 전달할 수 있다.
어떤 방법을 사용하는 것이 좋은가?
Reconcile function과 custom struct를 사용하는 것과 reconcile.Func 중에 선택하는 것은
구현할 Reconcile 로직의 복잡성과 필요한 추가 디펜던시에 따라 달라진다.
만약 Cilent를 이용해 Kubernetes API server에 접근이 요구되는 로직이라면
아래의 예시 처럼 custom struct를 정의하는 것이 일반적인 구성이다.
type ReplicaSetReconciler struct {
client.Client
}
custom struct를 사용하면 로직에 추가 필드나 메소드를 사용할 수 있다.
이 방법은 유연성을 제공하고 필요한 디펜던시를 캡슐화해서 struct에 제공한다.
반면에 추가 디펜던시나 custom 필드 없이 간단한 로직이라면 reconcile.Func은 좋은 옵션이다.
직접 정의한 로직을 간단하게 구현하는데에 사용하면 좋을 것 같다.
요약하자면 추가적인 디펜던시나 유연성 요구되는 customization 레벨에 따라 접근 방법을 결정하면 될 것이다.
Reconciler는 어떻게 사용되는가?
앞선 글에서 봤듯이 Reconciler는 Builder를 통해 Controller와 결합되어 생긴
새로운 Controller는 제공된 Reconciler를 호출한다.
Reconciler가 내부에서 어떻게 사용되는지 확인하기 위해 Controller에 대해 확인할 필요가 있다.
Reconciler 구성팁
1. Reconciler는 어떤 액션이 트리거 되는지 알 수 없다.따라서 로직은 트리거된 개체 액션에 의존해선 안된다. 대신에 Reconcile function 내에서 API 서버로 부터 개체의 현재의 상태에서 필요한 정보를 모은다.
Reconcile function은 개체의 생성, 수정, 삭제와 같은 특정 액션이 트리거되는지 알 수 없다.
2. Returning errors to retry reconciliation에러를 반환하는 것은 같은 요청을 다시 불러와 재 시도하게 한다.
Reconcile function을 구현할 때, 프로세스가 진행되는 동안 에러가 발생한다면 에러를 반환한다.
에러를 반환하는 것은 같은 요청을 다시 불러와 재 시도하게 한다.
func (r *MyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
...
err := someFunc()
if err != nil {
return Result{}, err // Return error to trigger a retry
}
}
Result struct로 requeue 동작을 컨트롤 하는 방법
- Reconcile이 정상 작동하고 재시작이 필요 없는 경우의 return
- ctrl.Result{}, nil
- 에러로 작동이 실패하고 재시작이 필요한 경우의 return
- ctrl.Result{}, err
- 에러가 아닌 다른 이유로 재시작이 필요한 경우
- ctrl.Result{Requeue: true}, nil
- 다음 작업을 특정 시간 후에 진행되게 구성 할 경우에는 Result struct 안에 RequeueAfter를 아래와 같은방식으로 정의
- ctrl.Result{RequeueAfter: 5 * time.Second}, nil
Reperence
* 본 글은 Masato Naka의 medium 게시글을 참조 & 번역함