리눅스 커널 네임스페이스와 cgroups를 통한 컨테이너 격리 및 가상화 아키텍처 비교
팀 내 세미나를 준비하면서 여러 논문과 레퍼런스를 뒤져봤는데, 한글로 속 시원하게 정리된 곳이 없더군요. 그래서 제 경험을 녹여 직접 작성해 봤습니다.
현대의 클라우드 네이티브 아키텍처와 마이크로서비스 배포 환경에서 도커(Docker)와 쿠버네티스(Kubernetes)를 필두로 한 컨테이너 기술은 인프라스트럭처의 패러다임을 송두리째 바꿔놓았습니다. 가상 머신(Virtual Machine)과 컨테이너는 어플리케이션을 격리하여 실행한다는 목적은 같지만, 그 근간이 되는 아키텍처적 접근 방식은 극명하게 다릅니다. 하이퍼바이저를 사용하는 전통적인 가상 머신은 호스트 운영체제 위에 완전히 독립적인 게스트 운영체제를 통째로 올리는 방식입니다. 가상 머신마다 자체적인 커널, 장치 드라이버, 메모리 관리 체계를 갖추며 가상화된 하드웨어 인터페이스를 통해 통신하기 때문에 보안적인 파티셔닝(Isolation) 측면에서는 타의 추종을 불허하는 완벽함을 자랑합니다. 그러나 게스트 커널 부팅 시간의 지연, 막대한 메모리 오버헤드, 그리고 디바이스 에뮬레이션으로 인한 성능 저하는 시스템 밀집도를 높이는 데 있어 언제나 거대한 장벽이었습니다.
이에 대한 실용적 대안으로 부상한 리눅스 컨테이너 기술은 본질적으로 별도의 운영체제를 띄우는 것이 아니라, 호스트의 단일 리눅스 커널을 수많은 프로세스들이 공유하면서도 서로가 완벽히 독립된 시스템인 것처럼 착각하게 만드는 일종의 환영과도 같습니다. 이 철저한 기만극을 가능하게 하는 핵심 커널 기술이 바로 네임스페이스(Namespaces)와 컨트롤 그룹(cgroups)입니다. 네임스페이스는 프로세스가 인식하는 시스템 자원의 뷰(View)를 분할하고 격리합니다. PID 네임스페이스를 통해 컨테이너 내부의 프로세스는 자신이 시스템의 유일한 프로세스 1번(init)이라고 생각하게 되며, 호스트나 다른 컨테이너의 프로세스 트리를 전혀 볼 수 없습니다. 또한 Mount 네임스페이스는 독립된 파일 시스템 마운트 트리를 제공하고, Network 네임스페이스는 완전히 분리된 가상 이더넷 디바이스, IP 주소, 라우팅 테이블 및 방화벽 규칙을 갖게 해 줍니다. 이 밖에도 UTS, IPC, User 네임스페이스 등의 조합을 통해 프로세스를 공간적으로 철저히 격리합니다.
공간적 격리가 네임스페이스의 몫이라면, 물리적 자원의 소비량을 통제하고 제한하는 것은 cgroups의 역할입니다. 하나의 악의적이거나 결함이 있는 컨테이너가 CPU 코어나 메모리를 독점하여 호스트 전체를 마비시키는 노이지 네이버(Noisy Neighbor) 문제를 방지하기 위해, 리눅스 커널의 cgroups 서브시스템은 계층적 트리 구조로 프로세스 그룹의 자원 한도를 통제합니다. 메모리 계층에서는 OOM(Out Of Memory) Killer를 통해 할당량을 초과한 컨테이너 프로세스를 가차 없이 종료시키며, CPU 계층에서는 CFS(Completely Fair Scheduler)의 대역폭 제어(bandwidth control)를 이용해 마이크로초 단위의 엄격한 실행 시간 쿼터를 할당합니다. 이처럼 경량화된 방식을 통해 컨테이너는 불과 밀리초 단위의 경이로운 시작 속도와 호스트 성능에 거의 근접하는 네이티브 실행 효율을 달성했습니다. 다만, 모든 컨테이너가 단일 커널의 시스템 콜 인터페이스를 직접 공유하기 때문에 한 컨테이너에서 커널 취약점(예: 권한 상승 공격, 커널 패닉 유발)이 뚫리게 되면 호스트 위에 떠 있는 모든 컨테이너가 동시에 침해당할 수 있다는 치명적인 보안 아킬레스건을 내포하고 있습니다. 이러한 태생적 한계를 보완하기 위해 seccomp, AppArmor, SELinux와 같은 강력한 리눅스 보안 모듈 샌드박싱 기법이 컨테이너 런타임과 필수적으로 병행되어야만 상용 환경 수준의 무결성을 담보할 수 있습니다.