Post

Part 19: Kubernetes Internals - 내부 동작 원리

Part 19: Kubernetes Internals - 내부 동작 원리

Part 19: Kubernetes Internals - 내부 동작 원리

Control Loop Pattern (제어 루프 패턴)

Reconciliation Loop의 핵심 개념

Kubernetes의 모든 Controller는 Reconciliation Loop를 통해 동작한다. 이는 Kubernetes의 가장 핵심적인 디자인 패턴이다.

동작 원리:

1
2
3
4
1. Watch: API Server를 통해 리소스 상태 변경 감시
2. Compare: 현재 상태(Current State)와 원하는 상태(Desired State) 비교
3. Act: 차이가 있으면 현재 상태를 원하는 상태로 수렴시키는 액션 수행
4. Repeat: 무한 반복

실제 예시: ReplicaSet Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-rs
spec:
  replicas: 3  # 원하는 상태: 3개의 Pod
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.21

Reconciliation Loop 동작:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
상황 1: Pod이 하나 삭제됨
  원하는 상태: replicas=3
  현재 상태: 2개 Pod 실행 중
  액션: 1개 Pod 생성

상황 2: 수동으로 Pod 4개 생성
  원하는 상태: replicas=3
  현재 상태: 7개 Pod 실행 중 (원래 3개 + 수동 4개)
  액션: 4개 Pod 삭제

상황 3: Node 장애로 2개 Pod 손실
  원하는 상태: replicas=3
  현재 상태: 1개 Pod 실행 중
  액션: 2개 Pod 생성 (다른 정상 노드에)

Controller의 종류와 역할

Deployment Controller:

  • ReplicaSet을 관리한다
  • Rolling Update와 Rollback을 제어한다
  • 배포 전략을 실행한다

Node Controller:

  • 노드 상태를 모니터링한다
  • 5분간 응답 없는 노드를 Not Ready로 표시한다
  • 해당 노드의 Pod를 다른 노드로 재배치한다

Endpoint Controller:

  • Service와 Pod를 연결한다
  • Pod의 IP 주소 변경을 감지하여 Endpoint를 자동 업데이트한다

참고 자료:


etcd Raft 합의 알고리즘

Raft 알고리즘이란?

Raft는 분산 시스템에서 합의(Consensus)를 달성하기 위한 알고리즘이다. etcd는 Raft를 사용하여 클러스터의 모든 데이터 일관성을 보장한다.

Raft의 핵심 개념

Leader 선출 (Leader Election):

1
2
3
4
5
1. 클러스터 시작 시 모든 노드는 Follower 상태
2. 일정 시간 동안 Leader로부터 heartbeat를 받지 못하면 Candidate로 전환
3. Candidate는 다른 노드들에게 투표 요청 (RequestVote RPC)
4. 과반수(Quorum) 득표 시 Leader로 선출
5. Leader는 주기적으로 heartbeat를 전송하여 자신의 상태를 알림

상태 전이:

1
2
3
[Follower] → (타임아웃) → [Candidate] → (과반수 득표) → [Leader]
     ↑                            ↓ (득표 실패)
     └────────────────────────────┘

Log 복제 (Log Replication):

1
2
3
4
5
6
7
Client → Leader: Write 요청
Leader → Followers: AppendEntries RPC (로그 전파)
Followers → Leader: ACK
Leader: 과반수 ACK 확인 후 Commit
Leader → Followers: Commit 알림
Followers: 로그 적용
Leader → Client: 성공 응답

etcd 클러스터 크기 선택

홀수 개 권장 이유:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
3노드 클러스터:
  Quorum: 2 (3/2 + 1)
  장애 허용: 1개 노드
  장애 시나리오: 2개 노드만 있어도 동작

4노드 클러스터:
  Quorum: 3 (4/2 + 1)
  장애 허용: 1개 노드
  장애 시나리오: 3개 노드 필요
  결론: 3노드와 같은 장애 허용성, 비용만 증가

5노드 클러스터:
  Quorum: 3 (5/2 + 1)
  장애 허용: 2개 노드
  장애 시나리오: 3개 노드만 있어도 동작

권장 구성:

  • 소규모 클러스터: 3개 etcd 노드 (1개 장애 허용)
  • 프로덕션: 5개 etcd 노드 (2개 장애 허용)
  • 대규모 엔터프라이즈: 7개 etcd 노드 (3개 장애 허용)

7개 이상은 비권장:

  • 네트워크 지연 증가
  • 합의 도달 시간 증가
  • 성능 저하

장애 복구 시나리오

Leader 장애:

1
2
3
4
5
1. Followers가 heartbeat 타임아웃 감지 (150-300ms)
2. 한 Follower가 Candidate로 전환하여 투표 시작
3. 과반수 득표로 새 Leader 선출
4. 새 Leader가 클러스터 운영 재개
5. 전체 과정 소요 시간: 약 1-2초

네트워크 분할 (Split Brain 방지):

1
2
3
4
5
6
7
8
9
10
11
12
13
상황: 5노드 클러스터가 3-2로 분할

파티션 1 (3노드):
  Quorum 충족 (3 >= 3)
  → 정상 동작 가능
  → Leader 선출 가능

파티션 2 (2노드):
  Quorum 미충족 (2 < 3)
  → 읽기 전용 모드
  → Leader 선출 불가

결과: 데이터 일관성 보장 (Split Brain 방지)

참고 자료:


Scheduler Pod 배치 알고리즘

Scheduling 프로세스

Kubernetes Scheduler는 2단계 알고리즘을 사용한다:

  1. Filtering (Predicates): 조건에 맞지 않는 노드 제외
  2. Scoring (Priorities): 남은 노드들의 점수를 계산하여 최적 노드 선택

Filtering 단계

리소스 요구사항 체크:

1
2
3
4
5
6
7
8
9
10
11
// PodFitsResources predicate
func PodFitsResources(pod, node) bool {
    nodeAvailableCPU := node.Capacity.CPU - node.Allocated.CPU
    nodeAvailableMemory := node.Capacity.Memory - node.Allocated.Memory

    podRequestedCPU := sum(pod.Containers[].Resources.Requests.CPU)
    podRequestedMemory := sum(pod.Containers[].Resources.Requests.Memory)

    return nodeAvailableCPU >= podRequestedCPU &&
           nodeAvailableMemory >= podRequestedMemory
}

Node Selector 체크:

1
2
3
4
5
6
7
# Pod 정의
spec:
  nodeSelector:
    disktype: ssd
    zone: us-west-1

# Scheduler는 disktype=ssd AND zone=us-west-1 라벨을 가진 노드만 선택

Taints and Tolerations 체크:

1
2
3
4
5
6
7
8
9
10
# Node에 Taint 설정
kubectl taint nodes node1 key=value:NoSchedule

# Pod이 이 노드에 스케줄되려면 Toleration 필요
spec:
  tolerations:
  - key: "key"
    operator: "Equal"
    value: "value"
    effect: "NoSchedule"

Pod Affinity/Anti-Affinity:

1
2
3
4
5
6
7
8
9
10
11
# 같은 zone의 노드에만 배치
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: failure-domain.beta.kubernetes.io/zone
          operator: In
          values:
          - us-west-1a
          - us-west-1b

Scoring 단계

점수 계산 알고리즘:

Scheduler는 여러 플러그인을 사용하여 각 노드의 점수를 계산한다.

1. LeastAllocated (리소스 균등 분산):

1
2
3
4
5
6
7
8
9
10
Score = ((nodeCapacity - nodeAllocated - podRequest) / nodeCapacity) * 10

예시:
Node1: CPU 16 cores, 사용 중 8 cores, Pod 요청 2 cores
  Score = ((16 - 8 - 2) / 16) * 10 = 3.75

Node2: CPU 16 cores, 사용 중 4 cores, Pod 요청 2 cores
  Score = ((16 - 4 - 2) / 16) * 10 = 6.25

→ Node2가 더 높은 점수 (더 많은 여유 리소스)

2. BalancedResourceAllocation (CPU/Memory 균형):

1
2
3
4
5
6
7
8
9
CPU 사용률과 Memory 사용률의 차이가 작을수록 높은 점수

Node1: CPU 50% 사용, Memory 70% 사용
  차이 = |50 - 70| = 20

Node2: CPU 55% 사용, Memory 50% 사용
  차이 = |55 - 50| = 5

→ Node2가 더 높은 점수 (더 균형잡힌 리소스 사용)

3. NodeAffinity (선호도):

1
2
3
4
5
6
7
8
9
10
11
12
affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
      preference:
        matchExpressions:
        - key: disktype
          operator: In
          values:
          - ssd

# disktype=ssd 노드에 가중치 1 추가

4. ImageLocality (이미지 존재 여부):

1
2
이미지가 이미 노드에 있으면 다운로드 시간 절약
→ 이미지가 있는 노드에 더 높은 점수

최종 점수 계산:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
최종 점수 = Σ (플러그인 점수 × 가중치)

예시:
Node1:
  LeastAllocated: 3.75 × weight(1) = 3.75
  BalancedResource: 8 × weight(1) = 8
  NodeAffinity: 0 × weight(1) = 0
  ImageLocality: 5 × weight(1) = 5
  총점: 16.75

