[번역] Learning Concurrent Reconciling

케찹 저장소·2022년 4월 23일
1

원문: https://openkruise.io/blog/learning-concurrent-reconciling/

컨트롤러 관련해서 글을 찾아서 읽어보다가 공유하면 좋을 것 같아 적어봅니다. 저의 개인적인 해석이나 정리, 생략이 포함되어 있습니다.

이 글의 내용에 대한 권리는 저에게 있지 않습니다. 문제가 된다면 삭제하도록 하겠습니다

Concurrent reconciling은 원래 단어의 맛을 해석하기가 어려워서... 부득이하게 원문을 사용하겠습니다.
goroutine의 concurrency + k8s의 reconcile ? ㅎㅎ..


3줄 요약:
1. 쿠버네티스 컨트롤러는 concurrent reconciling를 통해 큐에 쌓이는 요청을 빠르게 소모할 수 있다.
2. 동시성 기능으로 인해 발생할 수 있는 side-effect는 client-goworkqueue 에 의해 방지된다.
3. 하지만, 이 방지 로직으로 인해 concurrent reconciling을 사용하지 않을 경우 성능 저하가 발생할 수도 있다. (concurrent reconciling을 쓰면 쉽게 해결된다.)


쿠버네티스의 컨트롤러(controller) 컨셉은 쿠버네티스가 성공할 수 있었던 가장 큰 요인 중 하나입니다. 컨트롤러는 쿠버네티스 API가 시스템을 원하는 상태에 다다를 수 있도록 도와주는 코어 메커니즘입니다. CRD, 컨트롤러, 오퍼레이터를 통해서 다른 시스템이 쿠버네티스에 녹아드는 것이 쉬워졌습니다.

많은 개발자들이 자신이 원하는 컨트롤러를 만들기 위해 컨트롤러 런타임 라이브러리와 그 라이브러리를 사용하는 컨트롤러 개발 툴인 KubeBuilder 를 사용하고 있습니다. Kruise 프로젝트는 Kubebuilder를 통해 컨트롤러를 개발하고 있습니다. 이 포스트에서, Kruise 컨트롤러를 개발하면서 느낀점을 공유하려고 합니다. (특히 concurrent reconciling 부분에 있어서요!)

몇몇 독자분들은 이미 컨트롤러가 concurrent reconciling(동시적인 조정)을 지원하는것을 알고 있을겁니다. 컨트롤러를 생성할 때 사용할 수 있는 옵션을 확인해보세요. (source)

type Options struct {
    // MaxConcurrentReconciles is the maximum number of concurrent Reconciles which can be run. Defaults to 1.
    MaxConcurrentReconciles int

    // Reconciler reconciles an object
    Reconciler reconcile.Reconciler
}

Concurrent reconciling은 컨트롤러가 바라보는 오브젝트의 상태가 수시로 바뀌어서 너무 많은 조정 요청(reconcile request)이 큐에 쌓일 경우에 유용합니다. Concurrent reconciling을 사용하면, 말 그대로 여러개의 reconcile 루프을 통해 reconcile 큐를 더 빠르게 소진할 수 있습니다. 이 방법은 따로 코드에 대단한 변화를 주지 않고도 퍼포먼스 향상에 상당한 기여를 할 수 있지만, 동시에 동시성(Concurrency)으로 인해 발생하는 문제들에 대해 고민하게 됩니다.

  • 예를 들면, 두 개의 reconcile loop이 한번에 하나의 오브젝트를 조정할수 있지 않을까요?

위의 예시에 대한 정답은 아니오 입니다. 이 “마법"은 client-go 라이브러리에 구현된 workqueue 를 통해 발현됩니다. (이 workqueue는 컨트롤러의 reconcile queue로 사용됩니다!)

이 workqueue의 알고리즘은 다음과 같습니다.

우선 workqueue의 구성부터 알아볼까요?

workqueue는

  • 한 개의 큐
  • 두 개의 집합 (dirty-set, processing-set)
    으로 이루어져 있습니다.

그림 (a)

그림 (a)는 4개의 조정 요청을 처리하기 위한 초기 상태를 나타냅니다.

  • 일단 조정 요청이 도착하면, 조정 대상인 오브젝트가dirty set에 존재하는지 확인합니다. 만약 없다면, dirty set에 오브젝트를 넣고, 이미 존재하면 요청 자체를 drop 합니다.
  • 위의 step을 통과하고, processing set 에 조정 대상인 오브젝트가 존재하지 않을 때 queue 에 오브젝트를 집어넣습니다.

그림 (b)

그림 (b)는 3개의 요청이 순차적으로 추가된 케이스를 나타냅니다.

조정 루프가 요청을 처리할 수 있는 상태가 되면, 조정 루프는 queue 의 front에서 대상 오브젝트를 가져옵니다.
이 때,

  • 조정 대상 오브젝트는 processing set 에 들어가고,
  • dirty set 에서 제거됩니다.

그림 (c)

그림 (c)에서는 이미 processing set 에 존재하는 오브젝트에 대한 요청이 들어오는 케이스입니다.

이 경우에

  • 이 경우에는 dirty set 에는 추가되지만
  • queue 에는 추가되지 않습니다.

그림 (d)

그림 (d)는 reconcile loop에 의해 조정이 끝난 케이스 입니다.

reconcile이 종료되면,

  • 오브젝트는 processing set 에서 제거됩니다.
  • 이때, 만약에 dirty set 에 해당 오브젝트가 존재하면 queue 에 추가됩니다.

위의 알고리즘은 다음과 같은 결과를 가져옵니다
1. 동일한 오브젝트에 대한 concurrent reconciling을 회피한다.
2. 오브젝트 처리 순서는 워킹 스레드가 하나일지라도 도착 순서에 따라 달라질 수 있다. (도착한 순서대로 처리하지 않는다는 뜻입니다.)

예를 들면,

  • 오직 하나의 조정 스레드가 존재하고,
  • 동일한 오브젝트 A 에 대한 두 개의 조정 요청이 도착했다고 가정하면

  • 첫 요청에 의해 오브젝트 A가 dirty set 에 들어가고, queue에 추가됩니다.

  • 만약 reconcile 과정이 오래걸리고, 그 동안 다른 오브젝트에 대한 요청이 많이 들어왔다면 queue는 다른 오브젝트로 가득차게 됩니다.

  • 첫 번째 A 오브젝트에 대한 처리가 끝나고, dirty set에 추가되었던 두번째 A 조정 요청이 queue 의 마지막으로 추가됩니다.

    이는 곧 다른 오브젝트에 대한 요청들을 모두 처리하기 전에는 두 번째 A 요청을 처리할 수 없다는 뜻입니다.

이를 해결할 수 있는 방법은 Concurrent Reconcile을 사용하는 것입니다. 따라서 MaxConcurrentReonciles 값은 항상 1보다 큰 값으로 덮어 써야 합니다.

  1. 마지막으로 가장 중요한것은, 조정 요청은 해당 오브젝트가 이미 dirty set에 있을 경우 드랍된다는 것입니다. 이는 컨트롤러가 모든 오브젝트의 스테이트 변화를 대응하지는 않는다는 것을 의미합니다.

쿠버네티스 컨트롤러는 level-triggered 이지, edge-triggered 가 아닙니다. 즉, state를 reconcile하지, event를 reconcile 하지 않는다는 것입니다.

profile
Ketchup_ninja

0개의 댓글