WebPiki
tutorial

쿠버네티스 기초: 왜 필요하고 어떻게 시작할까

Docker만으로 부족한 이유, 쿠버네티스 핵심 개념, 로컬 환경에서 시작하는 방법을 정리했다.

Docker를 배우고 나면 자연스럽게 이런 의문이 생긴다. "컨테이너가 10개, 100개가 되면 이걸 어떻게 관리하지?" Docker 하나로 컨테이너를 띄우고 내리는 건 할 수 있다. 근데 서비스가 커지면 컨테이너가 죽었을 때 자동으로 재시작하고, 트래픽이 몰릴 때 자동으로 스케일링하고, 무중단 배포를 하고... 이런 걸 수동으로 하는 건 사실상 불가능하다.

쿠버네티스(Kubernetes, 줄여서 K8s)가 이 문제를 해결한다. 컨테이너 오케스트레이션 도구. 쉽게 말하면 컨테이너들을 자동으로 관리해주는 시스템이다.

Docker Compose로는 왜 안 되나

Docker Compose도 여러 컨테이너를 관리할 수 있잖아. 맞다. 개발 환경이나 소규모 서비스에서는 Docker Compose로 충분하다. 근데 프로덕션에서는 한계가 있다.

자동 복구가 안 된다. 컨테이너가 죽으면 직접 다시 띄워야 한다. Docker Compose에 restart: always 옵션이 있긴 한데, 서버 자체가 죽으면 답이 없다.

스케일링이 원시적이다. docker compose up --scale app=3으로 인스턴스를 늘릴 수 있지만, 트래픽에 따라 자동으로 늘었다 줄었다 하는 건 안 된다. 로드밸런싱도 별도로 구성해야 한다.

멀티 서버 지원이 안 된다. Docker Compose는 기본적으로 단일 호스트에서 동작한다. 서버 여러 대에 걸쳐서 컨테이너를 분산 배치하는 건 Compose의 영역이 아니다.

쿠버네티스는 이 모든 걸 해결한다. 컨테이너가 죽으면 자동 재시작, 트래픽에 따라 자동 스케일링, 여러 서버에 걸쳐 컨테이너 분산 배치, 무중단 배포까지.

아키텍처 — 어떻게 생겨먹었나

쿠버네티스 클러스터는 크게 **컨트롤 플레인(Control Plane)**과 **워커 노드(Worker Node)**로 나뉜다.

컨트롤 플레인

클러스터의 두뇌 역할이다. "이 컨테이너는 3개 띄워라", "트래픽을 이쪽으로 보내라" 같은 결정을 내린다.

주요 구성 요소:

  • API Server — 쿠버네티스의 모든 통신이 이 API 서버를 통한다. kubectl 명령어를 치면 API 서버에 요청이 간다
  • etcd — 클러스터의 모든 상태 정보를 저장하는 분산 키-값 저장소. 어떤 Pod가 어디서 돌아가고 있는지, 설정이 뭔지 전부 여기에
  • Scheduler — 새 Pod를 어느 노드에 배치할지 결정. 각 노드의 CPU, 메모리 여유를 보고 적절한 곳에 배치한다
  • Controller Manager — 클러스터의 상태를 감시하고, 원하는 상태(desired state)와 현재 상태(actual state)가 다르면 맞추는 역할

워커 노드

실제로 컨테이너가 돌아가는 서버들이다. 각 노드에는:

  • kubelet — 컨트롤 플레인의 지시를 받아서 실제로 컨테이너를 실행하고 관리하는 에이전트
  • kube-proxy — 네트워크 규칙을 관리하고, 서비스로 들어오는 트래픽을 적절한 Pod로 라우팅
  • Container Runtime — 컨테이너를 실제로 실행하는 런타임. Docker가 쓰이기도 했지만, 지금은 containerd가 기본

핵심 개념

쿠버네티스에는 용어가 많은데, 처음에는 네 가지만 알면 된다.

Pod

쿠버네티스에서 배포할 수 있는 가장 작은 단위다. 하나 이상의 컨테이너를 포함하는 그룹이라고 보면 된다.

"그냥 컨테이너랑 뭐가 달라?"라고 할 수 있는데, Pod는 같은 Pod 안의 컨테이너들이 네트워크와 스토리지를 공유한다. 하나의 IP를 같이 쓰고, localhost로 서로 통신할 수 있다. 실무에서는 Pod 하나에 컨테이너 하나를 넣는 게 가장 흔하다. 사이드카 패턴 같은 특수한 경우에만 여러 컨테이너를 묶는다.

Pod는 일시적(ephemeral)이다. 언제든 죽고 새로 만들어질 수 있다. IP도 바뀔 수 있다. 그래서 Pod를 직접 관리하지 않고, Deployment를 통해 관리한다.

Deployment

"이 컨테이너 이미지로 Pod를 3개 유지해라"라고 선언하는 오브젝트다. Pod가 죽으면 자동으로 새로 만들어주고, 업데이트할 때 롤링 업데이트를 해주고, 문제가 생기면 이전 버전으로 롤백도 해준다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-app:1.0
        ports:
        - containerPort: 3000

이 YAML 파일이 말하는 건 간단하다. my-app:1.0 이미지로 Pod를 3개 만들어라. 하나가 죽으면 즉시 새로 만들어서 항상 3개를 유지해라.

Service

Pod의 IP는 바뀔 수 있다. 그러면 다른 서비스에서 이 Pod를 어떻게 찾지? Service가 이 문제를 해결한다. Service는 Pod 그룹에 고정된 주소(DNS 이름과 IP)를 부여하고, 트래픽을 분산해준다.

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

