Post

Container - Foundation for Docker & Kubernetes

Container - Foundation for Docker & Kubernetes

목차

Part 1: 컨테이너의 이해

  1. 컨테이너 탄생 배경
  2. 컨테이너 기술의 역사

Part 2: Linux 격리 기술 (기초)

  1. Linux Namespaces - 7가지 격리 타입
  2. Control Groups (Cgroups) - v1 vs v2

Part 3: 컨테이너 런타임 (스택)

  1. 런타임 아키텍처
  2. OCI, CRI, containerd, runc 계층 구조
  3. CNI 플러그인 (Calico, Flannel, Cilium)

Part 1: 컨테이너의 이해

1.1 컨테이너 탄생 배경

전통적인 배포 방식의 문제

컨테이너 등장 이전의 애플리케이션 배포:

물리 서버 시대

  • 하나의 서버에 하나의 애플리케이션
  • 낮은 자원 활용률 (보통 10-15%)
  • 느린 프로비저닝 (주 단위)
  • 높은 비용
  • 확장성 부족

가상 머신 시대

  • 하이퍼바이저를 통한 가상화
  • 자원 활용률 향상 (50-70%)
  • 더 나은 격리
  • 하지만 여전히 무거움 (GB 단위 크기, 분 단위 부팅)
  • Guest OS 오버헤드

의존성 지옥 (Dependency Hell)

  • “내 컴퓨터에서는 되는데요” 문제
  • 라이브러리 버전 충돌
  • 환경 차이 (개발/스테이징/프로덕션)
  • 복잡한 설정 관리

1.2 컨테이너의 정의와 개념

컨테이너란?

컨테이너는 애플리케이션과 그 의존성을 하나로 패키징한 경량 실행 환경이다.

주요 특징

  • 프로세스 격리: 각 컨테이너는 독립된 프로세스 공간
  • 파일시스템 격리: 독립된 루트 파일시스템
  • 네트워크 격리: 독립된 네트워크 스택
  • 리소스 제한: CPU, 메모리 등의 자원 제한
  • 이식성: “한 번 빌드하면 어디서나 실행”

컨테이너 vs 가상머신

가상머신 구조

1
2
3
4
5
6
7
하드웨어
  ↓
하이퍼바이저 (Type 1, Type 2)
  ↓
Guest OS (완전한 OS)
  ↓
애플리케이션
  • 각 VM은 완전한 OS 포함
  • 무겁고 느림 (GB 단위, 분 단위 부팅)
  • 강력한 격리

컨테이너 구조

1
2
3
4
5
6
7
하드웨어
  ↓
Host OS (공유하는 커널)
  ↓
컨테이너 런타임
  ↓
애플리케이션
  • 호스트 커널 공유
  • 가볍고 빠름 (MB 단위, 초 단위 시작)
  • 프로세스 수준 격리

컨테이너의 장단점

장점

  • 빠른 시작 속도 (초 단위)
  • 적은 리소스 사용 (MB 단위)
  • 높은 밀도 (한 호스트에 수백 개 컨테이너)
  • 일관된 환경 보장
  • 마이크로서비스 아키텍처에 적합
  • Docker & Kubernetes의 기반

단점

  • VM보다 약한 격리 (같은 커널 공유)
  • 호스트와 다른 OS 커널 사용 불가
  • 보안 고려사항 더 많음

1.3 컨테이너 기술의 역사

컨테이너는 하나의 기술이 아니라 여러 Linux 기술의 조합이 진화한 결과다:

chroot (1979)

  • Unix V7에서 처음 등장
  • 루트 디렉토리를 변경하여 프로세스 격리
  • 컨테이너의 가장 초기 형태

FreeBSD Jails (2000)

  • 더 강력한 격리 기능
  • 네트워크, 파일시스템, 프로세스 격리

Solaris Zones (2004)

  • 완전한 가상 환경
  • 자원 관리 포함

Linux-VServer (2001) & OpenVZ (2005)

  • Linux에서의 OS-level 가상화
  • 패치된 커널 필요

LXC (Linux Containers, 2008)

  • Linux 커널 기능 활용 (Namespace, Cgroups)
  • 사용자 공간 도구 제공
  • Docker의 초기 백엔드

Docker (2013) - 컨테이너 대중화

  • 간단한 사용성
  • 이미지 레이어링 (효율적 저장)
  • Docker Hub (이미지 공유 플랫폼)
  • 개발자 친화적 도구
  • 산업 표준으로 확산

Kubernetes (2014) - 컨테이너 오케스트레이션

  • Google의 컨테이너 오케스트레이션 시스템
  • 컨테이너 관리 자동화
  • 사실상 표준이 됨

현대 (2020s)

  • OCI 표준화
  • containerd, CRI-O 등 다양한 런타임
  • Rootless 컨테이너
  • WebAssembly와 컨테이너 융합

1.4 Linux 커널의 주요 기술

컨테이너는 Linux 커널의 여러 기능을 조합하여 구현된다:

기술역할효과
Namespaces시스템 리소스 격리각 컨테이너가 독립된 시스템처럼 보임
Cgroups자원 사용 제한컨테이너가 호스트 자원을 독점하지 못함
Union Filesystem레이어드 파일시스템효율적인 이미지 저장 및 공유
Capabilities세분화된 권한root 권한을 최소 권한으로 제한
Seccomp시스템 콜 필터링위험한 시스템 콜 차단
SELinux/AppArmor강제 접근 제어추가 보안 계층

1.5 Union Filesystem과 컨테이너 이미지

Union Filesystem이란?

Union Filesystem은 여러 디렉토리를 레이어(layer)로 쌓아서 하나의 통합된 파일시스템처럼 보이게 하는 기술이다. 컨테이너 이미지의 효율성과 이식성의 핵심이다.

필요성:

  1. 저장 공간 절약: 여러 컨테이너가 동일한 base 이미지 레이어 공유
  2. 빠른 이미지 배포: 변경된 레이어만 전송
  3. 이미지 재사용: 한 번 다운로드한 레이어는 캐싱되어 재사용
  4. 효율적인 빌드: 변경된 레이어만 재빌드

주요 Union Filesystem 종류

파일시스템설명사용처
OverlayFS (Overlay2)현재 Docker 기본, 메인라인 커널 포함Docker, Podman, containerd
AUFSDocker 초기 기본, Ubuntu/Debian 위주구형 Docker
Device Mapper블록 레벨 스토리지, RHEL/CentOS 기본구형 RHEL/CentOS
Btrfs / ZFS파일시스템 자체 스냅샷 기능특수 환경

OverlayFS 구조 (현대 Docker의 기본)

OverlayFS는 두 개의 디렉토리를 하나로 합쳐서 보여준다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
컨테이너 뷰 (Merged)
         │
    ┌────▼────────────────────┐
    │ MergedDir               │ ← 컨테이너가 보는 통합된 파일시스템
    │ /var/lib/docker/        │
    │   overlay2/<id>/merged  │
    └────┬────────────────────┘
         │
    ┌────▼────────────────────┐
    │ UpperDir (R/W Layer)    │ ← 컨테이너의 쓰기 레이어
    │ - 새로 생성된 파일       │   /var/lib/docker/overlay2/<id>/diff
    │ - 수정된 파일 (CoW)      │
    │ - Whiteout 파일 (삭제)   │
    └─────────────────────────┘
         │
    ┌────▼────────────────────┐
    │ LowerDir (R/O Layers)   │ ← 이미지 레이어들 (읽기 전용)
    │  Layer N: App files     │
    │  Layer 3: Dependencies  │
    │  Layer 2: nginx binary  │
    │  Layer 1: ubuntu base   │
    └─────────────────────────┘

레이어 역할:

  • LowerDir: 읽기 전용 이미지 레이어들 (콜론으로 구분된 여러 경로)
  • UpperDir: 컨테이너의 읽기/쓰기 레이어 (변경 사항 저장)
  • MergedDir: UpperDir + LowerDir을 합친 최종 뷰
  • WorkDir: OverlayFS 내부 작업용 디렉토리

Copy-on-Write (CoW) 동작 원리

컨테이너가 파일을 수정할 때의 동작:

1. 읽기 작업:

1
2
3
4
5
6
7
8
9
Container → MergedDir에서 파일 찾기
           ↓
      UpperDir 확인
           ↓ (없으면)
      LowerDir 탐색 (레이어 순서대로)
           ↓
      파일 반환

성능: O(레이어 수) - 레이어가 많으면 느림

2. 쓰기 작업 (파일이 LowerDir에 있을 때):

