WASM, Web Assembly?
WebAssembly(또는 Wasm)는 다양한 소스 언어에서 이식 가능한 바이너리 실행 파일을 생성할 수 있는 바이너리 명령 형식을 정의하는 개방형 표준이다. 이러한 바이너리는 다양한 환경에서 실행될 수 있다. 이는 웹에서 기원했으며 모든 주요 브라우저에서 지원된다. 서버, 엣지 어디에서든 Wasm를 사용하면 이전보다 portable하고 secure하게 프로그램을 실행할 수 있다. 재밌는 사실은 2019년 6월에 발표된 연구에 따르면 Alexa 상위 100만 개 웹 사이트 중 600개 사이트 중 하나가 Wasm 코드를 실행하였고, Wasm를 사용하는 사이트 중 50% 이상이 암호화폐 채굴과 같은 작업에 Wasm를 적용했다는 것이다.
장점
- 리소스 효율성 및 속도 우수함 - Wasm 응용 프로그램은 최소한의 메모리 공간과 CPU 요구 사항으로 실행되도록 만들 수 있다. 그리고 네이티브와 같은 속도를 제공할 수 있다. VM 부팅 또는 컨테이너 시작과 달리 콜드 스타트가 없다.
- 보안 뛰어남 - Wasm 런타임은 기본적으로 샌드박스 처리되며 메모리에 안전하게 액세스할 수 있다. 기능 기반 모델은 Wasm 애플리케이션이 명시적으로 허용된 항목에만 액세스할 수 있도록 한다. 더 나은 공급망 보안이 있다.
- 대부분의 언어는 런타임에서 함수는 주소를 가진다. 메모리를 바이트 배열 형태로 보면 함수 코드가 제대로 구별되지 않기 때문에 안전하지 않다.
- Wasm은 프로그램 메모리를 안전한 영역에 캡슐화하기 때문에 프로그램을 실행하는 호스트에 영향을 미치거나 보안을 손상시킬 수 있는 코드를 허용하지 않는다.
- 휴대성이 좋음 - 여러 주요 런타임에서 대부분의 CPU(x86, ARM, RISC-V)와 Linux, Windows, macOS 및 비 Posix를 포함한 대부분의 OS를 지원한다.
- 이식성 좋음 - 40개 이상의 언어를 Wasm으로 컴파일할 수 있으며 현대적이고 지속적으로 개선되는 툴체인을 사용한다. 컴파일러는 LLVM(Low Level Virtual Machine) 백엔드를 활용해 LLVM 중간 표현(IR)으로 컴파일하여 Wasm 프로그램을 생성할 수 있다.
웹 브라우저를 넘어 서버사이드(wasm + wasi)나 블록체인(cosmwasm)에서 활발한 모습을 보고 wasm 기술은 무엇이고 왜 이러한 장점을 지니고 있는지 하나하나 파해쳐보았다.
WASM이 브라우저에서 동작하는 과정
컴퓨터는 본질적으로 0과 1의 시퀀스로 표시되는 바이너리 형식으로 데이터를 해석한다. 컴퓨터의 두뇌 CPU에는 사고를 수행하는 ALU, 제어장치,필요한 정보를 단기적으로 저장하는 레지스터, 장기적으로 저장하는 메모리(RAM)로 이루어져 있다. 그래서 프로그래밍한 코드가 실행될 때는 아래 그림과 같이 고수준 언어 C, C++, Java, Rust에서부터 x86 또는 ARM가 이해할 수 있는 저수준 언어로 전환되어 전달된다. 그래서 최종적으로는 0과 1로 이루어져있는 기계어로 CPU에 명령이 전달되어 하나하나 해석하여 처리된다.
그래서 우리는 고급 언어에서 각 CPU 아키텍처에 맞는 어셈블리 언어로 전환시키는 니즈를 늘 갖고있다. clang, gcc가 대표적으로 그러한 (AOT) 컴파일러이다. 이식성이 좋은 JVM도 마찬가지이다. (정확히는 java Compiler와 JVM 내부에 있는 Interpreter, JIT Compiler를 말함) JVM은 clang과는 달리 바로 머신코드로 번역하는 것이 아니라 플랫폼 종속적이지 않은 중간 언어(바이트코드)로 번역하고 이를 다시 플랫폼 아키텍처에 맞는 어셈블리 언어로 전환해준다. 그렇다면 Wasm도 하나의 어셈블리 언어일까? 같지만 다르다. Wasm은 실제 물리적 기계(x86, arm)가 아닌 개념적 기계를 위한 기계어이다. 이 때문에 Wasm 명령어를 가상 명령어라고도 한다. Wasm을 사용하여 C, C++, Rust와 같은 실행 속도가 빠른 언어로 웹 브라우저를 동작하게 한다면 빠르게 웹 브라우저를 사용할 수 있게 된다. 이게 Wasm의 등장 이유다.
현재 Wasm를 가장 많이 지원하는 컴파일러 도구 체인은 LLVM이다. LLVM에 연결할 수 있는 다양한 프론트엔드와 백엔드가 있다. clang, gcc도 LLVM의 일종이다. C에서 wasm으로 번역하는 프로세스는 다음과 같다.
- 고급 언어(C) → IR: clang 프론트엔드를 사용하여 C에서 LLVM 중간 표현(IR)으로 변환한다. LLVM의 IR에 있으면 LLVM이 이를 이해하므로 LLVM은 일부 최적화를 수행할 수 있다.
- IR → Wasm: 이제 LLVM의 IR에서 WebAssembly로 이동하려면 백엔드를 통해 변환해주면 된다. 19년에 v8팀이 llvm wasm 백엔드를 개발했다
JIT Compiler와 비교
자바스크립트는 1995년에 만들어졌다. 인터프리터 언어인 자바스크립트의 속도는 매우 느렸기 때문에 2008년에 브라우저들의 성능 경쟁이 시작되었다. 반복문에서 매번 같은 코드를 다시 번역해야 하는 문제를 지닌 인터프리터의 비효율성을 제거하기 위해 브라우저들은 인터프리터에 컴파일러를 혼합하기 시작했다. 그 결과 많은 브라우저들이 JIT(Just-in-time) 컴파일러를 사용하게 되었다. 자바스크립트가 인터프리터로 인해 실행되면서 JIT 컴파일러는 해석되는 코드 패턴들을 살펴보고 이 패턴을 기반으로 코드를 캐싱 및 최적화를 하여 더 빠르게 실행되게 하였다. JIT이 등장하면서 자바스크립트의 실행 속도가 크게 향상되었다.
JIT 컴파일러는 런타임 중에 자바스크립트 코드를 머신 코드로 동적으로 변환하여 실시간 최적화를 이룬다. 그러나 특정 런타임 가정이 유효하지 않은 경우 JIT 컴파일러는 코드를 다시 재최적화가 이루어지며 이전 작업 중 일부를 폐기해야 한다. 이러한 지속적인 모니터링과 잠재적인 재컴파일로 인해 오버헤드가 발생하는 게 단점이다. 웹 생태계가 발전하고 자바스크립트 애플리케이션이 더욱 복잡해짐에 따라 런타임에 이러한 오버헤드의 영향이 점점 더 분명해졌다.
- 최적화와 역최적화 작업
- 모니터의 기록 영역과 코드가 버려질 때 복구를 위한 정보가 사용하는 메모리
- 기본 버전과 최적화 버전의 함수를 저장하기 위한 메모리
Wasm은 다음과 같은 특징으로 이러한 오버헤드를 제거하여 성능을 더욱 예측 가능하게 만든다.
1. Wasm은 저수준 언어 (low-level)에 더 가깝다
Wasm는 머신 코드에 더 가깝게 설계되었다. 이러한 설계를 통해 더 나은 성능을 달성할 수 있고 바이너리 표현의 효율성을 높일 수 있다. 일반적으로 Wasm 모듈은 압축 시 동급의 압축 JavaScript 파일보다 설치 공간이 작아 웹에서 전송 시간이 더 빠르게 동작한다.
2. Wasm은 사전 컴파일을 한다 (AOT)
브라우저에 도착한 JavaScript 코드는 구문 분석 단계를 거쳐 추상 구문 트리(AST)로 변환되고 나서 AST는 브라우저의 JavaScript 엔진에 특화된 중간 표현(IR)으로 변환된다. 반면, Wasm은 저수준 IR로 설계되었기 때문에 이러한 단계를 생략한다. 그냥 오류가 없는지 확인하기 위해 디코딩하고 유효성을 검사하면 된다.
3. Wasm은 정적 타입으로 최적화을 이룬다
1, 2 특징에서 비롯된 Wasm의 고유한 장점은 컴파일 및 최적화 단계에서 특히 빛을 발한다. Wasm은 머신 코드에 더 가깝고 정적 타이핑을 사용하기 때문에 JavaScript와 같이 동적으로 타이핑되는 언어에 필요한 타입 추론과 같은 많은 런타임 프로세스를 피할 수 있다.
- 컴파일러는 최적화된 코드 컴파일을 시작하기 전에 어떤 타입이 사용되는지 관찰하기 위해 코드를 실행하는 데 시간을 소비할 필요가 없다
- 컴파일러는 관찰한 다양한 타입을 기반으로 동일한 코드의 다양한 버전을 컴파일할 필요가 없다
- LLVM에서는 이미 더 많은 최적화가 사전에 완료되었다. 따라서 컴파일하고 최적화하는 데 필요한 작업이 줄어든다.
더 자세한 내용은 Mozilla의 블로그 글 참고
서버 플랫폼에서의 WASM
더욱 Wasm 기술에 관심을 갖게 된 계기는 단순 웹 브라우저의 속도에만 영향이 있는 게 아니었기 때문이다. Docker의 공동 창립자 중 한 명인 Solomon Hykes이 다음과 같이 말했다.
2008년에 WASM+WASI가 있었다면 Docker를 만들 필요가 없었을 것입니다. 서버의 WebAssembly는 컴퓨팅의 미래입니다. https://twitter.com/solomonstre/status/1111004913222324225
WASM + WASI가 서버 인프라에 어떻게 사용될 수 있을까?
- 과거에는 작업할 물리적 하드웨어(메인 프레임)가 있었다. 각 상자에 OS와 응용 프로그램을 꼼꼼하게 설치하고 하나씩 유지 관리하였다.
- 그런 다음 VM을 사용하면서 작업이 더 쉬워졌다. 하드웨어 상자에서 VM을 복사하여 다른 상자로 이동할 수 있다. 하지만 여전히 VM에 OS와 애플리케이션을 다뤄야하므로 무거웠다.
- 그런 다음 호스트 OS의 다른 애플리케이션에 영향을 주지 않고 최소한의 래핑 컨텍스트에서 애플리케이션 구성을 보다 쉽게 실행할 수 있게 해주는 컨테이너가 등장했다. 그러나 런타임 및 필수 라이브러리와 함께 번들로 제공되는 애플리케이션을 배포해야 할 필요성은 여전히 채워야 할 니즈였다. 보안은 Linux 커널에 의해 제공되었다.
- 이제는 WebAssembly가 있다. WASM의 여러 특징, 그 중 특별히 Portabilty 덕분에 OS 수준 종속성을 제공하지 않고도 응용 프로그램을 배포할 수 있으며 엄격한 보안 제약 조건으로 실행할 수 있다.
이 모든 것을 고려할 때 개발자는 WebAssembly를 '컨테이너의 후계자'이자 인프라 배포의 다음 논리적 단계로 봐도 무방하다.
Docker와 WASM
WebAssembly(+ WASI)를 컨테이너의 다음 경량화 단계라고 볼 수도 있다. Docker Engine은 client-server 구조로 동작하는 엔진으로 앱을 빌드하고 컨테이너화 시키는 기술이다. `docker run` 명령을 실행하면 컨테이너 런타임에 의해 컨테이너 프로세스가 생성된다. Docker 컨테이너는 전체 파일 시스템(유틸리티, 바이너리 등)으로 실행되어 HostOS와 환경을 격리한다. 그래서 x86, arm과 같은 시스템 아키텍처에 따라 이미지를 생성해야 한다. 반면에 WASM 모듈은 사전 컴파일된 (C, C++, Rust, ...) 애플리케이션이며 WASM 런타임에서 빠르게 실행된다. 이는 docker 컨테이너처럼 하위 수준 OS 기본 요소를 포함하지 않으므로 x86, arm과 같은 시스템 아키텍처에 의존/결합되지 않는다. 모든 디렉토리, 시스템 리소스는 WASI를 통해 런타임 중에 WASM 모듈에 연결된다.
WASM은 네이티브에 가까운 성능, 빠른 배포 시간, 높은 보안을 제공한다. 반면에 Docker는 런타임 격리성과 더 나은 이식성을 제공한다.
"그럼 Wasm이 Docker를 대체할까요?" 아니요, 하지만 Docker가 리눅스 컨테이너, 윈도우 컨테이너, Wasm 컨테이너를 나란히 실행하는 미래를 상상해 보세요. 시간이 지나면 Wasm이 가장 인기 있는 컨테이너 유형이 될 수도 있습니다. Docker는 이 모든 것을 동등하게 사랑하고 모두 실행할 것입니다.)
https://twitter.com/solomonstre/status/1111113329647325185
현재 docker-wasm-technical-preview를 보면 containerd-wasm-shim을 만들어서 wasmEdge 런타임을 사용하여 앱을 실행하도록 설계했다.
이로 인해 Wasm의 네이티브에 가까운 성능, 보안성과 빠른 배포 장점을 취하고 Docker의 런타임 격리성과 높은 이식성을 지닌 기술이 만들어질 수 있다. 아직은 이론상으로만 다뤘지만 높은 성능과 보안을 지는 container 기술은 꽤나 기대되는 기술 진화의 방향이라고 생각한다.
WASI, 웹 외부에서 WebAssembly를 실행하기 위한 System Interface
애플리케이션 레벨에서는 파일 열기 또는 생성과 같은 작업을 할 수 있는 액세스 권한이 없다. 이유는 파일, 메모리 및 네트워크 연결과 같은 이러한 시스템 리소스는 안정성과 보안이 너무 중요하기 때문이다. 한 프로그램이 실수로 다른 프로그램의 리소스를 엉망으로 만들면 해당 프로그램이 중단될 수 있다. 게다가, 프로그램(또는 사용자)이 다른 프로그램의 리소스를 고의로 건드리면 중요한 데이터를 훔칠 수 있는 통로를 열어주는 셈이다.
protection ring security는 어떤 프로그램과 사용자가 어떤 리소스에 액세스할 수 있는지 제어할 방법으로 사용된다. 그래서 커널에 의해 OS는 기본적으로 시스템 리소스 주위에 보안 장벽을 세우게 되었다. 커널은 새 파일을 생성하거나 열거나 네트워크 연결을 하는 작업들을 수행한다. 사용자 프로그램은 '사용자 모드'라는 커널 외부에서 실행된다. 프로그램이 파일 열기와 같은 작업을 수행하려면 커널에게 요청해야 한다.
프로그램이 커널에게 작업을 요청해야 할 때 system call을 사용하여 요청한다. 이를 통해 커널은 어떤 사용자가 요청하는지 파악할 수 있고 해당 요청을 실행하기 전에 해당 사용자가 요청한 파일을 액세스할 수 있는지 확인한다. system call을 통한 시스템 리소스 액세스 방식은 대부분 장치에서 사용되는 방법이다. OS는 system call을 사용할 수 있게 만들어준다. 그러나 각 OS 자체에 system call이 있는 경우 OS마다 다른 버전의 코드가 필요하지 않을까? 다행히도 그렇지 않다. 시스템 인터페이스가 있기 때문이다.
System Interface
대부분의 언어는 표준 라이브러리를 제공한다. 코딩하는 동안 프로그래머는 OS나 시스템에 대해서 알 필요가 없다. 단지 인터페이스(프레임워크, 라이브러리)를 잘 다루면 된다. 그런 다음 컴파일할 때 툴 체인은 대상 시스템에 따라 사용할 인터페이스 구현을 선택한다. 이 구현은 OS API의 기능을 사용하므로 시스템에 따라 다르다. Docker Desktop, JVM와 같은 도구를 설치할 때 OS와 시스템 아키텍처(x86, arm)을 선택하는 것도 그 이유다. 예를 들어 `printf`가 Windows 머신용으로 컴파일되면 Windows API를 사용하여 머신과 인터랙션을 할 수 있다. Mac 또는 Linux용으로 컴파일되는 경우 POSIX를 대신 사용한다. 시스템 인터페이스는 이처럼 각각 다른 환경에 상관없이 사용될 수 있도록 제공되는 공통의 인터페이스를 의미한다.
WASM의 인터페이스 부재
이러한 구조는 Wasm에게 문제를 야기한다. Wasm를 사용하면 컴파일할 때에도 어떤 종류의 OS를 대상으로 하는지 알 수 없으므로, 표준 라이브러리의 Wasm 구현 내에서는 단일 OS의 시스템 인터페이스를 사용할 수 없다. 이전에 "WebAssembly는 실제 기계가 아닌 개념적 기계를 위한 기계어다." 라고 말한 적이 있다. 같은 맥락으로, WebAssembly는 실제 OS가 아닌 개념적 OS를 위한 시스템 인터페이스가 필요하다.
대부분 초기 Wasm 코드는 Emscripten으로 실행했기 때문에 브라우저 외부에서 Wasm를 실행하기를 원했을 때에도 마찬가지로 Emscripten으로 컴파일된 코드를 실행했다.여기에는 여러 문제가 있었다.
- 레거시 오버헤드: Emscripten은 ams.js 이후 Wasm을 통해 C/C++ 코드를 컴파일하도록 설계되었다. Emscripten의 에뮬레이션에 의존함으로써 브라우저 외부의 최신 Wasm 앱에서도 불필요한 레거시 오버헤드를 감당해야 했다.
- 에뮬레이션의 에뮬레이션: Emscripten은 JS 글루 코드를 사용하여 웹에서 POSIX를 에뮬레이션하여 사용한다. 브라우저 외부에서 이 구조에 의존한다는 것은 Wasm 런타임이 본질적으로 에뮬레이션을 에뮬레이트한다는 것을 의미한다. 이는 비효율성, 복잡한 코드 경로 및 성능 저하에 대한 잠재 가능성이 크다.
- 비표준 인터페이스: JS 글루 코드는 Wasm용 공용 시스템 인터페이스로 설계되지 않았다. 따라서 브라우저 외부의 런타임이 Emscripten의 글루 코드를 기반으로 버전을 구현할 때 비표준 동작을 포함할 위험이 있어 상호 운용성과 표준화를 달성하기가 더 어려워진다.
- 보안 문제: Emscripten은 샌드박스를 제공하지 않는다. 직접적인 시스템 상호 작용과 관련하여 위험에 노출될 가능성이 있다.
그렇기 때문에 이러한 Wasm 런타임 구조에서 JS 글루 코드에 있는 이러한 모든 기능에 대한 자체 구현을 만들어야 했다.
WASI의 등장
결론적으로 Emscripten은 Wasm 초기에 중요한 역할을 했지만 브라우저가 아닌 환경에서 에뮬레이션 계층에 과도하게 의존하면 비효율성, 보안 문제 및 표준화와 혁신에 대한 장벽이 발생할 가능성이 높다고 보았다. 상시적으로 표준이 에뮬레이션의 에뮬레이션이 될 수는 없다. Wasm 생태계가 성장함에 따라 레거시 에뮬레이션 계층보다는 견고하고 미래 지향적인 기반을 구축하는 것이 중요하다고 판단하였다. 그래서 나온 것이 WebAssembly 시스템 인터페이스인 WASI이다.
- Announcing WASI: WebAssembly's standard system interface
- Standardizing WASI: A system interface to run WebAssembly outside the web
WASI 특징
1. Portability 휴대성
POSIX 소스 코드는 portability을 제공한다. 서로 다른 버전의 libc로 동일한 소스 코드를 컴파일하여 서로 다른 시스템을 target 할 수 있다. WebAssembly는 이보다 한 단계 더 나아가서 '한 번 컴파일(Only Once)하고 다양한 기계 전체에서 실행'할 수 있도록 설계하였다. 이러한 portability 특징 덕분에 사용자에게 코드를 훨씬 쉽게 배포할 수 있게 되었다. 예를 들어, Node의 기본 모듈이 Wasm으로 작성된 경우 사용자는 기본 모듈이 있는 앱을 설치할 때 node-gyp를 실행할 필요가 없으며 개발자는 직접 수십 개의 바이너리를 구성하고 배포할 필요가 없다.
2. Security 보안
1) 기존 접근 권한 방식
코드가 OS에게 입출력을 수행하도록 요청할 때, OS는 해당 요청 작업을 수행하는 것이 안전한지 여부를 결정해야 한다. OS는 일반적으로 소유권이나 그룹으로 접근 제어를 결정한다. 이러한 방식은 사용자 서로를 보호하기 위해서다. 초기 OS가 개발되었을 때, 시스템은 종종 다중 사용자이고 관리자가 설치된 소프트웨어를 제어했다. 따라서, 가장 눈에 띄는 위협은 다른 사용자가 파일을 엿보는 것이었다. 보안을 위해 이러한 구조는 현재 변경되었다.
2) 써드파티 코드의 위험
현재 시스템은 일반적으로 단일 사용자이지만 신뢰할 수 없는 수많은 써드파티 코드들을 실행하고 있다. 이제 가장 큰 위협은 다른 사용자가 아닌 우리가 사용하고 있는 코드이다. 예를 들어, 오픈 소스에서 종종 발생하는 일로, 앱에서 사용중인 라이브러리가 새로운 관리자를 얻었다고 해보자. 만약 그가 나쁜 사람이라면... 그리고 그 시스템에서 무엇이든 액세스할 수 있는 권한(ex. 파일 열고 네트워크를 통해 전송)이 있는 상황이라면... 그들의 코드는 우리에게 많은 피해를 줄 수 있다. 이것이 바로 시스템과 직접 통신할 수 있는 써드파티 라이브러리를 사용하는 것이 위험한 이유다.
Wasm의 보안
Wasm의 보안은 이러한 문제(1,2)들을 해결한다. Wasm은 코드가 OS와 직접 인터랙션을 할 수 없게 샌드박스를 제공해주고 있기 때문이다. 호스트(브라우저 or wasm 런타임)가 함수를 샌드박스에 넣는다. 이는 호스트가 program-by-program으로 수행할 수 있는 작업을 제한할 수 있음을 의미한다. 그리고 프로그램이 사용자를 대신하여 사용자의 전체 권한으로 모든 system call을 호출하도록 허용하지 않는다. 그렇다고 해서 시스템 자체가 완벽하게 안전한 것은 아니다. 호스트는 여전히 모든 기능을 샌드박스에 넣을 수 있으니 말이다.
WASI의 핵심 wasi-core
WASI는 두 가지 핵심 원칙을 염두에 두고 표준화된 인터페이스의 모듈식 세트를 만드는 것을 기본 목표로 한다. 대표적으로 가장 기본 모듈인 'wasi-core'가 있다. wasi-core에는 모든 프로그램에 필요한 기본 사항이 포함된다. files, network connections, clocks 및 random number와 같은 것을 포함하여 POSIX와 동일한 기반을 많이 다룬다. 그렇다고 해서 오해하면 안되는 부분이 wasi-core는 POSIX가 수행하는 모든 것을 다루지는 않는다는 것이다. 예를 들어, process에 대한 개념이 Wasm에 정확하게 매핑되지 않는다. 현재, `fork` 와 같은 프로세스 연산을 지원하지 않기 때문이다.
사용자 코드는 이러한 WASI 함수를 어떻게 호출할까? 코드를 실행하는 런타임은 wasi-core 기능을 import로 호출한다.
- Portability: 이는 각 호스트가 (Mozilla의 wasmtime, Fastly의 Lucet과 같은 wasm 런타임)에서 Node 또는 브라우저에 이르기까지 플랫폼용으로 특별히 작성된 자체 wasi-core 구현을 가질 수 있기 때문에 Portability을 제공한다.
- Security: 또한 호스트가 전달할 wasi-core 함수를 선택할 수 있기 때문에 샌드박싱을 제공한다. 즉, 프로그램 별로 허용할 시스템 호출을 선택할 수 있다. 이렇게 하면 보안이 유지된다.
WASI는 이렇게 wasi-core를 시작으로, WASI의 주요 특징인 portability와 security를 유지하여 생태계를 위한 견고한 기반을 제공하기 위해 노력하고 있다.
WASM 런타임
Wasm 런타임은 웹어셈블리 코드를 로드하고 실행할 수 있는 환경으로, WASM 바이너리를 실행하는 데 필요한 인프라를 제공한다. 런타임은 WASM 명령어를 실행하기 위한 인터프리터 또는 가상 머신이라고 생각하면 된다. 브라우저가 아닌 환경을 위해 특별히 개발된 다양한 Wasm 런타임이 있다. 이러한 런타임은 점점 더 WASI 인터페이스를 지원하기 시작하여 표준화된 시스템 액세스를 통해 브라우저 외부에서 Wasm 애플리케이션을 실행할 수 있게 되었다. 현재 wasm 런타임으로 Wasmer, Wasmtime, WasmEdge 등 존재한다.
WASM이 서버에서 동작하는 과정
WASI + Wasm 런타임 덕분에 Linux, Windows, macOS와 같은 기존 운영 체제에서 Wasm 모듈을 안전하고 빠르게 실행할 수 있게 되었다. 이 프로세스는 브라우저 실행과 대체로 유사하다. WASI는 시스템별 차이를 추상화하는 일관된 인터페이스를 제공하여 서버에서 Wasm 바이너리가 보편적으로 실행될 수 있도록 보장한다.
- Wasm으로 컴파일: 브라우저 시나리오와 마찬가지로 소스 코드는 Emscripten과 같은 도구를 사용하거나 LLVM의 Wasm 백엔드를 통해 직접 Wasm으로 컴파일한다
- 런타임 활용: Wasmer 또는 Wasmtime과 같은 Wasm 런타임이 탑재된 서버에 Wasm 바이너리를 배포한다
- 실행: 런타임은 Wasm 모듈을 로드하고 WASI의 도움으로 파일 시스템, 네트워크 프로토콜 등을 포함한 서버 리소스와 원활하게 상호 작용한다
WASM + WASI로 Ruby, Rust 코드 실행하기
Ruby 관련 설치 사항
# 1. ruby
https://www.ruby-lang.org/ko/documentation/installation/
# 2. install wasi-vfs
https://github.com/kateinoigakukun/wasi-vfs
# 3. install wasmtime
curl https://wasmtime.dev/install.sh -sSf | bash
Rust 관련 설치 사항
# 1. rust
https://www.rust-lang.org/tools/install
# 2. install WASI-enabled Rust tool chain
$ rustup target add wasm32-wasi
# 3. install wasmtime
curl https://wasmtime.dev/install.sh -sSf | bash
1) Ruby + WASM
실습 참고 문서: https://github.com/ruby/ruby.wasm
Index.html 파일 생성 후 다음과 같이 Ruby 언어로 코딩을 한다.
<html>
<script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@1.0.1/dist/browser.script.iife.js"></script>
<script type="text/ruby">
puts "Hello, world!"
</script>
</html>
해당 파일을 실행하면 웹 브라우저 Console에 다음과 같이 Hello, world!를 확인해 볼 수 있다!
Ruby 앱을 WASI 앱으로 패키징하기
- 릴리즈된 ruby 3.2 wasi 인터페이스를 다운받아 추출한다
- ruby.wasm로 이름을 변경한다
- ruby 스크립트를 작성하고 wasi-vfs를 통해 전체 디렉토리 구조를 압축한다
- wasmtime으로 압축된 wasm 바이너리 파일에 있는 app.rb 스크립트 파일을 실행하도록 지시한다
# Download a prebuilt Ruby release
$ curl -LO https://github.com/ruby/ruby.wasm/releases/latest/download/ruby-3_2-wasm32-unknown-wasi-full.tar.gz
# Extract ruby binary not to pack itself
$ tar xfz ruby-3_2-wasm32-unknown-wasi-full.tar.gz
# move to ruby binary
$ mv 3_2-wasm32-unknown-wasi-full/usr/local/bin/ruby ruby.wasm
# Put your app code
$ mkdir src
$ echo "puts 'Hello'" > src/my_app.rb
# Pack the whole directory under /usr and your app dir
$ wasi-vfs pack ruby.wasm --mapdir /src::./src --mapdir /usr::./3_2-wasm32-unknown-wasi-full/usr -o my-ruby-app.wasm
# Run the packed scripts
$ wasmtime my-ruby-app.wasm -- /src/my_app.rb
Hello
2) Rust + WASM
새로운 Rust 바이너리 프로젝트를 생성한다.
$ cargo new demo
다음 코드를 `src/main.rs` 에 붙여넣는다.
use std::env;
use std::fs;
use std::io::{Read, Write};
fn process(input_fname: &str, output_fname: &str) -> Result<(), String> {
let mut input_file =
fs::File::open(input_fname).map_err(|err| format!("error opening input {}: {}", input_fname, err))?;
let mut contents = Vec::new();
input_file
.read_to_end(&mut contents)
.map_err(|err| format!("read error: {}", err))?;
let mut output_file = fs::File::create(output_fname)
.map_err(|err| format!("error opening output {}: {}", output_fname, err))?;
output_file
.write_all(&contents)
.map_err(|err| format!("write error: {}", err))
}
fn main() {
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
if args.len() < 3 {
eprintln!("usage: {} <from> <to>", program);
return;
}
if let Err(err) = process(&args[1], &args[2]) {
eprintln!("{}", err)
}
}
사전에 설치한 WASI-enabled Rust toolchain을 사용하여 해당 프로젝트를 빌드한다.
$ cargo build --target wasm32-wasi
WebAssembly 모듈이 생성되었다. target/wasm32-wasi/debug
$ file target/wasm32-wasi/debug/demo.wasm
demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
C 또는 Rust에서 컴파일된 결과 wasm 모듈 demo.wasm은 자체 포함된 wasm 모듈을 포함하는 단일 파일이며 어떠한 JS 코드도 필요로 하지 않는다. wasmtime 다음과 같이 직접 실행할 수 있다.
$ wasmtime demo.wasm
usage: demo.wasm <from> <to>
해당 명령어는 from, to 인자가 추가로 필요하다.
$ echo hello world > test.txt
$ wasmtime demo.wasm test.txt /tmp/somewhere.txt
error opening input test.txt: Capabilities insufficient
이제 샌드박싱이 작동하는 것을 볼 수 있다. 이 프로그램은 이름(test.txt)으로 파일에 액세스하려고 시도하고 있지만 wasmtime은 그러한 기능을 제공하지 않는다. 따라서 필수 디렉토리에 있는 파일에 액세스할 수 있는 기능을 부여해보자.
- `--dir=`옵션은 `wasmtime` 디렉토리를 미리 열고 해당 디렉토리 내의 파일을 여는 데 사용할 수 있는 기능으로 프로그램에서 사용할 수 있도록 지시한다.
$ wasmtime --dir=. --dir=/tmp demo.wasm test.txt /tmp/somewhere.txt
이제 프로그램이 정상적으로 실행된다!
$ cat /tmp/somewhere.txt
hello world
+++
- Ruby.wasm todo list was posted here recently. An example use of Ruby in the browser with WASI.
- Running WebAssembly on the Kernel: Sandboxed Linux kernel mode WebAssembly runtime
- Wasmer: an open-source runtime for executing WebAssembly on the Server.
- WebAssembly 실험: NGINX 에이전트 확장
Refs
https://docs.docker.com/engine/
https://hacks.mozilla.org/category/code-cartoons/a-cartoon-intro-to-webassembly/
https://devops.com/cosmonic-contributes-webassembly-runtime-to-cncf/
https://d2.naver.com/helloworld/8257914
https://wasmlabs.dev/articles/docker-without-containers/
https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-intro.md
'Dot Computer Science > Concept' 카테고리의 다른 글
컨테이너 Container: Linux Kernel, Docker (0) | 2023.06.22 |
---|---|
가상 머신 VM: 가상 메모리, 메모리 관리 기법, Hypervisor (0) | 2023.05.21 |
[Architectural pattern] Active Record, Data Mapper 패턴 (0) | 2023.01.24 |
[서비스 배포 전략 구상하기] 무중단 배포 3가지 방식 (Rolling, Blue-Green, Canary) (1) | 2022.03.22 |
[Naver DEVIEW 요약] RESTful API에 대해 잘 이해하고 있는가? REST의 출현과 올바른 사용법에 대해 (0) | 2021.12.28 |