쓰기만 했던 개발자가 궁금해서 찾아본 쿠버네티스 내부 2편

쓰기만 했던 개발자가 궁금해서 찾아본 쿠버네티스 내부 2편

요약: 이 글은 쿠버네티스 두 가지 주요 특징인 Hub and Spoke 패턴과 선언적 동작 방식을 소개하고, 실제 애플리케이션이 쿠버네티스에서 배포되는 과정을 레스토랑 역할 분담에 비유하여 설명합니다. API server, etcd, Controller Manager, Scheduler, Kubelet, Container Runtime 등 주요 컴포넌트가 각각 어떻게 협력하여 애플리케이션 배포를 관리하고 유지하는지 구체적으로 다룹니다. 이러한 비유 덕분에 쿠버네티스 내부 동작을 명확히 이해할 수 있습니다.

💡 리뷰어 한줄평

hunter.lee 쿠버네티스를 사용하는 개발자라면 꼭 한 번은 읽어 봐야 하는 글입니다! 항상 복잡하고 어렵게만 느껴진 쿠버네티스였다면, 잭의 글을 통해 쉽게 이해할 수 있습니다. 자신 있게 쿠버네티스를 활용해 보세요!

dory.m 사장님, 여기 ReplicaSet 2개요! 인프라에 관심 많은 백엔드 개발자분들께 강력히 추천드리는 쿠버네티스 맛집 아티클이에요! 잭의 설명을 따라가다 보면 레스토랑에서 맛있는 파스타를 먹듯 즐겁게 쿠버네티스를 이해할 수 있게 됩니다.

시작하며

안녕하세요. 머니제휴서비스파티 잭입니다.

이전 글에서 쿠버네티스의 2가지 특징인 “Hub and spoke 패턴”과 “선언적 동작 방식”에 대해서 알아봤습니다. 복습 차원에서 2가지 특징을 다시 요약해 보겠습니다.

특징동작 방식장점
Hub and Spoke 패턴컨트롤 플레인 내 통신은 API Server를 통해서 한다데이터 일관성, 기능 확장성
선언적 동작 방식쿠버네티스는 사용자가 원하는 상태는 무조건 맞추기 위해 노력한다쿠버네티스 자동 관리

이 2가지 특징을 통해서 쿠버네티스를 큰 그림으로 이해할 수 있었습니다. 이번에는 실제 사례를 통해서 쿠버네티스의 내부 동작을 알아보겠습니다. 살펴볼 사례는 내 애플리케이션이 쿠버네티스에서 배포되는 상황 입니다. 쿠버네티스에서 배포될 때 어떤 동작이 일어나는지 궁금하셨던 분들에게 도움이 되실거라 생각합니다. 이번에도 레스토랑 비유를 통해서 쿠버네티스의 내부 동작을 설명하겠습니다.

등장하는 컴포넌트들

아래 그림은 쿠버네티스의 주요 컴포넌트를 나타내는 그림입니다.

그림 속 쿠버네티스의 컴포넌트는 아래와 같습니다.

  • API server
  • etcd
  • Controller Manager
  • Scheduler
  • Kubelet
  • Container Runtime

각 컴포넌트의 위치를 확인해 보면 API server, etcd, Controller Manager, Scheduler는 마스터 노드 서버에 존재합니다. 반면에, Kubelet, Container Runtime은 각 노드 서버 별로 존재합니다.

다양한 컴포넌트 등장에 생소한 분들이 많을 거라 생각합니다. 그래서 이 컴포넌트들이 내 애플리케이션이 쿠버네티스에서 배포되는 상황 에서 어떤 역할을 하는지 차근차근 설명해 보겠습니다.

레스토랑 비유의 확장

이전 글에서 쿠버네티스를 점장과 요리사가 있는 레스토랑에 비유했습니다. 이번 글에서는 이 비유를 확장하여 쿠버네티스의 핵심 컴포넌트를 구체적으로 살펴보겠습니다. 쿠버네티스는 여러 개의 컴포넌트가 협력하여 애플리케이션을 관리하고 배포합니다. 이러한 각 컴포넌트가 어떻게 상호작용하는지 이해하는 것이 중요합니다.

