컨테이너 학습 노트 목차

컨테이너는 무엇인가 — 결국 프로세스다

"가벼운 가상 머신" 이라는 설명은 틀렸다. 이 편은 컨테이너가 실제로 무엇인지, 그리고 그 사실이 운영에서 무엇을 바꾸는지를 본다.


1. 격리된 프로세스일 뿐이다

# 호스트에서 본다
$ docker run -d --name web nginx
$ ps -ef | grep nginx
root  48231  48210  nginx: master process nginx -g daemon off;

호스트의 ps 에 그대로 보인다. 가상 머신이라면 절대 안 보일 것이다.

컨테이너 = 리눅스 프로세스 + 세 가지 제약

namespace무엇이 '보이는가' 를 제한한다
cgroup무엇을 '얼마나 쓰는가' 를 제한한다
파일시스템어떤 루트를 보는가 (chroot 의 발전형)

셋 다 원래 있던 리눅스 기능이다 컨테이너 런타임은 이것을 조합해 주는 도구일 뿐이다

그래서 컨테이너를 "만드는" 특별한 기술은 없다. docker run 이 하는 일은 네임스페이스를 만들고 cgroup 을 걸고 루트를 바꿔 프로세스를 실행하는 것이다.

2. namespace — 무엇이 보이는가

  • PID — 프로세스 목록. 컨테이너 안에서 자기가 PID 1 이 된다
  • Network — 네트워크 인터페이스·라우팅·포트. 컨테이너마다 자기 IP
  • Mount — 마운트 지점. 자기 루트 파일시스템만 본다
  • UTS — 호스트명·도메인명
  • IPC — 공유 메모리·세마포어
  • User — UID/GID 매핑. 안에서는 root, 밖에서는 일반 사용자
  • Cgroupcgroup 계층 자체를 가린다
$ docker run --rm alpine ps -ef
PID   USER     COMMAND
    1 root     ps -ef        ← 컨테이너 안에서는 자기가 1번이다

$ docker run --rm --pid=host alpine ps -ef | head -3
    1 root     /sbin/init    ← 호스트 네임스페이스를 쓰면 전부 보인다

격리는 선택적으로 풀 수 있다. --pid=host, --net=host 처럼. 이것이 편리하면서 동시에 위험한 지점이다.

3. cgroup — 얼마나 쓰는가

$ docker run -d --memory=512m --cpus=1.5 myapp
  • memory — 상한을 넘으면 커널이 프로세스를 죽인다 (OOM Killer)
  • cpu — cpu.max 로 주기당 실행 시간을 제한한다
    • --cpus=1.5 → 100ms 주기에 150ms 사용 허용
  • pids — 프로세스·스레드 수 제한 (fork 폭탄 방어)
  • io — 디스크 대역폭 제한

메모리 제한을 넘기면 무슨 일이 생기나

컨테이너 안 프로세스가 512MB 를 넘겨 쓰려 한다

  • 커널이 그 cgroup 안에서 프로세스를 하나 죽인다 (OOM Killer)
  • 보통 가장 많이 쓰는 프로세스, 즉 우리 애플리케이션
  • 컨테이너 상태가 OOMKilled, 종료 코드 137

중요 — 애플리케이션 로그에 아무것도 안 남는다

  • 커널이 SIGKILL 로 죽인 것이라 정리 코드가 돌지 않는다
  • docker inspect 나 이벤트 로그에서만 확인된다
$ docker inspect web --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
true 137

CPU 제한은 죽이지 않는다 — 느려진다

  • 메모리 — 초과하면 죽인다 (하드 리밋)
  • CPU — 초과하면 다음 주기까지 재운다 (스로틀링)

증상이 완전히 다르다

  • 메모리 → 갑자기 죽는다. 재시작된다
  • CPU → 안 죽는데 응답이 튄다. p99 만 나빠진다

CPU 스로틀링은 지표를 봐야 보인다

  • cgroup 의 throttled 시간·횟수를 확인한다
  • "CPU 사용률은 60% 인데 왜 느리지" 의 정체가 이것인 경우가 많다

4. 가상 머신과 정말로 다른 점

다이어그램 로딩 중…
가상 머신컨테이너
커널게스트마다 하나씩호스트 커널을 공유
기동수십 초밀리초
이미지GB 단위MB 단위
격리 강도강하다 (커널까지 분리)약하다 (커널 취약점이 곧 탈출 경로)
다른 OS가능 (리눅스 위 윈도우)불가 (같은 커널 계열만)

