Notice
Recent Posts
Recent Comments
Link
«   2026/04   »
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
Archives
Today
Total
관리 메뉴

근묵자흑

Kubernetes를 배우기 전에 Linux를 먼저 배워야 하는 이유 본문

k8s/K8s Annotated

Kubernetes를 배우기 전에 Linux를 먼저 배워야 하는 이유

Luuuuu 2025. 12. 24. 10:41

원문: Learn Linux before Kubernetes by Anish Narayan


들어가며: Google이 이미 수백만 개의 컨테이너를 운영하고 있었다

오래 전, Google은 이미 대규모로 수백만 개의 컨테이너를 운영하고 있었습니다.

Linux cgroups는 거의 아무도 몰랐던 숨겨진 초능력과 같았습니다.

Google은 "컨테이너화(containerization)"라는 용어가 유행어가 되기 훨씬 전부터, 수년간 자사의 방대한 인프라를 관리하기 위해 cgroups를 광범위하게 사용해왔습니다.

2007년에 등장한 고급 Linux 커널 기능인 cgroups는 프로세스를 격리하고 리소스를 제어할 수 있었습니다. 하지만 거의 아무도 그 존재를 알지 못했습니다. cgroups는 극도로 복잡했고, 사용하려면 깊은 Linux 전문 지식이 필요했기 때문입니다. 기술 업계 내에서도 대부분의 사람들은 cgroups가 무엇인지, 어떻게 효과적으로 사용하는지 알지 못했습니다.

그러다 2013년, Docker가 등장하면서 모든 것이 바뀌었습니다.

Docker는 컨테이너나 cgroups를 발명하지 않았습니다. 이 기술들은 이미 Linux 커널 안에 숨어 있었습니다.

Docker가 했던 영리한 일은 이러한 기존 Linux 기술들을 누구나 사용할 수 있는 단순한 인터페이스로 감싸고 단순화한 것입니다. Docker는 cgroups의 복잡성을 추상화했습니다.

수 시간의 설정 대신, 개발자들은 이제 단일 docker run 명령어로 컨테이너를 배포할 수 있게 되었고, 이 기술은 시스템 수준 전문가뿐만 아니라 모든 사람이 접근할 수 있게 되었습니다.

Docker는 컨테이너 기술을 민주화했습니다. 이전에는 Google과 같은 기업에게만 예약되어 있던 도구의 힘을 일반 개발자들의 손에 쥐어준 것입니다.

timeline
    title 컨테이너 기술의 진화
    2000 : FreeBSD Jails
         : 최초의 OS 수준 가상화
    2006 : Process Containers (Google)
         : 나중에 cgroups로 명명
    2007 : cgroups Linux 커널 병합
         : Linux 2.6.24
    2008 : LXC (Linux Containers)
         : Namespace + cgroups 결합
    2013 : Docker 등장
         : 컨테이너 기술의 민주화
    2014 : Kubernetes 발표
         : Google Borg의 오픈소스화
    2015 : CNCF 설립
         : Kubernetes 기부
    2020 : Kubernetes가 Docker 런타임 지원 중단 발표
         : containerd/CRI-O로 전환
    2024 : eBPF 기반 네트워킹 주류화
         : Cilium CNCF Graduated

핵심 메시지: Linux를 먼저 배워라

Namespaces, cgroups (Control Groups), iptables/nftables, seccomp/AppArmor, OverlayFS, eBPF는 단순한 Linux 커널 기능이 아닙니다.

이것들은 Kubernetes와 Docker의 강력한 기능들—컨테이너 격리, 리소스 사용 제한, 네트워크 정책, 런타임 보안, 이미지 관리, 네트워킹 및 관측성 구현—의 기반이 됩니다.

containerd와 kubelet부터 Pod 보안과 볼륨 마운트까지, 각 컴포넌트는 핵심 Linux 기능에 의존합니다.

"Kubernetes는 강력하지만, 진짜 일은 Linux 엔진실에서 일어납니다."

 

Linux namespaces, cgroups, 네트워크 필터링 및 기타 기능들이 어떻게 작동하는지 이해하면, Kubernetes를 더 빨리 파악할 수 있을 뿐만 아니라 훨씬 더 효과적으로 트러블슈팅하고, 보안을 강화하고, 최적화할 수 있습니다.

Docker를 깊이 이해하려면, Linux 컨테이너가 커널 기능을 사용하여 시스템의 격리된 뷰를 가진 프로세스에 불과하다는 것을 탐구해야 합니다. 이러한 도구들을 직접 실습함으로써, Docker가 강력한 Linux 기본 요소들 위의 편리한 래퍼처럼 보이게 하는 기초 지식을 얻게 됩니다.

Linux를 먼저 배우세요. Kubernetes와 Docker가 '클릭'하게 될 것입니다.


전체 아키텍처: Linux 기반 위의 Kubernetes

아래 다이어그램은 Kubernetes의 모든 핵심 기능이 어떻게 Linux 커널 기술 위에 구축되는지 보여줍니다.