1
2
3
4
5
6
7
8
9
10
11
12
Container가 /etc/nginx.conf 수정 요청
           ↓
1. LowerDir에서 nginx.conf 찾기
           ↓
2. 전체 파일을 UpperDir로 복사 (Copy-up)
   └─ 첫 쓰기만 오버헤드 발생
           ↓
3. UpperDir에서 수정 수행
           ↓
4. 이후 읽기는 UpperDir에서만 (빠름)

주의: 큰 파일 수정 시 Copy-up 오버헤드 존재

3. 삭제 작업:

1
2
3
4
5
6
7
8
9
10
Container가 /etc/hosts 삭제 요청
           ↓
LowerDir 파일은 실제로 삭제 불가 (읽기 전용)
           ↓
UpperDir에 "whiteout" 파일 생성
  (예: .wh..wh..opq 또는 .wh.hosts)
           ↓
MergedDir에서 해당 파일이 "보이지 않음"

실제 파일은 그대로, 마스킹만 됨

4. 새 파일 생성:

1
2
3
4
5
6
7
Container가 /app/data.txt 생성
           ↓
직접 UpperDir에 생성
           ↓
MergedDir에 즉시 보임

성능: 오버헤드 없음

컨테이너 이미지 레이어 구조

Dockerfile의 각 명령어는 하나의 레이어를 생성:

1
2
3
4
5
FROM ubuntu:20.04          ← Layer 1: Base OS (80MB)
RUN apt-get update         ← Layer 2: Package index (50MB)
RUN apt-get install nginx  ← Layer 3: nginx binary (30MB)
COPY app /app              ← Layer 4: Application (10MB)
CMD ["nginx"]              ← Layer 5: Metadata (메타데이터만, 크기 0)

레이어 스택 구조:

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────┐
│ Layer 5: CMD metadata   │ ← 최상위 (가장 최근)
├─────────────────────────┤
│ Layer 4: App files      │
├─────────────────────────┤
│ Layer 3: nginx          │
├─────────────────────────┤
│ Layer 2: apt update     │
├─────────────────────────┤
│ Layer 1: ubuntu base    │ ← 최하위 (가장 오래됨)
└─────────────────────────┘

레이어 공유와 재사용

여러 컨테이너가 같은 base 이미지를 사용하면:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Container A: nginx:latest
  Layer 1: ubuntu:20.04 (80MB)
  Layer 2: nginx (30MB)
  Layer 3: App A (10MB)
  + UpperDir A (실행 중 변경)

Container B: nginx:latest
  Layer 1: ubuntu:20.04 (80MB) ← Container A와 공유!
  Layer 2: nginx (30MB)        ← Container A와 공유!
  Layer 3: App B (5MB)
  + UpperDir B (실행 중 변경)

저장 공간:
  공유 없으면: (80+30+10) + (80+30+5) = 235MB
  공유하면:   (80+30) + 10 + 5 = 125MB
  절약: 110MB (47% 절감)

Image Digest vs Tag

Tag (가변):

1
2
3
4
5
6
docker pull nginx:latest  ← 시간에 따라 다른 이미지 참조 가능

문제점:
- latest는 계속 변경됨
- 재현성 없음
- 보안 위험 (변경된 이미지 배포 가능)

Digest (불변):

1
2
3
4
5
6
7
8
9
10
docker pull nginx@sha256:abc123...def456  ← 항상 동일한 이미지

장점:
- Content-addressable: 내용이 같으면 같은 digest
- 재현성 보장: 동일한 이미지 실행 보장
- 보안: 이미지 위변조 탐지 가능
- 프로덕션 권장: 정확한 버전 고정

확인:
docker images --digests

레이어 캐싱과 빌드 최적화

Docker는 레이어를 캐싱하여 빌드 속도 향상:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 나쁜 예: 매번 재빌드
FROM ubuntu:20.04
COPY . /app                    ← 코드 변경 시 이하 전부 재빌드
RUN apt-get update
RUN apt-get install -y python3
RUN pip install -r requirements.txt

# 좋은 예: 캐시 활용
FROM ubuntu:20.04
RUN apt-get update             ← 1. OS 업데이트 (변경 드뭄)
RUN apt-get install -y python3 ← 2. 런타임 설치 (변경 드뭄)
COPY requirements.txt /app/    ← 3. 의존성 파일만 (변경 드뭄)
RUN pip install -r /app/requirements.txt  ← 4. 의존성 설치
COPY . /app                    ← 5. 코드 복사 (자주 변경)

코드만 변경 시 1-4는 캐시 사용, 5만 재실행!

Multi-stage Build로 이미지 크기 최소화

빌드 도구를 최종 이미지에서 제외:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Stage 1: 빌드 단계 (큰 이미지)
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# Stage 2: 실행 단계 (작은 이미지)
FROM alpine:3.16
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]

결과:
  Builder 이미지: 800MB (Go 컴파일러 포함)
  최종 이미지:   15MB (실행 파일만)
  절약: 98% 크기 감소!

OverlayFS 확인 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 컨테이너의 OverlayFS 마운트 정보 확인
docker inspect <container_id> | grep -A 20 GraphDriver

# 출력 예시:
"GraphDriver": {
  "Data": {
    "LowerDir": "/var/lib/docker/overlay2/l/ABC123:/var/lib/docker/overlay2/l/DEF456",
    "MergedDir": "/var/lib/docker/overlay2/XYZ789/merged",
    "UpperDir": "/var/lib/docker/overlay2/XYZ789/diff",
    "WorkDir": "/var/lib/docker/overlay2/XYZ789/work"
  },
  "Name": "overlay2"
}

# 실제 레이어 디렉토리 확인
ls -la /var/lib/docker/overlay2/<id>/diff    # UpperDir (컨테이너 변경)
ls -la /var/lib/docker/overlay2/<id>/merged  # MergedDir (통합 뷰)

# OverlayFS 마운트 확인
mount | grep overlay
# 출력:
# overlay on /var/lib/docker/overlay2/XYZ789/merged type overlay (rw,...)

OverlayFS 성능 특성

장점:

  • 레이어 수가 적을 때 매우 빠름
  • 메인라인 커널 지원 (안정성)
  • AUFS보다 간단한 구조

단점:

  • 레이어가 많으면 파일 조회 느림 (O(N))
  • 큰 파일의 첫 수정 시 Copy-up 오버헤드
  • inode 사용량 증가 (작은 파일 많을 때)

최적화 팁:

  1. 레이어 수 최소화: Dockerfile에서 RUN 명령을 && 로 합치기
  2. 자주 변경되는 파일은 Volume 사용: DB 데이터, 로그 등
  3. Base 이미지 공유: 팀 내 공통 base 이미지 사용
  4. Multi-stage build: 불필요한 레이어 제거

면접 질문 예시

Q1: “OverlayFS의 Copy-on-Write는 어떻게 동작하는가?”

A: 컨테이너가 LowerDir(읽기 전용 이미지 레이어)의 파일을 수정하려 할 때:

  1. 파일을 LowerDir에서 찾음
  2. 전체 파일을 UpperDir(쓰기 레이어)로 복사 (Copy-up)
  3. UpperDir에서 수정 수행
  4. 이후 읽기는 UpperDir에서만 수행 (LowerDir는 원본 유지) 첫 쓰기만 Copy-up 오버헤드 발생. 큰 파일 수정 시 성능 영향 가능.

Q2: “컨테이너 이미지의 레이어 구조가 효율적인 이유는?”

A:

  1. 레이어 공유: 여러 컨테이너가 동일한 base 레이어를 공유하여 저장 공간 절약 (수백 MB 절감 가능)
  2. 빠른 배포: 새 이미지 배포 시 변경된 레이어만 전송 (전체 이미지 전송 불필요)
  3. 빌드 캐싱: 변경되지 않은 레이어는 재사용하여 빌드 속도 향상
  4. 이미지 버전 관리: 각 레이어가 불변이므로 재현 가능한 빌드

Q3: “Image Tag와 Digest의 차이는?”

A:

  • Tag: 변경 가능한 참조 (예: nginx:latest). 시간에 따라 다른 이미지를 가리킬 수 있어 재현성 없음
  • Digest: 이미지 내용의 SHA256 해시. 불변이며 Content-addressable. 동일한 내용이면 동일한 digest
  • 프로덕션에서는 digest 사용 권장 (재현성 + 보안)

Part 2: Linux 격리 기술

이 장은 Docker와 Kubernetes의 기반이 되는 Linux Namespace와 Cgroups를 설명한다.

