infra
Platform

모듈 맵

[Kubernetes] 수십 개의 컨테이너를 스스로 관리하게 만드는 오케스트레이션

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 01 / 29

[Kubernetes] 수십 개의 컨테이너를 스스로 관리하게 만드는 오케스트레이션

Docker 수동 운영의 한계를 넘어 Kubernetes가 왜 필요한지, 무엇을 해결하는지 이해합니다

🚨INCIDENT ALERT
HIGH

새벽 3시, 운영 서버의 컨테이너가 OOM으로 죽었고 당직자가 노트북을 열기 전까지 요청은 계속 실패합니다. 컨테이너가 수십 개로 늘어난 팀에서는 사람이 하나씩 재시작하고 배치하는 방식이 더 이상 버티지 못합니다. Kubernetes는 이런 반복 운영을 컨트롤러와 스케줄러가 대신 처리하게 만드는 출발점입니다.

컨테이너 오케스트레이션 입문

새벽 3시, Slack 알림이 울렸다. 운영 서버의 컨테이너가 OOM으로 죽었다. 당직 팀원이 노트북을 열어 docker start 명령을 입력하는 동안, 사용자 요청은 5분째 전부 실패하고 있었다. 알림을 못 봤다면 30분, 1시간이 걸렸을 수도 있다. 같은 상황에서 Kubernetes 클러스터라면 어떻게 됐을까. 컨테이너가 죽는 순간 Controller가 감지하고, 사람이 잠든 사이에 새 컨테이너가 자동으로 살아난다. 이것이 오케스트레이션의 시작이다. Docker는 컨테이너를 실행하는 도구이고, Kubernetes는 수십~수천 개의 컨테이너를 사람 없이도 안정적으로 운영하기 위한 플랫폼이다.


이번 챕터에서 배울 것

Docker 컨테이너를 실제 서비스에서 안정적으로 운영하려면 왜 오케스트레이션이 필요한지, Kubernetes가 어떤 문제를 해결하는지 이해합니다.

  • 1Docker 단독 운영의 4가지 실무 한계점
  • 2컨테이너 오케스트레이션이 자동화하는 것들 (자동 복구, 스케일링, 배포, 로드밸런싱)
  • 3Kubernetes의 선언적(Declarative) 관리 철학
  • 4K8s가 해결하는 문제: 자가 치유(Self-Healing), 수평 스케일링, 무중단 배포
실습 환경 준비

이번 모듈은 개념 중심입니다. Docker가 설치된 환경이면 충분하며, kubectl과 minikube는 다음 모듈부터 본격적으로 사용합니다.

Docker 동작 확인
docker ps
kubectl 설치 확인 (없어도 이번 모듈은 개념 중심)
kubectl version --client 2>/dev/null || echo 'kubectl 미설치 — 다음 모듈에서 설치'
minikube 설치 여부 확인 (선택)
minikube version 2>/dev/null || echo 'minikube 미설치 — 선택 사항'
💡개념

Docker만으로 운영할 때 생기는 4가지 한계

서버 한 대에서 컨테이너 5개를 돌릴 때는 Docker만으로도 충분합니다. 그런데 서비스가 커지면 어떤 일이 벌어질까요? 컨테이너가 죽어도 아무도 모릅니다. 트래픽이 몰려도 자동으로 늘어나지 않습니다. 새 버전 배포 중에 서비스가 잠깐 끊깁니다. 서버가 10대가 되면 어느 서버에 어느 컨테이너를 띄울지 일일이 결정해야 합니다.

Docker만으로 운영할 때 생기는 4가지 한계

문제 1: 자동 복구 없음

Docker
# 컨테이너가 죽었다 — 아무일도 일어나지 않는다
docker run -d --name my-api my-api:latest
# 5분 후, 메모리 부족으로 컨테이너 종료됨

docker ps
# CONTAINER ID   IMAGE   COMMAND   ...
# (아무것도 없음 — 죽은 채로 있음)

