Kubernetes 오케스트레이션 기초 #컨테이너 관리와 클러스터 아키텍처

오케스트레이션이란?

컨테이너는 애플리케이션과 실행 환경을 함께 패키징하여 일관된 환경에서 실행할 수 있도록 만든 기술이다. Docker를 사용하면 애플리케이션을 컨테이너로 실행할 수 있지만, 실제 서비스 환경에서는 컨테이너를 생성하는 것만으로 충분하지 않다.

서비스 규모가 커지면 여러 서버에 컨테이너를 분산 배치하고, 장애가 발생한 컨테이너를 복구하며, 트래픽 증가에 따라 실행 인스턴스를 확장해야 한다. 배포 과정에서 서비스 중단을 최소화하고, 컨테이너 간 네트워크와 리소스 사용량도 관리해야 한다.

이러한 작업을 자동화하고 통합적으로 관리하는 기술이 컨테이너 오케스트레이션(Container Orchestration) 이다.

대표적인 오케스트레이션 플랫폼으로는 Kubernetes가 있다. Kubernetes는 컨테이너의 배포, 스케줄링, 상태 관리, 서비스 검색, 확장 및 복구를 자동화하는 플랫폼이다.

컨테이너 오케스트레이션의 목적

컨테이너 배포 자동화

컨테이너를 직접 실행하는 방식에서는 서버에 접속하여 이미지를 내려받고, 컨테이너를 생성하고, 필요한 설정을 적용해야 한다. 관리 대상이 늘어날수록 이러한 작업은 복잡해진다.

오케스트레이션 플랫폼에서는 원하는 애플리케이션의 상태를 선언하면 시스템이 해당 상태를 유지하도록 필요한 작업을 수행한다.

예를 들어 특정 애플리케이션을 여러 개의 인스턴스로 실행하도록 선언하면 Kubernetes가 적절한 노드에 컨테이너를 배치한다. 배포 이미지가 변경되었을 때도 설정된 배포 전략에 따라 기존 인스턴스를 새로운 버전으로 교체할 수 있다.

장애 감지와 자동 복구

컨테이너는 프로세스 오류, 메모리 부족, 노드 장애 등 다양한 이유로 종료될 수 있다.

오케스트레이션 플랫폼은 현재 상태를 지속적으로 관찰하고, 선언된 상태와 차이가 발생하면 이를 조정한다. 실행 중이어야 하는 컨테이너가 종료되면 새로운 컨테이너를 생성하거나 다른 노드에서 실행하도록 처리할 수 있다.

다만 복구가 모든 장애를 해결하는 것은 아니다. 데이터베이스 연결 실패, 애플리케이션 내부의 논리 오류, 외부 서비스 장애 등은 별도의 대응이 필요하다. 자동 복구가 정상적으로 작동하려면 헬스 체크와 리소스 제한, 애플리케이션의 오류 처리도 적절하게 설계해야 한다.

수평 확장과 부하 분산

트래픽이 증가하면 애플리케이션 인스턴스를 추가하여 처리 용량을 늘릴 수 있다. 이를 수평 확장(Horizontal Scaling)이라고 한다.

Kubernetes에서는 실행할 Pod의 수를 지정하거나 메트릭을 기반으로 자동 확장하도록 구성할 수 있다. 서비스 리소스는 여러 Pod로 트래픽을 전달할 수 있어 개별 인스턴스에 부하가 집중되는 문제를 줄인다.

단, Pod를 늘리는 것만으로 처리량이 무조건 증가하지는 않는다. 데이터베이스 연결 수, 외부 API 제한, 네트워크 대역폭 등이 병목 지점이라면 애플리케이션 인스턴스를 늘려도 성능이 개선되지 않을 수 있다.

선언적 상태 관리

Kubernetes의 핵심 개념 중 하나는 선언적 관리(Declarative Management)다.

명령형 방식은 특정 작업을 직접 지시한다. 반면 선언적 방식은 시스템이 도달해야 하는 최종 상태를 정의한다.

예를 들어 애플리케이션을 실행할 Pod의 개수와 사용할 이미지를 설정 파일에 선언하면 Kubernetes는 현재 상태를 관찰하면서 목표 상태와 일치하도록 조정한다.

이 구조를 통해 배포 설정을 코드로 관리하고, 변경 사항을 버전 관리하며, 동일한 설정을 여러 환경에서 재사용할 수 있다.