2.1 Linux Namespaces 개요

Namespace란?

Namespace는 전역 시스템 리소스를 추상화하여 각 프로세스가 자신만의 독립된 인스턴스를 가지게 하는 Linux 커널 기능이다.

7가지 Namespace 타입

Linux는 다음 7가지 타입의 Namespace를 제공한다:

타입격리 대상목적
PID Namespace프로세스 ID프로세스 격리
Network Namespace네트워크 스택네트워크 격리
Mount Namespace파일시스템 마운트파일시스템 격리
UTS Namespace호스트명, 도메인명호스트명 격리
IPC Namespace프로세스 간 통신IPC 메커니즘 격리
User NamespaceUID/GID 매핑사용자 권한 격리
Cgroup NamespaceCgroup 루트Cgroup 구조 격리

(Linux 5.6+ : Time Namespace 추가)


2.2 PID Namespace (프로세스 격리)

PID Namespace의 역할

PID Namespace는 프로세스 ID 공간을 격리하여 각 컨테이너가 독립된 프로세스 트리를 가지도록 한다.

주요 특징

  • 각 PID Namespace는 독립적인 PID 번호 체계를 가짐
  • 새 Namespace의 첫 프로세스는 PID 1 (init 역할)
  • Namespace 내부에서는 외부 프로세스를 볼 수 없음
  • 부모 Namespace에서는 자식 Namespace의 프로세스를 볼 수 있음

계층 구조

1
2
3
4
5
6
Host PID 1 (init)
├── Container A (PID 1 in namespace, PID 2000 in host)
│   ├── Process (PID 2 in namespace, PID 2001 in host)
│   └── Process (PID 3 in namespace, PID 2002 in host)
└── Container B (PID 1 in namespace, PID 3000 in host)
    └── Process (PID 2 in namespace, PID 3001 in host)

PID 1의 특별한 역할

각 PID Namespace의 PID 1 프로세스는:

  • 고아 프로세스를 입양 (adoption)
  • 좀비 프로세스를 수확 (reaping)
  • 신호에 대한 특별한 처리
  • Namespace 내 모든 프로세스의 부모 역할

이는 컨테이너가 자체 init 시스템을 필요로 하는 이유다.

실습

1
2
3
4
5
6
7
8
9
10
# 새 PID Namespace 생성
unshare --pid --fork --mount-proc /bin/bash

# 새 Namespace 내에서 프로세스 확인
ps aux
# PID 1이 bash임을 확인

# 호스트에서 확인 (다른 터미널)
ps aux | grep bash
# 실제 PID는 수천 번대

2.3 Network Namespace (네트워크 격리)

Network Namespace의 역할

Network Namespace는 전체 네트워크 스택을 격리하여 각 컨테이너가 독립된 네트워크 환경을 가지도록 한다.

격리되는 요소

  • 네트워크 인터페이스 (물리적, 가상)
  • IP 주소 및 라우팅 테이블
  • iptables 규칙
  • /proc/net 디렉토리
  • 포트 번호 및 소켓
  • 각 프로토콜 스택 (TCP/IP 등)

기본 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 네트워크 Namespace 생성
ip netns add container1

# Namespace 목록 확인
ip netns list

# Namespace 내에서 명령 실행
ip netns exec container1 ip link show
# 기본적으로 lo(loopback)만 존재 (DOWN 상태)

# lo 활성화
ip netns exec container1 ip link set lo up

# Namespace 삭제
ip netns delete container1

veth pair로 Namespace 연결

veth (Virtual Ethernet)는 Namespace 간 통신의 핵심:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 두 Namespace 생성
ip netns add ns1
ip netns add ns2

# veth pair 생성 (양쪽이 연결된 가상 케이블)
ip link add veth1 type veth peer name veth2

# 각 끝을 다른 Namespace에 배치
ip link set veth1 netns ns1
ip link set veth2 netns ns2

# IP 주소 할당
ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth1
ip netns exec ns1 ip link set veth1 up
ip netns exec ns1 ip link set lo up

ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth2
ip netns exec ns2 ip link set veth2 up
ip netns exec ns2 ip link set lo up

# 연결 테스트
ip netns exec ns1 ping 10.0.0.2
# 성공

컨테이너 네트워킹의 실제 구조

Docker와 Kubernetes의 네트워킹 원리:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────────┐
│ Host OS                                 │
│  docker0 (또는 cni0) - Linux Bridge     │
│  ├─ veth123 (Container A로 연결)        │
│  └─ veth456 (Container B로 연결)        │
└──────────┬──────────────────────────────┘
           │
    ┌──────┴──────┐
    │             │
┌───▼───────┐  ┌─▼────────┐
│Container A│  │Container B│
│Network NS │  │Network NS │
│eth0:172.. │  │eth0:172..│
└───────────┘  └──────────┘

2.4 Mount Namespace (파일시스템 격리)

Mount Namespace의 역할

Mount Namespace는 파일시스템 마운트 포인트를 격리하여 각 컨테이너가 독립된 파일시스템 뷰를 가지도록 한다.

주요 특징

  • 각 Namespace는 독립적인 마운트 테이블
  • 한 Namespace의 마운트/언마운트가 다른 Namespace에 영향 없음
  • 컨테이너가 자신만의 루트 파일시스템 가짐

Mount Propagation (전파 유형)

마운트 Namespace 간 마운트 이벤트 전파 방식:

1
2
3
4
5
6
7
8
9
10
11
12
# shared (공유): 양방향 전파
mount --bind /src /dst
mount --make-shared /src

# slave (슬레이브): 마스터 → 슬레이브만 전파
mount --make-slave /src

# private (프라이빗): 전파 없음 (기본값)
mount --make-private /src

# unbindable: bind mount 불가능
mount --make-unbindable /src

실습

1
2
3
4
5
6
7
8
9
10
11
12
# 새 Mount Namespace에서 실행
unshare --mount /bin/bash

# 새 tmpfs 마운트
mount -t tmpfs tmpfs /mnt

# 이 Namespace에서만 보임
ls /mnt

# 호스트에서는 영향 없음 (다른 터미널에서 확인)
ls /mnt
# 빈 상태

컨테이너의 마운트 구조

1
2
3
4
5
6
Container Rootfs (Union FS)
├── /proc (procs 마운트)
├── /sys (sysfs 마운트)
├── /dev (devtmpfs 마운트)
├── /tmp (tmpfs 마운트)
└── 볼륨 (bind mount 또는 volume)

2.5 UTS Namespace (호스트명 격리)

UTS Namespace의 역할

UTS (Unix Time-Sharing) Namespace는 호스트명과 도메인명을 격리한다.

용도

  • 각 컨테이너가 독립적인 호스트명 가짐
  • 네트워크 식별 및 DNS
  • 애플리케이션 설정 (호스트명 기반)

실습

1
2
3
4
5
6
7
8
9
10
11
12
13
# 새 UTS Namespace에서 실행
unshare --uts /bin/bash

# 호스트명 확인
hostname

# 호스트명 변경
hostname container1

# 이 Namespace에서만 변경됨
hostname

# 호스트에서는 원래 호스트명 유지 (다른 터미널에서 확인)

Docker에서의 사용

1
2
3
4
5
6
7
8
9
10
11
# 기본: 컨테이너 ID가 호스트명
docker run --rm alpine hostname
# 출력: a1b2c3d4e5f6

# 커스텀 호스트명
docker run --rm --hostname mycontainer alpine hostname
# 출력: mycontainer

# 호스트의 UTS Namespace 공유 (보안 위험)
docker run --rm --uts=host alpine hostname
# 출력: (호스트 호스트명)

2.6 IPC Namespace (프로세스 간 통신 격리)

IPC Namespace의 역할

IPC Namespace는 프로세스 간 통신 리소스를 격리한다.

격리되는 IPC 메커니즘

  • System V IPC
    • 메시지 큐 (msgget, msgsnd 등)
    • 세마포어 (semget, semop 등)
    • 공유 메모리 (shmget, shmat 등)
  • POSIX 메시지 큐

특징

  • 각 Namespace는 독립적인 IPC 객체 집합
  • 다른 Namespace의 IPC 객체에 접근 불가
  • 보안 및 격리 강화

실습

1
2
3
4
5
6
7
8
9
10
11
12
13
# 호스트에서 공유 메모리 생성
ipcmk -M 1024
# 키: 0x12345678, ID: 123

# 호스트에서 확인
ipcs -m

