kubectl은 Kubernetes 클러스터를 관리하기 위해 개발된 툴이다.

kubectl의 apply 명령어는 Pod나 Deployment 같은 Kubernetes 리소스를 배포할 때 많이들 사용한다.

하지만 우리 솔직해져보자. kubectl apply 명령어를 실행한 이후부터 클러스터 내부에서 일어나는 일에 대해 얼마나 잘 알고 있는가?

클러스터를 운영하는데 왜 이런 것까지 알아야 하는지 궁금할 수도 있다.

Kubernetes 컨트롤 플레인 내부에서 각 컴포넌트의 상호작용을 이해하면 클러스터 운영 중 문제가 발생했을 때 더욱 정확히 문제 원인 지점을 가려낼 수 있기 때문이다. Kubernetes 클러스터를 잘 쓰려면 반드시 알아야 하는 내용인 것이다.

그래서 이번 아티클에서는 kubectl apply 명령어를 실행하면 컨트롤 플레인 내부에서 어떤 과정을 거치는지 순서대로 살펴보려 한다. 이번 기회에 컨트롤 플레인 내 컴포넌트들이 어떻게 일을 하는지도 정확히 알게 될 것이다.


1. kubectl apply 명령어 입력

우리가 터미널 창에서 kubectl apply 명령어로 Deployment를 배포한다고 가정해보자. 해당 Deployment의 명세를 적어둔 매니페스트 YAML 파일(deployment.yaml)도 이미 갖추고 있는 상태이다.

그렇다면 kubectl apply -f deployment.yaml 명령어를 실행해야 할 것이다. 하지만 이 명령어는 단순히 deployment.yaml 파일을 실행하라는 뜻이 아니다. 내가 Deployment에 대해 정의한 상태(Desired State)를 클러스터에 적용하겠다는 뜻이다.

kubectl apply 명령어를 실행함으로써 kubectl은 로컬에 저장된 deployment.yaml 파일을 읽은 다음 k8s API Server로 보낼 것이다. 이때 k8s API Server는 해당 Deployment 리소스가 이미 존재하는지 확인한다. 만약 없다면 새로 생성하고, 이미 존재한다면 변경된 부분만 수정한다.

아마 이쯤에서 ‘kubectl create 명령어와 다른 점은 무엇인가?‘라는 궁금증이 생길 수도 있다. 꽤 많은 사람들이 혼동하는 두 명령어의 차이도 짚고 넘어가자.

kubectl apply는 기본적으로 선언형(Declarative)이다. 즉, ‘최종적으로 되어야 하는 상태’를 제시한다는 뜻이다.

그래서 아까 설명한 것처럼 리소스가 이미 존재하면 업데이트, 없으면 새로 만드는 것이다. 리소스의 ‘현재 설정’과 ‘적용하려는 매니페스트에 정의된 설정’, ‘직전에 kubectl apply 명령어로 적용했던 설정’을 종합적으로 비교해 최신 변경분만 적용할 수 있다.

kubectl create는 명령형(Imperative)이다. 현재 클러스터의 상태를 고려하기보다는 일회성 지시에 가까운 방식이다.

이미 A라는 이름의 리소스가 존재하는 상황에서 kubectl create 명령어로 A 리소스를 생성하려고 하면 오류가 발생한다. 명령형 접근법은 필요한 동작을 정의하는 데에 집중하기 때문이다.

반면에 kubectl apply 명령어는 원하는 상태만 정의해주면 알아서 업데이트까지 되기 때문에 더욱 편리하다. 우리가 kubectl apply 명령어를 자주 사용하는 이유이다.


2. 컨트롤 플레인 내부 컴포넌트들은 어떤 상호작용을 하는가

이제 kubectl의 차례는 끝났다. 사용자가 입력한 kubectl apply -f deployment.yaml 명령어에 대한 요청은 k8s API Server로 보내진다.

kubectl은 결국 k8s API Server의 REST 엔드포인트를 호출하는 HTTP 클라이언트 역할을 수행하는 것이다.