아래 표는 레스토랑의 등장인물들이 어떤 역할을 하며, 쿠버네티스에서는 어떤 컴포넌트와 매칭되는지 작성한 표입니다.

등장인물역할매칭되는 컴포넌트
점장레스토랑을 총괄하며 모든 대화는 점장을 통해서만 진행API server
주문장부레스토랑의 모든 주문서가 기록되며 요리 상태에 따라 수시로 변경etcd
총 주방장주방을 총괄하며 주문에 맞는 추가 요리 준비. 모든 주방이 잘 동작하는지 관리Controller Manager
주방 배정자모든 주방의 리소스를 파악해 어떤 주방에 요리를 맡길지 결정Scheduler
주방 담당자각 주방의 담당자이며, 주방 내 요리가 잘 만들어지는지 확인하고, 수시로 점장에게 보고Kubelet
요리사실제 요리를 만드는 역할. 주방 담당자에게 지시를 받음Container Runtime

이 표만으로는 레스토랑에서 실제로 어떤 일이 일어나는지 완전히 이해하기 어려울 수도 있습니다. 그래서 레스토랑에 주문이 들어오면 각 등장인물들이 어떤 역할을 하는지 전체적인 플로우로 살펴보겠습니다.

이 전글에서 설명했던 내용처럼 이 레스토랑은 점장을 통해서만 대화가 진행된다는 것을 확인할 수 있습니다. 또한, 레스토랑에서 각 직원이 자신이 맡은 역할을 성실히 수행해야 파스타 주문이 완성됩니다.

쿠버네티스도 마찬가지입니다. 각 컴포넌트가 자신의 역할을 성실히 수행하고 협력해야만 애플리케이션을 성공적으로 배포할 수 있습니다. 이제, 내 애플리케이션이 쿠버네티스에 배포될 때 실제로 어떤 일이 일어나는지 레스토랑에 비유하여 단계별로 살펴보겠습니다.

“내 애플리케이션이 쿠버네티스에서 배포될 때 일어나는 일”

주문서: 애플리케이션 배포를 위한 매니패스토 파일

고객이 점장에게 주문서를 전달하는 것처럼 쿠버네티스에게 전달하는 주문서를 매니패스토 파일이라 불렀습니다.

참고) 이해를 돕기 위해 매니패스토 파일 내 필요한 필드만 예시로 가져왔습니다.

# my-app.yaml

kind: Deployment # 종류
metadata:
  name: my-app # 이름
spec:
  replicas: 3 # 개수
  template:
    spec:
      containers:
        - name: nginx
          image: nginx:1.21.6 # 컨테이너 이미지
          ports:
            - containerPort: 80

이 매니패스토 파일에는 제가 원하는 걸 적었고, 저는 쿠버네티스에게 이렇게 말하고 있습니다.

  • “내 애플리케이션을 배포하려고 해. Deployment라는 객체를 사용할 거고”
  • “그 내부에 Pod의 개수는 3개로 할 거야”
  • “그리고 Pod 내부 컨테이너의 이미지는 nginx로 할 거야”

쿠버네티스와 대화하는 도구: kubectl

매니패스토 파일을 쿠버네티스에게 전달하기 위해서는 kubectl이라는 도구를 이용해야 합니다. 제 매니패스토 파일을 전달해 보겠습니다.

kubectl apply -f my-app.yaml

kubectl은 내부적으로 HTTP 통신으로 my-app.yaml을 쿠버네티스에게 전달합니다. 실제 HTTP 통신을 하는지 보고 싶다면 -v 옵션을 추가하면 됩니다. -v=1~10까지 로그 레벨을 지정할 수 있고, 레벨에 따라 상세한 로그를 볼 수 있습니다. 한번 -v=8을 지정해 HTTP 통신 로그를 살펴보겠습니다.

  • 명령어
kubectl apply -f my-app.yaml -v=8
  • 예시 결과