커널을 공유한다는 사실이 만드는 제약

  • 호스트 커널 버전이 곧 컨테이너의 커널 버전이다

    • 이미지에 든 배포판은 유저 공간(라이브러리·바이너리)일 뿐이다
    • "우분투 이미지" 를 써도 커널은 호스트 것이다
  • 커널 파라미터(sysctl)는 대부분 호스트와 공유된다

  • 리눅스 컨테이너는 리눅스에서만 네이티브로 돈다

    • macOS·윈도우의 Docker Desktop 은 내부에 리눅스 VM 을 띄운다
    • 그래서 파일 마운트 성능이 느리고, 그 위에서 도는 것이다

5. 이미지와 컨테이너는 다른 것이다

  • 이미지 — 읽기 전용 템플릿. 파일 시스템 스냅숏 + 메타데이터
  • 컨테이너 — 이미지를 기반으로 만든 '실행 중인 인스턴스'
    • 그 위에 쓰기 가능한 얇은 레이어가 하나 얹힌다

클래스와 인스턴스의 관계와 같다 이미지 하나로 컨테이너를 100개 만들어도 이미지는 한 벌만 저장된다

$ docker images        # 이미지 목록
$ docker ps -a         # 컨테이너 목록 (종료된 것 포함)

컨테이너를 지워도 이미지는 남는다 이미지를 지우려면 그것을 쓰는 컨테이너가 먼저 없어야 한다 "디스크가 찼는데 뭘 지워야 하지" 의 출발점이다

6. 무엇이 표준화됐나

OCI (Open Container Initiative) 표준
  이미지 스펙    이미지가 어떤 구조인지
  런타임 스펙    컨테이너를 어떻게 실행하는지
  배포 스펙      레지스트리와 어떻게 주고받는지
덕분에 도구를 갈아 끼울 수 있다
  빌드     Docker Buildx · BuildKit · Buildah · Kaniko
  런타임   containerd · CRI-O  (그 아래 runc · crun)
  레지스트리  Docker Hub · ECR · GHCR · Harbor

계층을 정리하면

  docker CLI
    → containerd        (고수준 런타임 — 이미지 관리 · 생명주기)
      → runc            (저수준 런타임 — 실제로 네임스페이스·cgroup 을 만든다)
        → 리눅스 커널

쿠버네티스는 CRI 로 containerd·CRI-O 를 직접 부른다 그래서 "쿠버네티스가 Docker 를 뺐다" 는 말이 나온 것이다 이미지는 그대로 쓴다 — OCI 표준이기 때문이다

7. 그래서 운영에서 무엇이 달라지나

  • 이미지는 불변이다 → 컨테이너 안에서 고친 것은 사라진다

    • "들어가서 설정을 바꿨는데 재배포하니 원복됐다" 는 당연한 결과다
    • 바꿀 것은 이미지나 환경변수·볼륨에 둔다
  • 컨테이너는 언제든 죽고 다시 뜬다고 가정한다

    • 상태를 안에 두지 않는다 (로그·업로드 파일·세션)
  • 프로세스가 하나 죽으면 컨테이너가 끝난다

    • PID 1 이 무엇인지, 시그널을 어떻게 받는지가 중요해진다 (04편)
  • 리소스 제한이 없으면 한 컨테이너가 호스트를 잠식한다

    • limit 은 선택이 아니라 기본이다

한눈에 정리

  • 컨테이너 — 격리된 리눅스 프로세스. namespace + cgroup + 루트 변경
  • namespace — 무엇이 보이는가 (PID · Network · Mount · UTS · IPC · User)
  • cgroup — 얼마나 쓰는가. 메모리는 죽이고(137) CPU 는 스로틀한다
  • VM 과의 차이 — 커널 공유 여부. 그래서 가볍고, 그래서 격리가 약하다
  • 이미지/컨테이너 — 읽기 전용 템플릿 vs 실행 인스턴스 + 쓰기 레이어
  • OCI — 이미지·런타임·배포 스펙. 도구를 갈아 끼울 수 있는 근거
  • 계층 — docker → containerd → runc → 커널
  • 운영 전제 — 이미지 불변 · 언제든 죽는다 · limit 은 기본

출처 — OCI Image/Runtime Specification · Docker Docs "Container runtime" · Linux man pages: namespaces(7) · cgroups(7) · Kubernetes Docs "Container Runtimes"

이미지와 레이어 — 빌드가 빨랐다 느렸다 하는 이유