k8s API Server

k8s API Server는 컨트롤 플레인의 중앙 창구 역할을 수행한다. Controller Manager나 k8s Scheduler와 같은 컨트롤 플레인 내 모든 컴포넌트는 k8s API Server와 소통하기 때문이다.

Admission Controller

kubectl로부터 온 요청이라고 k8s API Server가 다 받아주는 것은 아니다. 이 요청의 주체를 신뢰할 수 있는지(인증), 이 주체가 자신의 권한 내의 요청을 하는지(인가) 먼저 확인한다.

이렇게 k8s API Server 내부에서 인증과 인가를 도와주는 컴포넌트를 Admission Controller라고 한다. 요청 내용 중 빠진 설정을 자동으로 추가 및 수정하거나(Mutating), 요청이 클러스터의 정책에 위배되지 않는지(Validating) 검사하는 역할도 Admission Controller가 수행한다.

etcd

Admission Controller까지 정상적으로 거친 요청은 k8s API Server에 의해 etcd에 저장된다. etcd는 클러스터의 모든 상태 정보를 담고있는 유일한 Key-Value 저장소이다.

이렇게 시스템 상에서 참조해야 할 데이터를 단일 지점에 모아두는 것을 Source of Truth라고 한다. ‘진실만을 공급하는 원천’이란 뜻이다.

k8s API Server에 들어온 Deployment 생성 요청이 etcd에 기록되면 그때부터 deployment.yaml로 정의한 Deployment는 클러스터 상에서 존재해야 할 대상으로 확정된다.

Controller Manager

Kubernetes 클러스터의 컨트롤 플레인에는 Controller Manager라는 컴포넌트가 있다. 클러스터의 현재 상태와 사용자가 새로 원하는 상태(새로운 Deployment 생성)의 차이를 인지하고, 그 차이를 없애기 위해 필요한 작업을 k8s API Server에게 요청하는 역할을 수행한다.

Controller Manager가 이런 일을 할 수 있는 이유는 실시간으로 k8s API Server와 통신하기 때문이다. k8s API Server를 통해 etcd에 저장된 현재 클러스터 상태와 사용자가 원하는 상태를 받아서 두 상태의 차이를 줄이기 위해 계속해서 조정하는 것이다.

사실 Controller Manager는 단일 컴포넌트가 아니다. 각 리소스의 상태를 담당하는 여러 Controller를 하나의 프로세스로 실행한 것이라고 보는 게 더 정확하다.

대부분의 리소스마다 담당하는 Controller가 있다. 특히 워크로드 리소스(Pod를 포함하는 리소스)에는 모두 담당 Controller가 클러스터에서 동작 중이다. 예를 들면 아래와 같은 식이다.

  • Deployment Controller: Deployment 리소스 감시, 버전 관리(Rolling Update)와 ReplicaSet 생성 담당
  • ReplicaSet Controller: Pod의 개수가 사용자가 설정한 복제본(Replicas) 수와 일치하는지 계속 확인하고 조정

이제 deployment.yaml 매니페스트로 Deployment를 새로 배포하는 시나리오를 계속 이어가보자.

Deployment Controller는 k8s API Server를 통해 etcd로부터 우리의 새로운 Deployment의 명세(Spec)를 가져온 다음, 해당 spec에 맞춰 k8s API Server에게 필요한 ReplicaSet를 생성하라고 요청할 것이다.

만약 k8s API Server가 etcd에 ReplicaSet의 Spec을 저장했다면, ReplicaSet Controller가 이를 감지하고 k8s API Server에게 필요한 Pod를 생성하라고 요청한다.

k8s Scheduler

Controller Manager로 인해 k8s API Server가 필요한 Pod의 Spec을 etcd에 기록했다면, 이제 Pod가 어느 노드에 배포될지 정해줘야 한다. k8s Scheduler가 바로 그 역할을 수행한다.

