6 분 소요

Summary

Docker 와 Kubernetes(이하 K8s) 는 거의 한 묶음으로 같이 언급되는데, 정작 둘이 무슨 관계인지 헷갈리기 쉬워요. 경쟁자도 아니고, 같은 일을 하는 것도 아니에요. 한 줄로 줄이면 Docker 는 워커(worker), K8s 는 오케스트레이터(orchestrator) 입니다. 이 글에서는 “워커” 와 “오케스트레이션” 이라는 두 단어를 기준으로 둘이 각자 무슨 일을 하는지, 어디서 만나는지, 실무에서는 어떻게 분업하는지 차근차근 정리해볼게요.

💡 이 글에서 다루는 것

  • 컨테이너 세계에서 “워커” 와 “오케스트레이터” 가 각각 뭘 의미하는지
  • Docker 가 워커로서 실제로 하는 일
  • K8s 가 오케스트레이터로서 실제로 하는 일
  • 두 도구가 만나는 지점 (OCI 이미지, CRI, containerd)
  • 로컬 개발 ~ 운영까지 둘이 어떻게 손을 잡고 가는지



1. 비유부터: 일꾼 한 명과 작업반장

본격적으로 들어가기 전에 비유 하나 깔고 갈게요. 컨테이너 인프라를 식당 주방이라고 생각해봅시다.

  • 요리사 한 명 이 있어요. 레시피(이미지)를 받아서 요리(컨테이너) 를 만들어내요. 본인 주방(노드) 안에서만 일해요.
  • 작업반장(셰프) 은 직접 요리하지 않아요. 대신 “지금 손님이 몰리니까 이 메뉴는 세 명이 동시에 만들어”, “저 친구가 손 다쳤으니 다른 친구한테 옮겨”, “오늘부터 메뉴 바뀌었으니 한 명씩 새 레시피로 교체해” 같은 결정을 내려요.

요리사가 Docker, 작업반장이 K8s 라고 생각하면 거의 맞아요. 요리사 없이 작업반장만 있으면 아무것도 안 만들어지고, 요리사만 있으면 손님이 늘었을 때 우왕좌왕하게 됩니다.



2. Docker: 한 노드 위의 워커

Docker 는 우리가 흔히 “컨테이너” 라고 부르는 것을 실제로 만들고, 실행하고, 멈추는 도구예요. 한 대의 머신 위에서 동작하고, 그 머신 안의 일에 집중해요.

Docker 가 워커로서 하는 일을 정리하면 이래요.

명령어 예시 설명
이미지 빌드 docker build Dockerfile 을 OCI 이미지로 굽기
컨테이너 실행 docker run 이미지 → 격리된 프로세스로 띄우기
여러 컨테이너 묶기 docker compose up DB + 앱 + Redis 같이 한 머신에 한꺼번에
네트워크/볼륨 관리 docker network, docker volume 컨테이너 간 통신, 영속 데이터
상태 확인 docker ps, docker logs 누가 떠 있고, 뭘 찍고 있는지

작은 예시로, 로컬에서 nginx 컨테이너 하나 띄우는 건 이렇게 끝나요.

docker run -d --name web -p 8080:80 nginx

이 한 줄이면 머신에 nginx 가 8080 포트로 뜨고, 죽으면 --restart 옵션 정도로 재시작도 시킬 수 있어요. 한 대 서버 안에서 “이거 띄우고, 저거 띄우고” 까지는 Docker 만으로 충분히 끝납니다.

💡 핵심: Docker 는 한 노드 안의 일꾼. 그 노드 밖의 일에는 관여하지 않아요. “다른 서버에도 똑같이 띄워줘” 같은 건 Docker 의 일이 아닙니다.



3. Kubernetes: 여러 워커를 부리는 오케스트레이터

이제 머신이 한 대가 아니라 수십 ~ 수천 대 라고 생각해보세요. 각 머신마다 Docker(또는 동급 런타임) 가 깔려 있어요. 누군가는 이런 결정을 내려야 합니다.

  • 새로 띄울 컨테이너를 어느 노드 에 올릴까?
  • 노드 한 대가 죽으면 그 위에서 돌던 컨테이너는 어디로 옮길까?
  • 트래픽이 두 배가 됐어요. 컨테이너를 몇 개까지 늘릴까?
  • 새 버전 배포할 때 한 번에 다 바꿀까, 조금씩 굴릴까?

