컨테이너 하나를 배포하는 것은 간단합니다. 열 개를 배포하는 것은 관리할 만합니다. 하지만 수십 대의 머신에서 수백 개의 컨테이너를 실행하게 되면, 수동 관리는 단순히 어려운 수준을 넘어 불가능해집니다. 어떤 컨테이너가 어디에 있는지 파악할 수 없게 됩니다. 서버가 다운되면 누군가 깨어나서 재시작할 때까지 애플리케이션은 사라져 버립니다. 새로운 인스턴스를 생성하기도 전에 트래픽 급증이 시스템을 압도해 버립니다. 바로 이 지점에서 Kubernetes가 등장합니다. 이는 단순한 DevOps 도구가 아닙니다. 컨테이너 관리를 스크립팅 작업이 아닌 제어 문제로 다루는 오케스트레이션 레이어입니다.

왜 스크립트는 결국 한계에 부딪히는가

대부분의 팀은 쉘 스크립트나 기본적인 자동화로 시작합니다. 이미지를 가져오고, 컨테이너를 시작하고, 로그를 모니터링하고, 실패한 프로세스를 재시작하는 명령어를 작성합니다. 이러한 방식은 개념 증명(PoC) 단계에서는 작동하지만, 실제 운영 환경의 부하 앞에서는 무너집니다. 마이크로서비스는 서로 다른 호스트 간에 통신하고, 특정 환경 변수에 의존하며, 컨테이너 재시작 후에도 유지되는 영구 스토리지(persistent storage)가 필요하고, 버전 간의 일관된 네트워킹을 기대합니다. 스크립트는 가상 머신이 사라졌을 때 워크로드를 자동으로 재스케줄링할 수 없습니다. 또한 크래시 루프(crash loop)에 빠진 인스턴스는 건너뛰면서 정상적인 인스턴스에만 네트워크 트래픽을 분산시킬 수도 없습니다. Kubernetes는 클러스터 자체가 이러한 결정을 내리도록 함으로써 이 문제를 해결합니다. 사용자가 원하는 상태를 기술하면, 시스템이 해당 상태를 지속적으로 유지합니다.

Kubernetes가 제공하는 세 가지 핵심 가치

Kubernetes는 수동적인 문제 해결(firefighting)을 자동화된 신뢰성으로 대체하는 세 가지 핵심 기능을 제공합니다.

**고가용성(High availability)**은 인프라의 일부가 실패하더라도 애플리케이션이 온라인 상태를 유지함을 의미합니다. 컨테이너가 충돌하면 Kubernetes는 몇 초 내에 이를 교체합니다. 워커 노드 전체가 다운되면, 스케줄러가 하트비트(heartbeat)가 끊긴 것을 감지하고 영향을 받은 워크로드를 클러스터 내 다른 정상적인 머신으로 이동시킵니다. 시스템은 정의된 원하는 상태(desired state)를 지속적으로 감시하며, 사람의 개입 없이 편차를 수정합니다.

**확장성(Scalability)**은 사용자의 증가에 맞춰 애플리케이션도 함께 성장함을 의미합니다. 단 2시간의 트래픽 피크를 견디기 위해 20대의 서버를 미리 준비하는 대신, CPU 사용량이나 요청 지연 시간(latency)과 같이 중요한 지표를 정의하고 임계값을 넘을 때 클러스터가 더 많은 컨테이너 인스턴스를 추가하도록 합니다. 수요가 줄어들면 복제본(replica) 수도 다시 줄어듭니다. 필요한 만큼, 필요한 때에만 비용을 지불하면 됩니다.

**재해 복구(Disaster recovery)**는 장애 발생 후에도 데이터와 설정이 복구됨을 의미합니다. Kubernetes는 클러스터의 전체 상태를 분산 키-값 저장소(distributed key-value store)에 저장합니다. 치명적인 장애로 워커 노드나 컨트롤 플레인의 일부가 삭제되더라도, 저장된 상태 덕분에 시스템은 워크로드를 구성된 그대로 재구축할 수 있습니다. 오케스트레이터가 원래 어떤 모습이어야 하는지를 기억하고 있기 때문에 데이터가 복구되는 것입니다.

설정: 두뇌와 근육

Kubernetes 클러스터에는 이 글에서 '두뇌(brain)'와 '근육(muscle)'으로 묘사하는 두 가지 근본적인 역할이 있으며, 이 비유는 실제 환경에서도 매우 적절합니다.

**마스터 노드(Master Node)**는 두뇌입니다. 고객에게 직접 노출되는 애플리케이션을 실행하지 않습니다. 대신 작업을 스케줄링하고, 클러스터 상태를 관리하며, 변경 사항에 대응하는 컨트롤 플레인(control plane) 구성 요소를 호스팅합니다. 명령을 내리거나 설정 파일을 제출하면, 마스터 노드는 워크로드가 어디에서 실행되어야 하는지, 상태가 정상인지, 그리고 상태가 좋지 않을 때 어떻게 해야 할지를 결정합니다.

**워커 노드(Worker Nodes)**는 근육입니다. 각 워커는 마스터와 통신하는 경량 에이전트를 실행하며, 컨테이너 런타임(container runtime)을 사용하여 실제 포드(pod)를 실행합니다. 이 노드들은 애플리케이션 코드가 CPU와 메모리를 사용하는 곳입니다. 워커 노드를 추가하면 클러스터의 물리적 용량이 늘어납니다. 중복성(redundancy)을 위해 마스터 노드를 추가하면 컨트롤 플레인이 개별 하드웨어 장애에 대해 탄력성을 갖게 됩니다.

