작년 연말, 우리 서비스가 새벽 2시에 다운됐다. 트래픽이 갑자기 터지면서 docker-compose로 겨우 붙들고 있던 3개 서버가 한꺼번에 나가떨어진 거다. 당시 우리는 이미 너무 오래 그 방식으로 버티고 있었다 — 솔직히 그 상황이 언젠가 올 거라고 다들 알고 있었다. 그냥 그게 연말 새벽 2시가 될 줄 몰랐을 뿐.
그날 이후 나는 제대로 된 컨테이너 오케스트레이션 도구를 도입하기로 결심했다. 5명짜리 팀, 12개 마이크로서비스, AWS EC2 기반 환경. 약 2주를 투자해서 Kubernetes, Docker Swarm, Nomad를 직접 테스트했고, 지금부터 그 경험을 공유하겠다.
Kubernetes: 기대 이상이면서 기대 이하인 이유
처음 kubeadm으로 클러스터를 구성할 때, 이틀이면 충분할 거라고 생각했다. 결과적으로 첫 번째 실제 배포하기” rel=”nofollow sponsored” target=”_blank”>배포하기” rel=”nofollow sponsored” target=”_blank”>배포까지 4일 반이 걸렸다.
클러스터 초기 구성 자체는 어렵지 않다. 문제는 그 다음부터다. Ingress 컨트롤러 설정, RBAC 권한 정리, PersistentVolume 연결, 그리고 모니터링 스택까지 — 각각은 복잡하지 않은데, 이걸 전부 묶어서 돌아가게 만드는 과정이 생각보다 훨씬 길다. Kubernetes 1.32 기준으로 얘기하는 거고, 예전보다 많이 개선됐음에도 불구하고 그렇다.
내가 제대로 삽질한 부분을 하나 공유하면 — ResourceQuota를 설정하지 않은 채로 팀원이 실수로 메모리 리밋 없는 파드를 배포했다. 금요일 오후에. 그 파드가 노드 메모리를 전부 잡아먹으면서 같은 노드에 있던 다른 파드 6개가 OOMKilled됐다. 실제 서비스 영향은 운 좋게 없었지만, 그 슬랙 알림들을 보면서 식은땀이 났다. 그 이후로 모든 네임스페이스에 LimitRange를 반드시 설정한다.
# 네임스페이스 기본 리소스 제한 — 이거 안 하면 나중에 꼭 후회한다
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default:
memory: "256Mi"
cpu: "500m"
defaultRequest:
memory: "128Mi"
cpu: "100m"
max:
memory: "2Gi"
cpu: "2"
Kubernetes의 진짜 강점은 생태계다. Helm, Argo CD, KEDA, Karpenter — 필요한 게 있으면 거의 다 있다. 내가 만난 에러들은 거의 전부 GitHub Issues나 Stack Overflow에 답이 이미 있었다. 커뮤니티 크기가 곧 문제 해결 속도라는 걸 이때 실감했다.
5명짜리 팀에게 이 모든 게 정말 필요한가 — 이 질문은 계속 따라다닌다. Kubernetes는 확실히 강력하지만 운영 부담이 만만치 않다. etcd 백업, 인증서 갱신 주기 관리, 버전 업그레이드 전략 — 이게 다 누군가의 실제 시간을 먹는다. EKS나 GKE 같은 매니지드 서비스를 쓰면 많이 줄어들긴 하는데, 그러면 비용이 올라간다. 우리 규모에서 EKS 클러스터 하나 운영하면 월 $300~$400은 기본으로 깔린다 (노드 인스턴스 비용 제외).
한 번 비용을 치르고 나면 못 할 게 없다. 다만 그 초기 비용이 생각보다 크다는 걸 미리 알아야 한다.
Docker Swarm: 단순함의 유혹, 그리고 현실
Docker Swarm은 세팅이 정말 빠르다. 비교가 안 될 정도로.
# 마스터 노드에서 — 이게 전부다
docker swarm init --advertise-addr <MANAGER-IP>
# 워커 노드 추가도 한 줄
docker swarm join --token <TOKEN> <MANAGER-IP>:2377
# 기존 compose 파일을 그대로 배포
docker stack deploy -c docker-compose.prod.yml myapp
이게 Swarm의 핵심 장점이다. 기존에 docker-compose를 쓰고 있다면 마이그레이션 비용이 거의 없다. 환경 변수, 볼륨, 네트워크 설정 — 대부분 그대로 가져올 수 있다. 처음 클러스터를 띄우는 데 30분도 안 걸렸다.
문제는 거기서부터다.
Docker Swarm의 개발 속도가 사실상 멈춰있다. Docker가 Kubernetes 통합에 집중하면서 Swarm은 유지보수 모드에 가까워졌다. GitHub Issues를 보면 2022년에 open된 버그들이 아직도 그냥 있다. 이게 처음에는 별거 아닌 것처럼 느껴지는데, 장기 운영을 생각하면 신경 쓰이는 부분이다.
롤링 업데이트에서도 차이가 있다. update_config로 어느 정도 제어는 되는데, Kubernetes의 readiness probe 같은 세밀한 헬스체크 연동이 없다. 업데이트 중에 잠깐 에러 응답이 튀는 경우가 있었다. 작은 차이 같지만 트래픽이 몰리는 엔드포인트에서는 실제로 체감된다.
모니터링도 마찬가지다. Prometheus + Grafana를 Swarm 위에서 돌리면 서비스 디스커버리 설정이 번거롭다. Kubernetes는 kube-prometheus-stack 하나로 거의 다 되는데, Swarm에서는 수동으로 해줘야 하는 부분이 꽤 있다.
Swarm이 맞는 상황이 있긴 하다. 2~3명이 단순한 웹 서비스를 운영하고, 복잡한 스케일링 로직이 필요 없고, 학습 비용을 최소화해야 할 때. 그런 팀에게는 Swarm이 합리적인 선택이다. 다만 나라면 새 프로젝트에는 처음부터 선택하지 않겠다. 기술 부채를 미리 사 오는 느낌이라서.
Nomad: 내가 몰랐던 선택지
솔직히 말하면, Nomad를 처음에 진지하게 고려하지 않았다. HashiCorp 제품이라는 건 알고 있었는데, Terraform이나 Vault처럼 엔터프라이즈 포커스 도구라고 막연하게 생각했던 것 같다. 직접 써보니 생각이 완전히 바뀌었다.
Nomad의 첫 번째 특징 — 단일 바이너리다. 클라우드 서버” rel=”nofollow sponsored” target=”_blank”>서버든 클라이언트든 nomad 하나다. 설치가 말 그대로 바이너리 하나 복사하는 게 전부다. 처음 클러스터를 띄우는 데 40분 걸렸는데, 그 중 20분은 HCL 설정 문법을 익히는 시간이었다.
# Nomad job 파일 — HCL 문법이 처음엔 낯설지만 읽기 쉽다
job "api-server" {
datacenters = ["dc1"]
type = "service"
group "api" {
count = 3
update {
max_parallel = 1
min_healthy_time = "30s"
healthy_deadline = "5m"
# 헬스체크 실패 시 자동으로 이전 버전으로 롤백
auto_revert = true
}
task "api" {
driver = "docker"
config {
image = "myapp/api:v2.1.4"
ports = ["http"]
}
resources {
cpu = 500 # MHz 단위
memory = 256 # MB 단위
}
}
}
}
Nomad의 진짜 차별점은 워크로드 유연성이다. Docker 컨테이너만 실행하는 게 아니라, raw exec(베어 프로세스), Java 앱, Qemu 가상머신까지 같은 클러스터에서 돌릴 수 있다. 우리 팀에는 Python 배치 잡이 몇 개 있었는데, Kubernetes에서는 CronJob으로 전부 컨테이너화해야 했던 게 Nomad에서는 그냥 바이너리로 실행하는 게 가능했다. 사소한 것 같지만 기존 스크립트 마이그레이션 비용이 확 줄었다. 나는 이 부분을 처음에 과소평가했다가, 실제로 해보고 나서 생각이 바뀌었다.
불편한 점도 있다. Consul과의 연동이 사실상 필수인데 — 서비스 디스커버리를 제대로 쓰려면 — 그러면 Consul도 함께 운영해야 한다. Vault까지 붙이면 세 개 시스템을 동시에 관리하는 셈이다. 각각은 잘 만들어진 도구들이지만, 전체 스택 복잡도가 올라가는 건 사실이다.
생태계도 Kubernetes에 비하면 작다. Nomad 클러스터에서 Prometheus 메트릭 수집 설정을 할 때 삽질을 꽤 했다. 공식 문서는 잘 돼있는데, 커뮤니티 Q&A가 부족하다 보니 엣지 케이스를 만나면 막힌다.
그리고 한 가지 더 — 2023년에 HashiCorp가 BSL(Business Source License)로 라이선스를 변경했다. 오픈소스 프로젝트에 사용하거나, 직접 경쟁 서비스를 만드는 게 아닌 한 대부분 문제없지만, 프로덕션 클라우드 호스팅” rel=”nofollow sponsored” target=”_blank”>클라우드” rel=”nofollow sponsored” target=”_blank”>프로덕션 클라우드” rel=”nofollow sponsored” target=”_blank”>프로덕션 도입 전에 법무팀 확인이 필요한 회사라면 이 부분을 먼저 확인해야 한다. Terraform 쪽은 OpenTofu라는 fork가 활발하게 운영 중인데, Nomad 쪽은 아직 그 정도 수준의 대안이 없다.
세 도구를 나란히 놓고 봤을 때
2주 동안 테스트하면서 느낀 걸 정리하면 이렇다.
운영 복잡도는 예상대로 Kubernetes > Nomad+Consul > Docker Swarm 순이다. 복잡도와 기능 범위는 비례하니까 당연한 결과긴 한데, 그 간극이 생각보다 크다는 게 포인트다. Kubernetes와 Swarm 사이는 숫자로 표현이 안 될 만큼 벌어진다.
학습 비용은 복잡도와 비슷하게 간다. 여기서 내가 놀란 건 Nomad가 Kubernetes보다 꽤 많이 쉬웠다는 점이다. HCL이 낯설어서 처음에 거부감이 있었는데, 이틀 지나니까 오히려 읽기 편하다는 느낌이 들었다. YAML을 수천 줄 써본 입장에서 하는 말이다.
생태계 차이는 극단적이다. Kubernetes 커뮤니티는 에러 메시지를 그대로 복붙하면 답이 나오는 수준이고, Nomad는 그보다 훨씬 얇다. Swarm은 유지보수 모드라 새로운 답변 자체가 드물다. 비용은 인프라만 보면 세 가지 큰 차이가 없는데, 매니지드 Kubernetes는 컨트롤 플레인 비용이 추가로 붙는다는 걸 염두에 둬야 한다.
내가 예상하지 못한 건 Nomad의 job 스케줄링이 생각보다 훨씬 세밀하다는 점이었다. affinity와 spread 설정으로 워크로드 배치를 제어하는 게 Kubernetes의 affinity rule만큼 유연하다. 이 부분은 테스트 전에는 몰랐다.
결론: 내가 실제로 선택한 것
우리 팀은 결국 EKS를 선택했다.
5명 팀에 12개 서비스, 앞으로 계속 늘어날 예정. 초기 비용이 조금 더 들더라도 Kubernetes 생태계가 주는 유연성이 장기적으로 낫다고 판단했다. 컨트롤 플레인을 AWS가 관리해주니 운영 부담도 자체 구성보다 훨씬 낮다. Helm으로 배포하기” rel=”nofollow sponsored” target=”_blank”>배포하기” rel=”nofollow sponsored” target=”_blank”>배포를 표준화하고, Argo CD로 GitOps를 붙이는 데 약 1주일이 걸렸다. 지금은 배포가 훨씬 안정적이다 — 새벽 2시 알림도 없고.
다른 팀에게 추천을 한다면 이렇게 말하겠다. 팀이 10명 이상이고 서비스가 계속 성장할 예정이라면 그냥 Kubernetes로 가라. 단기적으로 학습 비용이 아프지만 장기적으로 선택지가 훨씬 많다. 2~3명이 MVP를 빠르게 굴려야 하는 상황이라면 Swarm도 나쁘지 않다 — 단, 언젠가는 마이그레이션한다는 전제 하에. 이미 HashiCorp 스택을 쓰고 있거나 컨테이너가 아닌 워크로드가 섞여 있는 팀이라면 Nomad를 진지하게 고려해볼 만하다. 솔직히 이 케이스에서 Nomad는 과소평가받고 있다.
도구 선택보다 더 중요한 건 팀이 그 도구를 실제로 이해하고 운영할 수 있느냐다. 가장 좋은 오케스트레이터도 아무도 내부를 모르면 장애 상황에서 아무 도움이 안 된다. 내가 2주를 투자한 건 단순히 비교를 위해서가 아니라, 팀원들이 실제로 다룰 수 있는 게 뭔지 확인하고 싶어서였다. 그게 결국 가장 중요한 기준이었다.