flowchart TB
    subgraph K8S["☸️ Kubernetes 계층"]
        direction TB
        POD["Pod"]
        SVC["Service"]
        NP["NetworkPolicy"]
        PSS["Pod Security Standards"]
        PV["PersistentVolume"]
        OBS["Observability"]
    end

    subgraph RUNTIME["🐳 컨테이너 런타임 계층"]
        direction TB
        CONTAINERD["containerd"]
        CRIO["CRI-O"]
        RUNC["runc"]
    end

    subgraph LINUX["🐧 Linux 커널 계층"]
        direction TB
        NS["Namespaces<br/>(격리)"]
        CG["cgroups<br/>(리소스 제한)"]
        NETFILTER["iptables/nftables<br/>(네트워크 필터링)"]
        LSM["seccomp/AppArmor/SELinux<br/>(보안)"]
        OVERLAY["OverlayFS<br/>(파일시스템)"]
        EBPF["eBPF<br/>(관측성/네트워킹)"]
    end

    POD --> NS
    POD --> CG
    SVC --> NETFILTER
    NP --> NETFILTER
    NP --> EBPF
    PSS --> LSM
    PV --> OVERLAY
    OBS --> EBPF

    CONTAINERD --> RUNC
    CRIO --> RUNC
    RUNC --> NS
    RUNC --> CG
    RUNC --> OVERLAY

1. Linux Namespaces: 컨테이너 격리의 근간

Namespace(네임스페이스)를 이해하는 가장 쉬운 방법은 "가상의 방"으로 생각하는 것입니다.

여러분이 큰 사무실 건물에서 일한다고 상상해보세요. 모든 직원이 같은 공간에서 일하면 소음, 프라이버시, 리소스 충돌 문제가 생깁니다. 그래서 우리는 파티션으로 나눈 개별 사무실을 만듭니다.

Linux Namespace도 마찬가지입니다. 하나의 Linux 커널 위에서 여러 프로세스가 실행되지만, 각 프로세스는 마치 자신만의 독립된 시스템에서 실행되는 것처럼 느끼게 해줍니다.

flowchart LR
    subgraph HOST["호스트 시스템"]
        KERNEL["Linux 커널"]

        subgraph NS1["컨테이너 A의 뷰"]
            PID1["PID 1: nginx"]
            NET1["eth0: 172.17.0.2"]
            MNT1["/app/data"]
        end

        subgraph NS2["컨테이너 B의 뷰"]
            PID2["PID 1: redis"]
            NET2["eth0: 172.17.0.3"]
            MNT2["/var/lib/redis"]
        end

        subgraph REAL["실제 호스트 뷰"]
            RPID1["PID 12345: nginx"]
            RPID2["PID 12400: redis"]
            RNET["eth0: 192.168.1.100"]
        end
    end

    NS1 -.->|"격리된 뷰"| KERNEL
    NS2 -.->|"격리된 뷰"| KERNEL
    REAL -->|"실제 상태"| KERNEL

8가지 Namespace 유형 상세 설명

Linux 커널은 현재 8종류의 네임스페이스를 지원합니다. 각각이 어떤 리소스를 격리하는지 살펴보겠습니다.

Namespace Clone Flag 격리 대상 실생활 비유 Kubernetes 활용
PID CLONE_NEWPID 프로세스 ID 공간 각 방에서 직원 번호가 1번부터 시작 Pod 내 컨테이너가 PID 1을 가짐
Network CLONE_NEWNET 네트워크 스택, 인터페이스, 포트 각 방에 독립된 전화번호 Pod마다 고유 IP 주소 할당
Mount CLONE_NEWNS 마운트 포인트 각 방에 독립된 서류함 컨테이너 rootfs, 볼륨 마운트
User CLONE_NEWUSER UID/GID 매핑 각 방에서 "사장"이지만 건물에선 일반 직원 Rootless 컨테이너
IPC CLONE_NEWIPC 공유 메모리, 세마포어 각 방의 독립된 화이트보드 Pod 간 IPC 격리
UTS CLONE_NEWUTS 호스트명, 도메인명 각 방의 독립된 명패 컨테이너별 hostname
Cgroup CLONE_NEWCGROUP cgroup 계층 뷰 각 방에서 자신의 예산만 보임 호스트 cgroup 정보 숨김
Time CLONE_NEWTIME monotonic/boot 시계 각 방의 시계가 다른 시간대 컨테이너 마이그레이션 (커널 5.6+)

PID Namespace 깊이 들여다보기

컨테이너 내부에서 ps aux를 실행하면 프로세스가 PID 1부터 시작하는 것을 볼 수 있습니다. 이것이 PID Namespace의 마법입니다.

# 호스트에서 실행
$ ps aux | grep nginx
root     12345  0.0  0.1 nginx: master process

# 컨테이너 내부에서 실행
$ docker exec -it my-nginx ps aux
PID   USER     COMMAND
1     root     nginx: master process

