[CKS] Minimize Microservice Vulnerabilities (6) - Container Sandboxing
Kubernetes 보안을 위한 컨테이너 격리 기술을 소개합니다. Seccomp, AppArmor, gVisor, Kata Containers 등 샌드박싱 방식과 RuntimeClass를 통한 런타임 구성 방법을 재정리했습니다.
개요
CKS 자격증을 위해 아래 내용을 정리했습니다.
VM과 Container의 차이점
모든 VM은 물리적 인프라를 기반으로 구축됩니다.
- 가상 머신
- 호스트 OS 위 하이퍼바이저를 통해 여러 개의 CPU와 메모리를 할당한 가상 머신이 생성됩니다.
- 각 가상 머신은 자체 운영 체제와 자체 커널로 구성됩니다.
- Container
- Docker Engine 위에서 여러 개의 CPU와 메모리를 할당한 컨테이너를 실행합니다.
- 호스트 OS 커널을 공유합니다.

Container는 애플리케이션 추상화를 통해 더 가벼운 격리 공간을 제공합니다.
하지만 Host OS를 공유하는만큼 A 컨테이너에서 취약점이 악용되면 B 컨테이너까지 영향받을 수 있습니다.
컨테이너 프로세스 격리
호스트는 모든 컨테이너 프로세스를 볼 수 있지만, 컨테이너는 자체 격리된 PID Namespace를 유지합니다.
root@ubuntu-server:~# docker run -d --name sleeping-container busybox sleep 1000
e2fd5090c9a51eb7cc91a466871f84adb55c2e6c1cf4ea0028a8
root@ubuntu-server:~# docker exec -ti sleeping-container ps -ef
PID USER TIME COMMAND
1 root 0:00 sleep 1000
11 root 0:00 ps -ef
컨테이너 애플리케이션도 동일하게 사용자 공간에서 호출되기 때문에 Syscall 을 통해 하드웨어에 접근합니다.
하지만 단일 컨테이너로 처리하기에, 모든 Syscall 은 동일한 커널에서 처리됩니다. Dirty Cow 같은 취약점으로 커널을 손상시킨다면 모든 컨테이너를 위험에 노출시키고 Host OS 접근을 허용할 수 있습니다.
Container Sandbox
공유 커널 문제를 완화하기 위해 샌드박싱으로 추가 보호 조치를 적용합니다.
시스템 내 구성 요소를 서로 격리하는 모든 기술을 의미합니다.
Seccomp(보안 컴퓨팅)
Docekr Container가 Syscall 을 하지 못하도록 기본 Seccomp 프로필로 보호합니다.
- 필수 시스템 호출만 허용하여 컨테이너 권한을 제어 → 화이트 리스트 기반 Seccomp Profile
- 화이트리스트 방식으로 잠재적인 취약점을 최소화합니다.
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"execve",
"brk",
"access",
"capset",
"clone"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
AppArmor
Seccomp가 Syscall 을 막는다면 AppArmor 는 파일, 디렉토리(리소스)를 제어합니다.
AppArmor 는 광범위한 허용 정책으로 특정 작업을 제한합니다.
- 특정 디렉토리의 읽기/쓰기 접근 차단 → 블랙리스트 접근 방식
- 블랙리스트 방식은 유연성을 제공하며 기능 구현의 자유도를 제공합니다.
profile apparmor-deny-write flags=(attach_disconnected) {
# Deny all file writes to /proc.
deny /proc/* w,
}
참고
완벽한 샌드박싱 방식은 없습니다. 최적의 구성이란 요구 사항과 애플리케이션에 따라 언제든지 달라질 수 있음을 유념합니다. 위험을 최소화한다는 점을 명확하게 확인합니다.
gVisor
컨테이너 격리를 강화하기 위해 Google에서 만든 gVisor를 확인합니다.
- gVisor는 Syscall을 가로채서 컨테이너와 Linux 커널 사이에 추가적인 격리 계층 생성
컨테이너가 리눅스 커널에 도달하기 전 훨씬 더 철저하게 격리될 수 있습니다.
gVisor 아키텍쳐
gVisor는 강력한 격리 계층을 위해 두 가지 주요 구성요소로 구성됩니다.

Sentry
컨테이너 환경에 특화된 애플리케이션 레벨 커널입니다.
컨테이너 최적화 설계로 Sentry는 전체 Linux 커널 대비 제한된 기능을제공합니다.
- 간소화된 기능으로 취약점 발생 위험을 최소화
Gofer
파일 접근 요청시 Sentry는 Syscall 요청을 커널로 직접 전달하지 않습니다. 대신 Gofer라는 전용 프로세스와 통신하며, Gofer는 파일 프록시 역할을 수행합니다.
Gofer는 컨테이너화된 애플리케이션이 시스템 파일에 접근하기 위해 필요한 로직을 처리합니다.
- 컨테이너와 OS 사이 Proxy역할을 수행하며 취약점을 방지
Sentry와 Gofer는 Isolation(격리)하여 애플리케이션 로직과 파일 시스템 로직을 구분했습니다.
역할을 정리하면 다음과 같습니다.
- 일반 시스템 호출 → Sentry가 처리
- 파일 시스템 접근 → Sentry → Gofer → 호스트 파일 시스템
또한 gVisor는 운영 체제 네트워크 코드 대신 자체 네트워크 스택을 사용합니다.
이를 통해 커널 네트워크와 격리되어 커널을 공유해서 생기는 문제를 최소화합니다.
gVisor 이점과 단점
컨테이너에는 자체적으로 Host OS 커널과 격리된 gVisor 커널이 실행되므로 상대적으로 안전하며 Attack Surface도 최소화할 수 있습니다.
- gVisor 환경: A → gVisor 커널A, B → gVisor 커널B, C → gVisor 커널C (각각 독립된 샌드박스)
gVisor 인스턴스 하나가 손상돼도 다른 컨테이너에는 영향 없음.
하지만 Sentry, Gofer의 격리 구조는 모든 애플리케이션과 완벽히 호환되는 건 아닙니다. 중간자로 인해 약간의 성능 저하, 애플리케이션 호환성 문제가 발생할 수 있으므로 테스트하는 것이 중요합니다.
kata Containers
Kata Container는 각 컨테이너 별 경량 VM을 사용합니다.
여러 애플리케이션이 동일한 운영 체제를 공유하는 방식과 달리 Kata Containers는 모든 컨테이너에 전용 커널을 할당합니다. 이런 격리를 통해 컨테이너의 내부 오류가 해당 컨테이너에만 영향을 미치게 만들 수 있습니다.
kata는 추가 메모리 공간과 컴퓨팅 용량을 요구하지만 VM을 그냥 할당하는 것보다는 적습니다.
Hardware Requirements
Kata는 하드웨어 가상화에 의존합니다. 따라서 클라우드 환경을 활용하기 어렵습니다. 대부분의 클라우드는 중첩 가상화(VM안에 VM) 구성을 지원하지않습니다. 가능하다고 하더라도 많은 수동 구성이 필요하며 최적화하기 어렵습니다.
- 베어메탈 혹은 전용 물리적 서버(온프레미스) 환경에 적합
Runtime Classes
gVisor, kata Container가 어떻게 배포되는지 확인합니다.이를 이해하려면 Docker 내부에서 컨테이너가 실제로 생성되기까지 여러 컴포넌트를 알아야합니다.
내부 구조
- Docker CLI가 명령어를 호출하여 컨테이너 실행 요청
- 이미지 검증(로컬 → Docker Hub)
- Docker 데몬 → containerd에게 컨테이너 생성 지시 → OCI 번들로 변환 후 containerd shim 전달
- OCI(Open Container Initiative)
- containerd shim은 conatiner runtime(runc) 호출하여 컨테이너 생성
- runc는 시스템 namespace, gcroup과 상호작용하여 격리된 환경 생성
run의 역할
runc는 OCI(Open Containers Initiative)에서 정의한 표준을 구현하는 기본 컨테이너 런타임
runc를 통해 Docker 관리 기능 의존없이 CLI를 사용하여 직접 생성 가능 가능합니다
runc run nginx- Podman, CRI-O같은 다른 도구들도 runc를 기본적으로 사용
대체 컨테이너 런타임
샌드박싱 기술향승으로 특수 컨테이너 런타임 존재. Docker 실행 시 런타임 지정 가능.
- Kata container는 kata Runtime
docker run --runtime kata -d nginx
- gVisor는 runsc Runtime
docker run --runtime runsc -d nginx
Kubernetes에서 Runtime 사용하기
gVisor를 사용하여 runsc 런타임으로 컨테이너 생성 방법을 정리했습니다.
Runtime class 생성
RuntimeClass 객체 생성을 위해 중요한 필드는 2가지입니다.
Name: RuntimeClass 식별자handler****: 사용할 런타임 지정(gVisor의 경우)handler는 항상 유효한 이름을 사용해야합니다(runsce, kata)
apiVersion: node.k8s.io/v1beta1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
$ kubectl create -f gvisor.yaml
# Expected output:
# runtimeclass.node.k8s.io/gvisor created
gVisor Runtime으로 Pod 배포하기
runtimeClassNamePod를 배포하여 생성합니다.
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
runtimeClassName: gvisor
containers:
- image: nginx
name: nginx
아래 명령어를 통해 Nginx 프로세스를 확인합니다.
node01:~# pgrep -a nginx