리눅스 컨테이너 기술 완전정복

Updated:

리눅스 컨테이너 기술 완전정복

도커와 쿠버네티스 공부에 앞서 리눅스 컨테이너 기술에 대해 공부 및 실습을 하였다.


목차

  1. 컨테이너란 무엇인가
  2. 리눅스가 제공하는 두 축: Namespacescgroups
  3. 루트 파일시스템과 레이어드 스토리지 (OverlayFS)
  4. OCI(오픈 컨테이너 이니셔티브) 표준과 런타임 스택
  5. 컨테이너 네트워킹 한눈에 보기 (veth, bridge, iptables/nftables, CNI)
  6. 컨테이너 보안: capabilities, seccomp, AppArmor/SELinux, rootless
  7. 실습 A: unshare + chroot로 “핸드메이드 컨테이너” 만들기
  8. 실습 B: runc로 OCI 번들 실행하기
  9. 실습 C: veth 페어와 브리지로 간단한 컨테이너 네트워크 구성
  10. 프로세스/신호/로그: “PID 1 문제”와 init
  11. 성능 이야기: copy-on-write, 페이지 캐시, cgroup v2
  12. 디버깅/관찰 도구 모음(nsenter, strace, perf, eBPF)
  13. VM과 컨테이너 차이 요약

1) 컨테이너란 무엇인가

  • 정의: 컨테이너는 리눅스 커널 기능(네임스페이스, cgroups, FS 격리 등)을 조합해 만든 프로세스 격리 실행환경입니다. 하이퍼바이저 기반 VM처럼 OS 전체를 가상화하지 않고, 호스트 커널을 공유합니다.
  • 핵심 특징

    • 시작/종료가 빠름(프로세스 스폰 수준)
      • 부팅이 아닌 프로세스 시작 수준 → 수 ms~수 초.
      • 이미지 레이어 재사용(COW) → 다운로드/배포 효율.
    • 이미지 레이어로 배포 용이
    • 밀집 배치 가능(오버헤드↓)
    • 커널을 공유하므로 메모리/CPU 오버헤드가 작음 → 고밀도 배치에 유리.
      • 대신 커널을 공유하므로 커널 취약점은 공통 리스크

2) 리눅스의 두 축: Namespaces & cgroups

Namespaces (격리)

  • PID: 프로세스 트리 격리 (컨테이너 내부 PID 1은 호스트에선 일반 PID)
  • UTS: hostname, domainname 격리
  • IPC: System V IPC, POSIX message queue 격리
  • NET: 네트워크 스택 격리 (veth, 라우팅 테이블, iptables 등 별도)
  • MNT: 마운트 네임스페이스(루트FS, 마운트포인트 격리)
  • USER: UID/GID 매핑을 통한 권한 격리(루트리스의 핵심)
  • CGROUP: cgroup 네임스페이스(경로 격리)

cgroups (자원 제어)

  • CPU: shares, quota/period로 CPU 할당 제한
  • 메모리: 상한/스왑/oom 정책
  • IO/Block: IOPS/스루풋 제한
  • PIDs: 프로세스 개수 제한
  • cgroup v2: 단일 계층 계보, 일관된 컨트롤러 모델

컨테이너 = 여러 네임스페이스 + cgroup 제한 + 독립 루트FS 조합


3) 루트 파일시스템과 레이어드 스토리지

  • 컨테이너 루트FS는 보통 이미지 레이어의 합성 결과(읽기 전용) + 쓰기 레이어(읽기-쓰기)로 구성됩니다.
  • OverlayFS: 상단(upperdir)과 하단(lowerdir)을 합쳐 보이는 union FS. 쓰기 시 copy-up이 일어남(COW, Copy-On-Write).
  • 장점: 이미지 레이어 재사용으로 배포/캐시 효율 ↑
  • 주의: 작은 파일 다량 write/rename 패턴은 copy-up 비용 고려

4) OCI 표준과 런타임 스택

  • OCI Image Spec: 이미지 포맷 표준 (레이어, config)
  • OCI Runtime Spec: 컨테이너 실행 번들(루트FS + config.json) 표준
  • 런타임 계층

    • High-level: Docker Engine, containerd, CRI-O, Podman
    • Low-level: runc, crun (OCI Runtime 구현)
  • 도식

    CLI → (Docker/Podman) → containerd/CRI-O → runc/crun → 리눅스 커널(Namespaces/cgroups)
    

5) 컨테이너 네트워킹 핵심

  • veth pair: 양 끝이 연결된 가상 이더넷 NIC. 한쪽은 호스트 네임스페이스, 다른쪽은 컨테이너 네임스페이스에 붙임.
  • 브리지(bridge): 리눅스 브리지(br0, docker0)가 L2 스위치 역할. 컨테이너 veth를 브리지에 붙여 L2 통신.
  • IPAM: IP 할당. 단일 호스트는 브리지+NAT가 보편.
  • NAT/포워딩: iptables/nftables로 SNAT/DNAT 수행(포트포워딩).
  • Kubernetes: CNI 플러그인(Flannel, Calico, Cilium 등)이 네트워크를 자동 구성.

6) 컨테이너 보안

  • Linux capabilities: 루트 권한을 세분화. 컨테이너는 기본적으로 일부 capability만 가짐.
  • seccomp: 허용된 시스템 콜 화이트리스트로 커널 면적 축소.
  • AppArmor/SELinux: LSM을 통한 MAC 정책 적용.
  • rootless: USER 네임스페이스로 호스트 root 없이 컨테이너 실행.
  • 이미지 서명/스캐닝: 공급망 안전(SBOM, cosign, trivy 등).