# 새 IPC Namespace에서 실행
unshare --ipc /bin/bash

# 새 Namespace에서 확인
ipcs -m
# 호스트의 공유 메모리가 보이지 않음

2.7 User Namespace (사용자 권한 격리)

User Namespace의 역할

User Namespace는 UID/GID 매핑을 격리한다. 이는 Rootless 컨테이너의 기반 기술이며 컨테이너 보안의 핵심이다.

주요 개념

  • Namespace 내부의 UID를 외부(호스트)의 다른 UID로 매핑
  • 컨테이너 내부의 root(UID 0)를 호스트의 일반 사용자로 매핑 가능
  • 핵심: 컨테이너 탈출 시에도 호스트에서는 제한된 권한

UID/GID 매핑 메커니즘

매핑 파일:

  • /proc/<PID>/uid_map: UID 매핑
  • /proc/<PID>/gid_map: GID 매핑

매핑 형식: <namespace_id> <host_id> <range>

1
2
3
4
5
6
7
# 예시 1: 직접 매핑
0 1000 1
# Namespace UID 0 → Host UID 1000 (1개)

# 예시 2: 범위 매핑 (Rootless)
0 100000 65536
# Namespace UID 0-65536 → Host UID 100000-165535

보안 이점

  • 컨테이너 내부에서 root여도 호스트에서는 일반 사용자
  • 컨테이너 탈출 시에도 제한된 권한
  • 더 안전한 멀티테넌시
  • Rootless Container의 기반

실습

1
2
3
4
5
6
7
8
9
10
11
12
13
# User Namespace 없이 (위험)
unshare --mount --pid --fork /bin/bash
id
# uid=0(root) - 실제 호스트의 root

# User Namespace와 함께 (안전)
unshare --user --map-root-user /bin/bash
id
# uid=0(root) - Namespace 내부에서만 root

# 호스트에서 확인 (다른 터미널)
ps aux | grep bash
# 실제로는 일반 사용자 권한으로 실행 중

Rootless 컨테이너의 실제 예

1
2
3
4
5
6
7
8
9
10
# Rootless Docker 시작
dockerd-rootless.sh &

# 컨테이너 실행
docker run --rm alpine id
# uid=0(root) - 하지만 호스트에서는 일반 사용자

# 호스트에서 확인
ps aux | grep docker
# 모든 프로세스가 일반 사용자 권한

2.8 Cgroup Namespace (Cgroup 계층 격리)

Cgroup Namespace의 역할

Cgroup Namespace는 프로세스가 보는 cgroup 계층의 루트를 가상화한다.

특징

  • 컨테이너가 자신의 cgroup 계층만 볼 수 있음
  • 호스트의 전체 cgroup 구조 숨김
  • 정보 누출 방지 (보안 향상)

사용 시기

  • Kubernetes 같은 중첩 컨테이너 환경
  • Docker-in-Docker (DinD)
  • 정보 격리가 중요한 멀티테넌트 환경

2.9 Namespace 관리 명령어

unshare - Namespace 생성 및 실행

1
2
3
4
5
6
7
8
# PID와 Mount Namespace 생성하여 bash 실행
unshare --pid --mount --fork /bin/bash

# 모든 Namespace 격리 (완전한 컨테이너 유사 환경)
unshare --pid --net --mount --ipc --uts --user --fork /bin/bash

# 사용자 매핑과 함께
unshare --user --map-root-user /bin/bash

nsenter - 기존 Namespace로 진입

1
2
3
4
5
6
7
8
9
# Docker 컨테이너의 Namespace에 진입
docker inspect -f '{{.State.Pid}}' <container-id>
# PID 확인: 예를 들어 1234

# 해당 컨테이너의 네트워크 Namespace에 진입
nsenter --target 1234 --net ip addr

# 모든 Namespace 공유
nsenter --target 1234 -a /bin/bash

3. Control Groups (Cgroups)

3.1 Cgroup의 개념

Cgroup이란?

Control Groups (Cgroups)는 프로세스 그룹의 자원 사용을 제한, 계산, 격리하는 Linux 커널 기능이다.

주요 기능

기능설명
Resource LimitingCPU, 메모리, I/O 사용량 제한
Prioritization프로세스 그룹 간 우선순위 설정
Accounting리소스 사용량 측정 및 기록
Control프로세스 그룹 제어 (freezing, restarting)

Namespace vs Cgroup

  • Namespace: “무엇을 볼 수 있는가” (격리)
  • Cgroup: “얼마나 사용할 수 있는가” (자원 제한)

둘이 함께 완전한 컨테이너를 구성한다.


3.2 Cgroup v1 vs v2 (아키텍처 진화)

Cgroup v1 (레거시 아키텍처)

여러 독립적인 계층 구조:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/sys/fs/cgroup/
├── cpu/
│   ├── docker/
│   │   └── <container-id>/
│   │       ├── cpu.cfs_quota_us
│   │       ├── cpu.cfs_period_us
│   │       └── cpu.shares
├── memory/
│   ├── docker/
│   │   └── <container-id>/
│   │       ├── memory.limit_in_bytes
│   │       ├── memory.usage_in_bytes
│   │       └── memory.oom_control
└── blkio/
    ├── docker/
    │   └── <container-id>/
        ├── blkio.weight
        └── blkio.throttle.*

문제점

  • 각 컨트롤러(cpu, memory 등)마다 별도 계층
  • 프로세스가 여러 cgroup에 속할 수 있음 (일관성 문제)
  • 복잡한 관리
  • 리소스 그룹화 어려움

Cgroup v2 (통합 아키텍처)

단일 통합 계층:

1
2
3
4
5
6
7
8
9
10
11
/sys/fs/cgroup/
└── docker/
    └── <container-id>/
        ├── cpu.max
        ├── cpu.weight
        ├── memory.max
        ├── memory.high
        ├── memory.current
        ├── io.max
        ├── io.weight
        └── pids.max

장점

  • 모든 컨트롤러가 하나의 계층에서 관리
  • 일관된 인터페이스
  • 프로세스 하나가 정확히 하나의 cgroup에 속함
  • 더 나은 성능
  • 고급 기능 (delegation, pressure stall info 등)

마이그레이션 상태

현재 과도기:

  • 대부분 시스템이 v1 사용 중
  • 일부 최신 배포판(Fedora, Ubuntu 21.04+)은 v2로 전환
  • systemd 246+ 버전부터 v2 지원 강화
1
2
3
4
5
# 시스템이 v2를 사용하는지 확인
stat -fc %T /sys/fs/cgroup

# cgroup2fs: v2 사용 중
# tmpfs: v1 사용 중

3.3 CPU 제어 (모든 Namespace가 공유하는 리소스)

Cgroup v1 CPU 컨트롤러

1
2
3
4
5
6
7
# CPU 쿼터 설정 (100ms 중 50ms 사용 = 50%)
echo 50000 > /sys/fs/cgroup/cpu/docker/mycontainer/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/docker/mycontainer/cpu.cfs_period_us

# CPU 공유 비율 (상대적 가중치)
echo 512 > /sys/fs/cgroup/cpu/docker/mycontainer/cpu.shares
# 기본값 1024, 512 = 절반의 우선순위

Cgroup v2 CPU 컨트롤러

1
2
3
4
5
6
7
# CPU 최대 사용량 설정
echo "50000 100000" > /sys/fs/cgroup/docker/mycontainer/cpu.max
# 형식: <quota> <period>

# CPU 가중치
echo 100 > /sys/fs/cgroup/docker/mycontainer/cpu.weight
# 범위: 1-10000, 기본값 100

Docker/Kubernetes에서의 CPU 제한

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Docker: CPU 제한
docker run --cpus="1.5" nginx
# 최대 1.5 CPU 사용

# Docker: CPU 공유 (상대적 우선순위)
docker run --cpu-shares=512 nginx

# Kubernetes: CPU 요청 및 제한
spec:
  containers:
  - name: nginx
    resources:
      requests:
        cpu: "500m"      # 0.5 CPU 보장
      limits:
        cpu: "1000m"     # 최대 1 CPU

3.4 Memory 제어 (가장 중요한 리소스)

Cgroup v1 Memory 컨트롤러

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 메모리 제한 (512MB)
echo 536870912 > /sys/fs/cgroup/memory/docker/mycontainer/memory.limit_in_bytes

# Swap 포함 제한
echo 1073741824 > /sys/fs/cgroup/memory/docker/mycontainer/memory.memsw.limit_in_bytes