# 알림을 받은 누군가가 수동으로 재시작
docker start my-api

--restart=always 옵션으로 단순 재시작은 가능하지만, 헬스체크 실패나 의존 서비스 장애, 노드 다운 시에는 속수무책입니다.

문제 2: 수동 스케일링

Docker
# 트래픽이 5배 폭증 — 수동으로 컨테이너를 추가해야 함
docker run -d --name my-api-2 my-api:latest
docker run -d --name my-api-3 my-api:latest
docker run -d --name my-api-4 my-api:latest

# 그리고 로드밸런서 설정도 수동으로 변경해야 한다
# nginx.conf 수정 → nginx -s reload

트래픽이 줄어들면 다시 수동으로 줄여야 합니다. 새벽에 폭증하면? 아무도 없습니다.

문제 3: 무중단 배포 어려움

로컬 터미널
# 새 버전 배포 — 이 사이에 다운타임 발생
docker stop my-api
docker rm my-api
docker run -d --name my-api my-api:v2

# 또는 Blue-Green 배포를 수작업으로 구현해야 함
# → 로드밸런서 전환, 이전 컨테이너 관리, 롤백 스크립트...

문제 4: 멀티 호스트 관리 복잡성

서버가 10대라면? 어느 서버의 CPU가 여유로운지, 어느 서버에 컨테이너를 배치할지, 서버 간 네트워크는 어떻게 구성할지 — 모두 수작업입니다.

서버1: docker run ...  # SSH 접속 후 실행
서버2: docker run ...  # SSH 접속 후 실행
서버3: docker run ...  # SSH 접속 후 실행
# 10대면 10번 반복
💡개념

Kubernetes가 해결하는 것: 선언적 관리

Docker 환경에서 컨테이너 수십 개를 명령어로 직접 관리하다 보면, 어느 서버에 어떤 버전이 떠 있는지 파악하기 어려워지고 배포할 때마다 수작업 실수가 생깁니다. 특히 장애가 났을 때 "무엇을 실행해야 하는지"를 사람이 일일이 판단해야 하는 구조에서는 복구 속도가 개인 역량에 달리게 됩니다. Kubernetes의 선언적 관리는 이 문제를 "원하는 상태를 기록하면 시스템이 알아서 맞춘다"는 방식으로 해결합니다. 운영자가 "3개를 실행하라"고 선언해두면 컨테이너가 죽든, 노드가 바뀌든 Kubernetes가 현재 상태를 원하는 상태로 계속 맞춰주는 것이 Reconciliation Loop입니다.

Kubernetes가 해결하는 것: 선언적 관리

선언적 배포 예시

YAML
# deployment.yaml — "이런 상태이길 원한다"고 선언
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 3          # 항상 3개 실행
  selector:
    matchLabels:
      app: my-api
  template:
    metadata:
      labels:
        app: my-api
    spec:
      containers:
      - name: my-api
        image: my-api:v2   # 이 버전으로
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
Kubernetes
kubectl apply -f deployment.yaml
# deployment.apps/my-api created
# → K8s가 알아서 3개의 Pod를 적절한 노드에 배치
# → 하나가 죽으면 자동으로 새로 생성
# → 버전 변경 시 롤링 업데이트 자동 진행

K8s가 자동으로 처리하는 것들

기능Docker 단독Kubernetes
컨테이너 죽으면수동 재시작자동 재시작 (Self-Healing)
트래픽 폭증 시수동 docker runHPA 자동 스케일링
새 버전 배포다운타임 or 복잡한 스크립트롤링 업데이트 자동화
멀티 노드 배치SSH로 각 서버 접속스케줄러 자동 배치
헬스체크직접 구현 필요liveness/readinessProbe 내장
로드밸런싱nginx 별도 설정Service 리소스로 자동화

Reconciliation Loop — K8s의 작동 원리

