• **도커(Docker)**는 애플리케이션을 이미지로 패키징해 컨테이너로 실행하는 컨테이너 플랫폼/런타임
  • **쿠버네티스(Kubernetes, K8s)**는 여러 서버(노드)에 흩어진 컨테이너를 자동으로 배치·복구·확장·분산하는 컨테이너 오케스트레이션 플랫폼
  • 둘은 경쟁 관계가 아니라 “컨테이너를 만드는 도구 vs 컨테이너 무리를 운영하는 도구”라는 역할 분담
  • L4 로드밸런서는 IP + TCP/UDP 포트만 보고 트래픽을 여러 서버로 나눠주는 전송 계층(4계층) 부하 분산 장치
  • 쿠버네티스에서 L4가 구체화된 형태가 Service(ClusterIP/NodePort/LoadBalancer)

해당 개념이 필요한 이유

  • 도커 이미지 하나를 “빠르게 올리는 것”까지는 도커로 되지만, 운영 환경에서는 컨테이너가 죽고, 트래픽이 몰리고, 여러 서버에 나눠 배치해야 하는 문제가 남음
  • 이걸 사람이 수동으로 하면 새벽에 컨테이너 죽을 때 직접 재시작해야 하는 무중단·자동화의 한계
  • 쿠버네티스는 “원하는 상태(desired state)“만 선언하면 알아서 맞춰주는 선언적 자동 운영을 제공
  • L4는 그 자동 운영 안에서 **“들어온 트래픽을 살아있는 컨테이너들에게 어떻게 나눠줄까”**를 담당

AS-IS (도커 단독 운영)

sequenceDiagram
    autonumber
    participant Dev as 운영자(사람)
    participant S1 as 서버 A
    participant S2 as 서버 B
    Dev->>S1: docker run (수동 배치)
    Dev->>S2: docker run (수동 배치)
    Note over S1: 컨테이너 다운!
    S1--xDev: 사람이 직접 알아채야 함
    Dev->>S1: docker run (수동 재시작)
    Note over Dev: 트래픽 분산·스케일·롤백도 전부 수동

TO-BE (쿠버네티스 선언적 운영)

sequenceDiagram
    autonumber
    participant Dev as 운영자(사람)
    participant CP as Control Plane
    participant N1 as Node A
    participant N2 as Node B
    Dev->>CP: "이 이미지로 Pod 3개 유지해줘" (선언)
    CP->>N1: Pod 배치
    CP->>N2: Pod 배치
    Note over N1: Pod 다운!
    N1--xCP: 컨트롤 플레인이 상태 감지
    CP->>N2: 새 Pod 자동 생성 (self-healing)
    Note over CP: 스케일·트래픽 분산·롤백도 자동

도커와 쿠버네티스는 경쟁 관계가 아니다 (역할 구분)

  • 도커 = 컨테이너를 만들고(build) 실행(run) 하는 계층. 클라이언트 개발자가 이미 아는 docker build / docker run이 여기에 해당
  • 쿠버네티스 = 이미 만들어진 컨테이너들을 여러 서버에 걸쳐 오케스트레이션(배치·복구·확장·분산) 하는 계층
  • 비유: 도커가 “레고 블록(컨테이너)을 찍어내는 틀”이라면, 쿠버네티스는 “그 블록 수백 개를 어느 판에 꽂고, 부서지면 다시 꽂고, 손님 몰리면 더 꽂는 관리자”
  • 쿠버네티스는 하드웨어가 아니라 컨테이너 레벨에서 동작하며, 애플리케이션이 이미 컨테이너화되어 있다고 전제함

”쿠버네티스가 도커를 버렸다”는 오해 정리

  • 2020년 K8s v1.20에서 “Docker deprecated” 발표 → 가장 많이 오해받은 뉴스
  • 사실은 “컨테이너 런타임으로서의 도커”만 제거된 것 (v1.24, 2022년 완전 제거)
  • 핵심은 “도커”가 하나가 아니라 여러 겹의 스택이라는 점:
Docker (제품 전체)
├── CLI / 사람 친화적 UX 도구   ← 개발자가 쓰는 부분
├── containerd (고수준 런타임)   ← 실제로 컨테이너를 굴리는 엔진
└── runc (저수준 OCI 런타임)