왜 PID 1이 중요할까요?

Unix/Linux에서 PID 1 프로세스는 특별한 의미를 가집니다. 첫째로 Init 프로세스 역할을 하여 고아(orphan) 프로세스의 부모가 됩니다. 둘째로 신호 처리 책임이 있어 SIGTERM, SIGKILL 등의 신호를 적절히 처리해야 합니다. 셋째로 좀비 프로세스 회수 의무가 있어 자식 프로세스가 종료되면 wait()를 호출해 정리해야 합니다.

컨테이너에서 애플리케이션이 PID 1로 실행되면, 이러한 책임을 져야 합니다. 이것이 많은 컨테이너 이미지가 tinidumb-init 같은 경량 init 시스템을 사용하는 이유입니다.

Network Namespace와 Kubernetes Pod

Kubernetes Pod의 네트워킹을 이해하려면 먼저 Network Namespace를 알아야 합니다.

flowchart TB
    subgraph NODE["Kubernetes 노드"]
        subgraph HOST_NS["호스트 Network Namespace"]
            ETH0["eth0<br/>192.168.1.100"]
            BRIDGE["cni0 브릿지<br/>10.244.0.1"]
        end

        subgraph POD1_NS["Pod A Network Namespace"]
            VETH1A["eth0<br/>10.244.0.10"]
        end

        subgraph POD2_NS["Pod B Network Namespace"]
            VETH2A["eth0<br/>10.244.0.11"]
        end

        VETH1B["veth123"]
        VETH2B["veth456"]
    end

    VETH1A <-->|"veth pair"| VETH1B
    VETH2A <-->|"veth pair"| VETH2B
    VETH1B --> BRIDGE
    VETH2B --> BRIDGE
    BRIDGE --> ETH0

모든 Kubernetes Pod에는 숨겨진 pause 컨테이너(sandbox/infra 컨테이너)가 있습니다. 이 pause 컨테이너가 Pod의 Network, IPC, UTS 네임스페이스를 생성하고 유지하며, 다른 애플리케이션 컨테이너들이 이를 공유합니다.

# Pod 네임스페이스 공유 설정 예시
apiVersion: v1
kind: Pod
metadata:
  name: shared-namespace-pod
spec:
  shareProcessNamespace: true   # PID 네임스페이스 공유
  hostNetwork: false            # 호스트 네트워크 사용 안 함 (기본값)
  hostPID: false                # 호스트 PID 사용 안 함 (기본값)
  hostIPC: false                # 호스트 IPC 사용 안 함 (기본값)
  containers:
  - name: app
    image: nginx
  - name: sidecar
    image: busybox
    # 두 컨테이너는 같은 네트워크와 PID 네임스페이스를 공유

2024-2025 최신 동향: User Namespace 지원

Kubernetes 1.30부터 User Namespace가 Beta로 승격되어 기본 활성화되었습니다. 이는 컨테이너 보안의 큰 진전입니다.

문제 상황: 컨테이너 내부에서 root(UID 0)로 실행되는 프로세스가 컨테이너 탈출(escape)에 성공하면, 호스트에서도 root 권한을 가지게 됩니다.

해결책: User Namespace를 사용하면 컨테이너 내부의 root가 호스트에서는 높은 번호의 비특권 사용자(예: UID 100000)로 매핑됩니다.

apiVersion: v1
kind: Pod
spec:
  hostUsers: false  # User Namespace 활성화 (Kubernetes 1.30+)
  containers:
  - name: app
    image: nginx
    securityContext:
      runAsUser: 0  # 컨테이너 내부에서는 root
      # 하지만 호스트에서는 UID 100000+ 으로 매핑됨

요구사항으로는 Linux 커널 6.3+, containerd 1.7+ 또는 CRI-O 1.25+, 그리고 idmap 마운트를 지원하는 파일시스템 (ext4, XFS, btrfs 등)이 필요합니다.


2. cgroups: 리소스 제한과 QoS의 핵심

 

cgroups (Control Groups)를 이해하는 가장 쉬운 방법은 "예산 관리"로 생각하는 것입니다.

회사에 여러 부서가 있다고 상상해보세요. 각 부서에는 정해진 예산이 있고, 그 예산 내에서만 지출할 수 있습니다. 한 부서가 예산을 초과하면 더 이상 지출할 수 없고, 다른 부서의 예산에 영향을 주지 않습니다.

cgroups도 마찬가지입니다. 각 프로세스 그룹(컨테이너)에 CPU, 메모리, I/O 등의 리소스 사용량을 제한합니다.