7) 실습 A: unshare + chroot로 핸드메이드 컨테이너

목적: 도커 없이 리눅스 원시 도구만으로 “컨테이너 같은 것”을 체험.

busybox 설치

sudo apt-get update && sudo apt-get install -y busybox-static uidmap

루트FS 만들기

mkdir -p ~/mini-root/{bin,proc,sys,dev}
cp /bin/busybox ~/mini-root/bin/
# busybox 링크 생성
sudo chroot ~/mini-root /bin/busybox --install -s /bin

격리된 네임스페이스 생성 후 진입

sudo unshare --fork --pid --mount --uts --ipc --net --mount-proc bash -lc '
  hostname mini
  mount -t proc proc ~/mini-root/proc
  chroot ~/mini-root /bin/sh -lc "echo \"Hello from $(hostname)\"; ps; sleep 1000"
'

unshare 실습


8) 실습 B: runc로 OCI 번들 실행하기

runc 설치

OCI 번들 구성

mkdir -p ~/oci-bundle/rootfs

cp /bin/busybox ~/oci-bundle/rootfs/bin/
sudo chroot ~/oci-bundle/rootfs /bin/busybox --install -s /bin

cat > ~/oci-bundle/config.json <<'JSON'
{
  "ociVersion": "1.1.0",
  "process": {
    "terminal": true,
    "args": ["/bin/sh"],
    "cwd": "/",
    "user": { "uid": 0, "gid": 0 },
    "capabilities": { "bounding": ["CAP_CHOWN","CAP_DAC_OVERRIDE","CAP_FSETID","CAP_FOWNER","CAP_MKNOD","CAP_NET_RAW","CAP_SETGID","CAP_SETUID","CAP_SETFCAP","CAP_SETPCAP","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_KILL","CAP_AUDIT_WRITE"] }
  },
  "root": { "path": "rootfs", "readonly": false },
  "mounts": [
    { "destination": "/proc", "type": "proc", "source": "proc" }
  ],
  "linux": {
    "namespaces": [
      {"type": "pid"}, {"type": "uts"}, {"type": "ipc"}, {"type": "mount"}, {"type": "network"}
    ]
  }
}
JSON

실행

cd ~/oci-bundle
runc run mini

9) 실습 C: veth + bridge로 네트워크 체험

# 브리지 생성
sudo ip link add br0 type bridge
sudo ip link set br0 up
sudo ip addr add 10.10.0.1/24 dev br0

# veth 페어 생성
sudo ip link add veth-host type veth peer name veth-ns
sudo ip link set veth-host master br0
sudo ip link set veth-host up

# 네트워크 네임스페이스 생성 및 NIC 이동
sudo ip netns add blue
sudo ip link set veth-ns netns blue
sudo ip netns exec blue ip link set lo up
sudo ip netns exec blue ip addr add 10.10.0.2/24 dev veth-ns
sudo ip netns exec blue ip link set veth-ns up

# 통신 확인
ping -c 2 10.10.0.2
sudo ip netns exec blue ping -c 2 10.10.0.1

네트워크 실습 - NIC 예시

10) 프로세스/신호/로그: PID 1 문제

  • 컨테이너의 PID 1은 좀비 프로세스 수거신호 전달에 책임. bash가 PID 1이면 신호 처리가 기대와 다를 수 있음.
  • 해결책: tini, dumb-init, 또는 애플리케이션이 직접 SIGTERM/SIGINT 처리 및 reaping 구현.
  • 로그는 stdout/stderr로 흘리고, 수집기는 런타임/노드에서 담당(도커 로그 드라이버, K8s 로깅).

11) 성능 이야기

  • Copy-on-Write: 처음 쓰기 비용 증가 가능 → 쓰기 많은 경로는 별도 볼륨 마운트 고려.
  • 페이지 캐시 공유: 동일 레이어 재사용으로 메모리 이점.
  • CPU/메모리/IO 제한: cgroup v2로 일관된 제어, OOMKiller 동작 이해 필요.

12) 디버깅/관찰 도구

  • nsenter: 다른 네임스페이스에 진입
  • strace: 시스템콜 추적
  • lsof / ss / ip / ethtool: FD/소켓/링크 확인
  • perf / eBPF(bpftrace, bcc): 커널/성능 분석
  • criu: 컨테이너 체크포인트/복원

13) VM vs 컨테이너 요약

항목 VM 컨테이너
커널 게스트 커널 호스트 커널 공유
격리 강함(하이퍼바이저) 프로세스 격리(커널 공유)
부팅속도 수 초~분 수 밀리초~초
밀도 낮음 높음
보안 경계 강함 커널 취약점 공유 리스크

마무리

컨테이너는 리눅스 커널 기능의 조합입니다. 이 본질을 이해하면 도커/쿠버네티스의 많은 동작이 투명해집니다. 위의 실습을 먼저 손으로 익히고, 그 다음 도커/쿠버네티스 개념으로 확장해보세요. 블로그 게시 전, 본문 상단에 배포 환경(테스트 리눅스 배포판/커널 버전)을 명시하면 독자 재현성이 더 좋아집니다.

카테고리:

업데이트:

댓글남기기