본문 바로가기
1. K8s Core & Architecture/1.1. 컨트롤 플레인 (Control Plane) 심층 분석

컨트롤러 패턴(Controller Pattern) 완벽 이해: Reconcile 루프의 마법

by K8s Architect 2026. 3. 16.

컨트롤러 패턴(Controller Pattern) 완벽 이해: Reconcile 루프의 마법

1. 쿠버네티스의 심장, 컨트롤러 패턴이란?

쿠버네티스를 단순한 컨테이너 실행 도구가 아닌 '자가 치유(Self-healing)' 능력을 갖춘 지능형 오케스트레이션 플랫폼으로 만들어주는 핵심 설계 사상이 바로 '컨트롤러 패턴(Controller Pattern)'입니다. 이 패턴의 근간은 명령형(Imperative)이 아닌 선언적(Declarative) 상태 관리에 있습니다. 시스템에 "A를 하고 B를 해라"라고 절차를 명령하는 대신, "최종적으로 시스템이 C라는 상태가 되기를 원한다"라고 선언(Desired State)하면, 시스템 내부의 컨트롤러가 현재 상태(Current State)를 관찰하여 원하는 상태와 일치하도록 끊임없이 맞추어 나가는 자동화 루프를 의미합니다. 이 끊임없는 관찰과 조정의 과정을 'Reconcile(조정) 루프'라고 부릅니다.

2. Reconcile 루프의 3단계 핵심 메커니즘

모든 쿠버네티스 컨트롤러(ReplicaSet, Deployment, Node Controller 등)는 내부적으로 동일한 3단계의 무한 루프를 돌며 동작합니다.

  • 1단계: 관찰 (Observe)
    컨트롤러는 자신이 관리해야 할 대상 리소스와 그와 연관된 자원들의 현재 상태를 지속적으로 모니터링합니다. 예를 들어 ReplicaSet 컨트롤러는 선언된 ReplicaSet 객체의 상태뿐만 아니라, 해당 셀렉터(Selector)에 매칭되는 실제 파드(Pod)들의 생성, 삭제, 상태 변경 이벤트를 API 서버를 통해 관찰합니다.
  • 2단계: 비교 및 판단 (Diff & Analyze)
    관찰을 통해 파악된 '현재 상태(Current State)'와 사용자가 매니페스트(YAML)를 통해 선언한 '원하는 상태(Desired State)'를 비교합니다. 만약 사용자가 파드 3개를 원했는데 현재 실행 중인 파드가 2개뿐이라면, 1개의 파드가 부족하다는 '차이(Diff)'를 계산해냅니다.
  • 3단계: 조정 (Act / Reconcile)
    계산된 차이를 메우기 위해 실제 시스템에 변경을 가하는 단계입니다. 앞선 예시에서 컨트롤러는 API 서버에 "새로운 파드 1개를 생성하라"는 요청을 보냅니다. 중요한 점은 컨트롤러가 직접 컨테이너를 실행하지 않는다는 것입니다. 컨트롤러는 API 서버의 상태 데이터만을 수정하며, 실제 컨테이너 실행은 해당 워커 노드의 Kubelet이 담당하도록 역할을 철저히 분리합니다.

3. Edge-Driven Triggering과 Level-Driven 원칙의 결합

Reconcile 루프를 구현할 때 직면하는 가장 큰 문제는 효율성과 신뢰성의 트레이드오프입니다. 수만 개의 리소스를 매초마다 전체 조회(Polling)하는 것은 시스템 부하로 인해 불가능합니다. 쿠버네티스는 이를 두 가지 방식의 결합으로 해결했습니다.

  • Edge-Driven (이벤트 기반 트리거): 상태의 변화(Edge)가 발생한 그 순간에만 반응하는 방식입니다. etcd의 Watch API를 활용하여 리소스가 생성, 수정, 삭제될 때 API 서버로부터 이벤트를 푸시(Push) 받습니다. 이를 통해 불필요한 주기적 조회 부하 없이 변화를 즉각적으로 감지합니다.
  • Level-Driven (상태 기반 재동기화): 이벤트 기반 시스템의 치명적인 약점은 네트워크 단절이나 프로세스 재시작으로 인해 중간에 이벤트를 유실하면 영원히 상태 불일치에 빠진다는 것입니다. 쿠버네티스는 이를 보완하기 위해 주기적으로 전체 상태를 다시 읽어와서(Resync) 원하는 상태와 현재 상태를 비교하는 Level-Driven 방식을 병행합니다. 특정 이벤트가 누락되더라도 결국 주기적인 Reconcile을 통해 궁극적인 일관성(Eventual Consistency)을 보장하는 마법이 여기서 완성됩니다.