app: my-app 라벨이 붙은 Pod들에게 my-app-service라는 DNS 이름을 부여한다. 클러스터 내 다른 서비스에서 http://my-app-service로 요청하면 자동으로 건강한 Pod 중 하나에 전달된다.

Service 타입은 세 가지:

  • ClusterIP — 클러스터 내부에서만 접근 가능 (기본값)
  • NodePort — 노드의 특정 포트를 열어서 외부 접근 허용
  • LoadBalancer — 클라우드 로드밸런서를 자동 생성 (AWS ALB, GCP LB 등)

Namespace

클러스터를 논리적으로 분리하는 단위다. 같은 클러스터에서 팀별, 환경별(dev/staging/prod)로 나눠 쓸 수 있다. 이름 충돌을 방지하고, 리소스 할당을 제한할 수도 있다.

기본 Namespace는 default. 소규모 프로젝트에서는 Namespace를 나누지 않아도 되지만, 한 클러스터에 여러 서비스를 운영하면 유용하다.

로컬에서 시작하기

프로덕션 쿠버네티스 클러스터를 만드는 건 복잡하지만, 로컬에서 학습용으로 띄우는 건 간단하다.

minikube

가장 보편적인 로컬 K8s 도구다. 가상 머신이나 Docker 위에 단일 노드 클러스터를 만든다.

# 설치 (macOS)
brew install minikube

# 클러스터 시작
minikube start

# 상태 확인
minikube status

# 대시보드 (웹 UI)
minikube dashboard

kind (Kubernetes in Docker)

Docker 컨테이너 안에 K8s 노드를 만든다. minikube보다 가볍고, 멀티 노드 클러스터도 설정할 수 있어서 CI/CD 파이프라인에서도 많이 쓰인다.

# 설치
brew install kind

# 클러스터 생성
kind create cluster

# 클러스터 삭제
kind delete cluster

어떤 걸 쓰든 kubectl만 설치하면 동일한 명령어로 클러스터를 다룰 수 있다.

기본 kubectl 명령어

# 클러스터 정보 확인
kubectl cluster-info

# 노드 확인
kubectl get nodes

# Deployment 생성 (YAML 파일 사용)
kubectl apply -f deployment.yaml

# 모든 Pod 확인
kubectl get pods

# Pod 로그 보기
kubectl logs <pod-name>

# Pod 안에 셸 접속
kubectl exec -it <pod-name> -- sh

# Deployment 스케일링
kubectl scale deployment my-app --replicas=5

# 리소스 삭제
kubectl delete -f deployment.yaml

핵심 패턴은 kubectl apply -f <파일>로 원하는 상태를 선언하고, 쿠버네티스가 알아서 맞추게 하는 거다. 이걸 **선언적 관리(Declarative Management)**라고 한다. "컨테이너를 3개 시작해라"가 아니라 "컨테이너 3개가 돌아가고 있어야 한다"고 선언하는 차이.

ReplicaSet — 이건 Deployment가 알아서 한다

ReplicaSet은 "Pod를 N개 유지해라"를 담당하는 오브젝트다. 근데 실무에서 ReplicaSet을 직접 만들 일은 거의 없다. Deployment가 내부적으로 ReplicaSet을 만들어서 관리하기 때문이다.

Deployment를 업데이트하면 새 ReplicaSet이 만들어지고, 이전 ReplicaSet의 Pod를 하나씩 줄이면서 새 ReplicaSet의 Pod를 하나씩 늘린다. 이게 롤링 업데이트의 원리다.

존재는 알아야 하지만, 직접 다룰 필요는 없다.

쿠버네티스가 과한 경우

쿠버네티스는 강력하지만 복잡하다. 모든 상황에 필요한 건 아니다.

소규모 서비스 — 컨테이너 몇 개로 돌아가는 서비스에 K8s를 붙이면 오버헤드가 더 크다. Docker Compose나 단일 서버 배포로 충분한 경우가 많다.

팀이 작다 — 쿠버네티스를 운영하려면 인프라 지식이 필요하다. 2~3명짜리 팀에서 K8s 클러스터를 직접 관리하면 운영 부담이 개발 시간을 잡아먹는다. 차라리 매니지드 서비스(ECS, Cloud Run, Vercel)를 쓰는 게 합리적이다.

서버리스로 되는 일 — 트래픽이 불규칙하고 API 위주의 서비스라면, Lambda나 Cloud Functions 같은 서버리스가 더 적합할 수 있다. 관리할 인프라 자체가 없으니까.

반대로 K8s가 빛나는 상황:

  • 마이크로서비스가 10개 이상
  • 트래픽 변동이 크고 자동 스케일링이 필수
  • 무중단 배포, 카나리 배포 같은 고급 배포 전략이 필요
  • 멀티 클라우드나 하이브리드 클라우드를 고려

다음 단계

로컬에서 minikube나 kind로 클러스터를 띄우고, 간단한 웹 앱을 Deployment로 배포해보는 것부터 시작하면 된다. Deployment, Service, Pod — 이 세 가지 개념이 잡히면 쿠버네티스의 80%는 이해한 셈이다.

그 다음은 ConfigMap/Secret(설정 관리), Ingress(외부 트래픽 라우팅), PersistentVolume(영구 스토리지), Helm(패키지 관리) 같은 주제로 확장하면 된다. 한 번에 다 배우려고 하면 질리니까, 실제로 필요한 상황이 올 때마다 하나씩 찾아보는 게 낫다.

#쿠버네티스#Kubernetes#Docker#DevOps#컨테이너

관련 글