이 결정을 내려주는 게 K8s 예요. K8s 는 직접 컨테이너를 만들지 않아요. “어떤 컨테이너가 몇 개 떠 있어야 한다” 는 목표(desired state) 만 받고, 그걸 실제로 맞추기 위해 노드 위의 워커들에게 일을 시킵니다.

K8s 가 다루는 핵심 개념을 워커/오케스트레이션 관점으로 보면 이래요.

개념 무슨 역할
Node 실제 컨테이너가 도는 한 대의 머신. 여기 워커 런타임이 깔려 있음
Pod 같이 떠야 하는 컨테이너 1~N개 묶음. 스케줄링의 최소 단위
Deployment “이 Pod 가 항상 3개 있어야 해” 같은 선언
ReplicaSet Deployment 가 시키는 대로 Pod 개수를 맞추는 컨트롤러
Service Pod 들 앞에 붙는 안정적인 네트워크 주소 (Pod 가 죽고 살아도 주소는 그대로)
Scheduler 새 Pod 를 어느 노드에 올릴지 결정
kubelet 각 노드에 깔린 에이전트. K8s 명령을 받아서 실제로 컨테이너를 띄움

여기서 재미있는 게, 컨테이너를 실제로 띄우는 건 K8s 가 아니에요. K8s 가 kubelet 한테 “이 Pod 띄워줘” 라고 시키면, kubelet 이 노드 위의 컨테이너 런타임(과거에는 Docker, 지금은 보통 containerd) 한테 다시 명령을 내려서 띄워요. K8s 는 끝까지 지시만 합니다.

💡 핵심: K8s 는 여러 노드 위의 일꾼들을 부리는 작업반장. “어디서, 몇 개, 어떻게 떠 있어야 한다” 만 다루고, 실제로 컨테이너를 만드는 건 노드 위의 런타임이에요.



4. 같은 일을 두 도구로 비교해보기

같은 작업을 Docker 만으로 할 때와 K8s 로 할 때를 옆에 놓고 보면 둘의 영역 차이가 확 와닿아요.

작업 Docker Kubernetes
컨테이너 1개 띄우기 docker run Pod (보통 Deployment 로 감쌈)
여러 컨테이너 같이 띄우기 docker compose up Deployment + Service
한 머신에서만 실행 자연스러움 가능하지만 과한 셋업
여러 머신에 분산 직접은 어려움 (Swarm 별도) 기본 기능
컨테이너 죽으면 자동 재시작 --restart 옵션 자가복구가 기본
부하 따라 개수 자동 조절 없음 HPA(Horizontal Pod Autoscaler)
트래픽 여러 인스턴스에 분산 nginx/HAProxy 별도 Service / Ingress 가 기본
무중단 배포 (롤링 업데이트) 직접 스크립트 Deployment 가 기본 제공
시크릿/설정 관리 .env, docker secret (Swarm 한정) Secret, ConfigMap 이 기본

표를 한 번 훑고 나면 패턴이 보여요. 위쪽은 Docker 로도 충분하지만, 아래로 내려갈수록 “여러 대” 와 “오랫동안 안정적으로” 라는 키워드가 추가돼요. 그게 K8s 의 영역이에요.



5. 둘은 어디서 만나나? (OCI, CRI, containerd)

여기까지 보면 “그럼 K8s 가 Docker 를 안 쓰면 둘은 별개 아닌가?” 싶을 수 있어요. 둘이 실제로 만나는 지점이 두 군데 있어요.

5-1. OCI 이미지

OCI 는 Open Container Initiative 의 줄임말이에요. 컨테이너 이미지가 어떻게 생겨야 하는지 정해둔 표준 포맷 이에요. 우리가 Docker 로 빌드한 이미지는 사실 OCI 이미지예요.

  • 개발자가 로컬에서 docker build 로 이미지 굽기
  • 그 이미지를 레지스트리(Docker Hub, ECR, GHCR 등) 에 푸시
  • K8s 클러스터의 노드들이 그 이미지를 풀(pull) 해서 컨테이너로 띄움

여기서 K8s 는 Docker 그 자체를 쓰지 않아도 Docker 로 만든 이미지 는 그대로 받아요. 이미지 포맷이 표준이라서 가능한 일이에요. 둘이 만나는 가장 자연스러운 지점이에요.

5-2. CRI 와 containerd 이야기

옛날에는 K8s 가 노드 위에서 컨테이너를 띄울 때 진짜로 Docker 데몬을 호출해서 썼어요. 그런데 이게 좀 복잡했어요. Docker 안에는 우리가 안 쓰는 기능도 많이 들어있고, K8s 가 원하는 건 “컨테이너 띄우기” 정도였거든요.