런타임 스택 용어 (CRI · containerd · runc · OCI)

  • 컨테이너 런타임(container runtime): 노드에서 이미지를 pull하고 컨테이너를 실제로 띄우고 내리는 저수준 소프트웨어
  • CRI (Container Runtime Interface): kubelet(각 노드의 K8s 에이전트)이 런타임과 대화하기 위한 표준 gRPC API. 이 표준만 지키면 어떤 런타임이든 K8s에 꽂을 수 있음. 도커는 CRI가 생기기 전에 만들어져서 이 표준을 네이티브로 구현하지 않음
  • dockershim: 그래서 K8s가 도커를 쓰려고 사이에 끼워 넣던 어댑터(번역기). containerd에 닿기 위한 군더더기라서 제거됨
  • containerd: CRI를 구현하는 고수준 런타임. 이미지 관리와 컨테이너 생명주기를 담당. 도커 안에도 이미 들어있고 단독으로도 동작 → K8s가 이걸 직접
  • runc: containerd가 실제 리눅스 커널 기능(namespace·cgroup)으로 컨테이너를 띄우는 저수준 OCI 런타임
  • OCI (Open Container Initiative): 이미지 포맷과 런타임의 표준 스펙. docker build가 만드는 이미지가 OCI 표준이라, containerd·CRI-O 등 어떤 런타임에서도 동일하게 실행
flowchart LR
    subgraph 과거["과거 (dockershim 경유)"]
        K1[kubelet] --> DS[dockershim] --> DD[Docker 데몬] --> C1[containerd] --> R1[runc]
    end
    subgraph 현재["현재 (CRI 직접 연결)"]
        K2[kubelet] --> CRI[CRI 표준] --> C2[containerd] --> R2[runc]
    end
  • 클라이언트 개발자에게 중요한 결론: docker build로 만든 이미지는 OCI 표준이라 그대로 잘 돌아감. “도커 지원 중단”이 아니라 “노드에서 도커 데몬을 안 쓸 뿐”

쿠버네티스 핵심 구성요소

  • Node: 컨테이너가 실제로 도는 서버(물리/가상). 노드마다 kubelet이 컨테이너를, kube-proxy가 네트워크를 관리
  • Cluster: 노드들의 집합 전체
  • Control Plane: 클러스터의 두뇌. “원하는 상태”와 “현재 상태”를 계속 비교하며 맞춰줌(self-healing의 근원)

Pod — 최소 배포 단위

  • 쿠버네티스가 다루는 가장 작은 단위. 컨테이너 1개(또는 밀접하게 붙어 다니는 여러 개)를 감싼 껍데기
  • 같은 Pod 안의 컨테이너는 네트워크(IP)와 저장소를 공유
  • Pod는 일회용(cattle, not pet) — 언제든 죽고 새 IP로 다시 뜰 수 있음. 그래서 “Pod에 직접 붙기”가 아니라 앞단의 고정 진입점(Service)이 필요

Deployment — 원하는 상태를 선언하는 객체

  • “이 이미지로 Pod 3개를 항상 유지해줘” 같은 desired state를 선언하는 상위 객체
  • Pod를 직접 만들기보다 Deployment로 관리하는 게 일반적 — 롤링 업데이트/롤백/스케일을 자동 처리
  • 내부적으로 ReplicaSet이 “Pod 개수 유지”를 담당하고, Deployment는 그 위에서 버전 전환을 관리
  • 즉 앞의 TO-BE 다이어그램에서 “Pod 3개 유지해줘”가 바로 Deployment 선언

L4 로드밸런서란

  • 로드밸런서 = 들어온 트래픽을 여러 서버로 나눠 부하를 분산하는 장치/소프트웨어
  • L4(4계층, 전송 계층) 로드밸런서는 IP 주소 + TCP/UDP 포트만 보고 분산 결정
  • 패킷 안의 내용(HTTP 헤더, URL, 쿠키)은 들여다보지 않음 → 그래서 빠르고 단순
  • 분산 방식은 라운드 로빈, 최소 연결(least busy) 등 단순 알고리즘 위주
