Docker란?

애플리케이션과 그 실행 환경을 한데 묶어 어디서든 동일하게 실행할 수 있도록 해 주는 컨테이너 기술 스택.
커널을 공유하므로 VM보다 가볍고 빨리 뜬다.

언제 쓰면 좋은가
  • 마이크로서비스를 독립 배포·스케일할 때
    • 예: Node.js API 한 개 = 컨테이너 한 개
  • CI/CD 파이프라인에서 빌드·테스트 환경을 표준화할 때
    • 예: GitHub Actions에서 docker build로 동일 이미지 생성
  • 로컬 ↔ 클라우드 간 개발 환경을 맞춰야 할 때
    • 예: 개발자는 Mac, 서버는 Linux라도 컨테이너로 환경 통일
  • 데이터 과학·ML 모델을 재현 가능하게 배포할 때
    • 예: Python + Torch + CUDA 버전을 이미지에 고정해 추후 재실행
  • 데모·PoC를 빠르게 올리고 버리려 할 때
    • 예: Postgres, Redis, Kafka 등을 one-liner로 띄우고 실험
컨테이너와 가상머신 (VM)

컨테이너와 VM은 모두 ‘하나의 서버에 여러 워크로드를 격리해 돌린다’는 목표는 같지만, 격리 계층이 다르다.
격리 계층 차이 때문에 자원 요구량·부팅 속도·보안 모델·운영 방식이 크게 달라진다.

1. 커널 공유 vs. 전용 OS
  • 컨테이너
    • 호스트 커널을 그대로 사용
    • 네임스페이스(UTS, PID, NET, IPC, MNT, USER)와 cgroup이 프로세스·네트워크·자원을 논리적으로 분리
  • VM
    • 하이퍼바이저 위에 게스트 OS를 올려 커널까지 완전히 분리
    • 커널 패치나 모듈은 게스트 내부에서 따로 관리
2. 하이퍼바이저 계층1
  • 컨테이너
    • 하이퍼바이저가 없고 단순 프로세스 트리
    • 시스템콜은 호스트 커널에서 바로 처리돼 오버헤드가 적음
  • VM
    • Type-1(ESXi, KVM, Hyper-V)·Type-2(VirtualBox, VMware Workstation)가 존재
    • 하드웨어 가상화로 CPU, I/O, 메모리를 완전 에뮬레이션
3. 부팅 및 이미지 크기
  • 컨테이너
    • 프로세스 시작 = 컨테이너 시작
    • 수 초 이내 기동, 추가 메모리는 일반적으로 수 ~ 수십 MB
  • VM
    • BIOS/UEFI → 부트로더 → 커널 → 유저스페이스로 올라와야 해서 부팅에 수 분
    • 최소 메모리 예약만 512 MB ~1 GB, 디스크에 OS 이미지(수 GB) 필수
4. 성능
  • 컨테이너
    • CPU·메모리는 네이티브 성능에 근접
    • I/O의 경우 호스트 파일 시스템과 네트워크를 얇게 감싸 속도 빠름
  • VM
    • CPU·메모리는 가상화 계층으로 미세 손실
    • I/O의 경우 가상 디스크·가상 NIC로 한 번 더 래핑돼 지연 증가
5. 보안·격리
  • 컨테이너
    • 커널을 공유해 커널 취약점이 곧 컨테이너 전체에 영향
    • seccomp, AppArmor, SELinux, rootless 모드로 완화
    • 대규모 마이크로서비스·CI/CD에 적합
  • VM
    • 게스트 OS 침해가 하이퍼바이저를 뚫지 않는 한 호스트에 영향 없음
    • 민감 데이터(금융, 강한 규제 산업)에 적합
6. 운영·이미지 관리
  • 컨테이너
    • 컨테이너 이미지는 레이어(blobs) 조합이라 중복을 dedupe하여 저장·전송
    • 재빌드 시 변경 레이어만 업데이트
  • VM
    • VM 이미지는 전체 디스크 스냅샷
    • 압축·전송 부하가 크고 버전 관리가 어렵지만 OS 수준 커스터마이징이 자유로움

Docker 아키텍처

2

전체 요청 파이프라인

docker CLI → dockerd → containerd → containerd-shim → runc → 컨테이너 프로세스

  • 사용자가 명령을 치면 CLI가 REST 요청을 날리고, dockerd가 고수준 관리·조율을 맡는다.
  • 실제 컨테이너 생명주기는 containerd가 담당하며, 컨테이너마다 shim을 띄워 runc에게 실행을 넘긴다.
  • runc가 네임스페이스3을 설정하고 clone()4 시스템콜5로 애플리케이션을 시작하면 컨테이너가 완성된다.
docker CLI
  • 터미널 명령어 모음이자 HTTP REST 클라이언트
  • 소켓 (Socket)을 통해 dockred가 열어 둔 REST API에 HTTP 요청을 전송
  • context, buildx, compose 등 서브커맨드로 멀티플랫폼 빌드·오케스트레이션 확장
dockerd
  • Docker Engine 데몬으로 이미지, 컨테이너, 네트워크, 볼륨 같은 고수준 객체 관리
  • 이미지가 없으면 레지스트리 pull
  • 네트워크·볼륨을 준비한 뒤 containerd에 작업 위임
  • 플러그인 시스템으로 로그·네트워크·볼륨 드라이버를 동적으로 로드
  • REST API 서버 역할: CLI·GUI·원격 툴이 모두 같은 API로 접근