4. 컨트롤러 구현의 핵심 요소: client-go 아키텍처

쿠버네티스 생태계에서 컨트롤러를 개발할 때 사용하는 표준 라이브러리인 client-go는 Reconcile 루프를 극도로 효율적으로 처리하기 위한 정교한 아키텍처를 제공합니다.

  • Reflector (리플렉터): API 서버에 특정 리소스에 대한 List 및 Watch 요청을 보내는 역할을 합니다. 최초에 전체 데이터를 한 번 가져오고(List), 이후 발생하는 변경 사항(Watch)을 스트리밍받아 내부 대기열(Delta FIFO Queue)에 저장합니다.
  • Informer (인포머): Reflector가 가져온 데이터를 받아 로컬 메모리에 스레드 세이프(Thread-safe)한 캐시(Indexer)를 구축합니다. 컨트롤러가 매번 API 서버에 쿼리를 날리지 않고 이 로컬 캐시를 조회함으로써 시스템의 네트워크 및 백엔드 스토리지 부하를 획기적으로 낮춥니다.
  • Workqueue (작업 대기열): Informer는 리소스 변경 이벤트를 감지하면 해당 리소스의 고유 식별자(Namespace/Name)를 Workqueue에 밀어 넣습니다. 실제 Reconcile 비즈니스 로직을 수행하는 워커 스레드들은 이 큐에서 식별자를 순차적으로 꺼내어 작업을 수행합니다. 작업에 실패할 경우 지수 백오프(Exponential Backoff) 알고리즘을 통해 큐에 다시 넣어 안전하게 재시도(Rate Limiting)를 수행합니다.

5. 멱등성(Idempotency)과 낙관적 동시성 제어

컨트롤러가 수행하는 Reconcile 함수가 반드시 갖추어야 할 가장 중요한 덕목은 '멱등성(Idempotency)'입니다. 멱등성이란 동일한 Reconcile 로직을 1번 실행하든 100번 실행하든, 시스템의 최종 상태는 항상 동일해야 함을 의미합니다. 컨트롤러는 과거에 어떤 작업을 수행했는지 자체적인 상태(State)를 기억하지 않으며, 오직 호출되는 그 순간의 현재 상태만을 보고 판단하여 원하는 상태로 만들기 위한 명령만을 수행해야 합니다.

또한, 여러 컨트롤러가 동시에 동일한 리소스를 변경하려 할 때 발생하는 충돌을 막기 위해 '낙관적 동시성 제어(Optimistic Concurrency Control)'를 사용합니다. 모든 쿠버네티스 리소스는 ResourceVersion이라는 메타데이터 필드를 가집니다. 컨트롤러가 리소스를 수정하여 API 서버에 업데이트를 요청할 때, 자신이 읽었던 시점의 ResourceVersion을 함께 보냅니다. 만약 그 사이 다른 주체에 의해 해당 리소스가 변경되어 API 서버의 최신 ResourceVersion과 일치하지 않는다면 업데이트는 거부(Conflict)됩니다. 컨트롤러는 이 충돌 에러를 감지하면 Workqueue의 로직에 따라 다시 최신 데이터를 읽어와 Reconcile 루프를 처음부터 재시도함으로써 전체 데이터의 정합성을 완벽하게 지켜냅니다.