# OOM 제어
echo 0 > /sys/fs/cgroup/memory/docker/mycontainer/memory.oom_control
# 0: OOM kill 활성화, 1: OOM kill 비활성화 (프로세스 멈춤)

# 현재 사용량 확인
cat /sys/fs/cgroup/memory/docker/mycontainer/memory.usage_in_bytes

# 최대 사용 기록
cat /sys/fs/cgroup/memory/docker/mycontainer/memory.max_usage_in_bytes

Cgroup v2 Memory 컨트롤러

1
2
3
4
5
6
7
8
9
10
11
# 메모리 최대값
echo 536870912 > /sys/fs/cgroup/docker/mycontainer/memory.max

# Swap 최대값
echo 536870912 > /sys/fs/cgroup/docker/mycontainer/memory.swap.max

# 메모리 사용량 확인
cat /sys/fs/cgroup/docker/mycontainer/memory.current

# 상세 통계
cat /sys/fs/cgroup/docker/mycontainer/memory.stat

Docker에서의 메모리 제한

1
2
3
4
5
6
7
8
9
10
11
12
# 메모리 제한 (하드 제한)
docker run -m 512m nginx
docker run --memory=512m nginx

# 메모리 + Swap 제한
docker run -m 512m --memory-swap=1g nginx

# 메모리 예약 (소프트 제한, 경쟁 시에만 적용)
docker run --memory-reservation=256m nginx

# OOM Kill 비활성화 (위험)
docker run --oom-kill-disable nginx

OOM Killer의 동작

1
2
3
4
5
1. 컨테이너가 메모리 제한 초과
2. 커널이 페이지 캐시 회수 시도
3. 회수 실패 시 OOM Killer 발동
4. Cgroup 내에서 메모리를 가장 많이 사용하는 프로세스 종료
5. 컨테이너 전체가 종료될 수 있음

OOM 감지 및 모니터링

1
2
3
4
5
6
7
8
# 컨테이너가 OOM으로 종료되었는지 확인
docker inspect <container> | grep OOMKilled

# Cgroup에서 OOM 이벤트 확인
cat /sys/fs/cgroup/memory/docker/mycontainer/memory.oom_control

# 현재 메모리 사용량 실시간 확인
docker stats <container>

3.5 Block I/O 제어

Cgroup v1 Block I/O 컨트롤러

1
2
3
4
5
6
7
8
9
10
11
12
13
# 읽기 대역폭 제한 (10MB/s)
# 형식: <major>:<minor> <bytes_per_second>
echo "8:0 10485760" > /sys/fs/cgroup/blkio/docker/mycontainer/blkio.throttle.read_bps_device

# 쓰기 대역폭 제한 (5MB/s)
echo "8:0 5242880" > /sys/fs/cgroup/blkio/docker/mycontainer/blkio.throttle.write_bps_device

# IOPS 제한 (초당 1000 작업)
echo "8:0 1000" > /sys/fs/cgroup/blkio/docker/mycontainer/blkio.throttle.read_iops_device

# 블록 I/O 가중치 (상대적 우선순위)
echo 500 > /sys/fs/cgroup/blkio/docker/mycontainer/blkio.weight
# 기본값 500, 범위 10-1000

Cgroup v2 I/O 컨트롤러

1
2
3
4
5
# I/O 최대값 설정
echo "8:0 rbps=10485760 wbps=5242880" > /sys/fs/cgroup/docker/mycontainer/io.max

# I/O 가중치
echo "8:0 100" > /sys/fs/cgroup/docker/mycontainer/io.weight

Docker에서의 I/O 제한

1
2
3
4
5
6
7
8
9
10
11
# 블록 I/O 가중치 (상대적)
docker run --blkio-weight=500 nginx

# 디바이스별 읽기 대역폭 제한
docker run --device-read-bps=/dev/sda:10mb nginx

# 디바이스별 쓰기 대역폭 제한
docker run --device-write-bps=/dev/sda:5mb nginx

# 디바이스별 IOPS 제한
docker run --device-read-iops=/dev/sda:1000 nginx

3.6 PID 제한 (포크 폭탄 방어)

PIDs 컨트롤러

1
2
3
4
5
# Cgroup v1/v2 공통
echo 100 > /sys/fs/cgroup/pids/docker/mycontainer/pids.max

# 현재 프로세스 수
cat /sys/fs/cgroup/pids/docker/mycontainer/pids.current

용도

  • 포크 폭탄 (fork bomb) 방지
  • 리소스 고갈 공격 방어
  • 예측 가능한 리소스 사용

Docker에서의 사용

1
2
3
4
5
6
7
8
9
10
11
# PID 제한
docker run --pids-limit=100 nginx

# Kubernetes
spec:
  containers:
  - name: nginx
    securityContext:
      sysctls:
      - name: kernel.pid_max
        value: "100"

3.7 Cgroup 계층 및 systemd 통합

Cgroup 생성 및 관리

1
2
3
4
5
6
7
8
# Cgroup 생성 (v1)
mkdir /sys/fs/cgroup/cpu/mygroup

# 프로세스 추가
echo <PID> > /sys/fs/cgroup/cpu/mygroup/cgroup.procs

# Cgroup 삭제 (프로세스가 없어야 함)
rmdir /sys/fs/cgroup/cpu/mygroup

systemd와 Cgroup (현대적 관리)

현대 리눅스는 systemd가 Cgroup을 관리한다:

1
2
3
4
5
6
7
8
9
# systemd slice 확인
systemctl status

# 서비스의 Cgroup 확인
systemctl status docker.service

# 특정 서비스의 리소스 제한 설정
systemctl set-property docker.service MemoryLimit=2G
systemctl set-property docker.service CPUQuota=50%

Docker의 Cgroup 관리

