Container - Foundation for Docker & Kubernetes
목차
Part 1: 컨테이너의 이해
Part 2: Linux 격리 기술 (기초)
Part 3: 컨테이너 런타임 (스택)
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)로 쌓아서 하나의 통합된 파일시스템처럼 보이게 하는 기술이다. 컨테이너 이미지의 효율성과 이식성의 핵심이다.
필요성:
- 저장 공간 절약: 여러 컨테이너가 동일한 base 이미지 레이어 공유
- 빠른 이미지 배포: 변경된 레이어만 전송
- 이미지 재사용: 한 번 다운로드한 레이어는 캐싱되어 재사용
- 효율적인 빌드: 변경된 레이어만 재빌드
주요 Union Filesystem 종류
| 파일시스템 | 설명 | 사용처 |
|---|---|---|
| OverlayFS (Overlay2) | 현재 Docker 기본, 메인라인 커널 포함 | Docker, Podman, containerd |
| AUFS | Docker 초기 기본, 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 사용량 증가 (작은 파일 많을 때)
최적화 팁:
- 레이어 수 최소화: Dockerfile에서 RUN 명령을 && 로 합치기
- 자주 변경되는 파일은 Volume 사용: DB 데이터, 로그 등
- Base 이미지 공유: 팀 내 공통 base 이미지 사용
- Multi-stage build: 불필요한 레이어 제거
면접 질문 예시
Q1: “OverlayFS의 Copy-on-Write는 어떻게 동작하는가?”
A: 컨테이너가 LowerDir(읽기 전용 이미지 레이어)의 파일을 수정하려 할 때:
- 파일을 LowerDir에서 찾음
- 전체 파일을 UpperDir(쓰기 레이어)로 복사 (Copy-up)
- UpperDir에서 수정 수행
- 이후 읽기는 UpperDir에서만 수행 (LowerDir는 원본 유지) 첫 쓰기만 Copy-up 오버헤드 발생. 큰 파일 수정 시 성능 영향 가능.
Q2: “컨테이너 이미지의 레이어 구조가 효율적인 이유는?”
A:
- 레이어 공유: 여러 컨테이너가 동일한 base 레이어를 공유하여 저장 공간 절약 (수백 MB 절감 가능)
- 빠른 배포: 새 이미지 배포 시 변경된 레이어만 전송 (전체 이미지 전송 불필요)
- 빌드 캐싱: 변경되지 않은 레이어는 재사용하여 빌드 속도 향상
- 이미지 버전 관리: 각 레이어가 불변이므로 재현 가능한 빌드
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 Namespace | UID/GID 매핑 | 사용자 권한 격리 |
| Cgroup Namespace | Cgroup 루트 | 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 Limiting | CPU, 메모리, 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 지원 | 비고 |
|---|---|---|---|
| Docker | 20.10- | 20.10+ | 최신은 둘 다 지원 |
| Kubernetes | 1.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 수집 및 저장 |
| CRI | Kubernetes 통합 |
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-O | Kubernetes 전용 경량 런타임 |
| Docker | dockerd가 CRI 구현 |
| Podman | daemonless 런타임 |
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
| 특성 | containerd | CRI-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 | 성능 중요 | 매우 빠름 | 중간 | 매우 낮음 |
| containerd | Kubernetes | 빠름 | 중간 | 낮음 |
| CRI-O | K8s 전용 | 빠름 | 중간 | 낮음 |
| 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의 책임
- Pod IP 할당: IPAM (IP Address Management)
- 네트워크 연결: veth/bridge 생성
- 라우팅: 멀티호스트 통신 설정
- 정책 적용: 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 | 네트워킹 (멀티호스트 통신) |