etcd에 갓 기록된 Pod의 Spec에는 아직 nodeName 필드가 비어있다. 이렇게 어느 노드로 배포될지 정해지지 않은 Pod가 있다면 k8s Scheduler가 k8s API Server를 통해 실시간으로 감지한다.

이후 k8s Scheduler는 필터링 작업을 시작한다. 해당 Pod가 요구하는 자원(CPU, 메모리)을 가지고 있는 노드를 확인하며, NodeSelector나 Affinity, Taint/Toleration 등의 노드 할당 관련 규칙도 함께 체크한다.

클러스터 내 모든 노드의 상태는 k8s API Server를 통해 주기적으로 etcd에 업데이트된다. k8s Scheduler는 k8s API Server를 통해 etcd에 저장된 노드들의 상태를 확인하는 것이다.

다음으로 점수 매기기(Scoring)를 수행한다. 필터링을 통과한 후보 노드들 중에 해당 Pod가 배포되기 가장 좋은지를 점수로 매기는 것이다. 자원의 여유 정도나 필요한 컨테이너 이미지 보유 여부 등이 점수 기준이 된다.

필터링된 후보 노드들 중에 최고 점수를 받은 노드가 최종 배포 노드로 선정되는 것이다.

마지막으로 k8s Scheduler가 k8s API Server에게 요청해 해당 Pod의 nodeName에 최종 선정된 노드 이름을 기입하도록 한다.

지금까지 살펴본 컨트롤 플레인 내부 컴포넌트들은 모두 k8s API Server와만 소통한다. 그 이유는 아래와 같은 장점이 있기 때문이다.

  1. 보안에 유리: k8s API Server 간의 통신에 대한 보안만 신경쓰면 된다.
  2. 복잡성 감소: 모든 노드에 대해 직접 작업하지 않고 k8s API Server에게 요청하기만 하면 되므로 로직 복잡성과 네트워크 부하가 줄어든다.

마무리

지금까지 kubectl apply 명령어 실행 후 컨트롤 플레인 내부에서 어떤 작업이 이뤄지는지 살펴봤다. 위 내용을 종합한 전체적인 그림은 아래와 같다.

이렇게 Kubernetes 리소스 생성을 위한 컨트롤 플레인 내부의 작업 흐름을 살펴보면 클러스터 운영에 필요한 디버깅 능력을 키울 수 있다.

예를 들면 이렇다. 매니페스트 파일에는 문제가 없는데 kubectl apply 명령어를 실행하자마자 에러가 발생하거나 요청이 거부되면 어떻게 해야 할까?

kubectl 명령어 실행에 대한 요청은 k8s API Server가 받게 되어있으므로 k8s API Server의 로그를 가장 먼저 살펴봐야 할 것이다.

만약 kubectl apply 명령어 실행은 성공했는데 Pod가 계속 Pending 상태에 머무르면 어떻게 해야 할까?

Pod를 조회할 수 있다는 것은 Controller Manager가 k8s API Server를 통해 etcd에 Pod의 Spec이 잘 저장되었다는 뜻이다. 그래서 k8s API Server나 Controller Manager의 문제는 아니라고 추측할 수 있다.

이럴 땐 kubectl describe pod 명령어의 출력으로 먼저 원인이 되는 오류 메시지를 확인할 수 있겠다. 만약 별다른 오류 메시지가 보이지 않을 땐 Pod를 노드에 할당시키는 작업이 수행되지 않았을 수 있으므로 k8s Scheduler의 로그를 살펴봐야 할 것이다.

이번 아티클을 통해 Kubernetes 클러스터에서 컨트롤 플레인 내부 컴포넌트가 Pod를 생성하기 위해 어떻게 일을 하는지 감을 잡고 이해했길 바란다.

추후 다른 아티클에서는 Pod 생성을 위해 클러스터의 워커 노드 내부에서 어떤 작업이 이뤄지는지 흐름을 따라 살펴보는 시간도 가져보겠다.