Kubernetes 아키텍처

Kubernetes는 여러 서버를 하나의 클러스터로 구성하고, 클러스터 전체의 상태를 관리한다.

클러스터는 크게 Control Plane과 Worker Node로 구성된다.

Control Plane은 클러스터의 상태를 관리하고 작업을 조정하는 역할을 담당한다. Worker Node는 실제 애플리케이션 컨테이너를 실행하는 역할을 담당한다.

Control Plane

Control Plane은 클러스터의 관리 영역이다. 사용자가 선언한 상태를 저장하고, 현재 상태를 관찰하며, 필요한 리소스 배치와 상태 변경을 조정한다.

kube-apiserver

Kubernetes API를 제공하는 핵심 구성 요소다. 사용자가 kubectl 명령을 실행하거나 자동화 시스템이 클러스터에 요청을 보내면 API Server가 해당 요청을 처리한다.

인증과 인가, 요청 검증 등의 과정을 거쳐 클러스터의 상태를 조회하거나 변경한다. 다른 핵심 구성 요소도 API Server를 통해 클러스터의 상태를 확인하고 변경 사항을 반영한다.

etcd

클러스터의 설정과 상태 정보를 저장하는 분산 키-값 저장소다. Kubernetes의 선언된 리소스와 관련된 영속적인 데이터를 보관한다.

etcd는 클러스터 운영에 중요한 구성 요소이므로 백업과 복구 전략이 필요하다. etcd 데이터가 손실되면 클러스터의 상태 정보를 복원하기 어려워질 수 있다.

kube-scheduler

새롭게 생성된 Pod 중 아직 노드가 할당되지 않은 Pod를 대상으로 적절한 Worker Node를 선택한다.

스케줄러는 노드의 리소스 여유, Pod의 리소스 요청량, 노드 선택 조건, 어피니티 및 안티어피니티, 테인트와 톨러레이션 등의 조건을 고려한다.

스케줄러는 컨테이너 프로세스를 직접 실행하는 구성 요소가 아니다. 실행할 노드를 결정하고 해당 정보를 클러스터 상태에 반영하는 역할을 담당한다.

kube-controller-manager

클러스터의 여러 상태를 지속적으로 관찰하고, 선언된 상태와 실제 상태의 차이를 조정하는 컨트롤러들을 실행한다.

예를 들어 Deployment가 원하는 Pod 수를 유지하도록 ReplicaSet이 관리하고, ReplicaSet은 필요한 Pod를 생성하거나 삭제한다. 노드 상태나 기타 리소스에 대한 관리도 관련 컨트롤러를 통해 수행된다.

이러한 구조를 통해 Kubernetes는 특정 시점의 작업 실행보다 지속적인 상태 조정에 초점을 맞춘다.

Worker Node

Worker Node는 애플리케이션의 Pod가 실제로 실행되는 서버다. 물리 서버나 가상 머신으로 구성할 수 있다.

Worker Node에는 일반적으로 kubelet, 컨테이너 런타임, 네트워크 구현을 위한 구성 요소가 존재한다.

kubelet

각 Worker Node에서 실행되는 에이전트다. API Server를 통해 전달되는 Pod 관련 정보를 확인하고, 해당 노드에서 Pod가 선언된 상태에 맞게 실행되도록 관리한다.

컨테이너의 상태를 확인하고 필요한 실행 작업을 컨테이너 런타임에 요청한다. 또한 컨테이너와 노드의 상태 정보를 Kubernetes에 보고한다.

컨테이너 런타임

실제로 컨테이너를 실행하는 소프트웨어다. Kubernetes는 컨테이너 런타임 인터페이스(CRI)를 통해 런타임과 통신한다.

대표적인 런타임으로 containerd와 CRI-O가 있다. Docker로 이미지를 빌드할 수 있지만, Kubernetes 노드가 반드시 Docker Engine을 직접 사용해야 하는 것은 아니다.

kube-proxy

일반적인 Kubernetes 클러스터에서는 kube-proxy가 Service의 가상 네트워크 동작을 구현하는 역할을 담당한다. 네트워크 규칙을 설정하여 Service로 들어오는 트래픽을 적절한 백엔드로 전달한다.

다만 모든 Kubernetes 환경이 kube-proxy를 사용하는 것은 아니다. 일부 CNI 및 eBPF 기반 네트워크 구현은 kube-proxy의 기능을 대체할 수 있다.