그래서 K8s 1.5 쯤에 CRI(Container Runtime Interface) 라는 표준 인터페이스가 생겼고, 1.24 부터는 K8s 가 Docker 데몬을 직접 호출하던 어댑터(dockershim) 가 제거됐어요. 지금 K8s 노드들은 보통 이런 런타임을 써요.

런타임 특징
containerd 원래 Docker 내부에 들어있던 핵심 런타임이 떨어져 나옴. 가장 흔히 씀
CRI-O K8s 전용으로 가볍게 만든 런타임
Docker Engine 어댑터(cri-dockerd) 통해서 여전히 쓸 수는 있음. 권장은 아님

여기서 재미있는 점. containerd 는 원래 Docker 의 일부였어요. Docker 가 워낙 잘 만든 런타임이라, 그 핵심 부분만 떼어서 K8s 노드에서 직접 쓰는 거예요. 그러니까 “K8s 가 Docker 를 떼어냈다” 보다는 “K8s 가 Docker 의 알맹이만 골라 썼다” 에 가까워요.

⚠️ 자주 헷갈리는 포인트: “K8s 가 Docker 를 안 쓴다 = Docker 이미지가 K8s 에서 안 돈다” 가 아니에요. 런타임 어댑터가 빠진 거고, 이미지 호환성은 그대로예요. 개발자 입장에서 보면 거의 바뀐 게 없습니다.



6. 로컬 개발 ~ 운영까지: 실무에서의 분업

실제 팀에서는 둘이 이런 흐름으로 손을 잡아요.

  1. 개발자 로컬Docker / docker compose 로 앱을 띄움. K8s 까지 안 끌고 와도 됨.
  2. CI 파이프라인docker build 로 이미지를 굽고, 레지스트리에 푸시.
  3. 스테이징/운영 클러스터 — K8s 가 새 이미지 태그를 가져와서 Deployment 를 굴림. 노드 위의 containerd 가 실제 컨테이너를 띄움.
  4. 장애/스케일 — 노드가 죽거나 트래픽이 늘면 K8s 가 알아서 다른 노드로 옮기거나 Pod 를 더 띄움.

여기서 핵심은, 개발자는 Docker 만 잘 알아도 일상 작업이 굴러간다 는 거예요. K8s 는 운영 단계에서 본격적으로 등장해요. 그리고 둘이 만나는 접점은 거의 항상 이미지 예요.

작은 팀이면 사실 K8s 까지 안 가도 돼요. 머신 한두 대에 Docker + Compose 로도 운영 잘 굴러갑니다. K8s 는 “여러 노드에 걸쳐 안정적으로 굴려야 하고, 자가복구/롤링업데이트/스케일이 필요한 시점” 에 등장하는 도구예요.

✅ 정리해두면 좋은 기준

  • 노드 1대, 컨테이너 < 10개 → Docker / Compose 로 충분
  • 노드 여러 대, 자가복구/스케일 필요 → K8s 로 넘어갈 시점
  • 이미지 빌드/배포 워크플로우는 어느 쪽이든 Docker 가 자연스럽게 끼어있음



7. 마무리: 경쟁자가 아니라 층이 다른 도구

다시 한 줄로 줄이면 이래요.

  • Docker = 한 노드 위의 워커. 이미지로 컨테이너를 굽고, 띄우고, 멈춤.
  • K8s = 여러 노드 위의 오케스트레이터. “어떤 컨테이너가 어디서 몇 개 떠 있어야 한다” 를 관리.
  • 둘은 대체 관계가 아니라 층이 다른 도구 예요. K8s 가 Docker 를 직접 호출하지 않더라도, OCI 이미지라는 표준 위에서 자연스럽게 같이 굴러갑니다.

처음 컨테이너 인프라를 익힐 때는 K8s 부터 들이대지 말고 Docker 로 컨테이너 한두 개 띄워보는 감각 을 먼저 잡는 게 훨씬 빠르게 와닿아요. 그 위에서 “여러 대에 걸쳐서 자동으로 굴러갔으면 좋겠다” 는 욕구가 생기는 시점이 K8s 로 넘어갈 타이밍입니다.

일단 오늘은 여기까지…..
다음 글에서는 K8s 의 핵심 오브젝트(Pod, Deployment, Service) 가 실제로 어떻게 같이 동작하는지 작은 예시로 풀어볼게요.