3.5.3 컨트롤러 패턴
우리는 바로 앞 절에서 쿠버네티스 자동화의 핵심 엔진인 ‘조정 루프(Reconciliation Loop)’가 어떻게 “관찰(Observe) -> 차이 분석(Diff) -> 실행(Act)”이라는 세 가지 단계를 끊임없이 반복하며 시스템의 현재 상태를 사용자가 선언한 ‘원하는 상태(Desired State)’로 끊임없이 수렴시키려고 노력하는지 그 원리를 살펴보았습니다. 그렇다면 이처럼 중요하고 지능적인 조정 루프를 실제로 구현하고, 각자의 전문 분야에서 묵묵히 실행하는 주체는 과연 누구일까요? 그 해답이 바로 이번 절에서 우리가 자세히 알아볼 ‘컨트롤러(Controller)’입니다.
컨트롤러는 쿠버네티스 시스템을 살아 움직이게 만드는, 보이지 않는 곳에서 헌신적으로 일하는 수많은 ‘지능적인 일꾼들’ 또는 ‘전문 관리자들’이라고 생각할 수 있습니다. 각 컨트롤러는 특정 종류의 쿠버네티스 리소스(오브젝트)에 대한 깊은 이해와 전문성을 가지고 있으며, 해당 리소스가 항상 사용자가 원하는 상태를 유지하도록 책임지고 관리하는 역할을 수행합니다. 마치 거대한 병원 시스템 내에 심장 전문의, 신경외과 전문의, 정형외과 전문의 등 각 분야의 전문가들이 각자의 역할을 수행하며 환자(시스템)의 건강을 유지하려고 노력하는 것과 유사합니다.
3.5.3.1.컨트롤러란 무엇인가?
쿠버네티스 아키텍처의 핵심을 이루는 ‘컨트롤러(Controller)’는, 그 이름에서 알 수 있듯이 무언가를 ‘제어’하고 ‘관리’하는 역할을 수행하는 소프트웨어 컴포넌트입니다. 좀 더 구체적으로 정의하자면, 쿠버네티스에서 컨트롤러는 자신이 책임지도록 지정된 특정 종류의 쿠버네티스 리소스(예: 파드, 디플로이먼트, 서비스, 노드 등)의 실제 현재 상태(Current State)를 쿠버네티스 API 서버를 통해 지속적으로 감시하고, 이를 사용자가 해당 리소스의 명세(spec 필드)에 정의한 ‘원하는 상태(Desired State)’와 비교하여, 만약 그 둘 사이에 어떠한 불일치나 차이가 발생하면, 그 차이를 해소하고 현재 상태를 원하는 상태로 되돌리기 위한 필요한 모든 조치를 자동으로 수행하는 백그라운드 프로세스 또는 비즈니스 로직입니다.
본질적으로, 각 컨트롤러는 바로 앞 절에서 우리가 상세히 살펴본 ‘조정 루프(Reconciliation Loop)’를 내부적으로 구현하고, 자신이 담당하는 특정 리소스 타입에 대해 이 루프를 끊임없이 실행하는 독립적인 소프트웨어 로직이라고 할 수 있습니다. 즉, “관찰(Observe) -> 차이 분석(Diff) -> 실행(Act)”이라는 일련의 과정을 통해, 컨트롤러는 자신이 관리하는 리소스들이 항상 사용자가 의도한 대로 존재하고 작동하도록 보장하는 책임을 집니다. 이는 마치 잘 프로그램된 로봇이 특정 구역의 청결 상태를 계속 확인하다가, 쓰레기가 발견되면 즉시 치우고, 물건이 어질러져 있으면 원래 위치로 정리하는 작업을 반복하는 것과 유사합니다.
컨트롤러들은 일반적으로 쿠버네티스 컨트롤 플레인(Control Plane), 즉 클러스터 전체를 지휘하고 관리하는 두뇌 역할을 하는 마스터 노드(들) 위에서 실행됩니다. 대부분의 핵심 내장 컨트롤러들(예: 레플리카셋 컨트롤러, 디플로이먼트 컨트롤러, 네임스페이스 컨트롤러, 서비스 어카운트 컨트롤러 등 수십 가지)은 kube-controller-manager라는 단일 바이너리 프로세스 내에 여러 개의 독립적인 컨트롤러 루프들이 함께 패키징되어 효율적으로 실행됩니다. kube-controller-manager는 이러한 다양한 컨트롤러들을 하나의 프로세스로 묶어 실행함으로써 리소스 사용을 최적화하고 관리를 용이하게 합니다.
물론, 모든 컨트롤러가 kube-controller-manager 내에서만 실행되는 것은 아닙니다. 예를 들어, 특정 클라우드 제공업체(AWS, Azure, GCP 등)의 인프라(로드 밸런서, 스토리지 등)와 쿠버네티스를 연동하는 역할을 하는 컨트롤러들은 cloud-controller-manager라는 별도의 컴포넌트로 분리되어 실행될 수 있습니다. 또한, 이후에 배우게 될 CRD(Custom Resource Definition)와 함께 사용되는 사용자 정의 컨트롤러(Custom Controller 또는 오퍼레이터(Operator))는 개발자가 직접 작성하여 클러스터 내에 별도의 파드 형태로 배포하고 실행하는 경우가 일반적입니다.
각 컨트롤러는 자신이 책임지고 있는 특정 리소스 타입(Resource Type 또는 Kind)에 대해서만 관심을 가지고 작동합니다. 이는 쿠버네티스 시스템 설계를 매우 모듈화하고 각 컴포넌트의 역할을 명확하게 분리하는 데 중요한 역할을 합니다. 예를 들어,
- 레플리카셋 컨트롤러(ReplicaSet Controller)는 오직 ‘레플리카셋(ReplicaSet)’이라는 종류의 오브젝트와, 그 레플리카셋이 관리하도록 selector 필드에 정의된 레이블을 가진 ‘파드(Pod)’들의 현재 상태에만 집중합니다. 다른 종류의 리소스(예: 서비스, 컨피그맵)의 변경에는 직접적으로 반응하지 않습니다.
- 디플로이먼트 컨트롤러(Deployment Controller)는 ‘디플로이먼트(Deployment)’ 오브젝트의 상태 변화를 감시하고, 이에 따라 새로운 ‘레플리카셋’을 생성하거나 기존 레플리카셋을 관리하며, 궁극적으로는 ‘파드’들의 롤링 업데이트나 롤백을 지휘합니다. 디플로이먼트 컨트롤러는 자신이 직접 파드를 생성하거나 삭제하는 것이 아니라, 주로 레플리카셋을 통해 이러한 작업을 간접적으로 수행합니다.
- 노드 컨트롤러(Node Controller)는 클러스터 내의 모든 ‘노드(Node)’ 오브젝트들의 상태(예: Kubelet의 응답 여부, 디스크/메모리 압력 상태 등)를 주기적으로 확인하고, 특정 노드에 문제가 발생했다고 판단되면 해당 노드의 상태를 업데이트하거나 필요한 조치를 취합니다.
이러한 ‘책임의 분리(Separation of Concerns)’ 원칙은 쿠버네티스 시스템 전체의 복잡성을 효과적으로 관리하고, 각 컨트롤러가 자신의 전문 분야(특정 리소스 타입)에만 집중하여 더욱 효율적이고 안정적으로 작동할 수 있도록 하는 매우 중요한 설계 철학입니다. 만약 하나의 거대한 단일 컨트롤러가 모든 종류의 리소스를 다 관리하려고 한다면, 그 컨트롤러의 로직은 상상할 수 없을 정도로 복잡해지고, 작은 버그 하나가 시스템 전체에 치명적인 영향을 미칠 수 있으며, 새로운 기능을 추가하거나 수정하는 것도 매우 어려워질 것입니다. 하지만 각자의 전문 분야를 가진 여러 개의 독립적인 컨트롤러들이 서로 API 서버를 통해 정보를 교환하며 협력하는 방식을 통해, 쿠버네티스는 매우 유연하고 확장 가능하며 견고한 시스템 아키텍처를 구현할 수 있게 된 것입니다. 이는 마치 거대한 조직이 여러 전문 부서로 나뉘어 각자의 역할을 수행하며 전체 목표를 달성해 나가는 것과 유사합니다.
3.5.3.2.쿠버네티스에 내장된 다양한 컨트롤러들의 예시와 그 역할
쿠버네티스라는 거대한 오케스트라가 아름답고 안정적인 연주를 계속 이어나갈 수 있는 비결은 바로 각자의 악기(관리 대상 리소스)에 대한 깊은 이해와 전문성을 가진 수많은 지휘자들, 즉 내장 컨트롤러(Built-in Controller)들의 헌신적인 노력과 정교한 협업 덕분입니다. 쿠버네티스 시스템 내부에는 이미 핵심적인 기능을 수행하고, 다양한 종류의 애플리케이션 워크로드를 효과적으로 관리하며, 클러스터 전체의 안정성을 유지하기 위한 수십 가지 종류의 내장 컨트롤러들이 존재합니다. 이들은 대부분 컨트롤 플레인의 kube-controller-manager라는 프로세스 내에서 각자의 고유한 임무를 부여받고, 자신이 책임지는 리소스 타입에 대해 앞서 설명한 조정 루프를 끊임없이 실행합니다.
이 컨트롤러들이 관리하는 각각의 리소스 오브젝트(예: 디플로이먼트, 서비스, 노드 등)에 대해서는 책의 이후 각 장에서 훨씬 더 자세하고 깊이 있게 다룰 예정입니다. 하지만 여기서는 쿠버네티스 자동화의 핵심 주체로서 컨트롤러들이 어떤 종류가 있으며, 각각 어떤 중요한 역할을 수행하는지 몇 가지 대표적인 예시를 통해 그들의 활약상을 미리 살짝 엿보도록 하겠습니다. 이들의 존재를 이해하는 것은 쿠버네티스가 어떻게 그토록 다양한 시나리오에 유연하게 대응하고 지능적으로 작동하는지 파악하는 데 큰 도움이 될 것입니다.
레플리카셋 컨트롤러(ReplicaSet Controller): 파드 복제본 수의 충실한 관리자
레플리카셋 컨트롤러는 가장 기본적인 워크로드 관리 컨트롤러 중 하나로, 그 이름에서 알 수 있듯이 사용자가 레플리카셋(ReplicaSet) 오브젝트의 spec.replicas 필드에 명시적으로 정의한 ‘원하는 파드 복제본 수’가 항상 클러스터 내에서 실제로 실행되도록 보장하는 임무를 가집니다. 또한, spec.selector 필드에 정의된 레이블 셀렉터(Label Selector)를 사용하여 자신이 관리해야 할 파드들을 정확하게 식별합니다. 조정 루프를 통해 현재 실행 중인 파드의 수를 지속적으로 관찰하고, 만약 현재 파드 수가 원하는 수보다 부족하면 파드 템플릿(spec.template)을 기반으로 새로운 파드를 생성하며, 반대로 초과되면 불필요한 파드를 삭제하여 항상 정확한 수의 파드가 안정적으로 운영되도록 합니다. 하지만 실제 운영 환경에서는 사용자가 레플리카셋을 직접 생성하고 관리하는 경우는 드뭅니다. 보통 더 상위 수준의 컨트롤러인 ‘디플로이먼트’를 통해 간접적으로 레플리카셋이 생성되고 관리됩니다.
디플로이먼트 컨트롤러(Deployment Controller): 애플리케이션 배포와 업데이트의 마에스트로
디플로이먼트 컨트롤러는 현대적인 애플리케이션 배포의 표준이라고 할 수 있는 디플로이먼트(Deployment) 오브젝트를 관리합니다. 이 컨트롤러는 단순히 파드의 복제본 수를 유지하는 것을 넘어, 애플리케이션의 선언적인 업데이트와 롤백을 지휘하고 자동화하는 훨씬 더 정교하고 고수준의 역할을 수행합니다. 사용자가 디플로이먼트의 spec에 정의된 ‘원하는 상태'(예: 사용할 컨테이너 이미지의 새로운 버전, 변경된 복제본 수, 새로운 업데이트 전략 등)를 수정하면, 디플로이먼트 컨트롤러는 이 변경 사항을 감지하고, 새로운 상태를 반영하는 새로운 레플리카셋을 생성합니다. 그리고 사전에 정의된 롤링 업데이트(Rolling Update) 전략에 따라, 새로운 버전의 레플리카셋이 관리하는 파드들을 점진적으로 늘려가면서 동시에 기존 버전의 레플리카셋이 관리하는 파드들을 점진적으로 줄여나가는 복잡한 업데이트 과정을 안전하고 자동으로 조율합니다. 또한, 배포 이력(revision history)을 관리하여, 만약 새로운 버전에 문제가 발생했을 경우 사용자가 간단한 명령으로 이전의 안정적인 버전으로 손쉽게 롤백할 수 있도록 지원합니다.
스테이트풀셋 컨트롤러(StatefulSet Controller): 상태 유지가 생명인 애플리케이션의 꼼꼼한 집사
데이터베이스(예: MySQL, PostgreSQL), 메시지 큐(예: Kafka, RabbitMQ), 또는 분산 파일 시스템과 같이 상태를 가지고 운영되어야 하며(stateful), 각 인스턴스(파드)가 고유하고 안정적인 네트워크 식별자(예: 고정된 호스트 이름)와 영구적인 전용 스토리지(예: 파드별로 고유한 퍼시스턴트 볼륨)를 필요로 하는 특별한 종류의 애플리케이션을 관리하기 위해 설계되었습니다. 스테이트풀셋 컨트롤러는 이러한 까다로운 요구사항을 만족시키기 위해 파드들을 예측 가능한 순서대로(예: web-0, web-1, web-2 순으로) 하나씩 생성하고 삭제하며, 각 파드에 대해 고유하고 안정적인 네트워크 ID와 스토리지 볼륨(보통 volumeClaimTemplates를 통해 파드별로 퍼시스턴트 볼륨 클레임(PVC)을 자동으로 생성하고 연결)을 보장하는 등 매우 특별하고 꼼꼼한 관리를 수행합니다.
데몬셋 컨트롤러(DaemonSet Controller): 모든 노드의 충실한 파수꾼
데몬셋 컨트롤러는 클러스터 내의 모든 (또는 특정 레이블을 가진 선택된) 워커 노드에 반드시 하나의 동일한 파드 인스턴스가 실행되도록 보장하는 독특한 역할을 합니다. 이는 마치 각 건물마다 반드시 경비원이 한 명씩 배치되어야 하는 것과 유사합니다. 예를 들어, 모든 노드에서 시스템 로그를 수집하여 중앙 로깅 시스템으로 전송하는 로그 수집 에이전트(예: Fluentd, Filebeat), 각 노드의 성능 지표를 수집하는 모니터링 에이전트(예: Prometheus Node Exporter), 또는 클러스터 전체에 분산 스토리지를 제공하는 스토리지 데몬(예: Ceph OSD, GlusterFS Daemon) 등을 배포하는 데 매우 유용하게 사용됩니다. 새로운 노드가 클러스터에 추가되면 데몬셋 컨트롤러는 해당 노드에도 자동으로 관련 파드를 배포하며, 노드가 클러스터에서 삭제되면 해당 노드에서 실행되던 파드도 함께 정리합니다.
잡 컨트롤러(Job Controller) 및 크론잡 컨트롤러(CronJob Controller): 시간과 작업 관리의 달인들
모든 애플리케이션이 웹 서비스처럼 항상 실행되어야 하는 것은 아닙니다. 때로는 특정 작업을 한 번만 성공적으로 실행하고 종료되기를 원하거나(배치 작업), 또는 특정 시간에 주기적으로 반복 실행되기를 원할 수 있습니다. 잡 컨트롤러는 바로 이러한 일회성 작업(one-off task)을 관리하며, 정의된 수만큼의 파드가 성공적으로 완료될 때까지(또는 설정된 실패 횟수 제한에 도달할 때까지) 파드를 재시작하는 등의 방법으로 작업의 완료를 보장합니다. (예: 데이터베이스 마이그레이션 스크립트 실행, 대량의 데이터 변환 작업 등)
크론잡 컨트롤러는 리눅스 시스템의 익숙한 cron 작업과 매우 유사하게, 사용자가 정의한 특정 스케줄(예: “매일 새벽 2시에”, “매주 월요일 오전 9시에”, “매 5분마다”)에 따라 주기적으로 잡(Job) 오브젝트를 생성하여 실행하도록 스케줄링하는 역할을 합니다. (예: 주기적인 데이터 백업, 리포트 생성, 시스템 정리 작업 등)
노드 컨트롤러(Node Controller): 클러스터 노드들의 건강 검진 의사
노드 컨트롤러는 쿠버네티스 클러스터에 등록된 각 워커 노드의 상태를 지속적으로 감시하고 관리하는 중요한 역할을 합니다. 각 노드의 Kubelet으로부터 주기적으로 상태 보고(heartbeat)를 받고, 노드의 디스크 공간이나 메모리 사용량이 위험 수준에 도달했는지(Node Conditions, 예: DiskPressure, MemoryPressure), 또는 노드가 네트워크에 정상적으로 연결되어 있는지 등을 확인합니다. 만약 특정 노드가 일정 시간 이상 응답하지 않거나 심각한 문제가 발생하여 비정상적인 상태(예: NotReady, Unknown)로 판단되면, 노드 컨트롤러는 해당 노드에 특정 상태(예: NoSchedule 테인트(Taint))를 표시하여 더 이상 새로운 파드가 해당 노드에 스케줄링되지 않도록 조치합니다. 또한, 필요한 경우 해당 노드에서 실행 중이던 파드들을 다른 건강한 노드로 안전하게 축출(evict)하고 재배치하는 과정을 시작하는 등의 중요한 결정을 내립니다.
서비스 컨트롤러(Service Controller) 및 엔드포인트/엔드포인트슬라이스 컨트롤러(Endpoints/EndpointSlice Controller): 서비스 연결의 숨은 조력자들
서비스(Service) 오브젝트는 특정 레이블을 가진 파드 그룹에 대한 안정적인 네트워크 접근점을 제공한다고 앞서 설명했습니다. 서비스 컨트롤러는 이러한 서비스 오브젝트의 정의, 특히 타입이 LoadBalancer로 지정된 경우, 실제 운영 환경(주로 퍼블릭 클라우드)에서 해당 클라우드 제공업체의 외부 로드 밸런서를 자동으로 생성하고, 서비스의 외부 IP 주소를 할당하며, 필요한 네트워크 규칙을 설정하는 복잡한 작업을 담당합니다.
한편, 엔드포인트 컨트롤러(또는 최신 버전에서는 더 효율적인 엔드포인트슬라이스 컨트롤러)는 각 서비스 오브젝트가 실제로 트래픽을 전달해야 할 대상 파드들의 실제 내부 IP 주소와 포트 번호 목록(즉, 엔드포인트 정보)을 지속적으로 감시하고 업데이트하는 매우 중요한 역할을 합니다. 서비스의 레이블 셀렉터에 매칭되는 파드들이 새로 생성되거나, 삭제되거나, 또는 건강 상태가 변경될 때마다, 이 컨트롤러는 해당 서비스의 엔드포인트 정보를 신속하게 갱신하여 항상 최신의 건강한 파드들로만 트래픽이 전달될 수 있도록 보장합니다. (쿠버네티스 내부 DNS인 CoreDNS도 이 엔드포인트 정보를 참조하여 서비스 이름을 실제 파드 IP로 해석하는 데 활용합니다.)
이 외에도, 쿠버네티스 시스템 내부에는 네임스페이스(Namespace)의 생성 및 삭제와 관련된 정리 작업을 수행하는 네임스페이스 컨트롤러, 퍼시스턴트 볼륨(PersistentVolume)과 퍼시스턴트 볼륨 클레임(PersistentVolumeClaim) 간의 바인딩을 관리하는 퍼시스턴트 볼륨 컨트롤러, 서비스 어카운트(ServiceAccount)와 관련된 토큰 생성을 담당하는 서비스 어카운트 컨트롤러, 그리고 가비지 컬렉션(Garbage Collection) 컨트롤러, 인증서(Certificate) 컨트롤러 등 거의 모든 종류의 쿠버네티스 핵심 리소스들은 각자를 전담하여 지능적으로 관리하는 하나 이상의 컨트롤러들에 의해 뒷받침되고 있습니다.
이처럼 쿠버네티스에 내장된 수많은 종류의 컨트롤러들은 각자의 전문 분야에서 묵묵히, 그리고 끊임없이 조정 루프를 실행하며, 사용자가 선언한 ‘원하는 상태’와 클러스터의 ‘현재 상태’ 사이의 간극을 메우기 위해 노력합니다. 이들의 정교하고 헌신적인 작업 덕분에 쿠버네티스는 그토록 강력한 자동화, 자가 치유, 그리고 확장성을 제공할 수 있는 것이며, 우리는 복잡한 분산 시스템 운영의 많은 부담을 덜고 더 가치 있는 일에 집중할 수 있게 되는 것입니다. 다음으로는 이러한 컨트롤러들이 어떻게 이벤트에 반응하며 다양한 자동화 기능을 실현하는지 그 동작 방식에 대해 좀 더 자세히 살펴보겠습니다.
3.5.3.3.컨트롤러를 통해 실현되는 다양한 자동화 기능과 이벤트 기반 동작
우리가 앞서 3.4.3절에서 그토록 감탄하며 살펴보았던 쿠버네티스의 놀랍고도 실용적인 자동화 기능들, 예를 들어 예기치 않은 장애 발생 시 시스템 스스로 문제를 해결하려는 ‘자가 치유(Self-healing)’ 능력, 애플리케이션 부하 변화에 맞춰 자동으로 처리 용량을 조절하는 ‘수평적 확장(Horizontal Scaling)’, 새로운 버전의 애플리케이션을 서비스 중단 없이 안전하게 배포하고 문제가 생기면 신속하게 이전 버전으로 되돌리는 ‘자동화된 롤아웃 및 롤백(Automated Rollout and Rollback)’, 그리고 수많은 마이크로서비스들이 서로를 쉽게 찾아 안정적으로 통신할 수 있도록 지원하는 ‘서비스 디스커버리 및 로드 밸런싱(Service Discovery and Load Balancing)’ 등은 모두 마법처럼 저절로 일어나는 현상이 아닙니다.
이러한 모든 경이로운 자동화 기능들은 바로 쿠버네티스 컨트롤 플레인 내부에서 각자의 전문 분야를 책임지고 있는 다양한 종류의 컨트롤러들이, 자신에게 주어진 임무에 따라 조정 루프를 끊임없이 실행하고, 때로는 서로 긴밀하게 정보를 교환하며 상호 협력한 결과물이라고 할 수 있습니다. 마치 잘 조율된 거대한 오케스트라에서 각 파트의 연주자들이 지휘자의 지시에 따라 각자의 악기를 연주하며 완벽한 하모니를 만들어내듯이, 쿠버네티스의 컨트롤러들은 API 서버를 중심으로 정보를 주고받으며 시스템 전체의 조화와 안정을 유지합니다.
그렇다면 이 컨트롤러들은 언제, 그리고 어떻게 자신들의 조정 루프를 시작하고 필요한 조치를 취하는 것일까요? 컨트롤러들은 대부분 매우 효율적이고 반응성이 뛰어난 ‘이벤트 기반(event-driven)’ 아키텍처로 동작합니다. 이것이 의미하는 바는, 컨트롤러가 단순히 정해진 시간 간격으로만 시스템 상태를 확인하는 것이 아니라(물론, 주기적인 상태 점검, 즉 폴링(polling) 방식도 보조적으로 사용될 수 있으며, 특히 초기 동기화나 예외 상황 처리 시 중요합니다), 쿠버네티스 API 서버의 강력한 ‘와치(watch)’ 기능을 통해 자신이 관리하고 있는 특정 종류의 리소스나, 또는 그 리소스와 관련된 다른 리소스에 어떠한 의미 있는 변경 사항(예: 새로운 리소스의 생성(Create) 이벤트, 기존 리소스의 수정(Update) 이벤트, 리소스의 삭제(Delete) 이벤트)이 발생했다는 실시간 알림을 받으면, 즉시 이에 반응하여 조정 루프를 실행한다는 것입니다.
예를 들어 몇 가지 구체적인 시나리오를 통해 이러한 이벤트 기반 동작과 컨트롤러들의 협력을 살펴보겠습니다.
사용자가 디플로이먼트의 복제본 수를 변경하는 경우 (Update Event):
사용자가 kubectl scale deployment my-app –replicas=5와 같이 명령을 실행하거나, 디플로이먼트 YAML 파일의 spec.replicas 필드 값을 3에서 5로 변경한 후 kubectl apply를 실행했다고 가정해 봅시다. 이 변경 사항은 쿠버네티스 API 서버에 전달되고, 해당 디플로이먼트 오브젝트의 ‘원하는 상태’가 업데이트됩니다.
이때, 디플로이먼트 컨트롤러는 API 서버의 와치 기능을 통해 이 디플로이먼트 오브젝트의 변경 이벤트를 즉시 감지합니다. 그러면 디플로이먼트 컨트롤러는 조정 루프를 시작하여, 현재 상태(예: 3개의 파드만 실행 중)와 새로운 원하는 상태(5개의 파드 실행) 간의 차이를 분석합니다. 그리고 이 차이를 해소하기 위해, 자신이 관리하는 하위 레플리카셋 컨트롤러에게 “파드 2개를 추가로 생성하라”는 지시(즉, 레플리카셋의 spec.replicas 값을 5로 업데이트)를 내립니다. 그러면 레플리카셋 컨트롤러는 이 변경 사항을 감지하고, 새로운 파드 2개를 실제로 생성하여 스케줄링하도록 API 서버에 요청하는 조치를 취하게 됩니다. 이처럼 여러 컨트롤러가 연쇄적으로 반응하며 원하는 상태를 달성해 나갑니다.
특정 파드가 예기치 않게 사라지는 경우 (Delete Event 또는 Node Failure Event):
만약 특정 워커 노드에 장애가 발생하여 해당 노드에서 실행 중이던 파드 중 하나가 예기치 않게 사라졌다고 가정해 봅시다. 이 파드를 관리하던 레플리카셋 컨트롤러는 노드 컨트롤러에 의해 해당 노드가 NotReady 상태가 되고 관련 파드들이 축출(evict)되거나, 또는 API 서버로부터 해당 파드의 삭제 이벤트를 감지함으로써 자신이 유지해야 할 파드의 수가 부족해졌다는 것을 인지하게 됩니다.
그러면 레플리카셋 컨트롤러는 즉시 조정 루프를 실행하여 “파드 1개가 부족하다”는 차이를 분석하고, 즉시 새로운 파드를 생성하여 다른 건강한 노드에 배치함으로써 ‘원하는 복제본 수’를 신속하게 복원하려고 시도합니다. 이것이 바로 쿠버네티스의 자가 치유 기능이 작동하는 방식입니다.
새로운 서비스가 생성되는 경우 (Create Event):
사용자가 특정 레이블을 가진 파드 그룹을 외부에 노출하기 위해 새로운 서비스(Service) 오브젝트를 생성했다고 가정해 봅시다. 서비스 컨트롤러와 엔드포인트/엔드포인트슬라이스 컨트롤러는 이 새로운 서비스 생성 이벤트를 감지합니다. 엔드포인트 컨트롤러는 서비스의 레이블 셀렉터와 일치하는 현재 실행 중인 파드들의 IP 주소와 포트 목록을 찾아 해당 서비스의 엔드포인트(Endpoints 또는 EndpointSlices) 오브젝트를 자동으로 생성하고 업데이트합니다. 만약 서비스 타입이 LoadBalancer라면, 서비스 컨트롤러는 해당 클라우드 제공업체의 API를 호출하여 외부 로드 밸런서를 프로비저닝하고 그 IP 주소를 서비스 오브젝트의 상태에 기록하는 등의 추가적인 조치를 취합니다.
이처럼 쿠버네티스 컨트롤러들은 마치 자율적으로 주변 환경의 변화를 감지하고, 그 변화에 맞춰 스스로 판단하고 행동하는 수많은 작은 로봇 에이전트들의 집합체와도 같습니다. 이들은 서로 직접적으로 통신하기보다는, 대부분 쿠버네티스 API 서버라는 중앙 정보 허브를 통해 리소스의 상태 정보를 공유하고, 다른 컨트롤러가 만들어낸 변화(이벤트)에 반응하며, 자신에게 주어진 책임을 다하기 위해 협력합니다. 사용자는 단지 시스템이 최종적으로 도달해야 할 ‘원하는 모습’만을 선언적으로 정의하고, 그 목표를 달성하기 위한 복잡하고 반복적이며 때로는 지루하기까지 한 모든 중간 과정은 이 지능적이고 헌신적인 컨트롤러들에게 전적으로 위임하는 것입니다.
이것이 바로 쿠버네티스가 제공하는 자동화의 진정한 힘이며, 운영의 복잡성을 획기적으로 줄이고 시스템의 안정성과 효율성을 극대화하는 핵심 비결입니다. 컨트롤러 패턴은 쿠버네티스 아키텍처의 가장 우아하고 강력한 측면 중 하나이며, 단순히 내장된 컨트롤러들에만 국한되지 않고 사용자가 직접 자신만의 커스텀 컨트롤러를 만들어 쿠버네티스의 기능을 무한히 확장할 수 있는 기반을 제공합니다. 다음 절에서는 바로 이러한 쿠버네티스의 놀라운 확장성의 세계로 함께 들어가 보겠습니다.