CNI 플러그인

CNI(Container Network Interface)는 컨테이너 네트워크를 구성하기 위한 인터페이스다. Kubernetes 환경에서는 CNI를 지원하는 네트워크 플러그인을 사용하여 Pod에 네트워크 연결을 제공하고, 노드 간 Pod 통신 등을 구현한다.

네트워크 플러그인에 따라 네트워크 정책, 트래픽 관측, 라우팅 및 보안 기능의 지원 범위가 달라질 수 있다.

Control Plane과 Worker Node의 관계

두 영역은 역할이 분리되어 있지만 서로 긴밀하게 연결되어 있다.

Control Plane은 클러스터의 목표 상태와 관리 정보를 바탕으로 작업을 조정한다. Worker Node는 실제 컨테이너를 실행하고 상태 정보를 보고한다.

예를 들어 사용자가 애플리케이션을 배포하면 다음과 같은 흐름으로 처리된다.

  • 사용자가 kubectl 또는 배포 자동화 시스템을 통해 API Server에 배포 설정을 전달한다.
  • API Server가 요청을 검증하고 클러스터의 상태를 반영한다.
  • 관련 컨트롤러가 필요한 리소스를 생성하고 원하는 상태를 유지하도록 조정한다.
  • Scheduler가 노드가 지정되지 않은 Pod에 적절한 Worker Node를 할당한다.
  • 해당 노드의 kubelet이 컨테이너 런타임에 컨테이너 실행을 요청한다.
  • 네트워크 플러그인이 필요한 Pod 네트워크를 구성한다.
  • kubelet과 기타 구성 요소가 상태를 보고하고, 컨트롤러가 실제 상태와 목표 상태의 차이를 계속 조정한다.

이 과정에서 모든 구성 요소가 반드시 단일한 순서로 한 번씩만 실행되는 것은 아니다. Kubernetes는 이벤트와 상태 변화에 따라 비동기적으로 동작하며, 원하는 상태가 달성될 때까지 조정 과정을 반복한다.

Pod와 Node

Node란?

Node는 Kubernetes 클러스터를 구성하는 실행 단위인 서버를 의미한다.

Node는 물리 서버 또는 가상 머신일 수 있으며, CPU와 메모리 등의 리소스를 제공한다. Kubernetes는 Node의 상태와 사용 가능한 리소스를 고려하여 Pod를 배치한다.

Node에는 다음과 같은 상태 및 정보가 중요하다.

  • CPU와 메모리의 용량 및 사용 가능 여부
  • 노드의 준비 상태와 네트워크 연결 상태
  • Pod를 배치할 수 있는 조건과 제한
  • 노드에 할당된 Pod와 리소스 요청량

Node가 장애를 일으키면 해당 노드에서 실행 중이던 Pod도 영향을 받는다. 다만 Pod가 자동으로 다른 노드에서 다시 실행되는지는 Deployment, StatefulSet 등의 상위 리소스와 Pod의 소유 관계, 장애 유형 및 스케줄링 조건에 따라 달라진다.

Pod란?

Pod는 Kubernetes에서 컨테이너를 실행하기 위한 가장 작은 배포 단위다.

Pod 하나에는 일반적으로 애플리케이션 컨테이너가 포함된다. 필요에 따라 로그 수집, 프록시, 데이터 공유 등의 역할을 담당하는 보조 컨테이너를 함께 배치할 수도 있다.

같은 Pod에 속한 컨테이너는 네트워크 네임스페이스를 공유하므로 동일한 IP 주소와 포트 공간을 사용한다. 또한 볼륨을 공유하여 파일을 교환할 수 있다.

예를 들어 메인 애플리케이션 컨테이너와 로그 처리용 사이드카 컨테이너가 같은 Pod에 있다면 서로 localhost를 통해 통신할 수 있다. 다만 포트 충돌에 주의해야 하며, 볼륨 공유 역시 별도의 설정이 필요하다.

Pod는 영구적인 서버가 아니다. 삭제되거나 교체될 수 있으며, 새로 생성된 Pod는 이전 Pod와 다른 UID 및 IP 주소를 가질 수 있다. 따라서 특정 Pod의 IP 주소를 고정하여 서비스 전체를 운영하는 방식은 적절하지 않다.