운영자: "replicas: 3으로 배포해줘"
     ↓
Kubernetes Controller Manager:
  ┌─ 현재 상태 확인: Pod 3개 실행 중 ─ OK
  │
  ├─ Pod 하나 OOM으로 사망
  │   현재 상태: Pod 2개
  │   원하는 상태: Pod 3개
  │   → 자동으로 새 Pod 스케줄링
  │
  └─ 노드 하나 다운
      현재 상태: Pod 1개 (해당 노드의 2개 소실)
      원하는 상태: Pod 3개
      → 다른 살아있는 노드에 2개 자동 배치

이 루프는 K8s가 실행되는 동안 끊임없이 반복됩니다. 운영자가 잠든 새벽에도, 휴일에도.

실습: Docker 한계 직접 경험하기

이번 실습은 Docker 환경에서 오케스트레이션 없이 컨테이너를 관리할 때 어떤 불편함이 있는지 체험합니다.

실습 1: 컨테이너 죽음 감지 없음 확인

Docker
# 테스트용 컨테이너 실행 (10초 후 종료되는 컨테이너)
docker run -d --name temp-test alpine sh -c "sleep 10 && exit 1"

# 실행 중 확인
docker ps
# CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS
# abc123         alpine    "sh -c 'sleep 10 && …"   3 seconds ago   Up 2 seconds

# 15초 후 확인 — 아무런 알림 없이 죽어있음
sleep 15 && docker ps
# (비어있음)

docker ps -a
# CONTAINER ID   IMAGE     STATUS
# abc123         alpine    Exited (1) 5 seconds ago

아무런 알림도 없습니다. 모니터링 시스템 없이는 서비스 다운을 사람이 먼저 발견해야 합니다.

실습 2: --restart=always의 한계

Docker
# restart 정책 설정
docker run -d --name restart-test --restart=always alpine sh -c "echo 'started'; sleep 5; exit 1"

# 계속 재시작되는지 확인
watch docker ps  # 또는 아래 명령어 반복 실행

docker ps
# STATUS: Restarting (1) 3 seconds ago  ← 재시작 중

# 재시작 횟수 확인
docker inspect restart-test | grep RestartCount
# "RestartCount": 5,

# 문제: 앱이 비정상 종료될 때마다 즉시 재시작 → 무한 루프
# CrashLoopBackOff는 K8s에서 이 패턴을 감지해 지연 재시작하는 방어 메커니즘
docker stop restart-test && docker rm restart-test
🔍실행 후 확인할 것
  • NAME조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
  • STATUS/READYRunning, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
  • RESTARTS/EVENTS재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.

실습 3: 수동 스케일링의 번거로움

로컬 터미널
# 웹 서버 3개를 수동으로 띄우기
for i in 1 2 3; do
  docker run -d --name web-$i -p $((8079+i)):80 nginx:alpine
  echo "web-$i started on port $((8079+i))"
done

# 결과 확인
docker ps --format "table {{.Names}}\t{{.Ports}}"
# NAMES    PORTS
# web-3    0.0.0.0:8082->80/tcp
# web-2    0.0.0.0:8081->80/tcp
# web-1    0.0.0.0:8080->80/tcp

# 로드밸런서 없이는 트래픽을 나눌 방법이 없음
# 포트도 일일이 달라야 함 → 외부에서 어떻게 접근?

# 정리
for i in 1 2 3; do docker stop web-$i && docker rm web-$i; done

K8s라면 replicas: 3으로 선언하고 Service로 단일 진입점을 자동 구성합니다.

상황

새벽 2:47 AM — 모니터링 대시보드에 갑자기 에러율 100%
docker ps 확인 → 컨테이너 없음
docker ps -a 확인 → Exited (137) 2 hours ago

Exit code 137 = 128 + 9 (SIGKILL). OOM Killer가 죽인 것입니다.

원인 파악

로컬 터미널
# 컨테이너 마지막 로그 확인
docker logs --tail 50 <container_id>