flowchart TB
    subgraph CGROUP["cgroups 계층 구조 (v2)"]
        ROOT["/ (root cgroup)"]

        subgraph KUBEPODS["kubepods.slice"]
            BESTEFFORT["besteffort/"]
            BURSTABLE["burstable/"]
            GUARANTEED["guaranteed/"]

            subgraph POD1["pod-abc123/"]
                C1["container1/<br/>cpu.max=50000 100000<br/>memory.max=128Mi"]
                C2["container2/<br/>cpu.max=100000 100000<br/>memory.max=256Mi"]
            end
        end

        SYSTEM["system.slice"]
        USER["user.slice"]
    end

    ROOT --> KUBEPODS
    ROOT --> SYSTEM
    ROOT --> USER
    KUBEPODS --> BESTEFFORT
    KUBEPODS --> BURSTABLE
    KUBEPODS --> GUARANTEED
    BURSTABLE --> POD1
    POD1 --> C1
    POD1 --> C2

cgroups v1 vs v2: 핵심 차이점

Kubernetes는 현재 cgroups v1에서 v2로의 전환기에 있습니다. 이 차이를 이해하는 것이 중요합니다.

특성 cgroups v1 cgroups v2
아키텍처 컨트롤러별 개별 계층 구조 단일 통합 계층 구조
파일 구조 /sys/fs/cgroup/cpu/, /memory/ 등 분리 /sys/fs/cgroup/ 통합
메모리 제어 memory.limit_in_bytes memory.max, memory.high, memory.low
CPU 제어 cpu.shares, cpu.cfs_quota_us cpu.weight, cpu.max
PSI 지원 미지원 지원 (리소스 압력 모니터링)
OOM 관리 프로세스 단위 cgroup 전체 단위 가능
flowchart LR
    subgraph V1["cgroups v1 구조"]
        direction TB
        CPU1["/sys/fs/cgroup/cpu/"]
        MEM1["/sys/fs/cgroup/memory/"]
        IO1["/sys/fs/cgroup/blkio/"]
        CPU1 --> PROC1["container-abc"]
        MEM1 --> PROC2["container-abc"]
        IO1 --> PROC3["container-abc"]
    end

    subgraph V2["cgroups v2 구조"]
        direction TB
        ROOT2["/sys/fs/cgroup/"]
        ROOT2 --> UNIFIED["container-abc/<br/>cpu.max<br/>memory.max<br/>io.max"]
    end

v2의 핵심 장점: memory.high

cgroups v2의 memory.high는 OOM kill 전에 쓰로틀링(throttling)을 적용합니다. 이는 갑작스러운 프로세스 종료 대신 성능 저하를 선택할 수 있게 해줍니다.

# cgroups v2 메모리 설정 예시
# memory.max: 하드 제한 - 초과 시 OOM kill
# memory.high: 소프트 제한 - 초과 시 쓰로틀링
# memory.low: 보호 수준 - 이 이하로는 회수 안 함
# memory.min: 절대 보호 - 절대 회수 안 함

echo 134217728 > /sys/fs/cgroup/mycontainer/memory.max    # 128Mi
echo 67108864 > /sys/fs/cgroup/mycontainer/memory.high    # 64Mi (쓰로틀링 시작)
echo 33554432 > /sys/fs/cgroup/mycontainer/memory.low     # 32Mi (보호)

Kubernetes 리소스와 cgroups 매핑

Kubernetes에서 Pod spec에 정의하는 resources.requestsresources.limits가 어떻게 cgroups로 변환되는지 이해하는 것이 중요합니다.

apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        cpu: "250m"       # 0.25 CPU -> cgroups cpu.weight에 영향
        memory: "64Mi"    # -> cgroups memory.min (Memory QoS 활성화 시)
      limits:
        cpu: "500m"       # 0.5 CPU -> cgroups cpu.max = "50000 100000"
        memory: "128Mi"   # -> cgroups memory.max = 134217728
Kubernetes 설정 cgroups v2 파일 의미
limits.cpu: "500m" cpu.max = "50000 100000" 100ms 주기 중 50ms CPU 사용 가능 (50%)
requests.cpu: "250m" cpu.weight 상대적 CPU 가중치 (스케줄링용)
limits.memory: "128Mi" memory.max = 134217728 하드 제한, 초과 시 OOM kill
requests.memory: "64Mi" memory.min 커널이 절대 회수하지 않는 메모리 (Memory QoS)

2024-2025 최신 동향: cgroups v1 지원 중단 일정

Kubernetes 버전 상태 주요 내용
1.25 (2022.08) cgroups v2 GA 공식 정식 지원 시작
1.31 (2024.08) cgroups v1 유지보수 모드 신규 기능 동결, 버그 수정만
1.35 (예정) cgroups v1 Deprecated kubelet 기본 시작 불가, 명시적 플래그 필요

현재 배포판별 기본값: Ubuntu 22.04+, Debian 11+는 cgroups v2 기본이며, RHEL 9, Fedora 31+도 cgroups v2 기본입니다. Amazon Linux 2023도 cgroups v2 기본이고, GKE, EKS, AKS 등 주요 관리형 Kubernetes 서비스는 대부분 cgroups v2로 전환 완료되었습니다.

# 현재 시스템의 cgroup 버전 확인
stat -fc %T /sys/fs/cgroup/
# 출력이 "cgroup2fs" -> v2
# 출력이 "tmpfs" -> v1

