Kubernetes 학습 정리 - 구조와 핵심 개념

왜 쿠버네티스를 써야 하나?

Docker만으로 컨테이너를 운영하면 어떤 문제가 생길까?

문제 설명
스케일링 트래픽 증가 시 수동으로 컨테이너를 올리고 연결해야 함
고가용성 컨테이너가 죽으면 직접 확인하고 다시 살려야 함
서비스 디스커버리 여러 컨테이너가 서로를 찾도록 직접 연결해야 함
로드밸런싱 트래픽 분산이 없으면 컨테이너를 여러 개 띄워도 의미가 없음
무중단 배포 롤링 업데이트를 직접 구현해야 함

쿠버네티스는 이 문제들을 선언적 구성으로 해결한다.

원하는 상태를 선언하면, 쿠버네티스가 그 상태를 유지해준다.


쿠버네티스의 구조

물리적 아키텍처와 무관하게, 가상의 쿠버네티스 아키텍처는 크게 두 부분으로 나뉜다.

  • 컨트롤 플레인: 선언적 구성을 만들고 유지하는 두뇌 역할
  • 워커 노드: 컨트롤 플레인이 할당한 작업을 실제로 수행

컨트롤 플레인과 워커 노드 내부 구성요소의 실제 구현 방식은 설치 방식이나 환경에 따라 달라진다.


핵심 단위: Pod, Deployment, Service

Pod

배포 가능한 가장 작은 단위. 하나 이상의 컨테이너를 포함한다.

  • 동일 파드 내 컨테이너는 동일한 네트워크 네임스페이스를 공유한다
  • 동일 파드 내 컨테이너는 스토리지 볼륨을 공유할 수 있다
  • 항상 동일한 노드에서 실행된다

Pod를 직접 띄우지 않는 이유

Pod는 일시적이다. 노드 장애가 발생하면 자동으로 복구되지 않는다. 따라서 Deployment나 Job 같은 상위 컨트롤러를 통해 관리해야 상태가 유지된다.

Pod 생명주기

Pending → Running → Succeeded / Failed
    │         │
    │         └── CrashLoopBackOff (재시작 반복 시)
    │
    └── ImagePullBackOff (이미지 다운로드 실패 시)

Deployment

Pod를 어떻게 유지할지 관리하는 것.

"파드 3개를 항상 유지해!"
  • 클러스터 전체에 존재하며, 특정 노드 안에 있지 않다

Service

Pod에 어떻게 접근할지 관리하는 것.

"이 레이블을 가진 파드들로 트래픽을 보내!"
  • 클러스터 전체에 존재하며, 특정 노드 안에 있지 않다
  • IP가 아닌 레이블 셀렉터로 파드를 찾아 트래픽을 라우팅한다

왜 레이블로 찾을까?

Pod가 죽고 새로 뜨면 IP가 바뀐다. 레이블 셀렉터를 사용하면 IP가 바뀌어도 항상 현재 살아있는 파드에 요청을 보낼 수 있다.


Pod, Deployment, Service의 관계

Pod를 Deployment와 Service가 감싸고 있는 구조가 아니다.

Pod만이 실제로 존재하는 형태의 구성요소이고, Deployment와 Service는 구조적인 특성에 가깝다.


컨트롤 플레인 구성요소

API Server

kubectl 명령어, 내부 컴포넌트 간 통신을 모두 중개한다.

모든 컴포넌트는 API Server하고만 통신한다. Controller Manager가 직접 kubelet에 명령을 보내는 게 아니라, 항상 API Server를 통해서 전달된다.


etcd

클러스터의 모든 상태를 저장하는 key-value 저장소.

저장하는 것들:

  • 파드 목록
  • ConfigMap, Secret
  • 서비스 정의

etcd가 손상되면 클러스터 전체 상태가 사라진다. 프로덕션에서는 etcd를 3개 이상 홀수로 구성하고 정기적으로 백업한다.


Scheduler

새 Pod를 어느 노드에 배치할지 결정한다.

스케줄링 과정:

1. Filtering (불가능한 노드 제거)
   - 리소스 부족 (CPU/Memory)
   - Node Selector, Affinity 조건 불일치
   - Taint/Toleration 불일치

2. Scoring (남은 노드 점수 평가)
   - 리소스 여유가 많은 노드 → 높은 점수
   - 기존 Pod 분산도

3. 가장 높은 점수의 노드 선택 → Pod 배치

Controller Manager

클러스터 상태를 원하는 상태로 유지하는 여러 컨트롤러들을 하나의 프로세스로 실행한다.

각 컨트롤러는 현재 상태를 원하는 상태로 맞추는 루프를 계속 실행한다.

컨트롤러 역할
ReplicaSet Controller 지정된 수의 Pod 유지
Deployment Controller 롤링 업데이트, 롤백 관리
Node Controller 노드 상태 모니터링, 장애 감지
Job Controller 일회성 작업 Pod 관리
Service Account Controller 기본 서비스 어카운트 생성

물리 아키텍처 vs 가상 아키텍처

3개의 서버로 클러스터를 구성한다고 하면 이렇게 된다.

서버 1 (컨트롤 플레인)     서버 2 (워커 노드)     서버 3 (워커 노드)
├── API Server             ├── kubelet             ├── kubelet
├── etcd                   └── kube-proxy          └── kube-proxy
├── Scheduler
└── Controller Manager

그리고 앱을 올리면 이런 식으로 Deployment가 구성된다.

[백엔드 Deployment]  →  replica: 3  →  파드 3개 (장애 시 다른 파드가 처리)
[프론트 Deployment]  →  replica: 2  →  파드 2개
[DB StatefulSet]     →  replica: 1  →  파드 1개 (DB는 보통 별도 관리)

여기서 드는 의문:

요청이 엄청 많이 들어오면 결국 API Server가 설치된 서버 1을 모두 지나야 하는 게 아닐까?

실제 트래픽은 API Server를 거치지 않는다. API Server는 클러스터 관리/조율용 내부 서버다. 외부 요청은 Ingress → Service → Pod 경로로 직접 처리된다.


쿠버네티스의 철학

어떻게(How)가 아니라 무엇을(What) 선언하고, 구현은 플러그인에 위임한다.

플러그인에 위임되는 것들: 네트워크, 스토리지, 로드밸런서 등 인프라 구현체.

플러그인들은 클러스터 안에서 파드로 뜬다. 일반 앱 파드와 분리하기 위해 kube-system 네임스페이스에서 실행된다.