구분L4 로드밸런서L7 로드밸런서
동작 계층전송 계층 (TCP/UDP)응용 계층 (HTTP/HTTPS)
판단 근거IP + 포트URL, 헤더, 쿠키 등 내용
특징빠름·단순, 프로토콜 무관느리지만 내용 기반 정교한 라우팅
예시DB 접속, 내부 API, TCP 서비스/api는 A로, /img는 B로 경로 라우팅

쿠버네티스에서 L4가 등장하는 곳 (Service)

  • Service는 L4(TCP/UDP)에서 동작하고, 경로 기반 라우팅이 필요한 **Ingress는 L7(HTTP)**에서 동작
  • Service 3가지 타입:
    • ClusterIP (기본): 클러스터 내부 전용 고정 IP. 외부 노출 없음
    • NodePort: 모든 노드의 특정 포트(30000~32767)로 외부 노출
    • LoadBalancer: 클라우드의 외부 L4 로드밸런서를 붙여 외부 IP 제공 (운영 표준)

kube-proxy — L4 분산이 실제로 일어나는 지점

  • kube-proxy는 각 노드에서 도는 네트워크 프록시. Service(ClusterIP)로 온 트래픽을 실제 살아있는 Pod로 전달하는 규칙을 커널에 설치
  • Pod가 죽고 새로 뜨면 EndpointSlice가 갱신되고, kube-proxy가 규칙을 다시 그려서 항상 살아있는 Pod로만 트래픽을 보냄
  • 동작 모드 두 가지:
    • iptables 모드: 리눅스 방화벽 규칙(iptables)으로 ClusterIP → Pod 라우팅. 확률 기반으로 Pod 선택
    • IPVS 모드: 리눅스 커널에 내장된 L4 로드밸런서(IP Virtual Server) 사용. 규모가 커질수록 iptables보다 성능이 좋고 라운드로빈·최소연결 등 알고리즘 선택 가능
  • 즉 “K8s에서 L4 로드밸런싱”의 실체는 상당 부분 kube-proxy가 커널에 심어 놓은 규칙
flowchart LR
    Client[외부 클라이언트] --> LB["외부 L4 LB<br/>(LoadBalancer 타입)"]
    LB --> NP["NodePort<br/>(노드:30007)"]
    NP --> CIP["ClusterIP<br/>(kube-proxy가 분산)"]
    CIP --> P1[Pod 1]
    CIP --> P2[Pod 2]
    CIP --> P3[Pod 3]

Ingress — L4 위에 얹히는 L7 진입점

  • Ingress는 클러스터 외부의 HTTP/HTTPS 트래픽 진입점. URL 경로/호스트 기반 라우팅과 TLS 종료 같은 L7 기능을 제공
  • Service(L4)가 “포트로 Pod 무리에 연결”까지라면, Ingress(L7)는 /api는 이 Service로, shop.example.com은 저 Service로” 처럼 내용을 보고 분기
  • 주의: Ingress는 규칙(선언)일 뿐이고, 실제 트래픽 처리는 Ingress Controller(nginx, traefik 등)가 담당
  • 전체 흐름: Client → Ingress(L7 라우팅) → Service(L4) → Pod
flowchart LR
    Client[외부 클라이언트] --> IG["Ingress Controller<br/>(L7: URL/호스트 라우팅)"]
    IG -->|"/api"| SvcA["Service A (L4)"]
    IG -->|"/shop"| SvcB["Service B (L4)"]
    SvcA --> PodA[Pod...]
    SvcB --> PodB[Pod...]

클라이언트 개발자가 알아둘 실무·소통 포인트

  • 백엔드/인프라 팀이 “이 서비스는 ClusterIP라 외부에서 못 붙어요”, “LoadBalancer 타입으로 열게요”라고 할 때 무슨 계층 이야기인지 이해 가능
  • “L4에서 막힌다” vs “L7에서 라우팅한다”는 결이 다름 — 앞은 포트/IP 문제, 뒤는 URL·헤더 문제
  • 내 도커 이미지가 쿠버네티스에서 안 뜰 때, 대개는 이미지 자체(OCI) 문제가 아니라 매니페스트/포트/헬스체크 설정 문제
  • “Pod가 재시작됐다”는 건 장애가 아니라 self-healing이 정상 동작한 경우일 수 있음

참고 문서