6.2.1 선언적 API와 상태 관리
쿠버네티스는 시스템의 상태를 관리하는 데 있어 독특하고 강력한 접근 방식을 사용합니다. 그것이 바로 ‘선언적’ 방식입니다. 이는 우리가 원하는 최종 목표 상태를 쿠버네티스에 알려주면, 쿠버네티스가 현재 상태를 파악하고 그 목표 상태에 도달하기 위해 필요한 조치들을 스스로 수행한다는 의미입니다. 마치 우리가 내비게이션에 목적지를 입력하면, 현재 위치에서 목적지까지의 경로를 계산하고 안내해주는 것과 비슷하다고 할 수 있습니다.
쿠버네티스(Kubernetes)는 클라우드 네이티브 환경에서 애플리케이션을 안정적으로 운영하기 위해 선언적 API(Declarative API)와 상태 관리(State Management)라는 핵심 개념을 채택하고 있습니다. 이 두 가지 개념은 쿠버네티스의 자동화와 자가 복구(self-healing) 능력을 가능하게 하며, 클러스터의 일관성과 안정성을 유지하는 데 중요한 역할을 합니다.
6.2.1.1 Desired State vs Current State (의도된 상태 vs 현재 상태)
쿠버네티스의 강력함을 제대로 이해하고 활용하기 위한 첫 번째 관문이자 가장 핵심적인 개념 중 하나가 바로 의도된 상태(Desired State) 와 현재 상태(Current State) 입니다. 이 두 가지 상태를 명확히 구분하고 그 관계를 이해하는 것은, 마치 자동차 운전법을 배우기 전에 액셀러레이터와 브레이크의 역할을 이해하는 것과 같이 기본적이면서도 중요합니다.
의도된 상태 (Desired State): 우리가 꿈꾸는 시스템의 청사진
의도된 상태(Desired State) 란, 이름에서 알 수 있듯이 우리가 시스템이나 애플리케이션이 궁극적으로 어떤 모습이기를 바라는지를 명확하게 기술한 일종의 ‘목표 명세서’ 또는 ‘청사진’이라고 할 수 있습니다. 이것은 단순히 “애플리케이션을 실행시켜줘”라는 추상적인 명령이 아닙니다. 훨씬 더 구체적이고 상세한 요구사항을 담고 있죠.
예를 들어, 우리가 새로운 웹 서비스를 배포한다고 가정해 봅시다. 이때 의도된 상태는 다음과 같이 상세하게 정의될 수 있습니다:
- “내 ‘awesome-app’이라는 이름의 웹 애플리케이션은 항상 3개의 동일한 컨테이너 인스턴스(복제본)로 실행되어야 한다.” (고가용성 및 부하 분산을 위해)
- “각 컨테이너 인스턴스는 ‘my-registry/awesome-app:v1.2’라는 특정 버전의 컨테이너 이미지를 사용해야 한다.” (정확한 소프트웨어 버전 명시)
- “이 애플리케이션은 내부적으로 8080 포트를 사용하며, 외부 사용자는 80번 포트를 통해 이 서비스에 접근할 수 있어야 한다.” (네트워크 연결 정의)
- “각 컨테이너는 최소 0.5 CPU 코어와 256MiB의 메모리를 요청하며, 최대 1 CPU 코어와 512MiB의 메모리까지 사용할 수 있도록 제한된다.” (리소스 요구량 및 한계 설정)
- “애플리케이션의 상태를 주기적으로 확인하기 위해 ‘/healthz’ 경로로 HTTP GET 요청을 보내고, 응답 코드가 200이면 정상으로 간주한다.” (헬스 체크 정의)
이처럼 의도된 상태는 애플리케이션의 복제본 수, 사용할 컨테이너 이미지 버전, 필요한 컴퓨팅 자원, 네트워크 설정, 스토리지 볼륨, 헬스 체크 방법 등 시스템이 갖춰야 할 모든 세부 사항을 포함합니다.
그렇다면 이 ‘의도된 상태’는 어떻게 쿠버네티스에게 전달될까요? 바로 매니페스트(Manifest) 라고 불리는 설정 파일을 통해서입니다. 이 매니페스트 파일은 대부분의 경우 YAML(YAML Ain’t Markup Language) 형식으로 작성되며, 때로는 JSON 형식으로도 사용됩니다. YAML은 사람이 읽고 쓰기 쉬운 데이터 직렬화 형식으로, 복잡한 설정을 구조적으로 표현하는 데 매우 효과적입니다. 사용자는 이 YAML 파일에 위에서 언급한 것과 같은 의도된 상태를 상세히 기술한 후, kubectl apply -f <manifest_file.yaml> 과 같은 명령어를 통해 쿠버네티스 API 서버(API Server) 에 이 정보를 전달합니다.
API 서버는 이렇게 전달받은 의도된 상태 정보를 클러스터의 ‘뇌’이자 ‘신경망의 중심’이라고 할 수 있는 분산 키-값 저장소, etcd 에 안전하게 저장합니다. etcd는 클러스터의 모든 설정 데이터와 상태 정보를 일관성 있게 유지하는 매우 중요한 역할을 합니다. 일단 etcd에 저장된 의도된 상태는 쿠버네티스 시스템 전체가 달성해야 할 단일 진실 공급원(Single Source of Truth) 으로 간주됩니다. 모든 쿠버네티스 컴포넌트들은 이 etcd에 저장된 의도된 상태를 기준으로 동작하게 됩니다.
여기서 중요한 점은, 의도된 상태는 ‘어떻게(how)’ 그 상태에 도달할지를 지시하는 것이 아니라, ‘무엇(what)’을 원하는지를 선언한다는 것입니다. 이것이 바로 쿠버네티스가 선언적(Declarative) 시스템이라고 불리는 이유입니다.
현재 상태 (Current State): 시스템의 실제 모습, 현실 그 자체
반면에 현재 상태(Current State) 란, 말 그대로 지금 이 순간 쿠버네티스 클러스터 내에서 실제로 운영되고 있는 애플리케이션과 인프라의 실제 모습을 의미합니다. 이는 마치 우리가 설계도를 보고 건물을 지었을 때, 실제로 지어진 건물의 현재 상황과 같습니다. 설계도(의도된 상태)대로 완벽하게 지어졌을 수도 있지만, 예기치 않은 문제로 인해 일부 창문이 깨져있거나, 페인트칠이 덜 되어 있을 수도 있는 것이죠.
예를 들어, 앞서 의도된 상태에서 “웹 애플리케이션 컨테이너 3개 실행”을 목표로 설정했지만, 다음과 같은 다양한 이유로 현재 상태는 다를 수 있습니다:
- 현재 웹 애플리케이션의 컨테이너 인스턴스가 단 2개만 실행 중일 수 있습니다. (예: 한 개가 아직 시작 중이거나, 리소스 부족으로 스케줄링되지 못했을 경우)
- 3개의 인스턴스 중 하나가 내부 오류로 인해 예기치 않게 종료(crash)되었을 수 있습니다.
- 특정 노드(서버)에 장애가 발생하여 해당 노드에서 실행 중이던 컨테이너들이 모두 중단되었을 수 있습니다.
- 네트워크 설정에 일시적인 문제가 생겨 외부에서 애플리케이션에 접근하지 못할 수도 있습니다.
쿠버네티스 클러스터 내에는 이러한 현재 상태를 지속적으로 감시하고 수집하는 다양한 ‘눈과 귀’들이 있습니다. 대표적인 예가 각 작업자 노드(Worker Node)에서 실행되는 Kubelet 입니다. Kubelet은 자신이 담당하는 노드 위에서 실행되는 파드(Pod, 하나 이상의 컨테이너 그룹)들의 건강 상태, 리소스 사용량 등을 면밀히 관찰하고, 그 정보를 주기적으로 마스터 노드의 API 서버에 보고합니다. 또한, 다양한 컨트롤러(Controller) 들(예: Deployment Controller, ReplicaSet Controller, Node Controller 등)도 각자가 책임지는 리소스들의 실제 상태를 모니터링합니다.
이렇게 Kubelet과 여러 컨트롤러들이 수집하여 API 서버로 보고한 정보들이 종합되어 클러스터 전체의 ‘현재 상태’를 구성하게 됩니다. API 서버는 이 현재 상태 정보 또한 etcd에 기록하거나, 혹은 리소스 객체의 status 필드와 같은 곳에 반영하여 관리합니다.
조정 (Reconciliation): 이상과 현실의 간극을 메우는 쿠버네티스의 끊임없는 노력
쿠버네티스가 진정으로 강력한 이유는 단순히 의도된 상태와 현재 상태를 파악하는 것에서 그치지 않는다는 점입니다. 쿠버네티스의 핵심 역할은 바로 이 의도된 상태와 현재 상태 간의 차이(delta)를 지속적으로 감지하고, 그 차이를 줄여나가기 위해 적극적으로 조치를 취하는 것입니다. 이 과정을 조정(Reconciliation) 이라고 부릅니다.
만약 우리가 의도된 상태로 “컨테이너 3개 실행”을 선언했는데, 현재 상태가 “컨테이너 2개 실행”이라면, 쿠버네티스는 이 불일치를 감지합니다. 그리고 이 차이를 해소하기 위해 자동으로 추가적인 컨테이너 1개를 더 실행시켜 의도된 상태인 3개를 맞추려고 노력합니다. 반대로, 만약 어떤 이유로 의도된 상태(3개)보다 더 많은 4개의 컨테이너가 실행 중이라면, 쿠버네티스는 불필요하다고 판단되는 컨테이너 1개를 종료시켜 의도된 상태를 유지하려고 합니다.
- 그림으로 이해하는 Reconciliation Loop(조정 루프)
- 1단계: Observe (관찰) : 쿠버네티스 컨트롤러는 주기적으로 클러스터의 현재 상태를 관찰합니다. 그림에서는 현재 상태(Current State)가 “파드 2개만 존재”하고 있는 상황입니다.
- 실제로는
kubelet
과controller manager
가 API Server와 함께 etcd의 상태를 바탕으로 이 정보를 확인합니다. - 이때 파드 2개만 존재하는 것을 확인합니다.
- 실제로는
- 2단계: Analyze (분석) : 컨트롤러는 매니페스트에 정의된 Desired State (의도된 상태)와 현재 상태를 비교합니다. 여기서 의도된 상태는 총 5개의 파드가 존재해야 함으로 명시되어 있습니다.
- 이 비교 결과, 현재 파드 수(2개)가 의도한 수(5개)보다 부족하다는 것을 감지합니다.
- 이 차이를 상태 차이(State Drift)라고 합니다.
- 3단계: Act (조정) : 컨트롤러는 현재 상태를 의도된 상태에 맞추기 위해 동작(Action)을 수행합니다. 나머지 부족한 3개의 파드를 추가로 생성합니다.
- 이 작업은 컨트롤러가 API 서버에 새로운 파드 생성을 요청하고, 스케줄러가 해당 파드를 적절한 노드에 배치하는 방식으로 진행됩니다.
- kubelet은 지정된 노드에서 이 요청된 파드를 실행합니다.
이러한 조정 과정은 단 한 번으로 끝나는 것이 아닙니다. 쿠버네티스 내부의 컨트롤러들은 마치 성실한 정원사처럼, 끊임없이 시스템의 현재 상태를 관찰하고, 우리가 원하는 정원의 모습(의도된 상태)과 비교하여, 잡초를 뽑거나(불필요한 리소스 제거), 물을 주거나(부족한 리소스 생성), 가지치기를 하는(리소스 설정 변경) 등의 작업을 자동으로 수행합니다. 이 끊임없는 조정 루프(Reconciliation Loop) 덕분에 쿠버네티스는 예상치 못한 장애가 발생하더라도 시스템의 안정성과 일관성을 스스로 유지할 수 있는 강력한 자기 치유(Self-healing) 능력을 갖게 됩니다. 예를 들어, 노드 하나가 갑자기 다운되어 해당 노드에서 실행되던 컨테이너들이 사라지면, 쿠버네티스는 이를 감지하고 다른 건강한 노드에 해당 컨테이너들을 자동으로 다시 스케줄링하여 의도된 수의 컨테이너를 유지하려고 시도합니다.
명령형 vs. 선언형: 패러다임의 전환
이러한 쿠버네티스의 선언적 접근 방식은 전통적인 명령형(Imperative) 방식과 뚜렷하게 대조됩니다. 명령형 방식에서는 우리가 시스템 관리자로서 “A 서버에 애플리케이션 X의 버전 1.0을 설치하라”, “B 서버에 방화벽 규칙을 추가하라”, “C 서비스를 시작하라”와 같이 모든 작업을 단계별로 구체적인 명령을 내려야 합니다. 만약 중간에 어떤 명령이 실패하거나, 시스템의 상태가 예기치 않게 변경되면(예: 서버 재부팅), 그에 대한 대처와 복구 역시 우리가 직접 고민하고 추가적인 명령을 내려야 하는 부담이 있습니다. 시스템이 복잡해질수록 이 부담은 기하급수적으로 커지게 됩니다.
하지만 쿠버네티스의 선언적 방식에서는 최종 목표(의도된 상태)만 명확하게 알려주면, 그 목표에 도달하기 위한 구체적인 실행 과정과 중간 단계의 문제 해결은 쿠버네티스가 내부의 컨트롤러들을 통해 알아서 처리해줍니다. 이는 운영의 복잡성을 크게 낮춰주고, 시스템 관리자가 반복적이고 지루한 작업에서 벗어나 더 가치 있는 일에 집중할 수 있게 해줍니다. 또한, 사람의 직접적인 개입을 최소화함으로써 휴먼 에러의 가능성도 줄어들고, 시스템의 일관성과 예측 가능성을 높여줍니다.
결국, 의도된 상태는 우리가 쿠버네티스에게 바라는 이상적인 시스템의 모습이며, 현재 상태는 그 이상에 대한 현실적인 반영입니다. 그리고 쿠버네티스는 이 둘 사이의 간극을 끊임없이 메워나가는 지능적인 조율자 역할을 수행함으로써, 복잡한 분산 시스템을 안정적이고 효율적으로 운영할 수 있도록 지원하는 것입니다. 이 기본 원리를 이해하는 것은 앞으로 여러분이 쿠버네티스의 다양한 기능과 객체들을 배우고 활용하는 데 튼튼한 기초가 될 것입니다.
6.2.1.2 컨트롤러 패턴 (Reconciliation Loop): 시스템을 살아있게 만드는 자동 조율의 마법사
앞서 우리는 쿠버네티스가 의도된 상태(Desired State) 와 현재 상태(Current State) 를 관리하며, 이 둘을 일치시키려 노력한다고 배웠습니다. 그렇다면 이 ‘일치시키는 작업’은 과연 누가, 어떻게 수행하는 것일까요? 바로 이 질문의 답이 컨트롤러(Controller) 와 그 핵심 동작 원리인 컨트롤러 패턴, 그리고 그 안에 숨겨진 조정 루프(Reconciliation Loop) 에 있습니다. 컨트롤러는 쿠버네티스라는 거대한 오케스트라의 지휘자처럼, 각자의 악기(리소스)들이 조화롭게 연주(의도된 상태 유지)하도록 끊임없이 지휘하는 역할을 합니다.
쿠버네티스 컨트롤러들
쿠버네티스 클러스터 내부에는 마치 전문 기술자 팀처럼, 각기 다른 전문 분야를 가진 다양한 종류의 컨트롤러들이 존재하며, 이들은 대부분 kube-controller-manager 라는 마스터 컴포넌트의 일부로서 실행됩니다. (물론, 클라우드 프로바이더에 특화된 컨트롤러나 사용자가 직접 만든 커스텀 컨트롤러는 별도로 실행될 수도 있습니다.) 각 컨트롤러는 특정 쿠버네티스 리소스(예: 파드, 디플로이먼트, 서비스 등)의 상태를 관리하는 책임을 맡습니다. 몇 가지 대표적인 내장 컨트롤러들을 살펴보면 그 역할을 짐작할 수 있습니다:
컨트롤러 이름 (영문) | 관리 대상 주요 리소스 | 주요 역할 및 설명 | 특징 및 추가 정보 |
---|---|---|---|
Deployment Controller | Deployment, ReplicaSet | 애플리케이션의 배포 및 업데이트(롤링 업데이트, 롤백 등) 전략을 관리합니다. 선언적으로 애플리케이션의 상태를 기술하고, 이를 달성하기 위해 ReplicaSet을 제어합니다. | – 직접 파드를 생성/관리하지 않고, ReplicaSet 컨트롤러를 통해 파드 수를 조절합니다. – 애플리케이션의 무중단 업데이트 및 버전 관리에 필수적입니다. – 배포 이력(revision)을 관리하여 특정 버전으로 쉽게 롤백할 수 있습니다. |
ReplicaSet Controller | ReplicaSet, Pod | 지정된 수의 파드 복제본이 항상 실행되도록 보장합니다. 파드의 수가 의도된 상태보다 적으면 새로 생성하고, 많으면 불필요한 파드를 종료시킵니다. | – 주로 Deployment에 의해 관리되며, 단독으로 사용하는 경우는 드뭅니다. – 파드의 가용성을 보장하는 기본적인 역할을 수행합니다. – 파드 셀렉터(selector)를 통해 관리할 파드를 식별합니다. |
StatefulSet Controller | StatefulSet, Pod | 상태 저장(stateful) 애플리케이션(예: 데이터베이스)을 관리합니다. 각 파드에 고유하고 안정적인 네트워크 식별자(hostname)와 영구 스토리지(PersistentVolumeClaim)를 제공합니다. | – 파드를 순서대로(0, 1, 2…) 배포, 업데이트, 삭제합니다. – 각 파드는 예측 가능한 이름을 가집니다 (예: web-0, web-1). – 스케일 다운 시에도 파드 번호가 큰 것부터 삭제됩니다. |
DaemonSet Controller | DaemonSet, Pod | 클러스터의 모든 노드(또는 특정 조건을 만족하는 노드)에 파드 복제본을 하나씩 실행하도록 보장합니다. | – 주로 로그 수집기(예: Fluentd), 모니터링 에이전트(예: Prometheus Node Exporter), 네트워크 플러그인 등 각 노드에서 실행되어야 하는 시스템 레벨의 데몬 배포에 사용됩니다. – 새 노드가 클러스터에 추가되면 자동으로 해당 노드에도 파드를 배포합니다. |
Node Controller | Node | 클러스터 내 노드들의 상태를 지속적으로 감시하고 관리합니다. 노드의 헬스 체크, 노드 장애 시 해당 노드를 NotReady 상태로 표시하고, 필요한 경우 해당 노드의 파드를 다른 노드로 이동(evict)시키는 등의 작업을 수행합니다. | – 클러스터의 전반적인 안정성과 워크로드의 가용성을 유지하는 데 중요한 역할을 합니다. – kubelet으로부터 노드 상태(Heartbeat)를 주기적으로 받습니다. |
Service Controller | Service | 쿠버네티스 서비스(Service) 리소스를 외부 환경(주로 클라우드 프로바이더)의 인프라와 연동합니다. 예를 들어, type: LoadBalancer 서비스를 생성하면 실제 클라우드 로드 밸런서를 프로비저닝하고 설정합니다. | – 클라우드 환경에서 쿠버네티스를 사용할 때 중요한 역할을 합니다. – 클라우드 공급자별로 구현된 cloud-controller-manager 내부에 포함되어 실행될 수 있습니다. |
Job Controller | Job, Pod | 하나 이상의 파드를 실행하여 특정 작업을 완료하고 종료하는 배치(batch) 작업을 관리합니다. 작업이 성공적으로 완료될 때까지 파드를 재시도할 수 있습니다. | – 데이터 처리, 백업, 임시 스크립트 실행 등 일회성 작업에 적합합니다. – 병렬 실행(parallelism) 및 완료 횟수(completions)를 지정할 수 있습니다. |
CronJob Controller | CronJob, Job | 유닉스(Unix)의 크론(cron) 표현식을 기반으로 주기적으로 Job을 생성하여 실행합니다. | – 정기적인 백업, 보고서 생성, 알림 발송 등 반복적인 배치 작업에 사용됩니다. – schedule 필드에 크론 표현식으로 실행 주기를 정의합니다. |
Namespace Controller | Namespace | 네임스페이스(Namespace)의 삭제를 관리합니다. 네임스페이스가 삭제되면 해당 네임스페이스 내의 모든 리소스를 정리(garbage collect)합니다. | – 네임스페이스를 통해 클러스터 리소스를 논리적으로 격리하고 관리할 수 있습니다. – Finalizer 메커니즘을 사용하여 리소스 정리가 완료될 때까지 네임스페이스 삭제를 지연시킵니다. |
PersistentVolume Controller | PersistentVolume (PV), PersistentVolumeClaim (PVC) | PV와 PVC 간의 바인딩(binding)을 관리하고, 스토리지 프로비저닝 및 재활용(reclaiming) 정책을 처리합니다. | – 동적 프로비저닝(Dynamic Provisioning)을 지원하여 필요에 따라 스토리지를 자동으로 생성할 수 있도록 합니다. – 스토리지 클래스(StorageClass)와 함께 사용됩니다. |
Endpoint Controller | Endpoints, Service, Pod | 서비스(Service)와 해당 서비스에 연결된 파드들의 IP 주소 및 포트 정보를 Endpoints 객체에 채워 넣습니다. | – 서비스 디스커버리(Service Discovery)의 핵심 요소로, 서비스 이름으로 파드에 접근할 수 있도록 합니다. – 파드의 상태 변경(생성, 삭제, IP 변경 등)에 따라 Endpoints 정보를 최신으로 유지합니다. |
참고:
- 위에 언급된 컨트롤러들은 쿠버네티스에 내장된 핵심 컨트롤러 중 일부이며, 실제로는 더 많은 종류의 컨트롤러들이 kube-controller-manager 내에서 또는 별도로 실행될 수 있습니다.
- 커스텀 컨트롤러(Custom Controller) 또는 오퍼레이터(Operator) 는 사용자가 직접 CRD(Custom Resource Definition)와 함께 개발하여 쿠버네티스의 기능을 특정 애플리케이션이나 서비스에 맞게 확장하는 데 사용됩니다. 이는 쿠버네티스 생태계의 중요한 부분입니다.
- 각 컨트롤러는 API 서버를 통해 자신이 관리하는 리소스의 의도된 상태(Desired State) 와 현재 상태(Current State) 를 지속적으로 감시하고, 이 둘을 일치시키기 위한 조정 루프(Reconciliation Loop) 를 실행합니다.
이 외에도 수많은 컨트롤러들이 각자의 영역에서 묵묵히 자신의 임무를 수행하며 쿠버네티스 클러스터 전체의 안정성과 효율성을 책임지고 있습니다.
컨트롤러의 핵심 엔진: 조정 루프 (Reconciliation Loop)
이처럼 다양한 컨트롤러들이 각자의 역할을 수행하지만, 그들이 따르는 기본적인 작동 원리는 놀랍도록 동일합니다. 바로 조정 루프(Reconciliation Loop) 라고 불리는 핵심 로직입니다. 이 루프는 마치 우리 몸의 항상성 유지 시스템처럼, 시스템의 상태를 바람직한 범위 내로 유지하기 위해 끊임없이 작동합니다. 조정 루프는 크게 세 가지 핵심 단계로 구성됩니다:

- 관찰 (Observe): 컨트롤러는 자신이 책임지고 있는 리소스의 현재 상태(Current State) 를 쿠버네티스 API 서버를 통해 주기적으로, 또는 특정 이벤트가 발생했을 때 조회하여 파악합니다. 예를 들어, 레플리카셋 컨트롤러는 현재 API 서버에 등록된, 자신이 관리하는 레이블을 가진 파드들의 목록과 각 파드의 상태를 확인하여 “현재 몇 개의 파드가 실제로 실행 중인가?”를 파악합니다. 이 과정에서 컨트롤러는 API 서버와 효율적으로 통신하기 위해 인포머(Informer) 라는 메커니즘을 사용합니다. 인포머는 특정 리소스의 변경 사항을 감시(watch)하고, 변경이 발생하면 컨트롤러에게 알려주어 불필요한 API 서버 부하를 줄입니다.
- 비교 (Compare/Diff): 관찰 단계에서 파악한 현재 상태를, 사용자가 매니페스트 파일을 통해 API 서버에 정의한 의도된 상태(Desired State) 와 비교합니다. 이때 두 상태 간의 차이점(delta)을 정확하게 식별합니다. 예를 들어, 레플리카셋 컨트롤러는 사용자가 “파드 3개를 실행하라” (replicas: 3)고 정의한 의도된 상태와, 현재 “2개의 파드만 실행 중”이라는 현재 상태를 비교하여 “1개의 파드가 부족하다”는 차이를 감지합니다.
- 조치 (Act): 현재 상태와 의도된 상태 사이에 차이가 발견되면, 컨트롤러는 이 차이를 해소하여 현재 상태를 의도된 상태로 수렴시키기 위한 구체적인 조치를 수행합니다. 이 조치는 대부분 API 서버를 통해 이루어집니다. 앞선 예시에서 레플리카셋 컨트롤러는 부족한 파드 1개를 추가로 생성하기 위해, 새로운 파드 객체를 정의하여 API 서버에 생성을 요청합니다. 만약 의도된 상태보다 파드가 더 많이 실행 중이라면, 불필요한 파드를 삭제하도록 API 서버에 요청할 것입니다. 또는, 파드의 이미지가 의도된 버전과 다르다면, 해당 파드를 삭제하고 새 버전의 이미지로 파드를 다시 생성하도록 유도할 수도 있습니다.
이러한 “관찰-비교-조치”로 이루어진 조정 루프는 한 번 실행되고 끝나는 것이 아니라, 마치 심장이 멈추지 않고 계속 뛰는 것처럼 끊임없이 반복적으로 실행됩니다. 이를 통해 쿠버네티스는 시스템의 상태가 예기치 않게 변경되더라도 (예: 하드웨어 장애로 인한 노드 다운, 컨테이너 크래시, 사용자의 실수로 인한 리소스 삭제 등) 이를 신속하게 감지하고 자동으로 복구하여 의도된 상태를 지속적으로 유지하려고 시도합니다. 이것이 바로 쿠버네티스가 자랑하는 강력한 자동화(Automation) 능력과 뛰어난 복원력(Resilience), 그리고 자기 치유(Self-healing) 능력의 비밀인 것입니다. 마치 온도 조절 장치(thermostat)가 설정된 온도(의도된 상태)를 유지하기 위해 현재 온도(현재 상태)를 계속 확인하고, 차이가 나면 냉난방기를 켜거나 끄는(조치) 것과 매우 유사한 원리입니다.
컨트롤러의 상호작용 방식: 직접 제어보다는 위임
여기서 한 가지 중요한 점은, 대부분의 컨트롤러가 시스템의 모든 구성요소를 직접 제어하지는 않는다는 것입니다. 예를 들어, 레플리카셋 컨트롤러가 새로운 파드를 생성해야 한다고 판단했을 때, 직접 특정 노드를 골라 컨테이너를 실행시키는 명령을 내리지는 않습니다. 대신, 레플리카셋 컨트롤러는 “이러한 명세를 가진 파드를 생성해달라”는 요청을 API 서버에 전달합니다.
그러면 이 요청은 다음과 같은 연쇄적인 과정을 거쳐 처리될 수 있습니다:
- API 서버는 이 파드 생성 요청을 받아 etcd에 기록합니다.
- 스케줄러(Scheduler) 컨트롤러는 아직 특정 노드에 할당되지 않은 새로운 파드가 생성된 것을 감지합니다.
- 스케줄러는 클러스터 내 여러 노드들의 상태(가용 리소스, 레이블, 테인트 등)를 고려하여 이 파드를 실행하기에 가장 적합한 노드를 선택하고, 그 정보를 파드 객체의 spec.nodeName 필드에 기록하여 API 서버에 업데이트합니다.
- 선택된 노드에서 실행 중인 Kubelet 은 자신에게 할당된 새로운 파드가 있음을 API 서버를 통해 감지합니다.
- Kubelet은 해당 파드의 명세에 따라 필요한 컨테이너 이미지를 내려받고, 컨테이너 런타임(예: containerd)을 통해 컨테이너들을 실행시킨 후, 파드의 상태를 다시 API 서버에 보고합니다.
이처럼 컨트롤러들은 직접적인 명령보다는 API 서버를 통해 다른 컴포넌트들과 상호작용하며, 각자의 전문 분야에 따라 작업을 위임하고 협력하는 방식으로 전체 시스템을 조율합니다. 이러한 느슨한 결합(Loose Coupling) 방식은 시스템의 유연성과 확장성을 높이는 데 크게 기여합니다.
컨트롤러 패턴의 힘: 확장성과 유연성
이 컨트롤러 패턴(또는 오퍼레이터 패턴이라고도 불리는 더 넓은 개념의 일부)은 쿠버네티스를 매우 유연하고 강력하게 만드는 핵심 요소입니다. 쿠버네티스 커뮤니티나 서드파티 개발자들은 단순히 내장된 컨트롤러를 사용하는 것을 넘어, 커스텀 리소스 정의(Custom Resource Definitions, CRD) 를 통해 자신만의 새로운 API 리소스 타입을 정의하고, 이 커스텀 리소스를 관리하는 커스텀 컨트롤러(Custom Controller) 를 직접 개발하여 쿠버네티스의 기능을 무한히 확장할 수 있습니다. 예를 들어, ‘데이터베이스 클러스터’라는 커스텀 리소스를 정의하고, 이 리소스의 생성, 백업, 복구, 스케일링 등을 자동화하는 커스텀 컨트롤러를 만들 수 있습니다. 이것이 바로 쿠버네티스 생태계가 폭발적으로 성장하고 다양한 분야에 적용될 수 있는 이유입니다.
결론: 선언적 API와 컨트롤러 패턴, 쿠버네티스의 양대 산맥
결론적으로, 선언적 API는 우리가 시스템에 “무엇을 원하는지(what)”를 명확하게 표현할 수 있는 방법을 제공하고, 컨트롤러 패턴과 그 심장인 조정 루프는 그 “원하는 바”를 현실로 만들고 지속적으로 유지하기 위해 “어떻게 할지(how)”를 끊임없이 고민하고 실행하는 강력한 자동화 엔진 역할을 합니다. 이 두 가지 개념은 마치 동전의 양면처럼 서로를 보완하며 쿠버네티스를 단순한 컨테이너 실행 도구를 넘어, 복잡한 클라우드 네이티브 애플리케이션을 구축하고 운영하기 위한 핵심 플랫폼으로 만들어주는 기반이 됩니다.
우리가 앞으로 쿠버네티스를 직접 설치하고 운영하는 과정에서 YAML 파일을 정성껏 작성하여 kubectl apply -f <filename.yaml> 명령을 실행하는 행위는, 바로 이 선언적 API를 통해 우리의 ‘의도된 상태’를 쿠버네티스라는 거대한 시스템에 전달하는 첫걸음입니다. 그리고 그 이후에 벌어지는 모든 ‘마법’ 같은 자동화와 자기 치유 과정은, 보이지 않는 곳에서 묵묵히 자신의 조정 루프를 돌리고 있는 수많은 컨트롤러들의 끊임없는 노력 덕분이라는 점을 기억하시면, 쿠버네티스를 더욱 깊이 있게 이해하고 활용하는 데 큰 도움이 될 것입니다.