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네트워킹 (멀티호스트 통신)

References

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