I0827 06:51:49.950850    8021 loader.go:375] Config loaded from file:  /home/user/.kube/config
I0827 06:51:49.954797    8021 round_trippers.go:420] POST https://example.com:6443/api/v1/namespaces/default/pods
I0827 06:51:49.954817    8021 round_trippers.go:427] Request Headers:
I0827 06:51:49.954821    8021 round_trippers.go:431]     Accept: application/json
I0827 06:51:49.954826    8021 round_trippers.go:431]     Content-Type: application/json
I0827 06:51:49.954831    8021 round_trippers.go:431]     User-Agent: kubectl/v1.19.0 (linux/amd64) kubernetes/e199641
I0827 06:51:49.954831    8021 round_trippers.go:431]     Authorization: Bearer
I0827 06:51:50.141125    8021 round_trippers.go:446] Response Status: 201 Created in 186 milliseconds
I0827 06:51:50.141185    8021 round_trippers.go:449] Response Headers:
I0827 06:51:50.141197    8021 round_trippers.go:452]     Content-Type: application/json
I0827 06:51:50.141214    8021 round_trippers.go:452]     Date: Fri, 27 Aug 2021 10:51:50 GMT

점장과 주문 장부: 배포 주문을 받은 API server와 데이터 저장소 etcd

주문서가 점장에게 전달되는 것처럼 쿠버네티스에서는 제 매니패스토 파일을 API server가 받습니다.

매니패스토 파일을 전달받은 API server는 해당 파일을 etcd 저장소에 저장합니다. 레스토랑에서 점장이 까먹지 않기 위해 주문서를 주문장부에 기록하는 행동과 동일합니다. 이 저장소는 모든 쿠버네티스 객체의 상태를 기록하는 중심 데이터베이스 역할을 하며, 덕분에 시스템 전체의 일관성과 복구 기능을 보장합니다. 이처럼 etcd는 쿠버네티스와 관련된 모든 데이터가 저장되는 장소입니다. 모든 데이터를 key-value 형식으로 저장합니다.

제가 전달했던 매니패스토 파일은 아래처럼 key-value 형식으로 변환되어 저장됩니다. 여기서 key는 쿠버네티스가 데이터를 구분하기 위한 식별자이고, value는 실제 매니패스토 파일을 기반한 데이터입니다.

참고) etcd의 key-value 값에 대한 자세한 설명은 블로그를 참고해 주세요.

'/registry/deployments/default/my-app':
  {
    'kind': 'Deployment',
    'metadata':
      {
        'name': 'my-app',
        'namespace': 'default',
        'labels': { 'app': 'my-app' },
      },
    'spec':
      {
        'replicas': 3,
        'template':
          {
            'spec':
              { 'containers': [{ 'name': 'nginx', 'image': 'nginx:1.21.6' }] },
          },
      },
  }

총 주방장: 쿠버네티스의 전체적인 상황을 체크하는 Controller Manager

주문서를 기록한 점장은 이제 주방을 총괄하는 총주방장에게 파스타 2개가 주문 접수된 사실을 알립니다. 총주방장은 각 주문에 기본적으로 추가되는 빵과 샐러드를 주문서에 추가합니다.

또한, 총주방장은 현재 모든 주방에서 만들어지는 요리가 주문서에 맞게 잘 만들어지는지 전체적으로 관리하는 역할도 있습니다. 예를 들어, 파스타 2개가 만들어져야 할 주문서인데 1개만 만들어지고 있다면 그걸 확인해 추가로 1개를 더 만들도록 지시합니다. 쿠버네티스에서는 이와 같은 역할을 Controller Manager가 담당합니다.

Controller Manager는 사용자가 전달한 매니패스토 파일을 처리하기 위해서 상황에 따라 추가적인 객체를 준비합니다. 예를 들어, 저희는 현재 쿠버네티스에게 배포를 위해 Deployment 객체 생성을 요청했습니다. Deployment 객체는 배포를 완전히 수행하기 위해서 추가적인 객체가 필요합니다. 바로, ReplicaSet이나 Pod입니다.

마치 파스타 주문이 들어오면 필수적으로 함께 나가야 하는 샐러드나 빵을 의미합니다. 손님은 주문서에 파스타만 적었지만 총주방장은 샐러드와 빵도 함께 준비해야 합니다. 또한, Controller Manager는 쿠버네티스의 전체적인 상황을 지켜보면서 현재 쿠버네티스의 상태와 사용자가 원하는 상태가 일치하는지 확인합니다.

Controller Manager가 관리하는 Controller들

