오늘의 포스팅은 간단한 한 의문으로 시작된 수 시간의 연구 일기입니다.
AWS는 Lambda와 같은 서버리스 기술을 어떻게 구현했을까?
막연하게 떠오르는 건 도커가 아닐까요? 흔히 도커 짱짱...이라고 많이 알려져 있으니까요.
혹은 가상머신을 수십만 대 활성화해두고 요청 때마다 작업, 작업 후 리셋?
이 글을 보고 계신 여러분들도 다양한 가정을 세우실 텐데, 과연 여러분들의 추측이 맞았을지 검증하면서 읽는 것도 하나의 재미요소 일 것입니다. 시작해볼까요?
(다만 글에서 등장하는 AWS 엔지니어는 허구의 인물입니다. 이해를 돕기 위해 등장시킨 인물이니 주의해주세요!)
일반적으로 클라우드 제공업체는 워크로드를 서로 분리하기 위해 멀티 테넌시 아키텍처를 구축해야 합니다. 즉, 보안을 위해 워크로드들을 모두 분리해야 하고 (한 워크로드에서 다른 워크로드를 액세스 할 수 없어야 한다.) 시끄러운 이웃 문제를 해결해야 합니다 (한 워크로드로 인해 다른 워크로드 진행이 방해되는 문제). AWS EC2 같은 경우 이 문제를 하이퍼바이저 기반의 가상화 기술 (ex. QEMU/KVM, XEN)이나 멀티 테넌시를 없애고 베어 메탈 형태의 가상화 기술로 해결하였습니다.
그런데 어느 날, AWS 엔지니어들에게 서버리스 서비스를 사용자들에게 제공하기 위해 시스템을 구축하라는 임무가 주어집니다. 서버리스는 한 컴퓨터에서 128M의 3초 정도의 많고 작은 요청들을 모두 격리해 처리해야 합니다. EC2처럼 베어 메탈 형태로 가상화해 한 서버마다 VM을 띄우고 지우고 한다면 비용 효율이 극악일 겁니다. 이와 같은 경우는 시스템 구축을 어떻게 해야 할까요?
2014년 처음 Lambda 서비스가 출시되었을 때, 초기 AWS Lambda는 고객별로 VM을 할당하고, 고객의 함수를 linux container 형식으로 가상화함으로 서버리스 함수들을 구현했습니다. 하지만 AWS 엔지니어들은 다음과 같은 이유들로 그 방식을 만족하지 못했습니다.
- 첫째, 컨테이너의 보안과 코드 호환성 사이의 trade-off 문제
- 둘째, 워크로드들에 적합하게 효율적으로 VM의 리소스를 할당하는 문제
보안과 호환성 사이의 trade-off 문제는 앞으로도 계속 나올 문제인데, 간단히 설명해 보겠습니다.
리눅스의 커널에 내장된 격리 기술을 사용한 컨테이너 기술(Docker나 LXC)은 다들 아시다시피 cgroup을 사용해 프로세스를 그룹화 하고 리소스를 조절, PID를 namespace로 커널 리소스를 분리하고, seccomp-bpf를 사용해 system call에 대한 access를 제한하는 방식으로 구현됩니다.
하지만 단일 OS 커널에 의존하므로 보안과 코드 호환성 사이의 근본적인 trade-off이 발생하게 되는데요. 예를들어, 컨테이너 기술은 시스템 콜을 제한시킴으로 보안을 향상할 수 있지만, 그 코드를 해석하는 과정에서 리소스 비용이 발생하고 코드의 호환성을 제한시키므로 이 부분에서 trade-off가 발생합니다. 보안을 향상하기 위해서는 코드 호환성을 제한시켜야 하고, 코드 호환성을 풀면 보안적으로 위협이 생깁니다.
다시 돌아와 여러가지 문제들로 기존 Lambda의 구현 방식을 만족스러워하지 않았던 AWS 엔지니어들은, 이러한 문제를 해결하고 Lambda의 격리 모델을 재설계하기 위한 다양한 옵션을 평가하여 이상적인 서버리스 솔루션의 6가지 특성을 정의했습니다.
- Isolation (격리성): 동일한 하드웨어에서 실행되는 여러 함수들이 privilege escalation(권한 변동), information disclosure (정보 노출), covert channels(비밀 채널) 등 다양한 위험으로부터 보호되어야 합니다.
- Overhead and Density (오버헤드와 밀집성): 최소한의 낭비로 단일 하드웨어에서 수천 개의 함수들을 실행할 수 있어야 합니다.
- Performance (성능) : 성능은 네이티브로 실행되는 것처럼 유사하게 실행되어야 합니다. 또한 성능은 일관되어야 하며 동일한 하드웨어에서 이웃의 동작(Lambda의 경우 다른 함수)으로부터 격리되어야 한다.
- Compatibility (호환성) : Lambda는 각 함수가 임의의 리눅스 바이너리 파일과 라이브러리들을 포함하도록 허용해야 합니다. (반드시 코드 변경이나 재컴파일 없이 실행되어야 합니다.)
- Fast Switching (빠른 전환) : 새로운 기능은 신속하게 시작하고 오래된 기능은 빠르게 정리할 수 있어야 합니다.
- Soft Allocation (소프트 할당) : CPU, 메모리 및 기타 리소스들을 over commit 할 수 있어야 하며, 각 함수들은 권한이 있는 리소스가 아니라 필요한 리소스만 소비해야 합니다.
이제 AWS 엔지니어들은 각 특성에 모두 부합되는, 그런 워크로드를 격리시키는 기술을 찾아나섰는데요. 다들 아시다시피 Linux에서 워크로드들을 격리시키는 기술은 크게 3가지가 있습니다.
- 컨테이너 : 모든 워크로드가 커널을 공유하고 커널의 시스템 콜들이 워크로드를 분리하며 가상화하는 기술입니다.
- VM 기반 가상화 : 워크로드가 하이퍼바이저의 자체 VM에서 실행되는 기술입니다.
- 가상 언어 시스템 : 언어 VM이 워크로드를 서로 분리하거나 운영 체제에서 분리하는 역할을 담당하는 시스템입니다.
하지만 그림에서 확인할 수 있듯이 컨테이너 모델은 워크로드끼리 호스트 OS에서 제공하는 커널, 파일 시스템과 페이지 캐시와 같은 다른 서비스와 직접 상호 작용하기에 보안 위협에 쉽게 노출됩니다. 가상화 기술 또한 신뢰할 수 없는 코드가 일반적으로 Guest 커널에 전체 액세스가 허용되므로 Guest 커널을 신뢰할 수 없게 됩니다. 그러기에 하드웨어 가상화 이후 VMM으로 Guest의 Host 커널 접근을 제한해야 합니다. 더 자세히 알아볼까요?
컨테이너 기술
컨테이너 기술은 cgroup을 사용해 프로세스를 그룹화 하고 리소스를 조절, PID를 namespace로 커널 리소스를 분리하고, seccomp-bpf를 사용해 프로세스가 사용할 수 있는 system call과 이러한 시스템 호출에 전달할 수 있는 인수를 제한하고 chroot로 파일 시스템을 격리하는 기술입니다. 다양한 컨테이너 기술들은 이 특징들을 여러 조합으로 묶어 생성되었는데, 이중에서도 주목해야 할 seccomp-bpf는 리눅스에서 sandbox 기반으로 시스템콜을 허용 및 차단하여 공격의 가능성을 막는 리눅스 보안 메커니즘입니다. system call을 제한시키는 기능이므로 보안적으로 매우 중요합니다. 하지만 앞서 말했듯 컨테이너가 보안을 위해 system call을 제한시키는 것은 보안과 호환성 사이의 trade-off 문제가 생김을 의미합니다.
일반적인 리눅스 응용 프로그램에는 15개의 단일 system call이 필요합니다. 뿐만 아니라 한 연구결과에 따르면, 일반적인 Ubuntu 리눅스 15.04 설치가 문제없이 실행되기 위해선 커널에서 224개의 system call과 52개의 고유한 ioctl 호출을 필요로 한다고 합니다. 이 말은 신경 쓰고 감시해야 할 system call이 적다는 것으로 생각할 수도 있지만, 역으로 말하면 일반적으로 자주 사용되지 않는 system call이 더 많은 버그를 가지고 있다고 생각해야 합니다.
즉, 컨테이너 기술을 사용하면서도 보안을 높이기 위해서는 수백개의 system call에 대해 코드와의 호환성, 보안을 검토하기 위해서 정말 많은 고려 사항이 필요하다는 겁니다.
이 문제를 해결하기 위한 한 가지 접근 방식은 사용자 공간에서 운영 체제 기능을 일부분만 제공하는 것인데, 프로그래머에게 일반적인 환경의 모양을 제공하기 위해선 커널 기능을 훨씬 줄여도 되기 때문입니다. Graphene, Drawbridge, Bascule과 Google의 gVisor가 이런 방식을 사용합니다.
하지만, /proc와 같은 풍부한 인터페이스를 통해 발생한 여러 보안 문제들로 인해(CVE-2018-17972 혹은 CVE-2017-18344) 이러한 해결책의 격리성이 우수하다고 증명하긴 어려워졌습니다.
가상 언어 시스템
워크로드를 분리하기 위해 널리 사용되는 두 번째 방법은 Java Virtual Machine(JVM)이나 V8과 같은 가상 언어 시스템의 기능을 활용하는 것입니다. 보통 이 가상 언어 시스템은 싱글 프로세스에서 여러 워크로드를 실행시키기 위해 존재합니다.
하지만 임의의 리눅스 바이너리 파일를 지원해야 하는 Compatibility 문제를 고려해 보면, 언어별 격리 기술은 서버리스 기술에 적합하지 않습니다.
가상화 기술
Intel의 VT-x와 같은 최신 가상화 가술은 하드웨어 기능을 사용합니다. 각 샌드박스에 자체 가상 하드웨어, page table과 운영 체제 커널을 갖춰 격리된 환경을 제공합니다. 하지만 이 가상화 방식의 가장 해결해야 할 문제는 바로 Overhead and Density(오버헤드와 밀집성) 문제입니다.
Guest OS와 연결된 각 커널 과 VMM은 주요 작업을 수행하기 전에 CPU 및 메모리를 일정량 소모하여 밀집도를 제한하게 됩니다. 뿐만 아니라 VM을 시작하는 데에 startup 시간이 매우 소요됩니다. 이 문제는 기능이 작아 상대적인 오버헤드가 더 크고 워크로드가 지속적으로 변화하는 Lambda 환경에서 특히 중요합니다.
이 startup 시간을 줄이는 한가지 방법 중 unikernel과 같이 full OS 커널을 줄여 최소화해 작은 OS를 부팅하는 방법이 있습니다. unikernel은 이미 컨테이너와 함께 사용하기 위해 연구 중인데, 우리의 목표인 Compatibility 문제(Linux에서 수정되지 않은 코드를 실행하기)와 부합하지 않기 때문에 사용할 순 없습니다.
한 가지 더 문제가 있습니다. 하이퍼바이저와 Vertual Machine Monitoirs(VMM)을 사용하면 더 넓고 복잡한 Trusted Computing Base(TCB)가 요구됩니다. VMM이 하이퍼바이저 type 1(베어 메탈 형) 또는 하이퍼바이저 type 2(Host OS형)중 하나가 필요하므로 복잡도가 올라가는 것입니다.
여기서 TCB란 신뢰할 수 있는 기반을 형성하여 보안 정책을 시행하는 하드웨어, 소프트웨어 및 컨트롤의 조합입니다.
예를들어 type 2의 경우 VMM은 I/O, 스케줄링, 다른 low-level와 같은 기능을 제공하기 위해 Host 커널에 의존하며 공유된 데이터 구조를 통해 Host 커널과 사이드 채널을 확장하므로 TCB의 규모와 복잡도가 올라갑니다.
이 복잡도를 해결하기 위해 사용되는 가장 범용성 있는 조합이 바로 Linux Kernel Virtual Machine (KVM)과 QEMU 조합입니다. 하지만 QEMU는 140만 줄이 넘는 코드와 270개가 넘는 시스템 콜이 필요합니다. (Ubuntu 15.04보다 더 많은 수치입니다.) 뿐만 아니라 KVM 코드도 120,000 라인의 코드가 있습니다. 여기서 다양한 문제들이 발생해 우리의 솔루션으로 활용하지 못합니다. 그 이유는 하단에서 설명드리겠습니다.
이렇듯 Hypervisor와 VMM의 크기를 크게 줄이기 위한 여러 시도들이 있었지만, 이러한 솔루션 중 어느 것도 AWS에 필요한 플랫폼 독립성, 운영 특성을 제공하지 못했습니다.
결국 AWS 엔지니어들은 서버리스를 구현하기 위해서 근본적인 오버헤드를 감수하고라도 사용할 하이퍼바이저 기반의 가상화 혹은 trade-off 문제가 발생하는 컨테이너 방식의 가상화 두 가지 중에서 선택해야 했는데, 어느 것도 원하진 않았기에 AWS 엔지니어들은 직접! firecracker를 만들기로 합니다.
Kata Containers , Intel의 Clear Containers, 그리고 NEC의 LightVM 같은 비슷한 프로젝트를 찾던 AWS 엔지니어들은 Kata Containers와 같은 QEMU/KVM 기반 기술에 관심을 가지게 됩니다. 앞서 설명드린 기술인데, 기억하시나요? QEMU는 유연성과 기능 완성도가 높다는 장점이 있지만 오버헤드, 보안 , 빠른 시작과는 거리가 멉니다. 이를 역 이용하면 원하는 대로 오버헤드는 줄이고 보안이 높고 빠른 시작이라는 장점을 얻을 수 있으므로, AWS 엔지니어들은 이 QEMU를 개조하기로 합니다.
이에 AWS 엔지니어들은 KVM은 기반으로 하되 QEMU를 싹 갈아엎어 microVM을 구성하고 API를 관리할 수 있는 새로운 Virtual Machine Monitor (VMM), Firecracker를 만들기로 합니다.
Firecracker의 접근법은 KVM을 사용하되 VMM을 안전한 언어로 작성된 최소한의 구현으로 QEMU를 대체하는 것입니다. VMM의 기능 세트를 최소화하면 TCB의 크기와 복잡도를 줄일 수 있습니다. Firecracker는 여러 단계의 자동화 테스트와 자동 생성된 바인딩을 포함해 약 50,000줄의 Rust 코드로 구성되었습니다. (QEMU에 비해 약 96% 적은 라인입니다.)
어떻게 그렇게 적은 코드와 효율성을 가질 수 있었을까요?
Firecracker는 QEMU와 달리 BIOS를 제공하지 않으며, 임의의 커널을 부팅할 수 없으며, 레거시 디바이스나 PCI를 에뮬레이트 하지 않으며, VM 마이그레이션을 지원하지 않습니다. Firecracker는 Docker와 Kubernetes의 관계처럼 VM을 오케스트레이션 하거나 패키징 하거나 앞서 말한 다양한 옵션을 지원하지 않고, 본래의 목적(서버리스 컨테이너와 Lambda 함수의 워크로드)에 집중함으로써 목표를 달성했습니다.
AWS 엔지니어들이 Firecracker를 구현하기 위한 또 다른 철학은 자체적인 구현으로 기능, 성능 및 디자인을 구성하기보다는 내장된 리눅스 구성 요소에 의존하는 것입니다. 예를 들어 블록 I/O를 Linux 커널로 전달하고, CPU와 메모리의 VM 간 경합을 처리하기 위해 Linux의 프로세스 스케줄러와 메모리 매니저에 의존하며, TUN/TAP 가상 네트워크 인터페이스를 사용합니다. 엔지니어들은 2가지 이유로 이 철학을 가져갔는데,
하나는 개발 비용입니다. 스케줄러와 같은 까다로운 운영 체제 구성 요소는 (예를 들어 멀티 테넌트(Multi-tenant) 워크로드를 처리해야 하는 경우) 올바르게 작동하도록 구현하는 데 수십 년이 걸릴 수 있습니다. Linux에서 이는 비록 몇 가지 단점이 있더라도 대규모 배포가 많이 된 만큼 입증이 된 기술입니다.
다른 하나는 운영 지식입니다. AWS의 엔지니어들은 Linux 시스템 운영, 자동화 및 최적화 경험이 많습니다. Linux에서 KVM을 사용하면 엔지니어들이 Firecracker 호스트와 게스트를 작동하고 문제를 해결할 때 이미 알고 있는 대부분의 도구(표준 Linux 프로그래밍 모델)를 사용할 수 있다는 의미입니다. 예를 들어, Firecracker 호스트에서 ps 커맨드를 실행하면 호스트의 모든 MicroVM이 프로세스 목록에 포함되며, top, vmstat, 심지어 kill과 같은 툴도 운영자의 예상대로 작동합니다. 이처럼 표준 리눅스 도구 세트를 사용할 수 있는 능력은 서비스 개발 및 테스트 과정에서 매우 유용하게 사용되었습니다.
이러한 철학으로 이루어진 Firecracker는 호스트 운영 체제 간의 이동성을 없애는 대신, 더 큰 신뢰할 수 있는 TCB을 이루게 되었습니다.
또한 Firecracker는 제공된 최소의 Linux Guest 커널 구성으로 프로세스당 5MB 미만의 메모리 오버헤드를 제공하고 125ms 미만의 시간 내에 애플리케이션 코드로 부팅하며 호스트당 초당 최대 150개의 MicroVM을 생성할 수 있습니다.
쓰다 보니 글이 너무 길어졌네요..!
다음 포스팅에서는 그래서 과연 Lambda가 어떻게 이 Firecracker로 동작하는지에 대해 알아보겠습니다.
출처
논문 : Firecracker: Lightweight Virtualization for Serverless Applications ,
저자 : Alexandru Agache, Marc Brooker, Andreea Florescu, Alexandra Iordache, Anthony Liguori, Rolf Neugebauer, Phil Piwonka, and Diana-Maria Popa, Amazon Web Services
'🐳AWS' 카테고리의 다른 글
Notion API 활용하기 - (3) AWS Lambda와 Notion API 연동하기 (0) | 2022.06.07 |
---|---|
[AWS] Lambda위에 자체 환경(Ubuntu)의 docker 컨테이너 올리기 (0) | 2021.11.23 |
[AWS] Step Function으로 Lambda 워크플로 자동화하기 (0) | 2021.09.08 |
[AWS] lambda에서 chrome-selenium 크롤링 환경 설정하기 (2) | 2021.08.26 |
[AWS][테라폼] 🐬 테라폼으로 AWS EC2 인스턴스 생성,삭제하기 (0) | 2021.06.19 |