Docker Compose vs Kubernetes: 2026년에 무엇을 골라야 하는가

2025년 1월, 나는 팀원 두 명과 함께 운영하던 서비스에서 Kubernetes를 완전히 걷어냈다. 당시 스택은 API 서버, Celery 워커, Redis, PostgreSQL, Nginx, 내부 어드민 툴까지 총 6개 컨테이너였다. EKS 비용은 컨트롤 플레인 포함해서 월 $420 정도. 규모 대비 운영 부담이 너무 컸다.

Docker Compose로 옮기고 나서 가장 먼저 든 생각은 “왜 이걸 진작 안 했지?”가 아니었다. 솔직히 말하면 처음 두 주는 불안했다. Kubernetes에서 당연하게 쓰던 것들 — 자동 재시작, 헬스체크 기반 롤아웃, 서비스 디스커버리 — 이게 없으면 뭔가 큰일이 날 것 같은 느낌. 그런데 막상 돌려보니 안 났다.

이 글은 그 경험을 바탕으로, 2026년 시점에서 두 기술을 어떻게 선택할지 내 기준을 공유하는 글이다.

2025년에 Kubernetes를 버린 이유

우리 팀은 백엔드 엔지니어 2명, 프론트 1명이었다. 서비스는 B2B SaaS였고, 동시 접속자가 피크 타임에 200명 수준이었다. 이 규모에서 Kubernetes는 — 맞는 표현인지 모르겠지만 — 오버킬이라는 말로도 부족했다.

문제는 비용만이 아니었다. Kubernetes를 제대로 운영하려면 계속 신경 써야 하는 것들이 있다. cert-manager 인증서 갱신, 노드 그룹 업그레이드, PodDisruptionBudget 설정, RBAC 권한 정리… 이 작업들이 실제 제품 개발 시간을 갉아먹고 있었다. 어느 순간 우리가 서비스를 만드는 팀인지 클러스터를 관리하는 팀인지 헷갈릴 정도였다.

결정적인 사건이 있었다. 2024년 11월 금요일 오후 5시, EKS 노드 그룹 업그레이드 중에 PVC가 마운트 안 되는 문제가 터졌다. 결국 두 시간 넘게 디버깅했다. 원인은 EBS CSI 드라이버 버전 호환 문제였는데, 처음 보는 에러라 삽질을 꽤 했다. 그 주말에 진지하게 마이그레이션을 고민하기 시작했다.

Docker Compose가 실제로 충분한 경우

Docker Compose를 쓰기에 좋은 환경이 있다. 팀이 5명 이하이고, 서비스 수가 10개 미만이고, 트래픽이 예측 가능한 경우. 여기서 Compose는 놀라울 정도로 충분하다.

아래는 우리가 현재 운영 중인 docker-compose.yml 일부다:

# docker-compose.yml (운영 환경)
services:
  api:
    image: registry.example.com/api:${TAG}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=redis://redis:6379
    deploy:
      resources:
        limits:
          memory: 512M

  worker:
    image: registry.example.com/api:${TAG}
    command: celery -A app worker --loglevel=info
    restart: unless-stopped
    depends_on:
      - redis
      - postgres

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_data:/var/lib/postgresql/data

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - api

volumes:
  postgres_data:

이 설정으로 배포는 docker compose pull && docker compose up -d 한 줄이다. GitHub Actions에서 이미지 빌드하고, SSH로 서버에 접속해서 저 명령어 실행하면 끝. 다운타임은 컨테이너 재시작 시간 포함해서 5-10초 수준이다.

그런데 여기서 내가 진짜 예상 못 한 게 있었다. restart: unless-stopped가 생각보다 훨씬 강력하다는 점. 컨테이너가 죽으면 자동으로 재시작하고, 서버 리부팅 후에도 Docker Daemon이 뜨면 자동으로 올라온다. Kubernetes에서 기대했던 “자동 복구” 기능의 80%는 이것만으로도 충분히 커버됐다. 마이그레이션하고 나서야 깨달은 사실이다.

한 가지 실수를 고백하자면 — 초반에 배포 스크립트에서 --remove-orphans 플래그를 빠뜨렸다. docker-compose.yml에서 서비스를 제거했는데 이전 컨테이너가 조용히 살아남아서 포트 충돌이 났다. 5분 만에 파악했지만, 찾기 전까지는 당황스러웠다. docker compose up -d --remove-orphans를 습관처럼 쓰는 게 맞다.

Kubernetes가 실제로 필요해지는 시점

그렇다고 Kubernetes가 필요 없다는 말은 아니다. 특정 조건에서 Kubernetes는 대체재가 없다.

가장 명확한 케이스는 트래픽에 따라 수평 확장이 자동으로 이루어져야 할 때다. Compose도 docker compose up --scale api=3 같은 걸 쓸 수 있지만, HPA처럼 CPU/메모리 메트릭 기반으로 자동 조절하는 건 없다. 트래픽 변동이 10배 이상 차이나는 서비스라면 이게 진짜 필요해진다.

