6.1.1 컨트롤 플레인 (Control Plane)
쿠버네티스 클러스터의 가장 핵심적인 두뇌이자 지휘 본부인 컨트롤 플레인(Control Plane)에 오신 것을 환영합니다. 이곳은 클러스터 전체의 상태를 관리하고, 우리가 내리는 명령을 해석하며, 모든 작업이 우리가 원하는 대로 이루어지도록 조율하는 중추적인 역할을 담당합니다. 만약 쿠버네티스 클러스터를 하나의 정교한 오케스트라에 비유한다면, 컨트롤 플레인은 바로 그 오케스트라를 이끄는 지휘자라고 할 수 있습니다. 지휘자가 각 악기 파트의 연주를 조율하여 아름다운 하모니를 만들어내듯, 컨트롤 플레인은 클러스터 내의 수많은 구성 요소들이 각자의 역할을 정확히 수행하도록 지시하고 관리합니다.
컨트롤 플레인은 단순히 하나의 프로그램이 아니라, 여러 핵심 컴포넌트들이 유기적으로 협력하여 작동하는 시스템입니다. 이 컴포넌트들은 클러스터의 ‘원하는 상태(Desired State)’를 정의하고, 현재 상태를 지속적으로 모니터링하며, 이 둘 사이의 차이를 감지하면 자동으로 조치를 취하여 원하는 상태로 수렴하도록 만듭니다. 이것이 바로 쿠버네티스가 자랑하는 ‘선언적 API’와 ‘자가 치유(Self-healing)’ 능력의 근간이 되는 것이죠. 이제 컨트롤 플레인을 구성하는 주요 배우들을 하나씩 만나보겠습니다.