Controller Manager는 쿠버네티스의 상태를 올바르게 유지하기 위해 노력한다고 말씀드렸죠. 구체적으로 이 역할은 Controller Manager 내부에서 여러 Controller들이 담당합니다. 내부에 각 Controller는 쿠버네티스의 올바른 상태를 유지하기 위해 각 역할에 맞는 작업을 합니다.

https://www.sobyte.net/post/2022-07/k8s-controller-manager/
https://www.sobyte.net/post/2022-07/k8s-controller-manager/

이미지 출처: How kubernetes Controller Manager works

위 그림을 보면 Controller Manager는 watch 메커니즘으로 변경사항 이벤트를 전달받습니다. 해당 이벤트의 종류에 따라서 처리하는 Controller가 달라집니다.

다시 애플리케이션을 배포하는 플로우로 돌아가겠습니다. API server가 etcd에 매니패스토 파일을 저장했습니다. 이 사실을 watch 메커니즘으로 지켜보고 있던 Controller Manager에게 전달합니다. 이때, 위 그림처럼 이벤트의 종류에 따라 처리하는 Controller가 달라지는데, 배포와 관련된 이벤트를 담당하는 Controller는 DeploymentController입니다.

watch 메커니즘으로 매니패스토 파일이 생성됐다는 이벤트를 전달받은 DeploymentController는 배포를 위해 필요한 객체들인 ReplicaSet, Pod을 준비합니다. 이렇게 DeploymentController가 Pod을 생성하면, 이제 쿠버네티스는 이 Pod을 어떤 노드에서 실행할지 결정해야 합니다.

주방 배정자: Pod이 어떤 노드에서 실행할지 결정하는 Scheduler

총주방장이 주문서에 빵과 샐러드를 추가해 주문서를 변경했다면, 이제는 어떤 주방에서 요리를 할지 결정해야 합니다. 레스토랑은 여러 개의 주방을 운영하고 있기 때문에 각 주방의 상황에 맞게 요리를 분배해줘야 합니다. 이미 많은 요리가 진행 중인 주방에 요리를 더 맡기면 주방 담당자가 힘들어하고, 요리가 늦어지기 때문입니다.

어떤 주방에 분배할지 고민하는 역할은 주방 배정자입니다. 주방 배정자는 여러 주방 중 요리를 맡길 수 있는 주방을 선별하고, 선별된 주방 중 가장 적절한 주방을 선택합니다.

그럼 쿠버네티스에는 이 역할을 어떤 컴포넌트가 수행할까요? 바로 Scheduler입니다.

Scheduler는 의미 그대로 애플리케이션이 어떤 노드에서 실행될지 결정하는 역할입니다. 여기서 노드는 레스토랑의 주방의 역할로 쿠버네티스에서는 애플리케이션이 실행되는 장소입니다. Scheduler는 여러 노드의 리소스를 확인하고 이미 리소스가 부족한 노드에게는 새로운 애플리케이션을 맡길 수 없겠죠.

Scheduler는 실제로 필터링과 스코어링이라는 과정을 수행합니다.

  • 필터링: 애플리케이션이 실행 가능할 정도로 리소스 여유가 있는 노드들을 구분합니다.
  • 스코어링: 필터링을 통과한 노드들에 점수를 부여하여 가장 적합한 노드를 선택합니다.

참고) Scheduler의 필터링과 스코어링에 대한 자세한 설명은 공식 문서를 참고해 주세요.

Scheduler가 애플리케이션이 실행될 노드를 결정했다면 이제는 애플리케이션이 해당 노드에서 실행되는 과정이 필요합니다.

주방 담당자: 각 노드에 상주하고 있는 Kubelet이 애플리케이션을 실행시키고 관리한다.

주방 배정자가 어떤 주방에서 요리가 진행될지 결정했다면, 이 소식은 해당 주방의 담당자에게 전달됩니다. 주방 담당자는 각 주방을 관리하며, 주방 내 요리사들이 요리를 잘 만들고 있는지 확인합니다. 또한, 수시로 주방의 상황을 점장에게 보고합니다.

이렇게 중요한 역할을 담당한 주방 담당자가 일을 제대로 못한다면 점장은 해당 주방이 정상적으로 운영되는지 확인할 방법이 없습니다.

쿠버네티스에서 이 역할을 담당하는 컴포넌트는 Kubelet입니다.