containerd
  • CNCF 프로젝트6
  • gRPC API7로 동작하며 독립 런타임으로도 사용 가능
  • Content Store와 Snapshotter로 이미지를 rootfs로 조합
    • Content Store : containerd 안에 있는 ‘창고’로 이미지 레이어 하나하나를 압축된 tar 파일 그대로 보관
    • Snapshotter : 창고에 있는 레이어들을 겹겹이 쌓아 컨테이너가 사용할 수 있는 루트 파일 시스템 (rootfs)으로 조립
  • 컨테이너 생명주기(Create/Start/Stop/Delete) 관리, Kubernetes CRI 플러그인 내장
  • ctr, nerdctl 같은 클라이언트로 dockerd 없이 직접 제어 가능
    • 중간에 dockerd가 빠져서 더 가볍고, 빠르고 단순한 환경에서 컨테이너 운용 가능
containerd-shim
  • 컨테이너마다 하나씩 생성되는 경량 프로세스
  • runc를 실행하고 난 뒤 표준입출력·PID·cgroup 계층을 보존해 docker logs, exec 가능하게 함
  • 컨테이너 종료 시 exit-code, OOM8 이벤트를 containerd로 전달
  • shim 덕분에 dockerd나 containerd가 재시작돼도 실제 애플리케이션은 끊기지 않음
runc
  • OCI Runtime 스펙 레퍼런스9 구현
  • shim이 전달한 config.json을 읽어
    • PID/NET/UTS/IPC/MNT/USER 네임스페이스 분리
    • cgroup v1/v2로 CPU·메모리·IO 한도 설정
    • seccomp, AppArmor, SELinux 정책 적용
  • 마지막에 clone()으로 격리된 프로세스를 생성 → 컨테이너 내부 PID 1이 실행
  • podman, cri-o, buildah 등 다른 엔진도 같은 runc 사용 → 생태계 호환

컨테이너가 종료되면 shim → containerd → dockerd → CLI 순으로 상태가 전파되고, CLI가 터미널에 “정상 종료(0)” 혹은 “비정상 종료(137)”처럼 알려 주면서 사이클이 끝난다.


Docker 객체

이미지 : 실행 가능한 애플리케이션 템플릿
컨테이너 : 이미지를 실행한 인스턴스
  • 격리된 환경에서 애플리케이션 실행
  • 변경 가능 (데이터 추가, 설정 변경 등)
  • 실행 중인 상태를 커밋해 새 이미지를 만들 수 있고, 스토리지·네트워크에 연결해 독립 서비스로 동작
네트워크
  • 브리지, 호스트, 오버레이, macvlan 등 드라이버로 컨테이너 간 통신 방식을 선택 가능
  • 참고
볼륨 : 도커가 직접 관리하는 영속 데이터 객체
  • 컨테이너가 만든 데이터(예: DB, 업로드 파일 등)를 컨테이너 생명주기와 분리해 호스트에 안전하게 저장  
  • 컨테이너를 삭제/재시작해도 데이터가 유지됨  
  • 여러 컨테이너가 같은 볼륨을 공유할 수 있고, 볼륨은 도커가 /var/lib/docker/volumes/에 별도로 관리  
  • 명령어 예:  
    docker volume create myvol
    docker run -v myvol:/container/path myimage
  • 특징: 호스트 경로 신경 안 써도 되고, 데이터 이식/관리/복구가 쉬움

컨테이너 파일 시스템 마운트 타입

컨테이너에서 외부 데이터와 연결하는 대표적인 세 가지 방식

  • Docker Volume: 도커가 직접 관리하는 영속 데이터 객체(위 내용)
  • Bind Mount: 호스트의 실제 디렉토리/파일을 컨테이너에 그대로 마운트  
    • 실시간 동기화, 호스트 파일 권한/구조에 영향 받음  
    • 명령어 예:  
      	docker run -v /host/path:/container/path myimage
  • tmpfs: 메모리 기반 임시 파일 시스템  
    • 컨테이너 종료/재시작 시 데이터 즉시 소멸  
    • 명령어 예:
      	docker run --tmpfs /container/path myimage

Footnotes

  1. 물리 서버의 CPU·메모리·I/O를 가상화해 여러 게스트 OS가 동시에 돌아가도록 자원을 중개·관리하는 소프트웨어 계층

  2. https://ikcoo.tistory.com/194

  3. 리눅스 커널이 프로세스 그룹마다 격리된 환경을 만드는 기술

  4. 리눅스에서 새로운 프로세스나 스레드를 만들 때 호출하는 시스템콜

  5. 커널 기능을 사용하기 위해 커널에 요청하는 특별한 함수 호출

  6. Cloud Native Computing Foundation이 공식적으로 인증하고 오픈소스 커뮤니티가 관리하는 소프트웨어

  7. 구글이 만든 오픈소스 원격 프로시저 호출 프레임워크로 언어/플랫폼에 관계없이 고성능 네트워크 API를 짤 수 있음

  8. Out-of-Memory. 컨테이너가 할당받은 메모리 한도를 넘거나 호스트 전체 메모리가 고갈돼 커널 OOM killer가 개입할 때 발생

  9. 리눅스 컨테이너 이미지와 런타임의 표준을 정하는 오픈소스 재단인 OCI (Open Container Initiative)에서 컨테이너를 실행하는 방법을 상세히 정의한 문서