Kubernetes가 필요한 이유
Docker로 컨테이너 하나를 실행하는 건 간단합니다. 하지만 프로덕션에서는 질문이 달라집니다.
- 컨테이너가 죽으면 누가 다시 띄우나?
- 트래픽이 몰리면 어떻게 확장하나?
- 새 버전을 무중단으로 어떻게 배포하나?
- 10개 서비스의 네트워크를 어떻게 관리하나?
Kubernetes(K8s)는 이 모든 것을 자동화하는 컨테이너 오케스트레이션 플랫폼입니다. Google이 내부 시스템(Borg)에서 15년간 쌓은 경험을 오픈소스로 공개한 것이 시작입니다.
핵심 아키텍처
Kubernetes 클러스터는 Control Plane(두뇌)과 Worker Node(손발)로 나뉩니다.
| 구성 요소 | 역할 | 비유 |
|---|---|---|
| Control Plane | 전체 클러스터 관리, 스케줄링, 상태 감시 | 관제탑 |
| API Server | 모든 요청의 진입점 (kubectl이 통신하는 대상) | 접수 창구 |
| etcd | 클러스터 상태를 저장하는 분산 키-값 저장소 | 데이터베이스 |
| Scheduler | Pod를 어떤 Node에 배치할지 결정 | 배차 담당 |
| Worker Node | 실제 컨테이너가 실행되는 서버 | 공장 |
| kubelet | 각 Node에서 Pod를 관리하는 에이전트 | 현장 감독 |
핵심 리소스 6가지
1. Pod — 최소 배포 단위
Pod는 하나 이상의 컨테이너를 묶은 그룹입니다. 같은 Pod 안의 컨테이너는 네트워크와 스토리지를 공유합니다.
# pod.yaml — 가장 단순한 Pod 정의
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app # 라벨: 다른 리소스가 이 Pod를 찾는 기준
spec:
containers:
- name: app
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests: # 최소 보장 리소스
memory: "64Mi"
cpu: "100m" # 0.1 CPU 코어
limits: # 최대 사용 제한
memory: "128Mi"
cpu: "250m"
# Pod 생성
kubectl apply -f pod.yaml
# Pod 상태 확인
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app 1/1 Running 0 30s
# Pod 상세 정보 (이벤트, IP, Node 등)
kubectl describe pod my-app
# Pod 로그 확인
kubectl logs my-app
# Pod 삭제
kubectl delete pod my-app
직접 Pod를 만드는 경우는 드뭅니다. 대신 Deployment를 사용합니다.
2. Deployment — 선언적 배포 관리
Deployment는 “이 앱을 3개 복제본으로 항상 유지해줘”라고 선언하면 Kubernetes가 알아서 관리합니다. Pod가 죽으면 자동 재생성되고, 업데이트 시 롤링 배포를 수행합니다.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3 # 항상 3개 Pod 유지
selector:
matchLabels:
app: my-app # 이 라벨을 가진 Pod를 관리
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
# Deployment 생성/업데이트
kubectl apply -f deployment.yaml
# Deployment 상태 확인
kubectl get deployments
# NAME READY UP-TO-DATE AVAILABLE AGE
# my-app 3/3 3 3 60s
# 이미지 업데이트 (롤링 배포 자동 실행)
kubectl set image deployment/my-app app=my-app:2.0
# 롤아웃 상태 확인
kubectl rollout status deployment/my-app
# Waiting for deployment "my-app" rollout to finish: 1 of 3 updated...
# deployment "my-app" successfully rolled out
# 문제 발생 시 롤백
kubectl rollout undo deployment/my-app
3. Service — 네트워크 접근점
Pod의 IP는 생성될 때마다 바뀝니다. Service는 여러 Pod 앞에 고정된 주소를 제공하고, 트래픽을 분산합니다.
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
type: ClusterIP # 클러스터 내부에서만 접근 (기본값)
selector:
app: my-app # 이 라벨의 Pod로 트래픽 전달
ports:
- port: 80 # Service 포트
targetPort: 3000 # Pod의 컨테이너 포트
Service 타입 3가지:
| 타입 | 접근 범위 | 용도 |
|---|---|---|
| ClusterIP | 클러스터 내부만 | 내부 서비스 간 통신 (기본값) |
| NodePort | 외부에서 Node IP:Port로 접근 | 개발/테스트용 |
| LoadBalancer | 클라우드 로드밸런서 자동 생성 | 프로덕션 외부 노출 |
4. ConfigMap과 Secret — 설정 분리
코드와 설정을 분리하여 같은 이미지를 dev/staging/prod에서 재사용합니다.
# configmap.yaml — 일반 설정값
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "production"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
---
# secret.yaml — 민감한 정보 (base64 인코딩)
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
DB_PASSWORD: cGFzc3dvcmQxMjM= # echo -n "password123" | base64
API_KEY: bXlzZWNyZXRrZXk= # echo -n "mysecretkey" | base64
Deployment에서 참조하는 방법:
# deployment에 추가
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config # ConfigMap의 모든 키를 환경변수로
- secretRef:
name: app-secret # Secret의 모든 키를 환경변수로
5. Ingress — HTTP 라우팅
외부 HTTP 요청을 도메인/경로 기반으로 내부 Service에 연결합니다. 하나의 IP로 여러 서비스를 노출할 수 있습니다.
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-svc
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-svc
port:
number: 80
6. Namespace — 환경 분리
하나의 클러스터 안에서 리소스를 논리적으로 분리합니다.
# Namespace 생성
kubectl create namespace staging
kubectl create namespace production
# 특정 Namespace에 배포
kubectl apply -f deployment.yaml -n staging
# Namespace별 리소스 확인
kubectl get pods -n staging
kubectl get pods -n production
kubectl get pods --all-namespaces # 전체
자주 쓰는 kubectl 명령어
| 명령어 | 설명 |
|---|---|
kubectl get pods -o wide | Pod 목록 + Node/IP 정보 |
kubectl logs -f pod-name | 실시간 로그 스트리밍 |
kubectl exec -it pod-name -- sh | Pod 내부 쉘 접속 |
kubectl port-forward svc/my-app 8080:80 | 로컬에서 Service 접근 (디버깅용) |
kubectl top pods | Pod CPU/메모리 사용량 |
kubectl get events --sort-by=.lastTimestamp | 최근 이벤트 (트러블슈팅) |
kubectl diff -f deployment.yaml | 적용 전 변경사항 미리보기 |
kubectl scale deployment/my-app --replicas=5 | 수동 스케일링 |
로컬 개발 환경
프로덕션 클러스터 없이 로컬에서 Kubernetes를 실습할 수 있습니다.
| 도구 | 특징 | 설치 |
|---|---|---|
| Docker Desktop | K8s 내장 (Settings에서 활성화) | Docker Desktop 설치 후 Enable Kubernetes |
| minikube | 단일 노드 클러스터, 가장 널리 사용 | brew install minikube |
| kind | Docker 컨테이너 안에서 K8s 실행, CI/CD에 적합 | brew install kind |
# minikube 시작
minikube start
# 대시보드 열기 (웹 UI)
minikube dashboard
# 상태 확인
kubectl cluster-info
정리
| 개념 | 핵심 | Docker Compose와 비교 |
|---|---|---|
| Pod | 컨테이너 실행 단위 | services: 의 한 항목 |
| Deployment | 복제, 롤링 업데이트, 자동 복구 | deploy.replicas (Swarm) |
| Service | 고정 주소 + 로드밸런싱 | ports: + 내부 DNS |
| ConfigMap/Secret | 설정/비밀 분리 | environment: / .env |
| Ingress | 도메인 기반 HTTP 라우팅 | Nginx 리버스 프록시 |
| Namespace | 환경 격리 | 별도 compose 파일 |
Docker Compose는 단일 서버에서 여러 컨테이너를 편리하게 관리하는 도구이고, Kubernetes는 여러 서버에 걸쳐 수백~수천 개 컨테이너를 자동으로 관리하는 플랫폼입니다. 소규모 서비스는 Docker Compose로 충분하지만, 고가용성과 자동 스케일링이 필요하다면 Kubernetes를 검토하세요.