6.1.1.1 kube-apiserver
쿠버네티스 컨트롤 플레인의 심장부이자 모든 소통이 시작되는 지점, 바로 kube-apiserver(큐브 API 서버)에 대해 좀 더 깊이 알아보겠습니다. 앞서 컨트롤 플레인의 가장 중요한 구성 요소이자 클러스터로 들어가는 유일한 정문(Gateway)이라고 말씀드렸는데요, 이 비유는 kube-apiserver의 역할을 매우 정확하게 표현합니다. 우리가 클러스터와 상호작용하기 위해 사용하는 커맨드라인 도구인 kubectl, 웹 브라우저를 통해 클러스터 상태를 시각적으로 보여주는 대시보드, 또는 애플리케이션 배포를 자동화하는 CI/CD 파이프라인 등 그 어떤 클라이언트라도 반드시 이 kube-apiserver를 거쳐야만 클러스터 내부의 리소스에 접근하거나 상태를 변경할 수 있습니다.
마치 한 국가의 국경을 관리하는 출입국 관리 사무소나, 대기업의 모든 방문객과 정보를 처리하는 중앙 안내 데스크를 상상해 보시면 이해가 쉽습니다. kube-apiserver는 클러스터로 들어오는 모든 요청을 가장 먼저 받아들이고, 이 요청이 정당한지, 요청을 보낸 주체는 누구이며 해당 작업을 수행할 권한은 있는지 등을 꼼꼼하게 검증하는 일련의 과정을 수행합니다. 이러한 철저한 검증 절차는 쿠버네티스 클러스터의 보안과 안정성을 유지하는 데 핵심적인 역할을 합니다.
kube-apiserver는 그 이름에서 알 수 있듯이 RESTful API 형태로 쿠버네티스의 모든 기능을 외부에 제공합니다. REST(Representational State Transfer)는 웹에서 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 아키텍처 스타일로, kube-apiserver는 HTTP/HTTPS 프로토콜을 통해 이 원칙을 따릅니다. 예를 들어, “새로운 파드를 생성해줘” 또는 “현재 실행 중인 서비스들의 목록을 보여줘”와 같은 사용자의 의도는 표준 HTTP 메소드(POST, GET, PUT, DELETE 등)와 특정 리소스 경로(예: /api/v1/pods, /api/v1/services)를 조합한 API 요청으로 변환되어 kube-apiserver에 전달됩니다. 이렇게 표준화된 API 인터페이스를 제공함으로써, 다양한 종류의 클라이언트와 도구들이 일관된 방식으로 쿠버네티스와 상호작용할 수 있게 됩니다.
사용자나 다른 시스템 컴포넌트로부터 API 요청을 받으면, kube-apiserver는 다음과 같은 주요 단계를 거쳐 요청을 처리합니다. 이 과정은 마치 다단계 보안 검색대를 통과하는 것과 유사하며, 각 단계를 통과해야만 최종적으로 요청이 실행될 수 있습니다.
인증(Authentication): “당신은 누구십니까?”
가장 먼저, 요청을 보낸 클라이언트가 누구인지 신원을 확인하는 과정입니다. kube-apiserver는 다양한 인증 전략을 지원하며, 클러스터 설정에 따라 하나 또는 여러 방법을 조합하여 사용할 수 있습니다.
- 클라이언트 인증서(Client Certificates): X.509 인증서를 사용하여 클라이언트를 인증합니다. 주로 kubelet이나 kube-scheduler와 같은 클러스터 내부 컴포넌트들이 kube-apiserver와 통신할 때 사용되며, 사용자 인증에도 활용될 수 있습니다.
- 정적 토큰 파일(Static Token File): 미리 정의된 Bearer 토큰 목록을 파일에 저장해두고, 요청 헤더에 포함된 토큰과 비교하여 인증합니다. 간단한 환경에서 사용할 수 있지만, 토큰 관리가 번거롭고 보안성이 낮아 널리 권장되지는 않습니다.
- 부트스트랩 토큰(Bootstrap Tokens): 새로운 노드를 클러스터에 안전하게 추가(join)하는 과정을 간소화하기 위해 사용되는 특수한 목적의 토큰입니다.
- 서비스 어카운트 토큰(Service Account Tokens): 쿠버네티스 클러스터 내에서 실행되는 파드(애플리케이션)가 kube-apiserver와 통신해야 할 때 사용되는 Bearer 토큰입니다. 각 네임스페이스에는 기본 서비스 어카운트가 있으며, 필요에 따라 추가적인 서비스 어카운트를 생성하여 파드에 할당할 수 있습니다. 이 토큰은 자동으로 파드 내의 특정 경로에 마운트됩니다.
- OpenID Connect (OIDC) 토큰: Google, Azure AD, Keycloak 등 외부 ID 공급자(Identity Provider)와 연동하여 사용자 인증을 처리할 수 있게 해줍니다. 주로 사람 사용자의 인증에 적합하며, 엔터프라이즈 환경에서 SSO(Single Sign-On)를 구현하는 데 유용합니다.
- 웹훅 토큰 인증(Webhook Token Authentication): 토큰 검증 로직을 외부 HTTP 서버(웹훅)에 위임하는 방식입니다.인증에 성공하면, 사용자의 이름(username)과 소속 그룹(groups) 정보가 후속 단계를 위해 컨텍스트에 저장됩니다. 인증에 실패하면 요청은 즉시 거부됩니다(HTTP 401 Unauthorized)
인가(Authorization): “이 작업을 수행할 권한이 있습니까?”
인증을 통해 신원이 확인된 사용자가 요청한 작업을 수행할 수 있는 권한이 있는지 검사하는 단계입니다. 예를 들어, 일반 개발자는 자신이 속한 네임스페이스의 파드만 조회할 수 있지만, 클러스터 관리자는 모든 네임스페이스의 리소스를 생성, 수정, 삭제할 수 있는 권한을 가질 수 있습니다. kube-apiserver는 여러 인가 모드를 지원하며, 이들 중 하나 이상을 조합하여 사용할 수 있습니다.
- RBAC (Role-Based Access Control): 현재 쿠버네티스에서 가장 널리 사용되고 권장되는 인가 모델입니다. ‘누가(Subject: User, Group, ServiceAccount)’, ‘어떤 리소스(Resource: Pods, Services 등)에 대해’, ‘어떤 행위(Verb: get, list, create, delete 등)를 할 수 있는지’를 Role과 RoleBinding(네임스페이스 범위) 또는 ClusterRole과 ClusterRoleBinding(클러스터 전체 범위)이라는 오브젝트를 통해 정의합니다. 매우 세분화되고 유연한 권한 관리가 가능합니다.
- ABAC (Attribute-Based Access Control): 사용자, 리소스, 환경 등 다양한 속성(Attribute)에 기반하여 정책 파일을 통해 접근 제어를 정의합니다. RBAC보다 복잡하고 관리하기 어려워 현재는 잘 사용되지 않습니다.
- 노드 인가자(Node Authorizer): kubelet이 API 서버에 보내는 요청에 대해 특별한 권한을 부여하는 인가자입니다. kubelet이 자신이 실행 중인 노드와 관련된 리소스(자신에게 스케줄된 파드, 시크릿, 컨피그맵 등)에만 접근할 수 있도록 제한하여 보안을 강화합니다.
- 웹훅 인가자(Webhook Authorizer): 인가 결정을 외부 HTTP 서버(웹훅)에 위임합니다. 외부 권한 관리 시스템과 연동할 때 유용합니다.만약 요청된 작업이 허용되지 않으면, kube-apiserver는 요청을 거부합니다(HTTP 403 Forbidden).
어드미션 컨트롤(Admission Control): “요청이 클러스터 정책에 부합하며, 필요한 변경을 가해도 괜찮습니까?”
인증과 인가를 모두 통과한 요청이라도, 실제로 클러스터의 영구 저장소(etcd)에 기록되기 전에 마지막으로 거치는 검문소입니다. 어드미션 컨트롤러는 요청된 오브젝트의 내용을 검증(Validating)하거나, 필요에 따라 수정(Mutating)하는 역할을 합니다. 이를 통해 클러스터 관리자는 보안 정책 강화, 거버넌스 준수, 자원 사용량 제한, 기본값 설정 등 다양한 운영 정책을 강제할 수 있습니다.
어드미션 컨트롤은 두 단계로 나뉩니다.
- 수정 어드미션 컨트롤(Mutating Admission Control): 요청된 오브젝트의 내용을 변경할 수 있습니다. 예를 들어, 파드가 생성될 때 특정 레이블을 자동으로 추가하거나, 컨테이너에 기본 리소스 요청량(request) 및 제한량(limit)을 설정하는 등의 작업을 수행할 수 있습니다.
- 검증 어드미션 컨트롤(Validating Admission Control): 수정 단계 이후, 오브젝트의 내용이 클러스터 정책에 부합하는지 최종적으로 검증합니다. 예를 들어, 모든 파드는 반드시 리소스 제한량이 설정되어야 한다는 정책이 있다면, 이를 어기는 요청은 여기서 거부됩니다.쿠버네티스에는 여러 빌트인 어드미션 컨트롤러(예: NamespaceLifecycle, LimitRanger, ResourceQuota, DefaultStorageClass 등)가 있으며, 관리자는 kube-apiserver 시작 시 활성화할 컨트롤러 목록을 지정할 수 있습니다. 또한, MutatingAdmissionWebhook과 ValidatingAdmissionWebhook을 통해 사용자 정의 어드미션 로직을 외부 HTTP 서버(웹훅)로 구현하여 동적으로 확장할 수 있습니다. 이는 OPA/Gatekeeper와 같은 정책 엔진이나 Istio와 같은 서비스 메쉬가 사이드카 컨테이너를 주입하는 데 널리 활용됩니다. 어드미션 컨트롤 단계에서 요청이 거부되면 해당 사유와 함께 에러가 반환됩니다.
- 요청 처리 및 상태 저장(스키마 유효성 검사 후 etcd에 저장):위의 모든 검증 단계를 성공적으로 통과한 요청은 마침내 처리될 준비가 됩니다.
- 스키마 유효성 검사(Schema Validation): 요청된 오브젝트가 해당 리소스 종류(예: Pod, Service)의 정의된 스키마(필수 필드, 데이터 타입 등)를 따르는지 확인합니다.
- etcd에 저장: 오브젝트 생성/수정/삭제 요청의 경우, kube-apiserver는 이 변경 사항을 클러스터의 신뢰할 수 있는 저장소인 etcd에 기록합니다. etcd는 클러스터의 ‘원하는 상태(desired state)’를 저장하는 핵심 컴포넌트입니다.
- 응답 반환: 오브젝트 조회 요청의 경우, kube-apiserver는 etcd에서 해당 정보를 읽어와 클라이언트에게 응답합니다. 상태 변경 요청의 경우, 작업 성공 여부와 함께 생성/수정된 오브젝트 정보를 반환할 수 있습니다.또한, kube-apiserver는 와치(watch) 기능을 제공하여 클라이언트가 특정 리소스의 변경 사항을 실시간으로 구독할 수 있게 해줍니다. 이는 kube-scheduler, kube-controller-manager 등 다른 컨트롤 플레인 컴포넌트들이 클러스터 상태 변화를 즉각적으로 감지하고 반응하는 데 필수적인 메커니즘입니다.
이처럼 kube-apiserver는 클러스터의 모든 상호작용을 중앙에서 통제하고 관리함으로써, 보안을 강화하고, 다양한 클라이언트와 컴포넌트들이 일관된 방식으로 클러스터 상태를 조회하고 조작할 수 있도록 하는 중추적인 역할을 수행합니다. kube-apiserver의 안정성은 클러스터 전체의 안정성과 직결되기 때문에, 운영 환경에서는 보통 여러 개의 kube-apiserver 인스턴스를 로드 밸런서 뒤에 두고 실행하여 고가용성(High Availability)을 확보하는 것이 일반적입니다. 만약 kube-apiserver가 다운되면, 클러스터는 새로운 명령을 받아들이거나 현재 상태를 조회할 수 없게 되어 사실상 관리 불능 상태에 빠지게 됩니다. 물론 기존에 실행 중이던 워크로드는 계속 동작할 수 있지만, 새로운 변경은 불가능합니다.
6.1.1.2 etcd: 클러스터 상태 저장소
etcd는 쿠버네티스 클러스터의 모든 설정 정보와 상태 데이터를 저장하는 분산형 키-값 저장소(Distributed Key-Value Store)입니다. 앞서 kube-apiserver를 클러스터의 관문이라고 비유했다면, etcd는 그 관문을 통과한 모든 중요한 정보가 기록되고 보관되는 클러스터의 ‘중앙 기록 보관소‘ 또는 ‘신뢰할 수 있는 단일 진실 공급원(Single Source of Truth)’이라고 생각하시면 정확합니다.
우리가 kubectl을 통해 “디플로이먼트를 생성해줘”, “파드의 개수를 3개로 유지해줘”, “이 서비스는 특정 포트를 사용해줘”와 같이 내리는 모든 명령, 즉 클러스터가 가져야 할 ‘원하는 상태(Desired State)’는 물론, 현재 클러스터가 실제로 어떤 상태인지(예: 현재 실행 중인 파드 목록, 각 노드의 리소스 사용량, 네트워크 구성, 사용자 역할 및 권한 등)를 나타내는 ‘현재 상태(Current State)’에 대한 모든 정보가 바로 이 etcd에 체계적으로 저장되고 관리됩니다. kube-apiserver가 처리하는 거의 모든 데이터, 즉 우리가 쿠버네티스에 정의하는 모든 오브젝트(Pod, Service, Deployment, ConfigMap, Secret 등)의 명세(spec)와 상태(status)가 이곳에 기록된다고 보시면 됩니다.
etcd는 단순히 데이터를 저장하는 것을 넘어, 분산 시스템 환경에서 데이터의 일관성(Consistency)과 고가용성(High Availability)을 보장하는 데 특화되어 설계되었습니다. 이것이 etcd가 쿠버네티스의 핵심 저장소로 선택된 가장 중요한 이유입니다.
- 일관성 (Consistency): etcd는 Raft 합의 알고리즘(Raft Consensus Algorithm)을 사용하여 클러스터 내의 여러 etcd 노드(멤버) 간에 데이터가 항상 동일하게 유지되도록 보장합니다. Raft 알고리즘은 분산 시스템에서 여러 노드가 동일한 상태를 공유하고, 장애 상황에서도 데이터 정합성을 유지하기 위한 프로토콜입니다. 간단히 말해, 여러 etcd 노드 중 하나가 리더(Leader)로 선출되고, 모든 데이터 변경 요청(쓰기 작업)은 반드시 리더를 통해서만 처리됩니다. 리더는 변경 사항을 로그로 기록하고, 이 로그를 다른 팔로워(Follower) 노드들에게 복제합니다. 과반수 이상의 노드가 해당 로그를 성공적으로 복제했다고 리더에게 응답하면, 비로소 변경 사항이 확정(committed)되고 클라이언트에 성공 응답을 보냅니다. 이 과정을 통해 모든 etcd 노드는 항상 동일한 데이터를 바라보게 되어, 어느 노드에서 데이터를 읽든 일관된 결과를 얻을 수 있습니다.
- 고가용성 (High Availability): etcd는 일반적으로 3대 또는 5대와 같이 홀수 개의 서버로 클러스터를 구성하여 운영됩니다. Raft 알고리즘 덕분에, 전체 etcd 클러스터 노드 중 과반수(Quorum)만 정상적으로 작동하고 있다면, 일부 노드에 장애가 발생하더라도 etcd 클러스터 전체는 데이터 손실 없이 계속해서 정상적으로 서비스를 제공할 수 있습니다. 예를 들어, 5대로 구성된 etcd 클러스터에서는 최대 2대의 노드에 장애가 발생해도 시스템은 중단 없이 작동합니다. 이러한 특성은 쿠버네티스 클러스터 전체의 안정성과 신뢰성을 크게 향상시킵니다.
kube-apiserver는 클러스터의 상태를 변경하거나 조회해야 할 때 항상 etcd와 직접 통신합니다. 사용자가 kubectl로 파드 생성을 요청하면, kube-apiserver는 인증, 인가, 어드미션 컨트롤을 거친 후 최종적으로 이 파드 정보를 etcd에 기록합니다. 반대로, 파드 목록을 조회하면 kube-apiserver는 etcd에서 해당 정보를 읽어와 사용자에게 보여줍니다.
여기서 중요한 아키텍처적 특징 중 하나는, kube-scheduler나 kube-controller-manager와 같은 다른 컨트롤 플레인 컴포넌트들은 일반적으로 직접 etcd에 접근하지 않는다는 점입니다. 대신, 이들은 kube-apiserver를 통해 etcd에 저장된 정보를 읽거나 변경을 요청합니다. 즉, kube-apiserver가 etcd로 가는 유일한 통로 역할을 수행하며, 모든 데이터 접근에 대한 검증, 어드미션 컨트롤, 감사 로깅 등을 중앙에서 일관되게 처리할 수 있도록 합니다. 이는 etcd 데이터의 무결성을 보호하고 보안을 강화하는 데 매우 중요한 설계 원칙입니다.
etcd는 키-값 저장소로서, 데이터를 파일 시스템의 경로와 유사한 계층적인 키 구조로 저장합니다. 예를 들어, default 네임스페이스에 있는 my-pod라는 이름의 파드 정보는 /registry/pods/default/my-pod와 같은 키 아래에 JSON 또는 Protocol Buffers 형태로 직렬화되어 저장될 수 있습니다. 또한, etcd는 변경 사항 감지(Watch) 기능을 제공하여, 특정 키 또는 특정 키 프리픽스 하위의 데이터 변경을 실시간으로 구독할 수 있게 해줍니다. kube-apiserver는 이 watch 기능을 적극적으로 활용하여 etcd의 상태 변화를 감지하고, 이를 다시 kube-scheduler나 kube-controller-manager와 같은 다른 컴포넌트들에게 전파합니다. 이 watch 메커니즘은 쿠버네티스가 선언된 ‘원하는 상태’와 ‘현재 상태’의 차이를 신속하게 감지하고 대응하는 반응형 시스템을 구현하는 데 핵심적인 역할을 합니다.
etcd는 쿠버네티스 클러스터의 ‘심장’ 또는 ‘중앙 신경계’와도 같아서, 만약 etcd의 데이터가 손상되거나 유실되면 클러스터 전체가 심각한 운영 장애를 겪거나 최악의 경우 복구가 불가능한 상태에 이를 수 있습니다. 따라서 etcd 클러스터의 안정적인 운영과 데이터의 정기적인 백업(스냅샷) 및 복구 절차 마련은 쿠버네티스 관리자에게 가장 중요한 책임 중 하나입니다. etcd는 디스크 I/O와 네트워크 지연 시간에 민감하므로, 고성능 SSD 사용과 저지연 네트워크 환경 구성이 권장됩니다. 또한, etcd 멤버 간의 통신 및 클라이언트(kube-apiserver)와의 통신은 TLS 암호화를 통해 보호하는 것이 보안상 매우 중요합니다.
정리하자면, etcd는 쿠버네티스 클러스터의 모든 상태 정보를 안정적으로 저장하고, Raft 합의 알고리즘을 통해 데이터의 일관성과 고가용성을 보장하는 핵심 분산 저장소입니다. kube-apiserver를 통해서만 접근이 제어되며, watch 기능을 통해 클러스터 상태 변화를 실시간으로 전파하는 중추적인 역할을 담당합니다. 그 중요성으로 인해 etcd의 안정적인 관리와 백업은 성공적인 쿠버네티스 운영의 필수 조건이라고 할 수 있습니다.
6.1.1.3 kube-scheduler: 파드 배치 결정
우리가 애플리케이션을 컨테이너화하여 파드(Pod) 형태로 클러스터에 배포하라는 요청을 보내면, 이 파드는 즉시 실행되는 것이 아니라 먼저 “어느 워커 노드에서 실행되어야 하는가?”라는 중요한 질문에 대한 답을 기다립니다. 바로 이 질문에 대한 최적의 답을 찾아내는 것이 kube-scheduler의 핵심 임무입니다. 클러스터 내에는 수많은 워커 노드가 존재할 수 있으며, 각 노드는 가용 자원, 탑재된 하드웨어, 지리적 위치 등 다양한 특성을 가질 수 있습니다. kube-scheduler는 이러한 복잡한 상황을 고려하여 각 파드가 가장 적합한 ‘집’을 찾도록 도와주는, 그야말로 스마트한 배차 담당자입니다.
kube-scheduler는 클러스터의 상태를 알기 위해 항상 kube-apiserver와 긴밀하게 소통합니다. 좀 더 구체적으로는, kube-apiserver의 ‘와치(watch)’ 기능을 사용하여 아직 특정 노드에 할당되지 않은(즉, spec.nodeName 필드가 비어있는) 새로운 파드가 생성되는지 지속적으로 감시합니다. 새로운 파드를 발견하면, kube-scheduler는 정교하게 설계된 스케줄링 알고리즘을 가동하여 해당 파드를 실행할 최적의 워커 노드를 선택하는 여정을 시작합니다. 이 과정은 크게 두 단계, 즉 필터링(Filtering)과 스코어링(Scoring)으로 나눌 수 있습니다.
필터링(Filtering): “이 파드를 실행할 수 있는 기본 자격 요건을 갖춘 노드는 어디인가?”
이 단계에서는 파드가 명시적으로 요구하는 조건들을 만족하지 못하는 워커 노드들을 후보군에서 제외합니다. 마치 입사 지원 서류를 검토하여 최소 자격 요건에 미달하는 지원자를 걸러내는 과정과 유사합니다. kube-scheduler는 다음과 같은 다양한 조건들을 확인합니다.
- 리소스 요구량(Resource Requests): 파드 명세(spec) 내의 각 컨테이너는 자신이 필요로 하는 최소한의 CPU, 메모리, 임시 스토리지(ephemeral-storage) 등을 requests 필드에 명시할 수 있습니다. kube-scheduler는 각 워커 노드의 현재 가용 리소스와 파드의 요구량을 비교하여, 충분한 리소스를 제공할 수 없는 노드는 탈락시킵니다.
- 노드 셀렉터(Node Selector) 및 노드 어피니티/안티-어피니티(Node Affinity/Anti-affinity):
- nodeSelector: 파드 명세에 spec.nodeSelector 필드를 사용하여 특정 레이블(label)을 가진 노드에만 파드를 배치하도록 강제할 수 있습니다. 예를 들어, disktype: ssd라는 레이블이 붙은 노드에만 파드를 스케줄링할 수 있습니다.
- 노드 어피니티/안티-어피니티: nodeSelector보다 훨씬 표현력이 풍부한 기능입니다. “반드시 특정 조건을 만족하는 노드에 배치되어야 한다”(hard, requiredDuringSchedulingIgnoredDuringExecution) 또는 “가급적 특정 조건을 만족하는 노드에 배치되길 원한다”(soft, preferredDuringSchedulingIgnoredDuringExecution)와 같은 복잡한 규칙을 정의할 수 있습니다. 예를 들어, “특정 CPU 아키텍처(예: arm64)를 가진 노드에 반드시 배치” 또는 “가급적이면 특정 지역(예: us-west-1a)에 위치한 노드에 배치”와 같은 요구사항을 표현할 수 있습니다. 안티-어피니티는 반대로 특정 노드를 회피하도록 합니다.
- 테인트(Taints)와 톨러레이션(Tolerations): 노드는 자신에게 ‘테인트’를 설정하여 특정 파드들이 자신에게 스케줄링되는 것을 거부할 수 있습니다. 마치 “특별한 허가증이 없으면 들어오지 마시오”라는 팻말과 같습니다. 파드는 ‘톨러레이션’을 명시하여 특정 테인트를 ‘용인’하고 해당 노드에도 스케줄링될 수 있음을 알릴 수 있습니다. 이는 특정 노드를 특정 용도(예: GPU 작업 전용, 특정 애플리케이션 전용)로 예약하거나, 노드 유지보수 시 파드가 자동으로 다른 곳으로 이동하도록 유도하는 데 사용됩니다.
- 볼륨 및 스토리지 요구사항: 파드가 특정 종류의 퍼시스턴트 볼륨(Persistent Volume)이나 특정 스토리지 클래스(StorageClass)를 요구하는 경우, 해당 볼륨을 제공할 수 있거나 마운트할 수 있는 노드만 후보로 남습니다. 예를 들어, 특정 클라우드 제공업체의 특정 스토리지 유형을 사용하는 볼륨은 해당 클라우드 존(zone) 내의 노드에만 마운트될 수 있습니다.
- 파드 어피니티/안티-어피니티(Pod Affinity/Anti-affinity): 이미 실행 중인 다른 파드들과의 관계를 기반으로 스케줄링 결정을 내립니다. 예를 들어, “웹 서버 파드와 같은 노드에 API 서버 파드를 배치하라”(파드 어피니티) 또는 “데이터베이스 파드와는 다른 노드에 백업 파드를 배치하라”(파드 안티-어피니티)와 같은 규칙을 설정할 수 있습니다. 이 역시 hard/soft 규칙을 지원합니다.
- 포트 충돌: 파드가 특정 호스트 포트(HostPort)를 사용하도록 요청한 경우, 해당 포트가 이미 사용 중인 노드는 제외됩니다. (일반적으로 서비스 오브젝트를 사용하는 것이 권장되므로 HostPort 사용은 제한적입니다.)
이 필터링 단계를 거친 후, 만약 어떤 워커 노드도 파드의 요구 조건을 만족시키지 못한다면, 해당 파드는 어느 노드에도 배치되지 못하고 ‘Pending’ 상태로 대기하게 됩니다. 이때 kubectl describe pod <pod-name> 명령을 실행하면 스케줄링 실패 이유를 이벤트(Events) 섹션에서 확인할 수 있습니다.
스코어링(Scoring) 또는 우선순위 지정(Prioritizing): “필터링을 통과한 노드들 중, 이 파드에게 가장 좋은 곳은 어디인가?”
필터링 단계를 통해 파드를 실행할 수 있는 자격을 갖춘 후보 노드들이 선정되면, 스코어링 단계에서는 이 후보 노드들 각각에 대해 ‘점수’를 매겨 순위를 결정합니다. kube-scheduler는 미리 정의된 여러 우선순위 함수(Priority Functions, 최신 버전에서는 스코어링 플러그인 내 로직)를 각 후보 노드에 적용하고, 각 함수에서 반환된 점수를 합산(또는 가중 합산)하여 최종 점수를 계산합니다. 점수가 가장 높은 노드가 최종 선택지로 낙점됩니다. 주요 스코어링 로직(우선순위 함수)의 예시는 다음과 같습니다.
- LeastRequestedPriority / MostAllocatedPriority (리소스 분배 관련):
- LeastRequestedPriority: CPU나 메모리 같은 자원의 요청량(request) 대비 여유 공간이 많은 노드에 높은 점수를 줍니다. 즉, 가능한 한 부하가 적은 노드에 파드를 분산시키려는 경향을 보입니다.
- MostAllocatedPriority (또는 유사한 개념의 플러그인): 반대로 이미 많은 자원이 할당된 노드에 높은 점수를 주어, 가능한 한 적은 수의 노드에 파드를 밀집시키려는(bin-packing) 전략입니다. 이는 클러스터 오토스케일러와 함께 사용될 때 유휴 노드를 줄여 비용을 절감하는 데 도움이 될 수 있습니다.
- BalancedResourceAllocation: CPU와 메모리 사용률이 균형 잡힌 노드에 높은 점수를 부여합니다. 예를 들어, CPU는 여유가 많지만 메모리는 거의 다 사용한 노드보다는 CPU와 메모리 모두 적절히 여유 있는 노드를 선호합니다.
- ImageLocalityPriority: 파드가 사용하려는 컨테이너 이미지가 이미 해당 워커 노드에 캐시되어 있는 경우 높은 점수를 부여합니다. 이미지가 이미 존재하면 파드 시작 시간을 크게 단축할 수 있기 때문입니다.
- 노드 어피니티/안티-어피니티 (preferredDuringSchedulingIgnoredDuringExecution): 필터링 단계의 ‘hard’ 규칙과 달리, 여기서는 ‘soft’ 선호도를 점수로 반영합니다.
- 파드 어피니티/안티-어피니티 (preferredDuringSchedulingIgnoredDuringExecution): 마찬가지로 다른 파드와의 ‘soft’ 선호/비선호 관계를 점수에 반영합니다.
- TaintTolerationPriority: 테인트를 용인(toleration)해야 하는 노드보다는 그렇지 않은 노드를 선호하는 경향을 줄 수 있습니다 (설정에 따라 다름).
- TopologySpreadConstraints (위상 분배 제약 조건): 파드를 특정 토폴로지 도메인(예: 리전, 존, 랙, 호스트)에 걸쳐 균등하게 분배하거나 특정 비율로 분배하도록 유도합니다. 이는 고가용성을 확보하고 장애 확산을 방지하는 데 매우 유용합니다. 이 제약 조건은 점수 계산에 영향을 미칩니다.
관리자는 kube-scheduler의 설정을 통해 각 우선순위 함수의 가중치를 조절하거나, 특정 우선순위 함수를 활성화/비활성화함으로써 스케줄링 정책을 클러스터의 특성과 요구사항에 맞게 세밀하게 조정할 수 있습니다. 최신 쿠버네티스 버전에서는 스케줄러 프레임워크(Scheduler Framework)라는 확장 가능한 플러그인 아키텍처를 도입하여, 사용자가 자신만의 스케줄링 로직(필터링, 스코어링, 바인딩 등 다양한 확장점)을 개발하고 통합하는 것을 더욱 용이하게 만들었습니다.
최종적으로 스코어링 단계를 통해 가장 높은 점수를 받은 워커 노드가 해당 파드를 실행할 최적의 장소로 결정됩니다. kube-scheduler는 이 결정 사항을 kube-apiserver에 알리는 바인딩(Binding) 작업을 수행합니다. 구체적으로는, 파드 오브젝트의 spec.nodeName 필드에 선택된 노드의 이름을 기록하도록 kube-apiserver에 요청합니다. 이 정보가 etcd에 저장되면, 선택된 워커 노드에서 실행 중인 kubelet이 이 변경 사항을 감지하고(자신에게 할당된 파드임을 인지하고) 해당 파드에 정의된 컨테이너들의 실제 실행을 시작하게 됩니다.
경우에 따라, 높은 우선순위(PriorityClass)를 가진 파드가 스케줄링될 공간이 없을 때, kube-scheduler는 선점(Preemption) 기능을 사용하여 이미 실행 중인 낮은 우선순위의 파드들을 종료시키고 공간을 확보하여 높은 우선순위의 파드를 스케줄링할 수도 있습니다. 또한, 하나의 클러스터 내에 여러 개의 kube-scheduler 인스턴스를 실행하고, 파드 명세의 spec.schedulerName 필드를 통해 특정 파드가 어떤 스케줄러에 의해 관리될지 지정하는 것도 가능합니다. 이는 특정 워크로드에 특화된 스케줄링 정책을 적용하고자 할 때 유용합니다.
kube-scheduler 덕분에, 클러스터 관리자나 개발자는 수동으로 각 파드를 어느 노드에 배치할지 일일이 고민하고 지정하는 번거로움에서 벗어날 수 있습니다. 단지 애플리케이션의 요구사항과 선호도를 선언적으로 명시하기만 하면, kube-scheduler가 알아서 클러스터 전체의 자원 상황과 정책을 고려하여 최적의 위치를 찾아줍니다. 이는 클러스터 자원의 효율적인 사용을 극대화하고, 애플리케이션의 안정성과 고가용성을 높이며, 전체 시스템 운영의 복잡성을 크게 낮추는 데 결정적인 기여를 합니다.
6.1.1.4 kube-controller-manager: 상태 관리 컨트롤러
독자 여러분, 쿠버네티스가 어떻게 스스로 문제를 해결하고(자가 치유, Self-healing), 우리가 선언한 대로 애플리케이션의 상태를 꾸준히 유지할 수 있는지 궁금하셨다면, 바로 지금부터 설명드릴 kube-controller-manager(큐브 컨트롤러 매니저)에 주목해 주십시오. 이 컴포넌트는 쿠버네티스 클러스터의 ‘원하는 상태(Desired State)’를 유지하기 위해 마치 24시간 쉬지 않고 일하는 부지런한 관리자들의 팀과 같습니다. 이 ‘관리자들’ 각각을 우리는 ‘컨트롤러(Controller)’라고 부릅니다.
그렇다면 ‘컨트롤러’란 정확히 무엇일까요? 쿠버네티스에서 컨트롤러는 특정 종류의 리소스(오브젝트, 예를 들어 파드, 노드, 서비스 등)의 현재 상태(Current State)를 지속적으로 감시하고, 사용자가 kube-apiserver를 통해 정의한 ‘원하는 상태’와 비교합니다. 만약 이 둘 사이에 차이가 발생하면, 컨트롤러는 현재 상태를 원하는 상태로 되돌리기 위한 조치를 자동으로 수행하는 논리적인 프로세스, 즉 일종의 자동화된 루프(loop)입니다. kube-controller-manager는 이러한 다양한 종류의 컨트롤러들을 하나의 실행 가능한 바이너리 파일로 묶어서 실행하는, 말 그대로 ‘컨트롤러들의 매니저’ 역할을 수행합니다.
마치 거대한 자동화 공장에서 각 생산 라인(예: 복제본 수 관리 라인, 노드 상태 관리 라인, 서비스 연결 관리 라인 등)을 책임지는 여러 명의 전문 관리자들이 한 사무실에 모여 각자의 업무를 빈틈없이 수행하는 모습을 상상해 보십시오. 각 관리자(컨트롤러)는 자신이 담당하는 영역에서 문제가 발생하거나 계획과 다른 상황이 전개되면, 즉각적으로 상황을 파악하고 필요한 수정 작업을 진행하여 생산 라인이 정상적으로 가동되도록 합니다. kube-controller-manager 내부의 컨트롤러들도 이와 유사하게 각자의 전문 분야에서 클러스터의 안정성과 자동화를 책임집니다.
kube-controller-manager에는 쿠버네티스의 핵심 기능을 담당하는 수많은 컨트롤러들이 포함되어 있습니다. 그중 몇 가지 주요 컨트롤러들의 역할과 동작 방식을 좀 더 자세히 살펴보겠습니다.
레플리케이션 컨트롤러(Replication Controller) / 레플리카셋 컨트롤러(ReplicaSet Controller):
이들은 아마도 가장 직관적으로 이해할 수 있는 컨트롤러일 것입니다. 주된 임무는 사용자가 지정한 수만큼의 파드 복제본(replica)이 항상 실행되도록 보장하는 것입니다. 예를 들어, “웹 서버 파드를 항상 3개 실행시켜줘”라고 ReplicaSet 오브젝트에 정의하면, 이 컨트롤러는 주기적으로 현재 실행 중인 해당 웹 서버 파드의 수를 확인합니다.
- 만약 파드 중 하나가 예기치 않게 종료되거나 노드 장애로 인해 사라지면 (현재 2개만 실행 중이라면), 컨트롤러는 즉시 새로운 파드를 생성하여 3개를 맞춥니다.
- 반대로, 어떤 이유로든 4개의 파드가 실행 중이라면 (예: 수동으로 추가 생성), 컨트롤러는 초과분인 1개의 파드를 자동으로 종료시킵니다.ReplicationController는 구 버전이며, 현재는 더 유연한 셀렉터 기능을 제공하는 ReplicaSet이 주로 사용됩니다. ReplicaSet은 보통 Deployment 컨트롤러에 의해 관리됩니다. 이 컨트롤러는 파드의 metadata.ownerReferences 필드를 통해 자신이 관리하는 파드들을 식별하고 추적합니다.
노드 컨트롤러(Node Controller):
이 컨트롤러는 클러스터를 구성하는 워커 노드들의 상태를 책임지고 관리합니다. 각 노드의 kubelet은 주기적으로 자신의 상태 정보(heartbeat)를 kube-apiserver에 보고하는데, 노드 컨트롤러는 이 정보를 감시합니다.
- 만약 특정 노드가 설정된 시간 동안 응답하지 않거나(unreachable), 디스크 부족, 메모리 부족 등 건강하지 않은 상태(NotReady)로 보고되면, 노드 컨트롤러는 해당 노드를 비정상으로 간주합니다.
- 비정상 노드에 대해서는 node.kubernetes.io/unreachable 또는 node.kubernetes.io/not-ready와 같은 테인트(Taint)를 자동으로 추가하여 새로운 파드가 해당 노드에 스케줄링되는 것을 막을 수 있습니다.
- 더 나아가, 일정 시간 이상 응답이 없는 노드에 실행 중이던 파드들은 ‘Eviction Timeout’ 이후 자동으로 축출(evict)되어 다른 건강한 노드에 재스케줄링되도록 조치합니다. 이는 노드 장애 시 애플리케이션의 가용성을 유지하는 데 매우 중요합니다.
디플로이먼트 컨트롤러(Deployment Controller):
현대적인 애플리케이션 배포에서 핵심적인 역할을 수행합니다. Deployment 오브젝트를 통해 사용자는 애플리케이션의 원하는 상태(예: 이미지 버전, 복제본 수)를 선언적으로 정의하고, 디플로이먼트 컨트롤러는 이 상태를 달성하기 위한 업데이트 및 롤백 전략을 자동화합니다.
- 주로 ReplicaSet을 이용하여 파드들을 관리합니다. 새로운 버전의 애플리케이션을 배포할 때, Deployment는 새로운 ReplicaSet을 생성하고, 설정된 전략(예: 롤링 업데이트 – RollingUpdate)에 따라 점진적으로 새 버전의 파드 수를 늘리면서 구 버전의 파드 수를 줄여나갑니다. maxSurge(업데이트 중 일시적으로 허용되는 최대 초과 파드 수)와 maxUnavailable(업데이트 중 일시적으로 사용 불가능해도 되는 최대 파드 수) 같은 파라미터를 통해 업데이트 속도와 안정성 사이의 균형을 조절할 수 있습니다.
- 만약 업데이트 후 문제가 발견되면, 이전 버전으로 쉽게 롤백(rollback)하는 기능도 제공합니다.
서비스 컨트롤러(Service Controller) / 엔드포인트[슬라이스] 컨트롤러(Endpoint[Slice] Controller):
쿠버네티스에서 파드들은 IP 주소가 동적으로 변경될 수 있기 때문에, 안정적인 서비스 접근을 위해서는 Service 오브젝트가 필요합니다.
- 엔드포인트[슬라이스] 컨트롤러는 Service 오브젝트의 셀렉터(label selector)를 감시하고, 이 셀렉터와 일치하는 레이블을 가진 파드들의 IP 주소와 포트 정보를 수집하여 Endpoints 또는 EndpointSlice 오브젝트를 생성하고 업데이트합니다. EndpointSlice는 대규모 클러스터에서 Endpoints 오브젝트의 확장성 문제를 해결하기 위해 도입된 새로운 리소스입니다.
- 서비스 컨트롤러는 Service 오브젝트의 타입이 LoadBalancer로 설정된 경우, 클라우드 제공업체(만약 cloud-controller-manager와 연동되어 있다면)의 API를 호출하여 외부 로드밸런서를 자동으로 프로비저닝하고 설정하는 역할을 합니다. 또한, NodePort 서비스의 경우 노드의 특정 포트를 개방하는 등의 작업을 조율합니다.이 컨트롤러들 덕분에 서비스 디스커버리와 로드 밸런싱이 원활하게 이루어질 수 있습니다.
네임스페이스 컨트롤러(Namespace Controller):
Namespace는 클러스터 내의 리소스를 논리적으로 격리하고 그룹화하는 데 사용됩니다. 네임스페이스 컨트롤러는 주로 네임스페이스가 삭제될 때의 정리 작업을 담당합니다.
- 네임스페이스 삭제 요청이 들어오면, 컨트롤러는 해당 네임스페이스 내에 존재하는 모든 리소스(파드, 서비스, 디플로이먼트 등)가 완전히 삭제될 때까지 기다립니다. 이 과정은 쿠버네티스의 ‘파이널라이저(Finalizer)’ 메커니즘을 통해 이루어집니다. 모든 내부 리소스가 정리되면, 비로소 네임스페이스 오브젝트 자체가 삭제됩니다.
서비스 어카운트 & 토큰 컨트롤러(Service Account & Token Controllers):
클러스터 내에서 실행되는 파드(애플리케이션)가 kube-apiserver와 안전하게 통신하기 위해서는 인증 수단이 필요합니다.
- 서비스 어카운트 컨트롤러는 새로운 네임스페이스가 생성될 때마다 자동으로 default라는 이름의 ServiceAccount 오브젝트를 생성합니다.
- 토큰 컨트롤러는 각 ServiceAccount에 대해 API 접근에 사용될 수 있는 인증 토큰(보통 JWT 형식)을 생성하고, 이를 Secret 오브젝트에 저장합니다. 이 Secret은 해당 ServiceAccount를 사용하는 파드에 자동으로 마운트되어, 파드 내의 애플리케이션이 API 서버와 통신할 때 이 토큰을 사용할 수 있게 합니다. (최신 쿠버네티스 버전에서는 토큰 관리가 TokenRequest API와 Bound Service Account Token Volume으로 개선되었습니다.)
위에 언급된 컨트롤러들 외에도 kube-controller-manager에는 정말 다양한 컨트롤러들이 포함되어 있습니다.
예를 들면 다음과 같습니다.
- 스테이트풀셋 컨트롤러(StatefulSet Controller): 안정적인 네트워크 식별자와 영구 스토리지가 필요한 상태 저장 애플리케이션(예: 데이터베이스)을 관리합니다.
- 데몬셋 컨트롤러(DaemonSet Controller): 클러스터의 모든 (또는 특정) 노드에 파드 복제본이 하나씩 실행되도록 보장합니다 (예: 로그 수집 에이전트, 모니터링 에이전트).
- 잡 컨트롤러(Job Controller): 하나 이상의 파드를 실행하고 성공적으로 완료되면 종료되는 배치(batch) 작업을 관리합니다.
- 크론잡 컨트롤러(CronJob Controller): Job을 주기적으로 또는 특정 시간에 실행하도록 스케줄링합니다 (리눅스의 cron과 유사).
- 퍼시스턴트 볼륨[클레임] 컨트롤러(PersistentVolume[Claim] Controller): 스토리지 자원의 프로비저닝 및 바인딩 생명주기를 관리합니다.
이 모든 컨트롤러들은 공통적으로 kube-apiserver를 통해 클러스터의 현재 상태를 감시(Watch)하고, 필요한 조치를 취할 때도 kube-apiserver를 통해 리소스의 원하는 상태를 변경(Update/Create/Delete)하는 방식으로 동작합니다. 이처럼 끊임없이 ‘현재 상태’를 ‘원하는 상태’로 맞추려는 노력, 즉 재조정 루프(Reconciliation Loop) 또는 제어 루프(Control Loop)가 바로 쿠버네티스가 제공하는 강력한 자동화와 자가 치유 능력의 핵심 원리입니다.
kube-controller-manager 자체도 고가용성을 위해 여러 인스턴스로 실행될 수 있지만, 각 컨트롤러 로직은 오직 하나의 활성 인스턴스(리더, Leader)에서만 실행됩니다. 이는 여러 인스턴스가 동시에 동일한 리소스에 대해 충돌하는 조치를 취하는 것을 방지하기 위함이며, 리더 선출(Leader Election) 메커니즘을 통해 구현됩니다.
결론적으로, kube-controller-manager는 쿠버네티스 클러스터가 사용자의 의도대로, 그리고 예측 가능하게 동작하도록 보이지 않는 곳에서 쉼 없이 일하는 핵심 두뇌 중 하나입니다. 다양한 전문 컨트롤러들의 협업을 통해 클러스터는 마치 살아있는 유기체처럼 스스로 상태를 유지하고 문제를 해결해 나가는 놀라운 능력을 보여줍니다.
6.1.1.5 cloud-controller-manager: 클라우드 연동
cloud-controller-manager(클라우드 컨트롤러 매니저, 이하 CCM)에 대해 알아보겠습니다. 이 책에서는 직접 설치하는 쿠버네티스를 중심으로 다루기 때문에 CCM에 대해서는 간략히 소개하고 넘어가겠지만, 현대의 많은 쿠버네티스 클러스터가 클라우드 플랫폼 위에서 운영되는 만큼 그 역할과 중요성을 이해하는 것은 매우 의미 있습니다.
CCM은 쿠버네티스 클러스터가 Amazon Web Services (AWS), Google Cloud Platform (GCP), Microsoft Azure, OpenStack 등과 같은 특정 클라우드 제공업체(Cloud Provider)의 고유한 서비스와 원활하게 연동될 수 있도록 설계된 컨트롤러 매니저입니다. 우리가 클라우드 환경에서 쿠버네티스를 사용할 때, 클라우드 플랫폼이 제공하는 네이티브 자원들, 예를 들어 가상 머신(VM) 인스턴스, 로드 밸런서, 블록 스토리지 볼륨, VPC 네트워크 라우팅 규칙 등을 쿠버네티스 오브젝트(Node, Service, PersistentVolume 등)와 자연스럽게 통합하고 관리할 필요가 생깁니다. CCM이 바로 이러한 통합 작업을 담당하는 전문 일꾼입니다.
그렇다면 왜 CCM이라는 별도의 컴포넌트가 필요하게 되었을까요? 초기 쿠버네티스 버전에서는 이러한 클라우드별 연동 로직(cloud-specific logic)이 kube-controller-manager 내에 직접 포함되어 있었습니다. 하지만 이는 여러 가지 문제점을 야기했습니다.
- 쿠버네티스 핵심 코드의 비대화: 새로운 클라우드 제공업체를 지원할 때마다 쿠버네티스 핵심 코드베이스에 해당 클라우드에 특화된 코드가 추가되어야 했습니다. 이는 kube-controller-manager를 점점 무겁게 만들었습니다.
- 릴리스 주기 의존성: 특정 클라우드 제공업체의 코드에 버그가 있거나 업데이트가 필요할 경우, 쿠버네티스 전체의 릴리스 주기에 영향을 미칠 수 있었습니다. 클라우드 제공업체의 혁신 속도와 쿠버네티스 자체의 발전 속도가 항상 일치하지는 않기 때문입니다.
- 확장성 및 유지보수 어려움: 새로운 클라우드 제공업체가 쿠버네티스를 지원하려면 쿠버네티스 메인 저장소에 코드를 기여하고 복잡한 승인 과정을 거쳐야 했습니다. 이는 진입 장벽을 높이고 다양한 클라우드 환경으로의 확장을 더디게 만들었습니다.
- 관심사 분리 원칙 위배: 쿠버네티스 코어의 관심사와 특정 클라우드 플랫폼의 세부 구현 사항이 섞여 있는 것은 좋은 소프트웨어 설계 원칙에 어긋났습니다.
이러한 문제들을 해결하기 위해, 쿠버네티스 커뮤니티는 클라우드별 로직을 쿠버네티스 핵심 코드로부터 분리하여 독립적인 cloud-controller-manager 컴포넌트로 옮기는 결정을 내렸습니다. 이를 “out-of-tree” 클라우드 제공업체 지원 방식이라고도 부릅니다. (완전한 out-of-tree는 CCM 외에 CSI, CNI 등도 포함합니다.) 이렇게 분리함으로써 쿠버네티스 코어는 특정 클라우드에 대한 직접적인 의존성을 줄일 수 있게 되었고, 각 클라우드 제공업체는 자신들의 플랫폼에 맞는 CCM을 독립적으로 개발하고 릴리스할 수 있게 되었습니다. 이는 쿠버네티스의 이식성과 확장성을 크게 향상시키는 중요한 변화였습니다.
cloud-controller-manager는 kube-controller-manager와 마찬가지로 여러 종류의 컨트롤러들을 실행하지만, 이 컨트롤러들은 클라우드 제공업체의 API와 상호작용하는 데 특화되어 있습니다. 주요 컨트롤러들은 다음과 같습니다.
- 노드 컨트롤러 (Node Controller – Cloud Specific):이 컨트롤러는 클라우드 플랫폼에서 실행 중인 가상 머신(VM) 인스턴스와 쿠버네티스 클러스터의 Node 오브젝트 간의 상태를 동기화합니다.
- 노드 존재 확인: 클라우드 제공업체의 API를 주기적으로 호출하여, 쿠버네티스에 등록된 각 Node에 해당하는 VM 인스턴스가 실제로 클라우드에 존재하는지, 그리고 정상적으로 작동 중인지 확인합니다. 만약 VM이 클라우드에서 삭제되었다면, 해당 쿠버네티스 Node 오브젝트도 클러스터에서 제거합니다.
- 노드 정보 업데이트: VM의 IP 주소 (내부/외부), 지역(region), 가용 영역(zone), 인스턴스 타입 등 클라우드 고유의 메타데이터를 가져와 Node 오브젝트의 레이블(labels)이나 어노테이션(annotations), 상태(status) 필드에 업데이트합니다. 이 정보는 스케줄러가 파드를 적절한 노드에 배치하거나, 특정 지역에 특화된 서비스를 구성하는 데 활용될 수 있습니다.
- 노드 헬스 체크: 일부 클라우드 제공업체는 자체적인 VM 헬스 체크 메커니즘을 제공하는데, 이를 활용하여 노드의 건강 상태를 판단하고 Node 오브젝트의 컨디션(conditions)을 업데이트할 수도 있습니다.
- 라우트 컨트롤러 (Route Controller):이 컨트롤러는 클라우드 환경의 가상 네트워크(예: AWS VPC, GCP VPC) 내에서 파드들이 서로 다른 노드 간에도 원활하게 통신할 수 있도록 네트워크 라우팅 규칙을 설정하고 관리합니다. 쿠버네티스 클러스터에서 각 파드는 고유한 IP 주소를 가지며, 다른 노드에 있는 파드와 통신하기 위해서는 적절한 라우팅 경로가 필요합니다. 라우트 컨트롤러는 각 노드에 할당된 파드 IP 대역(CIDR 블록) 정보를 바탕으로 클라우드 제공업체의 라우팅 테이블에 필요한 경로를 자동으로 추가하거나 업데이트합니다. (이는 특정 CNI 플러그인과 함께 사용될 때 더욱 중요해집니다.)
- 서비스 컨트롤러 (Service Controller – Cloud Specific):이 컨트롤러는 쿠버네티스 Service 오브젝트 중 특정 타입, 특히 Type=LoadBalancer로 설정된 서비스와 클라우드 제공업체의 로드 밸런서 서비스를 연동하는 핵심적인 역할을 합니다.
- 로드 밸런서 프로비저닝: 사용자가 Type=LoadBalancer로 서비스를 생성하면, 서비스 컨트롤러는 클라우드 제공업체의 API를 호출하여 해당 서비스에 대한 실제 로드 밸런서(예: AWS ELB/NLB/ALB, GCP Cloud Load Balancing, Azure Load Balancer)를 자동으로 생성하고 구성합니다.
- 백엔드 관리: 생성된 로드 밸런서의 백엔드 풀 또는 타겟 그룹에 서비스의 대상이 되는 파드들이 실행 중인 워커 노드들을 등록합니다. 파드들의 상태나 개수가 변경되면 이 정보도 동적으로 업데이트합니다.
- 상태 업데이트: 프로비저닝된 로드 밸런서의 외부 접근 가능한 IP 주소나 DNS 이름을 가져와 해당 Service 오브젝트의 status.loadBalancer.ingress 필드에 기록합니다. 이를 통해 사용자는 외부에서 서비스에 접근할 수 있는 엔드포인트를 알 수 있게 됩니다.일부 클라우드 제공업체에서는 CCM의 서비스 컨트롤러가 클라우드 스토리지를 사용하는 퍼시스턴트 볼륨(PersistentVolume)의 동적 프로비저닝과 관련된 일부 초기화 작업이나 관리를 보조하기도 했지만, 현대 쿠버네티스에서는 스토리지 관련 기능은 대부분 CSI(Container Storage Interface) 드라이버로 이관되었습니다.
만약 여러분이 AWS EKS, GCP GKE, Azure AKS와 같이 클라우드 제공업체가 관리해주는 쿠버네티스 서비스(Managed Kubernetes Service)를 사용한다면, CCM은 이미 클라우드 제공업체에 의해 설정되어 투명하게 작동하고 있을 것입니다. 반대로, 여러분이 클라우드 플랫폼의 VM 위에 직접 쿠버네티스 클러스터를 구축한다면(예: kubeadm 사용), 해당 클라우드 제공업체에 맞는 CCM을 별도로 설치하고 구성해야 클라우드 통합 기능을 온전히 활용할 수 있습니다. 예를 들어, –cloud-provider 플래그를 kube-apiserver, kube-controller-manager, kubelet 등에 설정하고, 해당 클라우드용 CCM 바이너리를 클러스터에 배포해야 합니다.
만약 여러분이 회사 내부의 데이터센터(온프레미스)에서 직접 베어메탈 서버나 자체 가상화 환경 위에 쿠버네티스를 구축한다면, 기본적으로 cloud-controller-manager는 필요하지 않거나 사용되지 않을 가능성이 높습니다. 이 경우, 로드 밸런싱이나 스토리지 연동은 MetalLB, OpenEBS와 같은 온프레미스 환경을 위한 별도의 솔루션을 통해 구현하게 됩니다.
결론적으로, cloud-controller-manager는 쿠버네티스가 특정 클라우드 플랫폼의 장점을 최대한 활용하면서도, 핵심 아키텍처의 독립성과 이식성을 유지할 수 있도록 하는 중요한 설계적 진화의 산물입니다. 이는 쿠버네티스가 오늘날 다양한 환경에서 성공적으로 채택될 수 있었던 유연성의 한 단면을 보여줍니다.
지금까지 컨트롤 플레인을 구성하는 핵심 배우들의 역할과 중요성에 대해 자세히 알아보았습니다. 이들이 서로 어떻게 정보를 주고받으며 협력하는지 이해하는 것은 쿠버네티스의 마법 같은 자동화 기능 뒤에 숨겨진 원리를 파악하는 데 큰 도움이 될 것입니다. 다음으로는 실제 컨테이너들이 실행되는 워커 노드의 구성 요소들을 살펴보겠습니다.