위 그림처럼 Kubelet은 각 노드에 1개씩 존재하며, 여러 가지 역할을 담당하고 있습니다. 내 애플리케이션과 직접적인 관련이 있는 컴포넌트이기 때문에 Kubelet의 역할에 대해서는 자세히 설명하겠습니다.

Kubelet의 역할

Kubelet의 역할은 크게 아래와 같습니다.

  • Pod spec 검토
  • 컨테이너와 관련된 여러 작업 지시: 컨테이너 이미지 pulling , 컨테이너 생성, 컨테이너 실행
  • Pod 헬스 체크 및 라이프 사이클 관리
  • 노드나 Pod의 로그 및 매트릭 수집

각 역할은 쿠버네티스의 안정적인 운영을 위해 필수적입니다. 하나씩 설명해 보겠습니다.

  • Pod spec 검토

Kubelet은 watch 메커니즘을 통해서 API server로부터 애플리케이션 실행 정보가 담긴 Pod spec을 전달받습니다.

# pod spec

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
  labels:
    app: my-app
spec:
  containers:
    - name: my-container
      image: nginx:1.21.6
      ports:
        - containerPort: 80

Pod spec을 전달받은 Kubelet는 자신이 담당하는 노드에서 이 Pod이 실행할 수 있는지 검토합니다. 노드의 자원이 넉넉한지, Pod이 사용하는 스토리지 설정이 올바른지 등 다양한 요소를 검토해 실행 가능한지 파악합니다.

레스토랑 비유로 한다면, 주문서를 전달받은 주방담당자가 이 주방에서 이 요리들을 만들 여력이 되는지 등을 판단하는 과정이라고 생각하시면 됩니다.

  • 컨테이너와 관련된 여러 작업 지시: 컨테이너 이미지 pulling , 컨테이너 생성, 컨테이너 실행

Pod spec을 검토 후 Pod을 실행해도 되겠다 판단한 Kubelet은 컨테이너 실행을 위한 여러 작업을 수행합니다. 바로 컨테이너 이미지 pulling, 컨테이너 생성, 컨테이너 실행입니다. 컨테이너 이미지는 Pod spec에 정의되어 있기에 해당 정보를 바탕으로 이미지 레지스트리에서 이미지를 가져옵니다.

컨테이너 생성과 실행이 헷갈리실 수 있지만, 생성은 컨테이너의 틀을 만드는 작업입니다. 컨테이너의 파일시스템이나 환경 변수 등을 준비하는 작업을 의미합니다. 반면, 실행은 준비된 컨테이너를 실제 프로세스로 동작시키는 과정입니다.

사실 위 작업을 Kubelet이 직접 하는 것은 아니고 해당 작업을 지시합니다. 실제 실행하는 역할은 뒤에서 설명하는 Container Runtime이라는 컴포넌트가 담당합니다. 레스토랑 비유로 하자면, 주방담당자가 주방 내 요리사들에게 재료 준비하고, 요리를 만들라고 지시하는 상황이라고 생각하시면 됩니다.

  • Pod 헬스 체크

Pod을 실행시킨 Kubelet은 정기적으로 이 Pod이 정상적으로 실행 중인지 확인하는데, 이를 헬스체크라고 합니다. 헬스체크는 크게 두 가지 요소가 있습니다.

  1. 살아있는지 체크: 만약 애플리케이션이 살아있지 않다면 Kubelet은 해당 Pod을 재시작합니다. Liveness Probe 메커니즘이라 부릅니다.
  2. 트래픽을 받을 수 있는지 체크: 애플리케이션이 살아있지만 실제 트래픽을 받을 수 있는지 확인하는 작업입니다. 만약 애플리케이션이 트래픽을 받을 준비가 되지 않았다면 Kubelet은 해당 Pod에 트래픽을 전달하지 않습니다. Readiness Probe 메커니즘이라 부릅니다.

주기적인 헬스 체크를 통해 Kubelet은 애플리케이션의 상태를 확인하며 필요한 조치를 합니다. 레스토랑 비유로 하자면, 요리사들이 만든 요리가 주문대로 잘 만들어졌는지 수시로 확인한다고 생각하시면 됩니다.

  • 노드나 Pod의 로그 및 매트릭 수집