Pods, Containers, and Services

Kubernetes를 사용하려면 소프트웨어가 어떻게 패키징되고 접근되는지를 정의하는 세 가지 용어를 이해해야 합니다.

**컨테이너(Containers)**는 애플리케이션을 종속성, 라이브러리 및 설정과 함께 묶은 패키지입니다. 컨테이너는 소프트웨어를 기본 호스트로부터 격리하여 개발, 스테이징, 운영 환경에서 동일하게 실행되도록 합니다.

Pod은 Kubernetes에서 배포 가능한 가장 작은 단위입니다. Pod는 리소스를 공유해야 하는 하나 이상의 컨테이너를 감쌉니다. 이들은 동일한 네트워크 네임스페이스를 공유하며 동일한 로컬 스토리지 볼륨에 액세스할 수 있습니다. 여기서 중요한 점은, 컨테이너를 직접 배포하는 것이 아니라 컨테이너를 담고 있는 Pod를 배포한다는 것입니다. 또한 Pod는 의도적으로 일시적(ephemeral)입니다. 상황이 변함에 따라 생성, 파괴 및 교체됩니다. 설계 단계부터 수명이 동적으로 변하도록 만들어졌습니다.

Service는 Pod가 일시적이기 때문에 존재합니다. Pod가 재시작될 때마다 새로운 내부 IP 주소를 받을 가능성이 높습니다. 만약 애플리케이션의 다른 부분들이 이러한 유동적인 주소에 직접 연결하려고 한다면, 연결은 끊임없이 끊어질 것입니다. Service는 Pod에 고정된 IP 주소와 DNS 이름을 부여합니다. 이는 안정적인 출입구 역할을 하며, 셀렉터(selector)와 일치하는 모든 정상적인 Pod로 들어오는 요청을 로드 밸런싱합니다. 이를 통해 클라이언트를 개별 컨테이너 생명 주기의 혼란으로부터 분리할 수 있습니다.

대규모 Kubernetes: Netflix 사례

Netflix는 콘텐츠 전송 네트워크(CDN)를 관리하기 위해 Kubernetes를 사용합니다. 이 인프라는 전 세계 수백만 명의 시청자에게 동시에 비디오 스트림을 전송합니다. 인기 프로그램이 공개되어 수요가 급증하면, 클러스터는 사용자에게 더 가까운 곳에 비디오 세그먼트를 저장하는 캐시 노드를 확장(scale out)합니다. 특정 지역 노드에 장애가 발생하면 트래픽은 자동으로 재라우팅됩니다. 그 결과, 엔지니어에게 수동으로 긴급 호출을 하지 않아도 수백만 명의 사람들이 끊김 없이 영화를 시청할 수 있습니다. 오케스트레이터가 규모 확장과 장애를 처리하므로 서비스는 중단 없이 계속됩니다.

시작하기: YAML, JSON, 그리고 API Server

Kubernetes를 시작한다는 것은 명령형(imperative) 방식의 클릭 기반 작업(click-ops)을 버리고 선언적(declarative) 설정을 받아들이는 것을 의미합니다. YAML 또는 JSON 파일에 원하는 내용을 작성합니다. 이러한 매니페스트(manifest)는 컨테이너 이미지부터 레플리카(replica) 수, 노출된 포트, 환경 변수, 스토리지 마운트에 이르기까지 모든 것을 설명합니다. 파일이 준비되면 마스터 노드의 API server로 전송합니다. 컨트롤 플레인(control plane)은 해당 선언을 수용하여 클러스터 상태 데이터베이스에 저장한 다음, 실제 환경을 사용자의 설명과 일치시키기 위해 작업을 시작합니다. Kubernetes에게 일을 어떻게 해야 하는지 정확히 지시하는 것이 아닙니다. 최종 결과가 어떠해야 하는지를 알려주면, Kubernetes가 그 단계를 스스로 찾아냅니다.

핵심 요약

Kubernetes는 학습 곡선이 있습니다. 처음에는 용어가 어렵게 느껴질 수 있습니다. 움직이는 구성 요소가 많고, 분산 시스템을 디버깅하는 것은 단일 서버를 디버깅하는 것보다 본질적으로 더 어렵습니다. 하지만 그 보상은 운영의 안정성입니다. 개별 머신을 일일이 관리할 필요가 없어집니다. 새벽 3시 장애 상황에서 스타트업 스크립트가 제대로 작동하기를 기도할 필요도 없습니다. 노드가 죽을 수 있음을 가정하고, 오케스트레이터가 애플리케이션을 온전하게 유지할 것이라고 믿으며, 기본적으로 장애에 대비한 설계(designing for failure)를 시작하게 됩니다. 아무것도 고장 나지 않기를 바라는 마음에서, 시스템이 고장을 처리할 수 있음을 아는 마음으로의 이러한 사고방식 전환이 바로 그 노력을 가치 있게 만드는 핵심입니다.

출처: What Is Kubernetes? Kubernetes Explained in 15 Mins

선택 사항 학습 커뮤니티: GyaanSetu AI on Telegram