kubelet cgroup 드라이버 설정: systemd를 사용해야 cgroups v2와 호환됩니다.

# kubelet 설정 (/var/lib/kubelet/config.yaml)
cgroupDriver: systemd  # cgroupfs 대신 systemd 사용 권장

3. iptables와 nftables: 네트워크 정책의 뒷받침

 

iptables/nftables를 이해하는 가장 쉬운 방법은 "공항 보안 검색대"로 생각하는 것입니다.

공항에서 모든 승객은 보안 검색대를 통과해야 합니다. 검색대는 다양한 규칙(금속 탐지, 액체 용량 제한 등)을 적용하여 승객을 통과시키거나 차단합니다.

Linux의 iptables/nftables도 마찬가지입니다. 모든 네트워크 패킷은 규칙 체인을 통과하며, 각 규칙에 따라 ACCEPT(허용), DROP(차단), REJECT(거부 응답과 함께 차단) 등의 결정이 내려집니다.

flowchart LR
    subgraph NETFILTER["Linux Netfilter 프레임워크"]
        direction TB

        subgraph CHAINS["체인 (Chains)"]
            PREROUTE["PREROUTING"]
            INPUT["INPUT"]
            FORWARD["FORWARD"]
            OUTPUT["OUTPUT"]
            POSTROUTE["POSTROUTING"]
        end

        subgraph TABLES["테이블 (Tables)"]
            RAW["raw"]
            MANGLE["mangle"]
            NAT["nat"]
            FILTER["filter"]
        end
    end

    PKT_IN["📥 들어오는 패킷"] --> PREROUTE
    PREROUTE -->|"로컬 목적지"| INPUT
    PREROUTE -->|"다른 곳으로 전달"| FORWARD
    INPUT --> LOCAL["💻 로컬 프로세스"]
    LOCAL --> OUTPUT
    OUTPUT --> POSTROUTE
    FORWARD --> POSTROUTE
    POSTROUTE --> PKT_OUT["📤 나가는 패킷"]

Kubernetes Service와 iptables

Kubernetes Service는 ClusterIP를 통해 Pod들을 로드밸런싱합니다. 이 마법은 iptables(또는 IPVS, nftables)로 구현됩니다.

# Kubernetes Service의 iptables 규칙 예시
# Service: my-service (ClusterIP: 10.96.0.100:80)
# Backend Pods: 10.244.0.10:8080, 10.244.0.11:8080

# 1. KUBE-SERVICES 체인에서 Service IP 매칭
-A KUBE-SERVICES -d 10.96.0.100/32 -p tcp --dport 80 \
  -j KUBE-SVC-XXXXX

# 2. KUBE-SVC-XXXXX에서 랜덤 로드밸런싱
-A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.5 \
  -j KUBE-SEP-POD1
-A KUBE-SVC-XXXXX \
  -j KUBE-SEP-POD2

# 3. KUBE-SEP-POD1에서 DNAT (목적지 IP 변경)
-A KUBE-SEP-POD1 -p tcp -j DNAT --to-destination 10.244.0.10:8080

# 4. KUBE-SEP-POD2에서 DNAT
-A KUBE-SEP-POD2 -p tcp -j DNAT --to-destination 10.244.0.11:8080
sequenceDiagram
    participant Client as 클라이언트 Pod
    participant IPT as iptables/kube-proxy
    participant SVC as Service IP<br/>10.96.0.100
    participant Pod1 as Pod 1<br/>10.244.0.10
    participant Pod2 as Pod 2<br/>10.244.0.11

    Client->>SVC: 요청 (dst: 10.96.0.100:80)
    SVC->>IPT: PREROUTING 체인
    Note over IPT: DNAT 규칙 적용<br/>(50% 확률 선택)
    alt Pod 1 선택
        IPT->>Pod1: dst 변경: 10.244.0.10:8080
        Pod1-->>Client: 응답
    else Pod 2 선택
        IPT->>Pod2: dst 변경: 10.244.0.11:8080
        Pod2-->>Client: 응답
    end

kube-proxy 모드 비교

kube-proxy는 세 가지 모드를 지원합니다. 각 모드의 특성을 이해하면 클러스터 규모에 맞는 선택을 할 수 있습니다.

모드 성능 특징 권장 환경
iptables O(n) 선형 안정적, 호환성 우수, 규칙이 많아지면 느려짐 소규모 클러스터 (<500 서비스)
IPVS O(1) 해시 다양한 LB 알고리즘, 성숙한 기술 대규모 클러스터 (>1,000 서비스)
nftables O(1) 해시 증분 업데이트, 일정한 지연, 최신 기술 최신 커널 (5.13+), 미래 기본값

2024-2025 최신 동향: nftables 모드 GA

Kubernetes 1.29에서 Alpha로 도입된 nftables 모드가 1.31에서 GA(General Availability)가 되었습니다.

