핵심 한 줄
쿠버네티스는 파드(컨테이너 묶음)를 컨트롤러(디플로이먼트→레플리카셋)로 자동 관리하고, 서비스로 네트워크를 추상화한다. 키워드는 셀프힐링·오토스케일·무중단 배포.
1. 파드 (Pod)
쿠버네티스의 최소 배포 단위. 1개 이상의 컨테이너를 묶고, 네트워크(같은 IP)·볼륨을 공유한다.
사이드카 패턴 (Sidecar)
- 한 파드에 메인 컨테이너 + 보조 컨테이너(로그 수집기, 프록시 등)를 함께 배치
- 볼륨 공유로 메인이 쓴 로그를 사이드카가 읽어 수집·외부 전송
- 메인 컨테이너 코드를 건드리지 않고 부가 기능(로깅·모니터링·통신)을 분리하는 게 목적
대표 사례
메인 앱 + 로그 컨테이너(Fluentd 등) → 공유 볼륨의 로그를 사이드카가 외부로 전송. 서비스 메시(Envoy 프록시 주입)도 같은 패턴.
2. 워크로드 컨트롤러
디플로이먼트 (Deployment)
- 파드 셀프힐링 가능: 파드가 죽으면 자동 재생성
- 정확한 구조: 디플로이먼트 → 레플리카셋 → 파드
- 레플리카셋(ReplicaSet): 지정한 개수의 파드를 유지·관리 (실제로 파드를 직접 감시)
- 디플로이먼트: 레플리카셋을 관리하며 배포(rollout)·롤백 담당
- 즉, 셀프힐링은 레플리카셋이, 버전 관리는 디플로이먼트가 한다.
Deployment ──관리──▶ ReplicaSet ──관리──▶ Pod, Pod, Pod
(배포/롤백) (개수 유지/셀프힐링)
롤링 업데이트 (Rolling Update)
- 새 버전 파드를 하나씩 만들면서 기존 파드를 하나씩 제거하는 과정을 반복
- 서비스 중단 없이 점진 교체 → 무중단 배포
- 문제 발생 시 디플로이먼트로 롤백 가능
HPA (Horizontal Pod Autoscaling)
- 부하량(CPU·메모리·커스텀 지표)에 따라 파드 개수를 자동 조절
- 수평 확장(파드 수 늘리기) — 트래픽 급증 시 자동 스케일아웃, 한산하면 스케일인
- (cf. 수직 확장 VPA는 파드 자원 자체를 키움 — HPA와 구분)
3. 서비스 (Service)
파드는 죽고 살아나며 IP가 계속 바뀐다. 서비스는 고정 진입점(고정 IP/DNS) + 부하분산을 제공해 클라이언트가 파드에 쉽게 접근하게 한다.
| 타입 | 접근 범위 | 용도 | 우리 케이스 |
|---|---|---|---|
| ClusterIP | 클러스터 내부 파드 간 통신만 | 내부 서비스 연결 (기본값) | risk·merchant-limit·order·redis·kafka-bootstrap — 파드끼리 DNS로 호출 |
| NodePort | 내부 + 외부에서 노드IP:포트 접근 (30000~32767) | 테스트·개발, 클러스터 밖 클라이언트 | merchant-limit-np:31574 — 클러스터 밖 k6 시드가 merchant 생성용으로 직접 호출 |
| LoadBalancer | 외부 LB를 통해 노출 | 클라우드 환경 외부 노출 | k3s 내장 **klipper-lb(svclb)**가 역할 담당 → Traefik이 이 타입으로 노드:80/443 개방 |
| ExternalName | DNS CNAME 리다이렉트 | 클러스터 외부 서비스에 내부 DNS명 부여 | 미사용 |
Ingress — L7 라우팅 규칙
서비스가 L4(IP:Port) 분산이라면, 인그레스는 L7(경로·호스트 기반) 라우팅 규칙이다. 서비스 타입이 아니라 별도 오브젝트.
- 인그레스 컨트롤러가 규칙을 실행함 — k3s 내장은 Traefik.
- 우리: 경로
/→ payment Service. Traefik이 LoadBalancer 타입 서비스로 노드에 바인딩.
외부 노출 전략
NodePort/LoadBalancer는 L4, 인그레스는 L7. 도메인·경로 기반 분기·TLS 종료까지 한 곳에서 처리하려면 인그레스를 쓴다.
4. 컨트롤 플레인 / 노드 컴포넌트
쿠버네티스(k3s 포함)는 **컨트롤 플레인(마스터)**과 워커 노드로 나뉜다.
컨트롤 플레인
| 컴포넌트 | 역할 | 우리 케이스 |
|---|---|---|
| kube-apiserver (:6443) | 모든 요청의 정문 (kubectl·kubelet·컨트롤러 전부 여기로) | agent join, kubectl 명령 전부 apiserver 경유 |
| kube-scheduler | 파드를 어느 노드에 배치할지 결정 (nodeSelector·taint/toleration·anti-affinity 반영) | payment를 전용 노드풀에 배치한 주체 |
| controller-manager | 제어 루프 집합체 (Deployment→ReplicaSet→Pod 관리, 롤아웃 조정) | 레플리카셋 개수 유지, 롤링업데이트 실행 |
| datastore | 클러스터 상태 저장. k3s 기본은 SQLite (단일 서버, etcd 아님) | 단일 서버 구성; HA 전환 시 etcd/외부 DB 필요 |
워커 노드 (전 노드에 존재)
| 컴포넌트 | 역할 | 우리 케이스 |
|---|---|---|
| kubelet | apiserver와 통신, containerd로 컨테이너 실행·상태 보고 | 각 노드에서 파드 실행·헬스 보고 |
| kube-proxy | iptables/ipvs 프로그래밍 → ClusterIP·NodePort L4 분산 | payment Service → 3파드 분산이 이것 |
| flannel (CNI) | 파드망 10.42.0.0/16, 노드 간 파드 트래픽 VXLAN 캡슐화 | 멀티 노드 간 파드 직통 통신 |
| CoreDNS | 서비스명 → ClusterIP DNS 해석 | payment-svc 같은 이름 → IP 변환 |
| Traefik + klipper-lb | k3s 내장 Ingress 컨트롤러 + 서비스 LB (노드:80/443) | 외부 트래픽 수신 → payment Service 전달 |
5. 한눈에 정리
- 파드: 컨테이너 묶음, 네트워크·볼륨 공유 → 사이드카로 부가기능 분리
- 레플리카셋: 파드 개수 유지(셀프힐링)
- 디플로이먼트: 레플리카셋 관리 + 롤링업데이트/롤백
- HPA: 부하 기반 파드 수 자동조절
- 서비스: 고정 IP + 부하분산 (ClusterIP=내부, NodePort=외부직접:30000~32767, LoadBalancer=LB위임, Ingress=L7 경로기반)
- 컨트롤 플레인: apiserver(정문:6443)·scheduler(배치결정)·controller-manager(제어루프)·datastore(k3s=SQLite)
- 노드: kubelet(실행·보고)·kube-proxy(L4분산)·flannel(CNI 10.42.0.0/16)·CoreDNS(이름해석)
6. Strimzi — Kafka 오퍼레이터
Kubernetes Operator 패턴
- CRD(Custom Resource Definition) 로 상태를 선언하면, 오퍼레이터가 실체(파드·서비스·스토리지)로 reconcile한다.
- StatefulSet을 손으로 관리하는 대신 원하는 상태를 YAML로 선언하고, 오퍼레이터가 현재 상태와 차이를 지속 조정.
우리 케이스
| CRD | 역할 |
|---|---|
Kafka | 클러스터 전체 선언 (KRaft 모드, 3-broker, RF3 / minISR2) |
KafkaNodePool | 브로커·컨트롤러 노드 풀 분리 선언 |
KafkaTopic | 토픽별 파티션·RF 선언 (4토픽) |
- 3-broker 클러스터를 Strimzi로 선언 배포 — ZooKeeper 없이 KRaft, RF3/minISR2 설정.
Operator 패턴 핵심
“어떻게 만들지”가 아니라 “무엇이 있어야 하는지” 를 선언. 오퍼레이터가 나머지를 책임진다.
7. ConfigMap / Secret
- ConfigMap = 비민감 설정(평문). Secret = 민감정보(base64 인코딩만 — 암호화 아님. RBAC + etcd 암호화로 보호).
| 구분 | ConfigMap | Secret |
|---|---|---|
| 저장 형식 | 평문 | base64 인코딩 |
| 용도 | DB URL·Kafka bootstrap·Redis host 등 | 비밀번호·토큰·인증서 |
| 보호 수단 | 없음 | RBAC + etcd 암호화 |
주입 방법
| 방식 | 설명 |
|---|---|
envFrom: configMapRef | ConfigMap의 모든 키를 환경변수로 한 번에 주입 |
env.valueFrom.secretKeyRef | Secret의 특정 키 하나만 환경변수로 주입 |
| 볼륨 마운트 | 파일로 마운트 — 값 변경 시 파드 재시작 없이 자동 반영 |
우리 케이스
- ConfigMap
app-config: DB URL, Kafka bootstrap, Redis host →envFrom: configMapRef로 일괄 주입 - Secret
db-cred: DB 크리덴셜 →SPRING_DATASOURCE_USERNAME/PASSWORD는valueFrom: secretKeyRef로 개별 주입
함정
envFrom주입값은 파드 시작 시 고정 → ConfigMap/Secret 값을 바꿔도 파드를 재시작해야 반영. 볼륨 마운트는 자동 반영.
8. 프로브와 셀프힐링
| 프로브 | 실패 시 동작 | 목적 |
|---|---|---|
| readinessProbe | Service Endpoints에서 제외 (파드 유지) | 트래픽 받을 준비 확인 |
| livenessProbe | kubelet이 컨테이너 재시작 | 생사 확인 |
| startupProbe | 성공 전까지 다른 프로브 보류 | 느린 초기화 보호 |
- 셀프힐링 관계: liveness 실패 → kubelet이 재시작. readiness 실패 → Endpoints에서 빠져 트래픽 차단(파드 자체는 유지).
- startupProbe가 성공하기 전까지 liveness/readiness는 평가되지 않는다 — 느린 앱의 재시작 루프 방지.
우리 케이스
- 엔드포인트:
/actuator/health/readiness,/actuator/health/liveness initialDelaySeconds: 20(readiness),40(liveness)
함정
liveness와 readiness를 같은 조건·타이밍으로 설정하면 readiness 실패 → liveness도 같이 실패 → 재시작 루프. 역할을 분리해서 설정해야 한다.
9. StatefulSet vs Deployment
| 구분 | Deployment | StatefulSet |
|---|---|---|
| 상태 | Stateless | Stateful |
| 파드 이름 | 랜덤(해시) | 고정(pod-0, pod-1, pod-2) |
| 파드 순서 | 순서 무관 생성/삭제 | 순서 보장(0→1→2 생성, 역순 삭제) |
| 스토리지 | 공유 또는 없음 | 파드별 전용 PVC (이동 안 됨) |
| 네트워크 ID | 재시작마다 변할 수 있음 | 안정적 DNS명(pod-0.svc-name) |
우리 케이스
- Deployment: payment·risk·merchant-limit·order 서비스 — stateless, 파드 교체 가능
- StatefulSet (Strimzi 관리): Kafka broker — 각 broker가 자기 로그 디렉토리를 가진 identity.
broker-0,broker-1,broker-2로 고정 식별 필요
10. taint/toleration · nodeSelector · anti-affinity
개념
| 메커니즘 | 방향 | 동작 |
|---|---|---|
| taint | 노드 → 파드 | ”이 조건 없으면 오지마” (effect: NoSchedule / PreferNoSchedule / NoExecute) |
| toleration | 파드 → 노드 | ”그 taint 견딜 수 있음” |
| nodeSelector | 파드 → 노드 | 라벨 매칭으로 특정 노드만 선택 |
| podAntiAffinity | 파드 ↔ 파드 | 같은 라벨 파드끼리 분산 (required=강제 / preferred=선호) |
topologyKey: kubernetes.io/hostname = 노드 단위로 분산.
우리 케이스 (payment 전용 노드풀)
노드 taint: role=payment:NoSchedule
파드 toleration: key=role, value=payment
nodeSelector: pool=payment
podAntiAffinity: required, topologyKey=hostname → 노드당 파드 1개
이 조합으로 payment 파드만 전용 노드에, 노드당 1개로 배치.
함정 — 롤아웃 데드락 04.배포가 조용히 터진다
전용 노드 N개 + required anti-affinity +
maxSurge: 1이면 surge 파드가 올라갈 빈 노드가 없어 롤아웃이 멈춘다.maxSurge: 0+maxUnavailable: 1로 교체.
11. PersistentVolume / PersistentVolumeClaim
- PV (PersistentVolume): 실제 스토리지 조각 (클러스터 리소스)
- PVC (PersistentVolumeClaim): 파드의 스토리지 요청. PV에 바인딩됨
- StorageClass: 동적 프로비저닝 — PVC 생성 시 PV 자동 생성
| 구분 | 역할 |
|---|---|
| PV | 실제 스토리지 (관리자가 사전 생성하거나 동적 생성) |
| PVC | 파드가 원하는 스토리지 크기/접근 모드 요청 |
| StorageClass | PVC 요청 시 PV를 자동 생성하는 템플릿 |
우리 케이스
- k3s 기본 StorageClass: local-path → PVC 생성 시 노드 로컬 디스크에 PV 자동 생성
- Kafka broker의
persistent-claimPVC가 이 StorageClass를 사용 → 각 broker 노드 로컬에 로그 디렉토리 생성
함정
local-path는 노드 고정 — 파드가 다른 노드로 스케줄되면 데이터가 따라가지 않는다. StatefulSet + anti-affinity로 broker를 특정 노드에 묶어 두는 이유.
12. 더 채울 거리
- HPA 오토스케일 실측 (CPU 지표 → 파드 개수 변화 관측)
- NetworkPolicy (파드 간 통신 제어, 화이트리스트)
- Ingress TLS 종료 설정
- Helm / Kustomize (매니페스트 패키징·환경별 오버라이드)
관련 노트
- DDD와 헥사고날 아키텍처
- 결제 취소 멱등성 설계
- Scale 시리즈 (k3s 수평 스케일아웃): 매니페스트 행단위 해부 편·부하 실측 편이 이 노트를 앵커로 링크