Cosmwasm에 대해 배우기에 앞서 해당 기술의 핵심이 되는 WebAssembly(Wasm)에 대한 이해를 키워나가고자 해당 아티클이 작성되었다.
WebAssembly(Wasm)는 다양한 소스 언어로부터 이식 가능한 바이너리 실행 파일을 생성할 수 있는 바이너리 명령 형식을 정의하는 개방형 표준이다. 이러한 바이너리는 다양한 환경에서 실행될 수 있으며, 웹에서 기원하여 모든 주요 브라우저에서 지원된다. Wasm을 사용하면 서버, 엣지 등 어디서든 이전보다 더 이식성(portable) 있고 보안(security)이 뛰어난 프로그램을 실행할 수 있다.
Wasm 응용 프로그램은 최소한의 메모리 공간과 CPU 요구 사항으로 실행되도록 설계되었다. 이는 네이티브 코드와 유사한 속도를 제공하며, VM 부팅이나 컨테이너 시작과 달리 콜드 스타트 시간이 없다.
Wasm 런타임은 기본적으로 샌드박스 환경에서 실행되며, 메모리에 안전하게 접근할 수 있다. 기능 기반 모델은 Wasm 애플리케이션이 명시적으로 허용된 항목에만 접근할 수 있도록 한다. 이로 인해 공급망 보안이 강화된다.
- 대부분의 언어는 런타임에서 함수에 주소를 할당한다. 메모리를 바이트 배열 형태로 보면 함수 코드가 제대로 구별되지 않아 안전하지 않다.
- Wasm은 프로그램 메모리를 안전한 영역에 캡슐화하여 프로그램을 실행하는 호스트에 영향을 미치거나 보안을 손상시킬 수 있는 코드를 허용하지 않는다.
Wasm은 여러 주요 런타임에서 대부분의 CPU 아키텍처(x86, ARM, RISC-V)를 지원하며, Linux, Windows, macOS 및 Non-POSIX 운영 체제에서도 실행될 수 있다.
다양한 프로그래밍 언어를 Wasm으로 컴파일할 수 있으며, 현대적이고 지속적으로 개선되는 툴체인을 사용한다. 컴파일러는 LLVM(Low Level Virtual Machine) 백엔드를 활용하여 LLVM 중간 표현(IR)로 컴파일한 후 Wasm 프로그램을 생성할 수 있다.
WASI와 Wasm 런타임은 Wasm 생태계에서 중요한 역할을 한다.
- WASI는 WebAssembly 프로그램이 운영 체제와 상호작용할 수 있도록 하여, WebAssembly의 활용 범위를 웹 브라우저 밖으로 확장한다.
- Wasm 런타임은 이러한 WebAssembly 프로그램이 다양한 환경에서 원활히 실행될 수 있도록 지원한다.
애플리케이션 레벨에서는 파일 열기 또는 생성과 같은 작업을 수행할 액세스 권한이 없다. 이는 파일, 메모리 및 네트워크 연결과 같은 시스템 리소스가 안정성과 보안에 있어 매우 중요하기 때문이다. 한 프로그램이 실수로 다른 프로그램의 리소스를 망가뜨리면 해당 프로그램이 중단될 수 있다. 더욱이, 프로그램(또는 사용자)이 다른 프로그램의 리소스를 고의로 건드리면 중요한 데이터를 탈취할 수 있는 위험이 있다.
protection ring security는 어떤 프로그램과 사용자가 어떤 리소스에 액세스할 수 있는지 제어하는 방법으로 사용된다. OS는 커널에 의해 시스템 리소스 주위에 보안 장벽을 세운다. 커널은 새 파일을 생성하거나 열거나 네트워크 연결을 하는 작업들을 수행한다. 사용자 프로그램은 '사용자 모드'라는 커널 외부에서 실행된다. 프로그램이 파일 열기와 같은 작업을 수행하려면 커널에게 요청해야 한다.
프로그램이 커널에게 작업을 요청할 때는 system call을 사용한다. 이를 통해 커널은 어떤 사용자가 요청하는지 파악할 수 있고, 해당 사용자가 요청한 파일에 접근할 권한이 있는지 확인할 수 있다. 대부분의 장치에서 이 system call을 통한 시스템 리소스 액세스 방식이 사용된다. OS는 system call을 사용할 수 있게 만들어준다. 그러나 각 OS마다 system call이 있다면 OS마다 다른 버전의 코드가 필요하지 않을까? 다행히도 그렇지 않다. 시스템 인터페이스가 있기 때문이다.
대부분의 프로그래밍 언어는 표준 라이브러리를 제공한다. 코딩할 때 프로그래머는 OS나 시스템에 대해 알 필요가 없고, 단지 인터페이스(프레임워크, 라이브러리)를 잘 다루면 된다. 컴파일할 때 툴 체인은 대상 시스템에 따라 사용할 인터페이스 구현을 선택한다. 이 구현은 OS API의 기능을 사용하므로 시스템에 따라 다르다. 이러한 예시는 다음고 같다:
- Docker Desktop, JVM 같은 도구를 설치할 때 OS와 시스템 아키텍처(x86, ARM)을 선택하는 것도 그 이유다.
printf
함수가 Windows 머신용으로 컴파일되 면 Windows API를 사용하고, Mac 또는 Linux용으로 컴파일되면 POSIX를 대신 사용한다.
시스템 인터페이스는 이처럼 각각 다른 환경에서도 사용될 수 있는 공통의 인터페이스를 의미한다.
이러한 구조는 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 글루 코드에 있는 모든 기능에 대한 자체 구현을 만들어야 했다.
결론적으로 Emscripten은 Wasm 초기에 중요한 역할을 했지만, 브라우저가 아닌 환경에서 에뮬레이션 계층에 과도하게 의존하면 비효율성, 보안 문제 및 표준화와 혁신에 대한 장벽이 발생할 가능성이 높다고 보았다. 지속적으로 표준이 에뮬레이션의 에뮬레이션이 될 수는 없다. Wasm 생태계가 성장함에 따라 레거시 에뮬레이션 계층보다는 견고하고 미래 지향적인 기반을 구축하는 것이 중요하다고 판단하였다. 그래서 나온 것이 WebAssembly 시스템 인터페이스인 WASI이다.
POSIX는 다양한 운영 체제에서 동일한 소스 코드를 컴파일할 수 있게 해주는 기술이다. 이를 통해 개발자는 여러 시스템에 맞게 코드를 작성할 필요 없이, 한 번 작성한 코드를 여러 곳에서 실행할 수 있다.
WebAssembly는 이보다 한 단계 더 나아간다. WebAssembly로 작성된 코드는 '한 번 컴파일하고 다양한 기기에서 실행'할 수 있도록 설계되었다. 예를 들어, Node.js의 기본 모듈이 WebAssembly로 작성된 경우, 사용자는 별도의 복잡한 설치 과정 없이 쉽게 앱을 설치할 수 있다. 개발자는 여러 플랫폼에 맞게 따로따로 코드를 작성할 필요가 없게 된다.
전통적으로 컴퓨터 운영 체제(OS)는 사용자가 파일에 접근하거나 네트워크를 사용할 때 권한을 확인한다. 이는 사용자 간의 보안을 위해 만들어진 방식이다.
- 예를 들어, 회사의 여러 직원이 같은 컴퓨터를 사용할 때, 각 직원의 파일이 다른 직원에 의해 쉽게 열리지 않도록 하는 것이다.
하지만 요즘은 대부분의 시스템이 한 명의 사용자가 여러 프로그램을 실행하는 방식이다. 여기서 문제가 되는 것은 우리가 사용하는 프로그램 자체가 믿을 수 없는 경우에 발생한다.
- 예를 들어, 우리가 사용하는 어떤 프로그램이 악의적인 코드를 포함하고 있다면, 그 프로그램은 우리의 파일을 몰래 열어보거나, 네트워크를 통해 우리 정보를 빼돌릴 수 있다.
이런 위험 때문에 신뢰할 수 없는 써드파티 프로그램을 사용하는 것은 매우 위험하다.
호스트 환경(브라우저나 Wasm 런타임)은 Wasm 프로그램이 사용할 수 있는 WASI 함수를 선택할 수 있다. 이는 프로그램이 불필요한 시스템 권한을 가지지 않도록 하여 보안을 강화한다. 그리고 WASI는 프로그램을 샌드박스 안에서 실행하여 직접적인 시스템 접근을 제한한다.
- 샌드박스 안에서 프로그램이 실행되기 때문에, 프로그램이 마음대로 파일에 접근하거나 네트워크를 사용할 수 없다.
- 호스트 환경이 프로그램이 할 수 있는 작업을 제한할 수 있다. 이렇게 하면 프로그램이 운영 체제 전체 권한을 갖고 위험한 작업을 하지 못하게 막을 수 있다.
WASI는 기본적인 시스템 인터페이스 기능을 제공하는 'wasi-core' 모듈을 포함한다. 파일 접근, 네트워크 연결, 시간 확인 및 난수 생성 등 필수 기능을 제공한다. WASI는 이러한 표준화된 인터페이스를 제공하여, 다양한 호스트 환경에서 일관된 방식으로 Wasm 프로그램이 동작할 수 있도록 한다.
Wasm 런타임은 웹어셈블리 코드를 로드하고 실행할 수 있는 환경으로, WASM 바이너리를 실행하는 데 필요한 인프라를 제공한다. 런타임은 WASM 명령어를 실행하기 위한 인터프리터 또는 가상 머신이라고 생각하면 된다. 브라우저가 아닌 환경을 위해 특별히 개발된 다양한 Wasm 런타임이 있다. 이러한 런타임은 점점 더 WASI 인터페이스를 지원하기 시작하여 표준화된 시스템 액세스를 통해 브라우저 외부에서 Wasm 애플리케이션을 실행할 수 있게 되었다. 현재 wasm 런타임으로 Wasmer, Wasmtime, WasmEdge 등 존재한다.
Wasmer는 WebAssembly 런타임 중 하나로, 다양한 환경에서 WebAssembly 바이너리를 실행할 수 있게 해준다. Wasmer는 CLI, 네이티브 애플리케이션, 서버 애플리케이션 등 여러 형태로 사용할 수 있다.
WASI와 Wasm 런타임 덕분에 Linux, Windows, macOS와 같은 기존 운영 체제에서 Wasm 모듈을 안전하고 빠르게 실행할 수 있다. 이 프로세스는 브라우저 실행과 대체로 유사하다. WASI는 시스템별 차이를 추상화하는 일관된 인터페이스를 제공하여 서버에서 Wasm 바이너리가 보편적으로 실행될 수 있도록 보장한다.
- Wasm으로 컴파일: 브라우저 시나리오와 마찬가지로 소스 코드는 Emscripten과 같은 도구를 사용하거나 LLVM의 Wasm 백엔드를 통해 직접 Wasm으로 컴파일된다.
- 런타임 활용: Wasmer와 같은 Wasm 런타임이 탑재된 서버에 Wasm 바이너리를 배포한다.
- 실행: 런타임은 Wasm 모듈을 로드하고 WASI의 도움으로 파일 시스템, 네트워크 프로토콜 등을 포함한 서버 리소스와 원활하게 상호 작용한다.
- https://hacks.mozilla.org/category/code-cartoons/a-cartoon-intro-to-webassembly/
- https://rsms.me/wasm-intro
- https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-intro.md
- https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/
- https://github.com/wasmerio/wasmer