nftables의 핵심 장점은 다음과 같습니다. 첫째, Verdict Maps를 통해 해시 테이블 기반 O(1) 조회가 가능합니다. 둘째, 증분 업데이트로 전역 락 없이 규칙 변경이 가능합니다. 셋째, 서비스 수에 관계없이 일정한 지연 시간을 유지합니다.

# nftables 버딕트 맵 예시
table ip kube-proxy {
    map service-ips {
        type ipv4_addr . inet_service : verdict
        elements = { 
            172.30.0.41 . 80 : goto svc-chain1,
            172.30.0.42 . 443 : goto svc-chain2
        }
    }
}

벤치마크 결과: 10,000개 서비스 기준, iptables 대비 20배 이상 낮은 지연 시간.


4. seccomp, AppArmor, SELinux: 런타임 보안

컨테이너 런타임 보안을 이해하는 가장 쉬운 방법은 "권한 등급"으로 생각하는 것입니다.

회사에서 직원마다 접근 권한이 다릅니다. 인턴은 사무실 출입만 가능하고, 일반 직원은 사무실 + 회의실 + 복사실에 접근할 수 있으며, 관리자는 위 모든 곳 + 서버실까지 접근 가능합니다.

Linux 보안 모듈도 마찬가지입니다. seccomp은 프로세스가 호출할 수 있는 시스템 콜을 제한합니다. AppArmor경로 기반으로 파일/네트워크 접근을 제어합니다. SELinux레이블 기반으로 모든 객체 접근을 제어합니다.

flowchart TB
    subgraph SECURITY["컨테이너 보안 계층"]
        direction TB

        subgraph SECCOMP["seccomp (시스템 콜 필터링)"]
            SC_ALLOW["허용된 syscall<br/>read, write, open..."]
            SC_BLOCK["차단된 syscall<br/>mount, reboot, kexec_load..."]
        end

        subgraph APPARMOR["AppArmor (경로 기반 MAC)"]
            AA_PATH["경로 규칙<br/>/etc/passwd r,<br/>/tmp/** rw,"]
            AA_NET["네트워크 규칙<br/>network tcp,"]
        end

        subgraph SELINUX["SELinux (레이블 기반 MAC)"]
            SE_TYPE["타입 강제<br/>container_t"]
            SE_MCS["MCS 레이블<br/>s0:c123,c456"]
        end
    end

    PROCESS["컨테이너 프로세스"] --> SECCOMP
    SECCOMP --> APPARMOR
    APPARMOR --> SELINUX
    SELINUX --> KERNEL["Linux 커널"]

seccomp: 시스템 콜 필터링

seccomp (Secure Computing Mode)은 프로세스가 커널에 요청할 수 있는 시스템 콜을 제한합니다.

컨테이너 런타임의 기본 seccomp 프로필은 약 44-64개의 위험한 syscall을 차단합니다. mount, umount는 파일시스템 조작을, reboot, kexec_load는 시스템 제어를, ptrace는 다른 프로세스 디버깅을, personality는 실행 도메인 변경을 담당하는데 이들이 모두 차단됩니다.

# Pod에서 seccomp 설정 (Kubernetes 1.19+)
apiVersion: v1
kind: Pod
metadata:
  name: seccomp-demo
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault  # 런타임 기본 프로필 사용
  containers:
  - name: app
    image: nginx

Kubernetes 1.27부터 SeccompDefault 기능이 GA로, --seccomp-default kubelet 플래그로 모든 Pod에 기본 프로필을 자동 적용할 수 있습니다.

Pod Security Standards (PSS)와 보안

Kubernetes 1.25에서 GA된 Pod Security Admission (PSA)은 세 가지 보안 정책 레벨을 정의합니다.

레벨 설명 seccomp 요구사항 사용 사례
Privileged 제한 없음 없음 시스템 컴포넌트
Baseline 알려진 권한 상승 방지 Unconfined 명시 금지 일반 워크로드
Restricted 현재 하드닝 모범 사례 RuntimeDefault 필수 보안 민감 워크로드
# 네임스페이스에 보안 정책 적용
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted

2024-2025 최신 동향: AppArmor GA

Kubernetes 1.30에서 AppArmor가 정식 securityContext 필드로 승격되었습니다 (기존에는 annotation 사용).

# 이전 방식 (annotation)
metadata:
  annotations:
    container.apparmor.security.beta.kubernetes.io/app: localhost/custom-profile

# 새로운 방식 (Kubernetes 1.30+)
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: custom-profile
  containers:
  - name: app
    image: nginx

5. OverlayFS: 컨테이너 이미지의 비밀

OverlayFS를 이해하는 가장 쉬운 방법은 "투명 필름 레이어"로 생각하는 것입니다.

애니메이션 제작을 상상해보세요. 배경 그림 위에 투명한 셀룰로이드 필름을 올려 캐릭터를 그립니다. 배경은 그대로 두고 캐릭터만 바꾸면 새로운 장면이 됩니다.