Node2:
  LeastAllocated: 6.25 × weight(1) = 6.25
  BalancedResource: 9 × weight(1) = 9
  NodeAffinity: 10 × weight(1) = 10
  ImageLocality: 0 × weight(1) = 0
  총점: 25.25

→ Node2 선택

참고 자료:


kube-proxy와 Service 메커니즘

kube-proxy의 역할

kube-proxy는 각 노드에서 실행되며, Service 추상화를 실제 네트워크 규칙으로 구현한다.

iptables 모드 (기본값)

동작 원리:

1
2
3
1. kube-proxy가 API Server를 Watch하여 Service/Endpoint 변경 감지
2. iptables 규칙을 동적으로 생성/수정
3. 클라이언트 요청이 들어오면 iptables 규칙에 따라 Pod로 라우팅

실제 iptables 규칙 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Service: myapp (ClusterIP: 10.96.1.100:80)
# Endpoints: 10.244.1.5:8080, 10.244.2.6:8080, 10.244.3.7:8080

# KUBE-SERVICES 체인
-A KUBE-SERVICES -d 10.96.1.100/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-MYAPP

# KUBE-SVC-MYAPP 체인 (로드 밸런싱)
-A KUBE-SVC-MYAPP -m statistic --mode random --probability 0.33 -j KUBE-SEP-1
-A KUBE-SVC-MYAPP -m statistic --mode random --probability 0.50 -j KUBE-SEP-2
-A KUBE-SVC-MYAPP -j KUBE-SEP-3

# KUBE-SEP-* 체인 (실제 Pod로 DNAT)
-A KUBE-SEP-1 -p tcp -j DNAT --to-destination 10.244.1.5:8080
-A KUBE-SEP-2 -p tcp -j DNAT --to-destination 10.244.2.6:8080
-A KUBE-SEP-3 -p tcp -j DNAT --to-destination 10.244.3.7:8080

패킷 흐름:

1
2
3
4
5
6
7
8
9
10
11
Client (10.244.0.5) → Service (10.96.1.100:80)
  ↓
iptables KUBE-SERVICES 체인 매칭
  ↓
KUBE-SVC-MYAPP으로 점프
  ↓
랜덤 확률로 KUBE-SEP-2 선택 (33.3% 확률)
  ↓
DNAT: 10.96.1.100:80 → 10.244.2.6:8080
  ↓
Pod-2로 패킷 전달

IPVS 모드 (고성능)

iptables vs IPVS 비교:

1
2
3
4
5
6
7
8
9
iptables:
  - 규칙이 많아질수록 선형적으로 성능 저하 (O(n))
  - 1만개 Service 시 눈에 띄는 지연
  - 로드 밸런싱 알고리즘 제한적 (랜덤만)

IPVS:
  - 해시 테이블 사용으로 일정한 성능 (O(1))
  - 수만개 Service도 처리 가능
  - 다양한 로드 밸런싱 알고리즘 지원

IPVS 로드 밸런싱 알고리즘:

1
2
3
4
5
6
7
8
9
10
11
# rr (Round Robin) - 기본값
ipvsadm -A -t 10.96.1.100:80 -s rr
ipvsadm -a -t 10.96.1.100:80 -r 10.244.1.5:8080
ipvsadm -a -t 10.96.1.100:80 -r 10.244.2.6:8080
ipvsadm -a -t 10.96.1.100:80 -r 10.244.3.7:8080

# lc (Least Connection) - 연결 수가 적은 Pod 선택
ipvsadm -A -t 10.96.1.100:80 -s lc

# sh (Source Hashing) - 같은 클라이언트는 같은 Pod로
ipvsadm -A -t 10.96.1.100:80 -s sh

IPVS 모드 활성화:

1
2
3
4
5
6
7
8
9
10
11
# kube-proxy ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-proxy
  namespace: kube-system
data:
  config.conf: |
    mode: ipvs
    ipvs:
      scheduler: rr  # rr, lc, dh, sh 등

참고 자료:


CNI 플러그인 패킷 흐름

Container Network Interface (CNI)

CNI는 컨테이너 네트워킹의 표준 인터페이스다. Kubernetes는 CNI 플러그인을 통해 Pod 네트워킹을 구현한다.

Pod 생성 시 네트워크 설정 과정

1
2
3
4
5
6
7
8
1. kubelet이 Container Runtime에게 Pod 생성 요청
2. Container Runtime이 네트워크 네임스페이스 생성
3. Container Runtime이 CNI 플러그인 호출
4. CNI 플러그인이 네트워크 설정:
   - veth pair 생성 (한쪽은 Pod, 한쪽은 Host)
   - Pod에 IP 주소 할당
   - 라우팅 규칙 설정
