LabHub
배우기 러닝패스 코스

仮想化とQEMU/KVM

隔離の境界はどこにあるのか

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

VM 은 하드웨어 경계로 격리하고 컨테이너는 커널 기능(namespace/cgroup) 으로 격리한다. 이 한 문장에서 나머지 차이가 전부 따라 나온다.

概念マップ: 하드웨어 경계・커널 기능(namespace/cgroup)・어떤 종류의 격리가 필요한가・커널 취약점 하나가 격리를 통째로 무너뜨린다.

왜 이게 필요했나

"컨테이너를 쓸까 VM 을 쓸까" 는 여전히 자주 나오는 질문이다. 답은 워크로드가 아니라 어떤 종류의 격리가 필요한가로 정해진다.

어떻게 동작하나

[VM]                          [컨테이너]
앱                             앱
게스트 라이브러리               라이브러리
게스트 커널      <- 별개!       (호스트 커널을 공유)
가상 하드웨어                   namespace + cgroup
하이퍼바이저                    컨테이너 런타임
호스트 커널                     호스트 커널

컨테이너는 게스트 커널이 없다. 그래서 시작이 빠르고(수십 ms) 메모리 오버헤드가 거의 없고 밀도가 높다. 대신 커널 취약점 하나가 격리를 통째로 무너뜨린다.

항목 VM 컨테이너
커널 게스트마다 별개 호스트와 공유
시작 시간 수십 초 수십 ms
메모리 오버헤드 게스트 커널 + 하이퍼바이저 거의 없음
밀도 호스트당 수십 호스트당 수백
격리 강도 강함(하드웨어 경계) 약함(커널 경계)
다른 OS/커널 가능 불가능
이미지 크기 GB MB

언제 무엇을

VM 을 골라야 하는 경우

컨테이너를 골라야 하는 경우

그 사이 — microVM

Firecracker, Kata Containers 같은 것들이 둘 사이를 메운다. VM 의 격리 경계를 유지하면서 시작 시간과 오버헤드를 컨테이너에 가깝게 줄인 것이다. 장치 모델을 극단적으로 줄이고(virtio 몇 개만), 부팅 경로를 짧게 만들어 100ms 안팎에 뜬다. AWS Lambda 가 Firecracker 위에서 돈다.

Kata Containers 는 한 걸음 더 나가서, 컨테이너 인터페이스(OCI/CRI)를 유지하면서 안에서는 경량 VM 을 띄운다. 쿠버네티스 관점에서는 그냥 파드인데 실제로는 VM 경계로 격리된다. RuntimeClass 로 워크로드별로 선택할 수 있어, "이 네임스페이스만 강한 격리" 같은 정책이 가능하다.

현장에서 만나는 모습

"컨테이너에서 커널 파라미터를 바꿔야 하는데 안 됩니다." 당연하다. 커널이 호스트와 공유되므로 컨테이너 안에서 sysctl -w 를 하면 호스트 전체에 영향을 준다. 그래서 대부분 막혀 있다. 정말 필요하면 노드 수준에서 설정하거나(DaemonSet, 노드 부트스트랩) VM 으로 가야 한다.

이 실습 환경이 바로 그 예다. capability 가 제거된 컨테이너라 mount, tcpdump, iptables 가 안 된다. 그래서 이 커리큘럼은 그 제약을 우회하는 대신 제약 자체를 교재로 삼았다 — 어떤 작업이 왜 특권을 요구하는지 아는 것이 곧 격리 구조를 이해하는 것이다.

밀도와 자원 관리가 다른 이유

VM 과 컨테이너는 자원을 다루는 방식도 다르고, 그 차이가 운영에서 자주 사람을 놀라게 한다.

VM 의 메모리는 미리 잡힌다. 4GB 를 준 VM 은 그 안에서 얼마를 쓰든 호스트에서 그만큼을 차지한다(풍선 드라이버 같은 장치로 일부 되돌릴 수는 있지만 즉각적이지 않다). 반면 컨테이너의 메모리는 실제로 쓴 만큼만 잡힌다. 한도는 상한일 뿐이고, 안 쓰면 다른 컨테이너가 쓴다. 그래서 컨테이너 쪽은 요청량의 합보다 실제 사용량이 훨씬 작은 상태가 정상이고, 이것이 밀도의 원천이다.

대신 컨테이너는 초과 배정의 위험을 진다. 한도의 합이 노드의 메모리보다 크게 배정되어 있다가 여럿이 동시에 많이 쓰면 노드가 메모리 압박에 빠지고, 커널이 프로세스를 골라 죽인다. 이때 죽는 것은 가장 많이 쓴 것이 아니라 요청량 대비 초과가 큰 것이라, 요청량을 안 적어 둔 워크로드가 먼저 희생된다.

CPU 는 성격이 또 다르다. 메모리와 달리 CPU 는 나중에 돌려받을 수 있으므로, 한도를 넘으면 죽이는 대신 앞에서 본 스로틀링으로 늦춘다. 그래서 CPU 한도는 넉넉히, 메모리 한도는 정확히 잡는 것이 일반적인 조언이 된다.

VM 안에서 컨테이너를 돌릴 때는 두 층이 겹친다. VM 에 준 메모리가 노드의 전체 메모리가 되고, 그 안에서 다시 컨테이너들이 나눠 쓴다. 이때 VM 쪽에서 메모리를 회수하는 장치가 켜져 있으면, 노드는 자기 메모리가 줄어든 것을 모른 채 스케줄을 계속해서 예측하기 어려운 실패가 난다. 가상화된 노드에서는 메모리 회수 기능을 끄는 것이 일반적인 권고인 이유가 이것이다.

앞에서 본 microVM 은 이 둘의 절충이기도 하다. 격리는 VM 이지만 메모리 오버헤드가 작아서, 밀도를 크게 잃지 않으면서 커널 경계를 얻는다.

다음에서 할 것

다음 코스에서 rootless 컨테이너를 다룬다. 특권 없이도 컨테이너가 도는 이유(user namespace)를 파고들면, 이 모듈에서 말한 "커널 경계로 격리한다" 는 문장이 구체적인 시스템 콜과 파일로 바뀐다.