일반적으로 Deployment와 Service 등의 리소스를 함께 사용하여 Pod의 실행 상태와 접근 경로를 관리한다.

Pod와 Node의 관계

Node는 Pod가 실행되는 서버이고, Pod는 Node에 배치되는 실행 단위다.

하나의 Node에는 여러 Pod가 배치될 수 있다. 반대로 하나의 Pod는 일반적으로 특정 시점에 하나의 Node에서 실행된다.

Kubernetes Scheduler는 Pod의 리소스 요청량과 노드 조건을 바탕으로 배치 위치를 결정한다. Pod가 실행 중인 Node에 장애가 발생하면 상위 컨트롤러가 새로운 Pod를 생성할 수 있으며, Scheduler는 해당 Pod를 다른 적절한 Node에 배치할 수 있다.

이때 기존 Pod가 그대로 다른 Node로 이동하는 것은 아니다. 일반적으로 기존 Pod는 교체되고 새로운 Pod가 생성된다.

이 차이는 상태 저장 애플리케이션을 설계할 때 중요하다. 컨테이너 내부의 임시 파일이나 메모리에만 데이터를 보관하면 Pod 교체 시 데이터가 사라질 수 있다. 영속적인 데이터는 PersistentVolume 등의 저장소와 애플리케이션의 복구 전략을 함께 고려해야 한다.

Deployment와 Service가 필요한 이유

Pod와 Node의 역할을 이해했다면, 실제 서비스 운영에서 Deployment와 Service가 왜 필요한지도 알아야 한다.

Deployment는 애플리케이션의 Pod를 원하는 상태로 유지하고, 버전 변경과 롤링 업데이트 등을 관리한다. Pod가 비정상적으로 종료되거나 필요한 복제본 수가 부족하면 관련 컨트롤러가 새로운 Pod를 생성하도록 조정한다.

Service는 Pod 집합에 안정적인 네트워크 접근 지점을 제공한다. Pod의 IP 주소가 변경되더라도 클라이언트는 Service의 이름이나 주소를 통해 대상 애플리케이션에 접근할 수 있다.

예를 들어 게임 플랫폼에서 매치메이킹 서버를 여러 Pod로 실행한다고 가정할 수 있다. Deployment는 매치메이킹 서버의 실행 개수를 유지하고, Service는 클라이언트 또는 다른 백엔드 서비스가 해당 서버에 접근할 수 있는 논리적 경로를 제공한다.

다만 일반적인 Service의 트래픽 분산이 애플리케이션의 모든 상태를 공유하거나, 실시간 연결의 상태를 자동으로 동기화하는 것은 아니다. WebSocket 연결 유지, 세션 공유, 매칭 상태의 일관성 등은 애플리케이션 아키텍처와 네트워크 구성에 맞게 별도로 설계해야 한다.

Kubernetes를 이해할 때 중요한 설계 원칙

원하는 상태와 실제 상태를 구분한다.

Kubernetes는 설정된 목표 상태와 실제 상태의 차이를 지속적으로 조정한다. 이 원리를 이해하면 Deployment, ReplicaSet, 자동 복구가 어떻게 동작하는지 쉽게 파악할 수 있다.

Pod를 일회성 실행 단위로 취급한다.

Pod는 교체될 수 있으므로 영구적인 식별자나 저장 공간으로 가정해서는 안 된다. 애플리케이션은 재시작과 재배포를 고려하여 설계해야 한다.

장애 복구와 데이터 복구를 구분한다.

컨테이너를 다시 실행하는 것과 데이터베이스의 데이터를 복구하는 것은 별개의 문제다. 영속성, 백업, 복제, 복구 절차를 독립적으로 설계해야 한다.

리소스 요청량과 제한을 명확히 설정한다.

CPU와 메모리의 요청량 및 제한을 적절하게 설정하지 않으면 스케줄링 효율이 떨어지거나 메모리 부족으로 컨테이너가 종료될 수 있다. 실제 사용량을 관측하면서 설정을 조정해야 한다.

클러스터 구성 요소의 역할을 구분한다.

API Server는 요청과 상태 관리의 진입점이고, Scheduler는 Pod의 배치 위치를 결정하며, kubelet은 노드에서 Pod가 실행되도록 관리한다. 각 구성 요소의 책임을 구분하면 장애 원인과 운영 문제를 분석하기 쉬워진다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다