5. Pod에서 네트워크 사용 가능

실제 패킷 흐름

Pod-to-Pod (같은 노드):

1
2
3
4
5
6
7
8
9
10
Pod A (10.244.1.5)
  ↓ veth0 (Pod 내부)
  ↓
Host의 veth1 (Bridge에 연결)
  ↓
Bridge (cbr0 또는 cni0)
  ↓
Host의 veth2 (Bridge에 연결)
  ↓ veth0 (Pod 내부)
Pod B (10.244.1.6)

Pod-to-Pod (다른 노드):

1
2
3
4
5
6
7
8
9
10
11
Node1의 Pod A (10.244.1.5)
  ↓ veth pair
Node1의 Bridge
  ↓ 라우팅 테이블 확인
Node1의 eth0 (물리 NIC)
  ↓ Overlay 네트워크 (VXLAN, IP-in-IP 등)
Node2의 eth0
  ↓ 라우팅 테이블 확인
Node2의 Bridge
  ↓ veth pair
Node2의 Pod B (10.244.2.6)

CNI 플러그인 비교

Flannel:

1
2
3
4
간단하고 안정적
Overlay: VXLAN, UDP, host-gw
성능: 중간
NetworkPolicy 지원: 없음 (Calico와 함께 사용)

Calico:

1
2
3
4
고성능, BGP 기반
Overlay: IP-in-IP, VXLAN
성능: 높음
NetworkPolicy 지원: 완전 지원

Weave:

1
2
3
4
자동 구성, 암호화 지원
Overlay: VXLAN (자동 구성)
성능: 중간
NetworkPolicy 지원: 지원

Cilium:

1
2
3
4
eBPF 기반, 차세대 CNI
Overlay: VXLAN, Geneve
성능: 매우 높음
NetworkPolicy 지원: 완전 지원 + L7 정책

참고 자료:


CSI와 Dynamic Provisioning

Container Storage Interface (CSI)

CSI는 컨테이너 스토리지의 표준 인터페이스다. Kubernetes는 CSI를 통해 다양한 스토리지 시스템을 지원한다.

CSI의 장점

기존 in-tree 볼륨 플러그인 문제:

1
2
3
4
5
6
7
8
9
10
문제 1: Kubernetes 코어에 스토리지 드라이버 포함
  → Kubernetes 릴리스 주기에 종속
  → 새 스토리지 지원 어려움

문제 2: 모든 스토리지 드라이버를 kubelet에 컴파일
  → kubelet 바이너리 크기 증가
  → 불필요한 의존성

문제 3: 보안 이슈
  → 스토리지 드라이버가 kubelet 권한으로 실행

CSI 해결 방법:

1
2
3
4
CSI Driver를 독립된 컨테이너로 실행
  → Kubernetes 코어와 분리
  → 각 스토리지 벤더가 독립적으로 릴리스
  → 필요한 드라이버만 설치

CSI 아키텍처

CSI Driver 구성 요소:

1
2
3
4
5
6
7
8
9
10
Node Plugin (DaemonSet):
  - 각 노드에서 실행
  - 볼륨을 노드에 마운트
  - 컨테이너에서 볼륨 사용 가능하게 설정

Controller Plugin (Deployment):
  - 클러스터 레벨 동작
  - 볼륨 생성/삭제
  - 볼륨 Attach/Detach
  - 스냅샷 생성

Dynamic Provisioning 동작 과정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
1. 사용자가 PVC 생성:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: fast-ssd

2. CSI Controller가 PVC 감지
3. StorageClass 확인:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"

4. CSI Controller가 스토리지 API 호출 (예: AWS EBS 생성)
5. PV 자동 생성:
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pvc-abc123
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: fast-ssd
  csi:
    driver: ebs.csi.aws.com
    volumeHandle: vol-0abc123

6. PV와 PVC 자동 바인딩
7. Pod 생성 시 CSI Node Plugin이 볼륨 마운트

CSI 고급 기능

Volume Snapshot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# VolumeSnapshot 생성
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: my-snapshot
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: my-pvc

# Snapshot에서 볼륨 복원
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restored-pvc
spec:
  dataSource:
    name: my-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Volume Cloning:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cloned-pvc
spec:
  dataSource:
    name: source-pvc
    kind: PersistentVolumeClaim
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Volume Resize (확장):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# StorageClass에서 allowVolumeExpansion 활성화
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: expandable-sc
provisioner: ebs.csi.aws.com
allowVolumeExpansion: true

# PVC 크기 확장
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  resources:
    requests:
      storage: 20Gi  # 10Gi → 20Gi로 확장

참고 자료:


This post is licensed under CC BY 4.0 by the author.