1
2
3
4
5
6
7
8
9
# Docker 컨테이너의 Cgroup 위치
ls /sys/fs/cgroup/*/docker/<container-id>/

# 실시간 리소스 사용량 확인
docker stats

# Cgroup 드라이버 확인
docker info | grep "Cgroup Driver"
# cgroupfs 또는 systemd

3.8 Cgroup v2 마이그레이션

현재 버전 확인 및 전환

1
2
3
4
5
6
7
8
9
10
11
12
# 시스템이 v2를 사용하는지 확인
stat -fc %T /sys/fs/cgroup

# cgroup2fs: v2 사용 중
# tmpfs: v1 사용 중

# GRUB 부팅 파라미터로 v2 강제
# /etc/default/grub 편집
GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"

# GRUB 재생성
sudo grub-mkconfig -o /boot/grub/grub.cfg

호환성 고려사항

도구v1 지원v2 지원비고
Docker20.10-20.10+최신은 둘 다 지원
Kubernetes1.24-1.25+점진적 마이그레이션
containerd모두모두우수한 지원
systemd모두모두246+ 강화

Part 3: 컨테이너 런타임

이 장은 Docker, Kubernetes, 그리고 모든 현대 컨테이너 플랫폼의 기반이 되는 런타임 아키텍처를 설명한다.

5.1 컨테이너 런타임 계층

고수준 vs 저수준 런타임

컨테이너 런타임은 두 계층으로 나뉜다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──────────────────────────────────────────────────────────┐
│ High-level Runtime (Container Managers)                  │
│ - Docker, containerd, CRI-O, Podman                      │
│ - 이미지 관리 (pull, push, build)                        │
│ - 컨테이너 생명주기 관리                                 │
│ - 네트워크 및 스토리지 설정                              │
│ - CRI 구현 (Kubernetes 통합)                             │
└──────────────────────────────────────────────────────────┘
                          ↓
┌──────────────────────────────────────────────────────────┐
│ Low-level Runtime (OCI Runtime)                          │
│ - runc, crun, kata-containers, gVisor                    │
│ - 실제 컨테이너 실행                                     │
│ - Namespace 및 Cgroup 설정                               │
│ - Rootfs 마운트                                          │
│ - OCI Runtime Spec 구현                                  │
└──────────────────────────────────────────────────────────┘

High-level Runtime의 역할

기능설명
이미지 관리Pull, Push, 빌드, 레지스트리 통신
컨테이너 관리생성, 시작, 정지, 삭제
스토리지OverlayFS, 볼륨 관리
네트워킹브리지, DNS, 포트 매핑
로깅stdout/stderr 수집 및 저장
CRIKubernetes 통합

Low-level Runtime의 역할

기능설명
컨테이너 실행OCI 번들 실행
Namespace 설정PID, Network, Mount 등
Cgroup 설정리소스 제한 적용
Capability 제한권한 제한
Seccomp 프로필시스템 콜 필터링
Rootfs 마운트파일시스템 준비

5.2 OCI (Open Container Initiative) - 업계 표준

OCI란?

OCI는 컨테이너 기술의 표준을 정의하는 Linux Foundation 오픈 소스 프로젝트다.

목표: 벤더 중립적이고 상호 운용 가능한 컨테이너 표준 정의

주요 OCI 스펙

1. Runtime Specification

컨테이너를 어떻게 실행할 것인가:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
  "ociVersion": "1.0.0",
  "process": {
    "terminal": true,
    "user": {
      "uid": 0,
      "gid": 0
    },
    "args": [
      "sh"
    ],
    "cwd": "/"
  },
  "root": {
    "path": "rootfs",
    "readonly": false
  },
  "mounts": [
    {
      "destination": "/proc",
      "type": "proc",
      "source": "proc"
    }
  ],
  "linux": {
    "namespaces": [
      {
        "type": "pid"
      },
      {
        "type": "network"
      },
      {
        "type": "mount"
      }
    ],
    "resources": {
      "memory": {
        "limit": 536870912
      },
      "cpu": {
        "quota": 50000,
        "period": 100000
      }
    }
  }
}

2. Image Specification

어떻게 이미지를 저장할 것인가:

  • 레이어 구조 정의
  • 매니페스트 형식
  • 설정 파일 형식
  • 보증 불변성 및 재현성

3. Distribution Specification

어떻게 이미지를 배포할 것인가:

  • 레지스트리 API
  • 푸시/풀 프로토콜
  • 인증 및 권한

OCI의 중요성

측면이점
상호 운용성Docker 이미지를 containerd에서도 실행 가능
벤더 중립성특정 회사에 종속되지 않음
혁신 촉진표준화된 인터페이스로 새 도구 개발 용이
생태계도구들이 안전하게 상호 호환

OCI 호환성 검증

1
2
# OCI 런타임이 표준을 따르는지 검증
oci-runtime-tool validate /path/to/config.json

5.3 runc - OCI 참조 구현

runc란?

runc는 OCI Runtime Spec을 구현한 공식 참조 구현이다.

주요 특징

  • Go 언어로 작성
  • Docker에서 분리되어 독립 프로젝트화 (2015)
  • 가장 널리 사용되는 low-level 런타임
  • 사실상 업계 표준
  • 대부분의 컨테이너 플랫폼에서 사용 (Docker, containerd, Kubernetes 등)

runc의 기본 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# OCI 번들 디렉토리 생성
mkdir mycontainer
cd mycontainer

# rootfs 생성
mkdir rootfs
docker export $(docker create alpine:latest) | tar -C rootfs -xf -

# config.json 생성 (OCI 스펙)
runc spec

# config.json 편집 (필요시)
# 프로세스, 리소스 제한 등 수정

# 컨테이너 실행
runc run mycontainer

# 컨테이너 목록
runc list

# 컨테이너 삭제
runc delete mycontainer

config.json의 주요 필드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
  "ociVersion": "1.0.0",
  "process": {
    "args": [
      "/bin/sh"
    ],
    "env": [
      "PATH=/bin",
      "TERM=xterm"
    ]
  },
  "root": {
    "path": "rootfs"
  },
  "mounts": [
    ...
  ],
  "linux": {
    "namespaces": [
      {
        "type": "pid"
      },
      {
        "type": "network"
      },
      {
        "type": "ipc"
      },
      {
        "type": "uts"
      },
      {
        "type": "mount"
      }
    ],
    "resources": {
      "memory": {
        "limit": 536870912
      },
      "cpu": {
        "quota": 50000
      }
    },
    "capabilities": [
      "CAP_CHOWN",
      "CAP_NET_BIND_SERVICE"
    ]
  }
}

5.4 containerd - 산업 표준 런타임

containerd란?

containerd는 Docker에서 분리된 고수준 컨테이너 런타임이다.

주요 특징

  • CNCF 졸업 프로젝트 (fully mature)
  • 산업의 core 컨테이너 런타임
  • Kubernetes의 기본 런타임 (1.24+)
  • Docker의 백엔드로도 사용 (Docker 20.10+)
  • 이미지 관리, 스토리지, 네트워킹 제공
  • gRPC API로 통신

containerd 아키텍처

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──────────────────────┐
│ Clients              │
│ - ctr (CLI)          │
│ - crictl             │
│ - kubectl            │
│ - Docker daemon      │
└──────────┬───────────┘
           │ (gRPC)
┌──────────▼───────────┐
│ containerd daemon    │
│ - Image management   │
│ - Content store      │
│ - Snapshot service   │
│ - Container API      │
└──────────┬───────────┘
           │
┌──────────▼───────────┐
│ containerd-shim      │
│ (컨테이너당 하나)    │
└──────────┬───────────┘
           │
┌──────────▼───────────┐
│ runc/crun/kata       │
│ (OCI Runtime)        │
└──────────────────────┘

containerd의 주요 컴포넌트

containerd daemon

  • gRPC API 서버
  • 이미지 및 컨테이너 관리
  • 스냅샷 및 content store 관리
  • Kubernetes CRI 구현

containerd-shim

  • 컨테이너와 containerd 사이의 중개자
  • 컨테이너 stdout/stderr 포워딩
  • 종료 코드 보고
  • containerd 재시작 시에도 컨테이너 유지

ctr 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 이미지 pull
ctr image pull docker.io/library/nginx:latest

# 이미지 목록
ctr image ls

# 컨테이너 실행
ctr run -d docker.io/library/nginx:latest mynginx

# 컨테이너 목록
ctr container ls

# Task 목록 (실행 중인 컨테이너)
ctr task ls

# 컨테이너 로그
ctr task logs mynginx

# 컨테이너 종료
ctr task kill mynginx
ctr container delete mynginx

Docker vs containerd

Docker 사용

1
2
3
docker run -d --name web nginx
docker ps
docker logs web

containerd 사용

1
2
3
ctr run -d docker.io/library/nginx:latest web
ctr task ls
ctr task logs web

5.5 CRI (Container Runtime Interface)

CRI란?

CRI는 Kubernetes가 컨테이너 런타임과 통신하는 표준 인터페이스다.

CRI의 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──────────────────┐
│ Kubelet          │
│ (CRI client)     │
└────────┬─────────┘
         │ (gRPC)
         │
┌────────▼────────────────────┐
│ CRI Server                   │
│ - ImageService               │
│ - RuntimeService             │
│ - PodSandboxService          │
└────────┬────────────────────┘
         │
    ┌────┴────┐
    │          │
┌───▼──┐   ┌──▼────┐
│ CNI  │   │ OCI   │
│ Plugin│  │ Runtime│
└──────┘   └───────┘

CRI를 구현한 런타임

런타임설명
containerd범용 고수준 런타임 + CRI
CRI-OKubernetes 전용 경량 런타임
Dockerdockerd가 CRI 구현
Podmandaemonless 런타임

5.6 CRI-O - Kubernetes 전용 런타임

CRI-O란?

CRI-O는 Kubernetes 전용으로 최적화된 경량 컨테이너 런타임이다.

주요 특징

  • Kubernetes CRI를 직접 구현
  • 불필요한 기능 제거 (Kubernetes에만 필요한 것만)
  • OCI Runtime Spec 준수
  • OCI Image Spec 준수
  • Red Hat, SUSE 등이 주도

CRI-O 아키텍처

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──────────────┐
│ Kubelet      │
│ (CRI client) │
└────────┬─────┘
         │ (gRPC/CRI)
┌────────▼──────────┐
│ CRI-O daemon      │
│ - Image pull      │
│ - Container mgmt  │
└────────┬──────────┘
         │
┌────────▼──────────┐
│ conmon (monitor)  │
│ - stdout/stderr   │
│ - Exit code       │
└────────┬──────────┘
         │
┌────────▼──────────┐
│ runc/crun         │
│ (OCI Runtime)     │
└───────────────────┘

containerd vs CRI-O

특성containerdCRI-O
용도범용 런타임K8s 전용
Docker 지원예 (Docker 백엔드)아니오
복잡도많은 기능단순 경량화
커뮤니티Docker + K8s주로 K8s
성능최적화됨경량화됨
권장 대상다양한 용도Kubernetes only

5.7 Docker 아키텍처 (완전한 스택)

Docker의 계층 구조

현재 (20.10+) Docker 스택:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌────────────────┐
│ docker CLI     │
│ (Client)       │
└────────┬───────┘
         │ (REST API)
┌────────▼──────────────┐
│ dockerd                │
│ (Docker Daemon)        │
│ - 이미지 빌드          │
│ - 이미지 관리          │
│ - 볼륨 관리            │
│ - 네트워크 관리        │
│ - API 서버             │
└────────┬──────────────┘
         │
┌────────▼──────────────┐
│ containerd             │
│ (고수준 런타임)        │
│ - 컨테이너 생명주기    │
│ - 이미지 pull/push     │
│ - 스토리지 관리        │
└────────┬──────────────┘
         │
┌────────▼──────────────┐
│ containerd-shim       │
│ (컨테이너당 하나)      │
└────────┬──────────────┘
         │
┌────────▼──────────────┐
│ runc                   │
│ (OCI Runtime)          │
│ - 실제 컨테이너 실행   │
└────────────────────────┘

각 계층의 역할

docker CLI

  • 사용자 인터페이스
  • REST API를 통해 dockerd와 통신

dockerd (Docker daemon)

  • Dockerfile 빌드
  • 이미지 관리
  • 볼륨 및 스토리지 관리
  • 네트워크 설정 (브리지, DNS 등)
  • API 서버
  • containerd에게 실제 컨테이너 작업 위임

containerd

  • 컨테이너 생명주기 관리
  • 이미지 레지스트리와의 통신
  • 스냅샷 및 content store

containerd-shim + runc

  • OCI 스펙에 따라 실제 컨테이너 실행
  • Namespace와 Cgroup 설정

Docker vs containerd vs runc의 관계

1
2
3
4
5
Docker = dockerd + containerd + runc + 네트워킹 + 볼륨 + 빌드

containerd = 컨테이너 관리 + 이미지 관리 + runc

runc = OCI 런타임 (컨테이너 실행만)

5.8 대체 OCI 런타임

crun - C 언어로 작성된 고성능 런타임

1
2
3
4
5
6
7
8
9
# runc 대비 특징
# - 더 빠른 컨테이너 시작
# - 더 적은 메모리 사용
# - Go 런타임 오버헤드 없음
# - systemd 통합 우수

# containerd와 함께 사용
mkdir -p /etc/containerd
containerd config default | sed 's/runc/crun/' > /etc/containerd/config.toml

kata-containers - VM 기반 컨테이너

1
2
3
4
5
6
7
8
9
10
11
12
13
# 각 컨테이너가 전용 VM에서 실행
# 장점:
# - 더 강력한 격리
# - 멀티테넌트 환경에 적합
# - 호스트 커널 보호

# 단점:
# - 시작 시간 느림
# - 메모리 오버헤드

# 사용 사례:
# - 신뢰할 수 없는 코드 실행
# - 강력한 보안이 필요한 환경

gVisor - 사용자 공간 커널

1
2
3
4
5
6
7
8
9
10
11
12
# Google의 보안 강화 런타임
# 시스템 콜을 가로채서 사용자 공간에서 처리

# 장점:
# - 커널 공격 표면 축소
# - 보안 강화

# 단점:
# - 성능 저하 (약 10-50%)
# - 일부 애플리케이션 비호환

# GKE에서 기본 옵션

Firecracker - AWS 서버리스 런타임

1
2
3
4
5
6
7
8
9
10
11
# AWS의 마이크로VM 기술
# Lambda, Fargate에서 사용

# 특징:
# - 밀리초 단위 부팅
# - 최소 메모리 오버헤드 (5MB)
# - 수천 개의 동시 컨테이너 지원

# 사용 사례:
# - 서버리스 함수
# - 함수형 컴퓨팅

5.9 컨테이너 런타임 선택 가이드

런타임용도시작 시간보안오버헤드
runc범용빠름중간낮음
crun성능 중요매우 빠름중간매우 낮음
containerdKubernetes빠름중간낮음
CRI-OK8s 전용빠름중간낮음
kata강한 격리느림높음높음
gVisor보안 중요빠름매우 높음중간-높음
firecracker서버리스초고속중간초저

7. 컨테이너 네트워킹

7.1 컨테이너 네트워크 모델

컨테이너 네트워킹은 Namespace와 가상 네트워크 장치의 조합:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Host Network Namespace
├── eth0 (물리 인터페이스)
├── docker0 / cni0 (브리지)
├── veth123 ──┐
├── veth456 ──┤
└── veth789 ──┘
               │
        ┌──────┴───────┬────────┐
        │              │        │
    ┌───▼────┐    ┌───▼────┐ ┌▼────┐
    │Container A│    │Container B│ │...  │
    │Network NS │    │Network NS │ │     │
    │eth0:10.* │    │eth0:10.* │ │     │
    └─────────┘    └─────────┘ └─────┘

7.2 Docker 네트워크 드라이버

Bridge (기본값)

1
2
docker network create mybridge
docker run --network mybridge nginx

Host

1
2
docker run --network host nginx
# 최고 성능, 격리 없음

None

1
2
docker run --network none nginx
# 네트워크 완전 격리

Overlay (멀티호스트)

1
2
docker network create --driver overlay myoverlay
# Swarm/Kubernetes 환경

Macvlan

1
2
docker network create -d macvlan -o parent=eth0 mymacvlan
# 레거시 애플리케이션

7.3 컨테이너 네트워킹 - CNI 플러그인

CNI (Container Network Interface)란?

CNI는 Kubernetes 컨테이너가 네트워크에 연결되는 방식을 정의하는 표준이다.

CNI의 책임

  1. Pod IP 할당: IPAM (IP Address Management)
  2. 네트워크 연결: veth/bridge 생성
  3. 라우팅: 멀티호스트 통신 설정
  4. 정책 적용: NetworkPolicy 등

주요 CNI 플러그인

1. Calico - BGP 기반 네트워킹

특징

  • BGP를 사용한 피어-투-피어 라우팅
  • 오버레이 없음 (언더레이 네트워킹)
  • NetworkPolicy 네이티브 지원
  • 고성능

구조

1
2
3
4
5
6
7
8
9
10
Pod A ──veth──┐           ┐──veth── Pod C
              │           │
        ┌─────▼─────┐ ┌──▼──────┐
        │  Host A   │ │ Host B  │
        │  (BGP)    │ │ (BGP)   │
        └─────┬─────┘ └──┬──────┘
              │          │
        ┌─────▼──────────▼──────┐
        │  Physical Network     │
        └──────────────────────┘

장점

  • 네이티브 라우팅 (오버레이 없음)
  • 최고 성능
  • NetworkPolicy 완벽 지원
  • 프로덕션 환경 검증

설치

1
kubectl apply -f https://docs.projectcalico.org/v3.x/manifests/calico.yaml

2. Flannel - 간단한 오버레이 네트워킹

특징

  • VXLAN 또는 Host-GW 모드
  • 구현이 간단하고 가벼움
  • 설정이 쉬움

작동 방식 (VXLAN)

1
2
3
4
5
6
7
Pod A ─┐           ┐─ Pod C
       │           │
    Host A      Host B
       │           │
    VXLAN 터널 ────── (UDP 4789)
       │           │
  Physical Network

Host-GW 모드

1
2
3
4
5
Pod A ─┐           ┐─ Pod C
       │           │
    Host A ──── Host B (라우팅)
       │           │
  Physical Network

설치

1
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

3. Cilium - eBPF 기반 고성능 네트워킹

특징

  • eBPF (Extended Berkeley Packet Filter) 활용
  • 커널 수준에서 최고 성능
  • L7 (애플리케이션 계층) 정책 지원
  • 고급 관찰성

아키텍처

1
2
3
4
5
6
7
8
9
10
11
┌──────────────────────────┐
│ Linux Kernel             │
│ ┌──────────────────────┐ │
│ │ Cilium eBPF Programs │ │
│ │ - XDP (수신)          │ │
│ │ - TC (송신)           │ │
│ │ - Kprobes (추적)      │ │
│ └──────────────────────┘ │
└──────────────────────────┘
         ↕
   Container Network

특장점

  • 매우 높은 성능 (거의 네이티브 수준)
  • L7 정책 (HTTP, DNS 등)
  • 관찰성 (Hubble)
  • 암호화 지원

설치

1
2
3
helm repo add cilium https://helm.cilium.io
helm install cilium cilium/cilium \
  --namespace kube-system

4. Weave - 다기능 오버레이 네트워킹

특징

  • 자동 네트워킹
  • 멀티호스트 컨테이너 통신
  • 암호화 지원
  • DNS 네이밍

설치

1
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64)"

CNI 플러그인 비교

플러그인성능복잡도정책오버레이권장
Calico⭐⭐⭐⭐⭐중간강함아니오프로덕션
Flannel⭐⭐⭐낮음약함VXLAN간단
Cilium⭐⭐⭐⭐⭐높음매우 강함아니오고급
Weave⭐⭐⭐중간약함다목적

CNI 플러그인 선택 가이드

Calico를 선택하면 좋은 경우

  • 최고 성능 필요
  • 네트워크 정책 중요
  • 멀티테넌트 환경
  • 프로덕션 환경

Flannel을 선택하면 좋은 경우

  • 빠른 구성 필요
  • 간단한 오버레이 충분
  • 개발/테스트 환경

Cilium을 선택하면 좋은 경우

  • 최고 성능
  • L7 정책 필요
  • 고급 보안 요구
  • Kubernetes 심화 환경

Weave를 선택하면 좋은 경우

  • 다기능 필요
  • 멀티클라우드 환경
  • 자동 구성 선호

결론: 컨테이너 기술 스택

전체 구조 다시 보기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────┐
│ 애플리케이션 (Docker/Kubernetes)                  │
└────────────────┬────────────────────────────────┘
                 │
┌────────────────▼────────────────────────────────┐
│ Part 3: 컨테이너 런타임                          │
│ - OCI Runtime (runc/crun/kata)                 │
│ - High-level Runtime (containerd/CRI-O)        │
│ - CNI Plugin (Calico/Flannel/Cilium)          │
└────────────────┬────────────────────────────────┘
                 │
┌────────────────▼────────────────────────────────┐
│ Part 2: Linux 격리 기술                          │
│ - Namespaces (PID, Network, Mount, UTS...)    │
│ - Cgroups (CPU, Memory, I/O 제한)              │
│ - Union FS (이미지 레이어)                       │
└────────────────┬────────────────────────────────┘
                 │
┌────────────────▼────────────────────────────────┐
│ Linux Kernel                                    │
│ - 시스템 콜, 메모리 관리, 프로세스 스케줄링      │
└─────────────────────────────────────────────────┘

Docker와 Kubernetes의 기반

Docker = Part 1 + Part 2 + Part 3의 조합

  • 컨테이너 이미지 포맷
  • Linux Namespace와 Cgroup 활용
  • OCI 런타임 사용
  • 사용자 친화적 도구

Kubernetes = Docker + 오케스트레이션

  • 여러 호스트에 걸친 컨테이너 배포
  • CNI를 통한 네트워킹
  • CRI를 통한 런타임 추상화
  • 자동 스케일링, 자가 치유 등

주요 개념 정리

개념역할
Namespace격리 (무엇을 볼 수 있는가)
Cgroup제한 (얼마나 사용할 수 있는가)
Union FS효율 (이미지 저장)
OCI Runtime표준 (컨테이너 실행)
CRI추상화 (런타임 교체 가능)
CNI네트워킹 (멀티호스트 통신)

기술 면접 대비

Q1: 컨테이너와 VM의 차이점은?

A:

항목컨테이너가상머신
격리 수준프로세스 격리 (커널 공유)하드웨어 가상화 (완전 격리)
부팅 시간밀리초분 단위
크기MB 단위GB 단위
리소스 효율높음낮음 (Guest OS 오버헤드)
보안 격리약함 (커널 취약점 공유)강함 (하이퍼바이저 격리)
이식성높음 (이미지 기반)낮음 (VM 이미지 크기)

꼬리 질문: “컨테이너가 VM보다 덜 안전한 이유는?” → 커널을 공유하기 때문입니다. 커널 취약점이 발견되면 호스트와 모든 컨테이너가 영향을 받습니다. 이를 보완하기 위해 gVisor, Kata Containers 같은 샌드박스 런타임을 사용합니다.

Q2: Docker와 containerd의 관계는?

A:

1
2
3
4
5
6
7
사용자 → Docker CLI → dockerd (Docker 데몬)
                          ↓
                     containerd (컨테이너 런타임)
                          ↓
                        runc (OCI 런타임)
                          ↓
                     컨테이너 프로세스

Kubernetes 1.24 이후:

  • Docker 직접 지원 중단 (dockershim 제거)
  • containerd 또는 CRI-O 직접 사용
  • Docker 이미지는 OCI 표준이므로 그대로 사용 가능

Q3: OCI 표준이 중요한 이유는?

A: 컨테이너 런타임 간 호환성과 이식성을 보장합니다.

OCI 표준 구성: | 표준 | 내용 | |——|——| | Image Spec | 이미지 포맷 (레이어, 메타데이터) | | Runtime Spec | 컨테이너 실행 방법 (config.json) | | Distribution Spec | 이미지 레지스트리 API |

효과:

  • Docker 이미지 → containerd, CRI-O, Podman에서 실행 가능
  • runc 대신 kata-containers, gVisor 사용 가능

Q4: rootless 컨테이너란?

A: root 권한 없이 실행되는 컨테이너입니다.

동작 원리:

  • User Namespace로 UID/GID 매핑
  • 컨테이너 내 root(0) → 호스트에서는 일반 사용자(1000)

보안 이점:

  • 컨테이너 탈출해도 호스트 root 아님
  • 권한 상승 공격 방어

Kubernetes에서의 활용:

1
2
3
securityContext:
  runAsNonRoot: true
  runAsUser: 1000

Q5: Seccomp와 AppArmor의 차이는?

A: 둘 다 컨테이너 보안 강화 기술이지만 접근 방식이 다릅니다.

기술역할방식
Seccomp시스템 콜 필터링허용/거부 목록 (syscall 단위)
AppArmor리소스 접근 제어프로필 기반 (파일, 네트워크 등)

Kubernetes에서의 사용:

1
2
3
4
5
6
7
8
# Seccomp
securityContext:
  seccompProfile:
    type: RuntimeDefault

# AppArmor (annotation 사용)
annotations:
  container.apparmor.security.beta.kubernetes.io/nginx: runtime/default

CKS 시험 포인트:

  • Seccomp: RuntimeDefault vs Unconfined 차이
  • AppArmor: 프로필 로드 및 적용 방법

Q6: CRI (Container Runtime Interface)란?

A: Kubernetes kubelet과 컨테이너 런타임 간의 표준 인터페이스입니다.

CRI 이전 (Kubernetes 1.5 이전):

  • Docker만 지원 (코드에 하드코딩)

CRI 이후:

1
2
3
kubelet ←→ CRI ←→ containerd
                  CRI-O
                  Docker (via dockershim, 1.24에서 제거)

CRI 주요 메서드:

  • RunPodSandbox: Pod 네트워크 네임스페이스 생성
  • CreateContainer: 컨테이너 생성
  • StartContainer: 컨테이너 시작
  • StopContainer: 컨테이너 중지

런타임 확인:

1
2
kubectl get nodes -o wide  # CONTAINER-RUNTIME 열 확인
crictl info  # CRI 런타임 정보

참고 자료

공식 문서

주요 개념 원문

원문 (OCI Runtime Specification): The goal of this specification is to define the configuration, execution environment, and lifecycle of a container. A container’s configuration file contains all of the information needed to create a container on a host.

번역: 이 명세의 목표는 컨테이너의 구성, 실행 환경 및 생명주기를 정의하는 것이다. 컨테이너의 구성 파일에는 호스트에서 컨테이너를 생성하는 데 필요한 모든 정보가 포함된다.

원문 (containerd.io): containerd is an industry-standard container runtime with an emphasis on simplicity, robustness, and portability. It is available as a daemon for Linux and Windows, which can manage the complete container lifecycle of its host system.

번역: containerd는 단순성, 견고성 및 이식성에 중점을 둔 산업 표준 컨테이너 런타임이다. Linux와 Windows용 데몬으로 사용 가능하며, 호스트 시스템의 전체 컨테이너 생명주기를 관리할 수 있다.

원문 (Kubernetes Documentation - Container Runtime Interface): The Container Runtime Interface (CRI) is the main protocol for the communication between the kubelet and Container Runtime. The CRI enables kubelet to use a wide variety of container runtimes, without having a need to recompile the cluster components.

번역: Container Runtime Interface(CRI)는 kubelet과 Container Runtime 간의 통신을 위한 주요 프로토콜이다. CRI는 kubelet이 클러스터 컴포넌트를 다시 컴파일할 필요 없이 다양한 컨테이너 런타임을 사용할 수 있게 한다.

다음 단계

This post is licensed under CC BY 4.0 by the author.