OverlayFS도 마찬가지입니다. lowerdir (읽기 전용)은 배경 그림, 즉 컨테이너 이미지 레이어입니다. upperdir (읽기/쓰기)는 투명 필름, 즉 컨테이너 변경사항입니다. merged (통합 뷰)는 완성된 장면, 즉 컨테이너가 보는 파일시스템입니다.

flowchart TB
    subgraph OVERLAY["OverlayFS 구조"]
        direction TB

        MERGED["📁 /merged (통합 뷰)<br/>컨테이너가 보는 파일시스템"]

        subgraph UPPER["upperdir (읽기/쓰기)"]
            U_NEW["새 파일: app.log"]
            U_MOD["수정된 파일: nginx.conf"]
            U_DEL["삭제 마커: .wh.old.txt"]
        end

        subgraph LOWER["lowerdir (읽기 전용)"]
            subgraph L3["Layer 3: 애플리케이션"]
                L3_APP["app/main.py"]
            end
            subgraph L2["Layer 2: 의존성"]
                L2_PKG["usr/lib/python3"]
            end
            subgraph L1["Layer 1: 베이스 이미지"]
                L1_BIN["bin/bash"]
                L1_OLD["old.txt ❌"]
            end
        end
    end

    MERGED --> UPPER
    MERGED --> LOWER
    UPPER -.->|"Copy-on-Write"| L3

Copy-on-Write (CoW) 메커니즘

OverlayFS의 핵심 최적화는 Copy-on-Write입니다.

읽기 작업은 lowerdir에서 직접 읽으며 복사가 없습니다. 쓰기 작업은 lowerdir의 파일을 수정할 때만 upperdir로 복사 후 수정합니다. 삭제 작업은 "whiteout" 특수 파일로 삭제를 표시하며 실제 삭제는 없습니다.

# Copy-on-Write 동작 확인 예시
# 1. 컨테이너에서 /etc/nginx/nginx.conf 수정 시
# 2. 해당 파일이 upperdir로 복사됨
# 3. upperdir의 복사본이 수정됨
# 4. lowerdir의 원본은 그대로 유지

# 컨테이너 레이어 확인
docker inspect nginx:latest --format='{{json .RootFS.Layers}}' | jq
# [
#   "sha256:abc123...",  # Layer 1
#   "sha256:def456...",  # Layer 2
#   "sha256:ghi789..."   # Layer 3
# ]

컨테이너 런타임 스토리지 구현

containerd는 Snapshotter 아키텍처를 사용합니다:

# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd]
  snapshotter = "overlayfs"  # 기본값
  # 대안: native, btrfs, zfs, devmapper

주의사항: XFS 파일시스템 사용 시 ftype=1로 포맷해야 overlay2가 동작합니다.

# XFS ftype 확인
xfs_info /dev/sdXn | grep ftype
# ftype=1 이어야 함

2024-2025 최신 동향: Lazy Pulling과 eStargz

연구에 따르면 컨테이너 시작 시간의 76%가 이미지 pull에 소요되지만, 실제로 읽는 데이터는 6.4%에 불과합니다.

eStargz (Extended Seekable tar.gz)는 이 문제를 해결합니다. 이미지 전체를 다운로드하지 않고 필요한 파일만 on-demand로 가져옵니다. 기존 레지스트리와 호환되며, containerd의 stargz-snapshotter로 활성화할 수 있습니다.


6. eBPF: Kubernetes 네트워킹과 관측성의 미래

eBPF (extended Berkeley Packet Filter)를 이해하는 가장 쉬운 방법은 "커널 내 프로그래밍"으로 생각하는 것입니다.

기존에는 커널 기능을 수정하려면 커널 모듈을 작성하거나 커널 자체를 수정해야 했습니다. 이는 위험하고, 복잡하고, 커널 버전에 종속적이었습니다.

eBPF는 안전한 샌드박스 환경에서 커널 내 프로그램을 실행할 수 있게 해줍니다. 네트워크 패킷 처리, 시스템 콜 추적, 성능 모니터링, 보안 정책 적용을 모두 커널 수정 없이, 런타임에, 안전하게 할 수 있습니다.

flowchart TB
    subgraph EBPF["eBPF 아키텍처"]
        direction TB

        subgraph USER["사용자 공간"]
            APP["애플리케이션<br/>(Cilium, Falco, Pixie...)"]
            LOADER["eBPF 로더"]
            MAP_USER["Maps 접근"]
        end

        subgraph KERNEL["커널 공간"]
            VERIFIER["Verifier<br/>(안전성 검증)"]
            JIT["JIT 컴파일러"]

            subgraph HOOKS["Hook Points"]
                XDP["XDP<br/>(NIC 드라이버)"]
                TC["TC<br/>(Traffic Control)"]
                SOCKET["Socket"]
                SYSCALL["Syscall"]
                LSM["LSM"]
            end

            MAPS["eBPF Maps<br/>(커널-유저 데이터 공유)"]
        end
    end

    APP --> LOADER
    LOADER --> VERIFIER
    VERIFIER -->|"검증 통과"| JIT
    JIT --> HOOKS
    HOOKS --> MAPS
    MAPS --> MAP_USER