Kubelet은 노드에 상주하며 노드와 노드 내 실행 중인 Pod들의 로그 및 메트릭을 수집합니다. 수집된 로그 및 메트릭은 모니터링 도구가 시각화하고 분석합니다. 시각화된 로그는 DevOps 관리자가 쿠버네티스의 상태를 확인하는 데 사용합니다.

Kubelet의 역할 요약

Kubelet은 각 노드에 상주하며 다양한 역할을 수행합니다. 마치 노드의 책임자라고 부를 수 있습니다. 특히 내 애플리케이션의 실행 및 라이프 사이클을 관리하기에 더욱 사용자와 밀접한 컴포넌트입니다.

Kubelet에서 언급했던 내용 중 작업을 지시한다는 내용이 있었습니다. Kubelt이 작업을 지시하면 실제로 역할을 수행하는 컴포넌트는 Container Runtime입니다.

이제 Container Runtime을 설명하겠습니다.

요리사: 실제 애플리케이션을 실행시키는 역할은 docker와 같은 Container Runtime이다.

레스토랑 비유로 돌아가서 주방 담당자는 배정된 요리를 주방 내 요리사에게 만들라고 지시합니다. 지시를 받은 요리사는 실제 요리를 만들어냅니다.

쿠버네티스에서는 이 역할은 Container Runtime이라는 컴포넌트가 수행합니다. 혹시 Container Runtime이라는 용어가 낯설다면 docker를 생각하시면 됩니다. Container Runtime은 Kubelet의 지시를 받아 컨테이너 이미지를 가져오고 해당 이미지로 컨테이너를 생성하고 실행합니다.

Container Runtime에 대해서도 다양한 주제가 있지만 깊은 내용이 될 거 같아 넘어가겠습니다. 더 관심 있으신 분들은 Explaining Container Runtimes: Docker, containerd and CRI-O를 참고해 주세요.

“내 애플리케이션이 쿠버네티스에서 배포될 때 일어나는 일” 요약

지금까지 숨 가쁘게 달려왔습니다. 레스토랑에서 요리가 만들어질 때 각 담당자가 어떤 역할을 하는지를 쿠버네티스의 컴포넌트와 비교해 보았습니다. 이제는 레스토랑에서 일어났던 전체 플로우를 쿠버네티스의 컴포넌트로 변경해서 정리해 보겠습니다.

각 컴포넌트들의 역할을 상세히 적어봤습니다.

컴포넌트역할레스토랑
API Server쿠버네티스 모든 통신의 중심점장
etcd쿠버네티스의 모든 정보가 기록되는 저장소주문장부
Controller Manager쿠버네티스의 상태와 사용자의 원하는 상태가 일치하는지 수시로 체크하고 조치총 주방장
Scheduler노드의 리소스를 계산해서 애플리케이션이 배포되기에 최적화된 노드 배정주방배정자
Kubelet각 노드의 상태를 보고하고 노드 내 애플리케이션이 정상 동작하는지 확인주방담당자
Container RuntimeKubelet의 명령을 받고 실제로 애플리케이션을 실행요리사

마치며

쓰기만 했던 개발자가 궁금해서 찾아본 쿠버네티스 내부라는 주제로 1, 2편에 걸쳐서 긴 글 읽어주셔서 감사합니다.

1편에서는 쿠버네티스의 2가지 특징인 Hub and Spoke 패턴과 선언적 동작 방식에 대해서 알아봤습니다.

2편에서는 쿠버네티스의 핵심 컴포넌트들이 내 애플리케이션이 쿠버네티스에서 배포되는 상황에서 어떤 역할을 하는지 알아봤습니다.

이번 블로그 시리즈는 저에게도 큰 도움이 됐습니다. 알고 있는 지식을 글로 적으면서 더 깊게 공부하고 이해할 수 있었습니다. 독자분들도 쿠버네티스의 내부 동작을 이해하는데 도움이 되셨길 바라겠습니다. 레스토랑 비유를 사용했지만, 때때로 실제 쿠버네티스의 동작과는 맞지 않는 맥락이 있을 수 있습니다. 넓은 마음으로 이해 부탁드립니다.

감사합니다.

참고자료

jack.comeback
jack.comeback

머니코어서비스파티팀 잭입니다. 계획과 피드백을 통한 성장을 좋아하는 개발자입니다.

태그