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 네임스페이스에서 실행된다.