두 번째는 여러 팀이 서비스를 독립적으로 배포해야 하는 구조. 마이크로서비스 아키텍처에서 팀 A가 배포할 때 팀 B에 영향을 안 주려면, Kubernetes의 네임스페이스와 RBAC 구조가 실질적으로 도움이 된다. Compose로 이걸 구현하려면 서버를 물리적으로 분리하거나 복잡한 편법을 써야 한다.

그리고 멀티 AZ 가용성이 진짜 비즈니스 요구사항인 경우:

# 멀티 AZ 분산 배포 — Compose로는 구현 불가
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: production
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # 롤링 업데이트 중 다운타임 없음
  template:
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api
      containers:
        - name: api
          image: registry.example.com/api:v1.2.3
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 10

topologySpreadConstraints로 파드를 AZ별로 분산시키는 건 Compose로는 구현이 안 된다. 이 수준의 가용성이 요구사항인 서비스라면 Kubernetes가 맞다.

비용과 운영 부담 — 실제 숫자로 보면

사람들이 Kubernetes 도입 시 과소평가하는 게 있다. 클러스터 운영 자체의 인건비다.

AWS EKS 기준으로 컨트롤 플레인 비용만 월 $73이다. 여기에 워커 노드 최소 구성(t3.medium 2대)이면 $60-120 정도 더 붙는다. 로드밸런서, NAT 게이트웨이, 스토리지까지 합치면 소규모 서비스 기준으로 월 $300-500은 기본으로 나온다.

그런데 이게 전부가 아니다. Kubernetes를 제대로 운영하려면 — 클러스터 업그레이드, 보안 패치, 모니터링 셋업을 제대로 하려면 — 엔지니어 1명의 주당 3-5시간이 지속적으로 필요하다. 스타트업에서는 엄청나게 비싼 비용이다.

Docker Compose + 단일 서버 구성은? 우리 현재 스택은 Hetzner CPX31(4코어, 8GB RAM) 한 대에 월 €16.90이다. 거기에 백업용 스냅샷 비용 조금. 모니터링은 Grafana Cloud 무료 플랜으로 충분했다. 월 총 비용이 $25 정도.

물론 서버 한 대가 SPOF(Single Point of Failure)라는 점은 사실이다. 그런데 여기서 놀란 건 — 지난 14개월 동안 Hetzner 서버가 다운된 게 한 번이었고, 복구 시간은 20분이었다. 서비스 SLA가 99.9%라면 월 허용 다운타임이 43분이다. 이 범위 안에 들어온다. 100% 가용성이 비즈니스 생사를 결정하는 서비스라면 얘기가 달라지지만, MVP나 초기 성장 단계에서 그 수준의 SLA가 진짜 필요한 경우는 생각보다 드물다.

2026년 기준 내 실제 판단 기준

Kubernetes 커뮤니티에서 최근 몇 년간 흥미로운 흐름이 있다. K8s를 경험한 팀들이 다시 단순한 도구로 돌아오는 “K8s 피로감” 얘기가 꾸준히 나온다. 동시에 Kubernetes 자체도 계속 좋아지고 있어서 — 1.29부터 sidecar 컨테이너가 GA가 됐고, Gateway API도 안정화됐다 — 이전보다 운영이 편해진 것도 사실이다. 어느 쪽이 더 중요한지는 팀마다 다르다.

내 판단을 단순하게 정리하면 이렇다. DevOps 전담 인력이 없고, 단일 서버로 버틸 수 있으면 Compose가 맞다. 트래픽 변동이 크거나, 여러 팀이 독립 배포해야 하거나, 멀티 AZ 가용성이 실제 요구사항이면 Kubernetes가 맞다. 당장 Kubernetes가 필요 없더라도, Docker Compose 설정을 깔끔하게 유지하면 나중에 마이그레이션이 생각보다 어렵지 않다. 컨테이너 이미지 자체는 그대로 쓸 수 있고, 환경변수 기반 설정이 잘 되어 있으면 YAML만 바꾸면 된다.

Kubernetes를 쓴다면 managed 서비스(EKS, GKE, AKS)를 쓰는 게 맞다. 직접 클러스터를 구성하고 etcd 백업까지 챙기는 건 — 경험상 — 그 시간을 제품 개발에 쓰는 게 낫다.

나는 Kubernetes를 걷어낸 결정을 지금도 맞다고 생각한다. “다들 Kubernetes 쓰니까”는 이유가 되지 않는다. 우리 팀 규모와 요구사항에는 Docker Compose가 더 맞았고, 절약한 시간과 비용을 제품에 썼다. 여러분의 상황이 다르다면 판단도 달라져야 한다.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top