# 시스템 OOM 로그 확인
dmesg | grep -i "out of memory" | tail -10
# [123456.789] Out of memory: Kill process 1234 (node) score 900 or sacrifice child
# [123456.790] Killed process 1234 (node) total-vm:1048576kB, anon-rss:524288kB

# 컨테이너 리소스 제한 확인
docker inspect <container_id> | grep -A5 Memory

임시 해결 (Docker 단독 환경)

로컬 터미널
# 수동 재시작
docker start <container_id>

# 또는 메모리 제한을 높여서 새로 실행
docker run -d --name my-api --memory="512m" --restart=on-failure:5 my-api:latest

근본 해결: Kubernetes에서는

YAML
# resources.limits로 OOM 전에 Pod를 재스케줄
resources:
  requests:
    memory: "256Mi"
  limits:
    memory: "512Mi"
# → OOM 발생 시 K8s가 자동으로 새 Pod 생성
# → 알림 없이 새벽에 자동 복구됨

핵심 교훈: 수동 관리 환경에서는 컨테이너 다운 = 서비스 다운 + 사람의 수동 개입 필요. Kubernetes는 이 개입을 자동화합니다.

💼
실무 맥락
현업 패턴

실무 시나리오: 스타트업 → 성장 → K8s 도입 시점

Phase 1 (초기 스타트업): Docker만으로 충분

  • 서비스 2-3개, 서버 1-2대
  • 팀 규모 소수, 트래픽 예측 가능
  • 복잡한 인프라보다 빠른 배포가 우선
로컬 터미널
# 이 정도는 docker-compose로 충분
docker-compose up -d

Phase 2 (성장기): 한계 도달 신호

  • 새벽 알림에 팀원이 지침
  • 서비스 개수 5개 이상, 서버 3대 이상
  • 배포 시 다운타임이 사용자에게 영향
  • 트래픽 급등 시 수동 대응에 지침

Phase 3 (K8s 도입): 자동화 전환

SRE 팀에서 흔히 듣는 말: "K8s 세팅 비용은 크지만, 그 이후 새벽 알림이 90% 줄었다."

Kubernetes
# K8s 도입 후 배포
kubectl apply -f deployment.yaml
# → 롤링 업데이트 자동
# → 실패 시 자동 롤백
# → 스케일링 자동
# → 새벽 장애 자동 복구

현실적 조언: K8s는 작은 서비스엔 과할 수 있습니다. 하지만 서비스가 성장하고 팀이 새벽 알림에 지치기 시작했다면, 그때가 도입 시점입니다. 이 트랙은 그 결정을 내리기 위한 기반 지식을 제공합니다.


다음 모듈 cluster-architecture에서는 Kubernetes 클러스터의 내부 구조, Control Plane과 Worker Node의 각 컴포넌트가 어떤 역할을 하는지 살펴봅니다.

지식 확인

퀴즈 — 4문제

Q1

컨테이너가 OOM으로 갑자기 종료되었을 때, Docker만 사용하는 환경에서 운영자가 직접 해야 하는 작업은?

Q2

트래픽이 갑자기 10배로 폭증했을 때, Kubernetes가 Docker 단독 운영 대비 제공하는 핵심 기능은?

Q3

Kubernetes에서 원하는 상태(Desired State)와 현재 상태(Current State) 개념의 의미는?

Q4

여러 서버(노드)에 컨테이너를 배치할 때 Kubernetes 스케줄러의 역할은?

0 / 4 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

kubectl로 nginx Pod를 생성하고 Deployment와 Service를 차례로 만들어 클러스터 외부에서 접근 가능한 상태까지 구성한다. K8s 3대 리소스의 역할과 관계를 직접 손으로 익힌다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

kubernetes입문 · 45
[Kubernetes] get, describe, logs, exec 필수 kubectl 명령어 10선
Kubernetes 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점