Cilium: eBPF 기반 Kubernetes 네트워킹

Cilium은 CNCF Graduated 프로젝트로, iptables 없이 순수 eBPF로 Kubernetes 네트워킹을 구현합니다.

핵심 기능으로는 kube-proxy 대체(소켓 레벨 로드밸런싱), L7 네트워크 정책(HTTP/gRPC/DNS 기반 필터링), Identity 기반 보안(IP 대신 워크로드 레이블 사용), Hubble(실시간 네트워크 관측성), 서비스 메시(사이드카 없이 mTLS, L7 라우팅)가 있습니다.

# Cilium L7 네트워크 정책 예시
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: api-policy
spec:
  endpointSelector:
    matchLabels:
      app: api-server
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: frontend
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: GET
          path: "/api/v1/users.*"
        - method: POST
          path: "/api/v1/orders"
flowchart LR
    subgraph CILIUM["Cilium eBPF 데이터플레인"]
        direction TB

        subgraph POD1["Frontend Pod"]
            APP1["Frontend App"]
            BPF1["eBPF (socket)"]
        end

        subgraph POD2["API Pod"]
            APP2["API Server"]
            BPF2["eBPF (socket)"]
        end

        subgraph POD3["DB Pod"]
            APP3["Database"]
            BPF3["eBPF (socket)"]
        end

        HUBBLE["Hubble<br/>(관측성)"]
    end

    APP1 -->|"L7 정책 검사"| BPF1
    BPF1 -->|"GET /api/v1/users ✅"| BPF2
    BPF2 --> APP2
    APP2 --> BPF2
    BPF2 -->|"TCP:5432 ✅"| BPF3
    BPF3 --> APP3

    BPF1 -.-> HUBBLE
    BPF2 -.-> HUBBLE
    BPF3 -.-> HUBBLE

주요 클라우드의 eBPF 채택

GKE Dataplane V2는 Cilium 기반으로 기본 옵션입니다. Azure CNI Powered by Cilium은 AKS에서 선택 가능합니다. AWS VPC CNI는 eBPF 지원 추가가 진행 중입니다.

2024-2025 최신 동향

Linux 6.9 - BPF Arena는 커널-유저스페이스 간 최대 4GB 공유 메모리를 지원합니다. Linux 6.12 - sched_ext는 eBPF로 CPU 스케줄러 정책 커스터마이징을 가능하게 합니다. eBPF for Windows는 Microsoft가 개발 중이며 Windows 11/Server 2022+ 지원 예정입니다.


정리: Linux 기술과 Kubernetes 매핑 요약

mindmap
  root((Kubernetes))
    Pod 격리
      Linux Namespaces
        PID
        Network
        Mount
        User 1.30 Beta
        IPC
        UTS
        Cgroup
    리소스 관리
      cgroups v2
        cpu.max
        memory.max
        memory.high
        PSI 모니터링
    네트워킹
      iptables/nftables
        Service ClusterIP
        NodePort
        LoadBalancer
      eBPF/Cilium
        L7 정책
        관측성
    보안
      seccomp
        RuntimeDefault
      AppArmor
        경로 기반 MAC
      SELinux
        레이블 기반 MAC
      PSS/PSA
        Baseline
        Restricted
    스토리지
      OverlayFS
        이미지 레이어
        Copy-on-Write
        Snapshotter

실무 체크리스트

환경 점검 명령어 모음:

# 1. cgroup 버전 확인
stat -fc %T /sys/fs/cgroup/
# cgroup2fs = v2, tmpfs = v1

# 2. 커널 버전 확인 (5.10+ 권장)
uname -r

# 3. XFS ftype 확인 (overlay2용)
xfs_info /dev/sdXn | grep ftype

# 4. kubelet cgroup 드라이버 확인
cat /var/lib/kubelet/config.yaml | grep cgroupDriver

# 5. 현재 네임스페이스 확인
ls -la /proc/self/ns/

# 6. seccomp 지원 확인
grep SECCOMP /boot/config-$(uname -r)

# 7. eBPF 지원 확인
ls /sys/fs/bpf/

권장 설정:

구성요소 권장 설정 비고
Linux 커널 5.10+ (6.x 권장) BTF 활성화, eBPF CO-RE
cgroups v2 + systemd 드라이버 1.35부터 v1 deprecated
컨테이너 런타임 containerd 2.0+ / CRI-O 1.25+ User Namespace 지원
스토리지 드라이버 overlay2 XFS는 ftype=1 필수
네트워킹 Cilium (eBPF) 또는 nftables 대규모 환경 권장
보안 PSS Restricted + seccomp RuntimeDefault 프로덕션 필수

참고 자료

공식 문서:

Kubernetes Enhancement Proposals (KEPs):