Skip to content

필사 모드: WebAssembly와 WASI 완전 해부 — 바이트코드, Sandbox, Component Model, Wasmtime, 엣지 런타임까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며 — "Docker보다 작고 JVM보다 빠른"

2019년 Docker 창시자 Solomon Hykes의 트윗이 화제였다.

> "If WASM+WASI existed in 2008, we wouldn't have needed to create Docker."

논란이 많은 발언이었지만, 핵심은 분명했다. **Wasm은 그저 브라우저용 기술이 아니다.** 2025년 지금, Cloudflare Workers, Fastly Compute@Edge, Shopify Functions, Figma 플러그인, 그리고 심지어 Kubernetes 위의 **SpinKube**까지 — 전부 Wasm을 엔진으로 쓴다.

이 글에서는:

- **WebAssembly의 탄생** — asm.js의 그림자에서 Wasm 1.0까지

- 바이트코드 구조와 **스택 가상머신** 실행 모델

- **Sandbox**의 수학적 보증 — 왜 컨테이너보다 안전한가

- **WASI** — 브라우저 밖의 Wasm 시스템 인터페이스

- **Wasmtime / Wasmer / WasmEdge** 런타임 비교

- **Component Model** — 언어 중립 모듈 시스템의 비밀

- **Cloudflare Workers**가 왜 Isolate 대신 Wasm도 지원하는가

- **Kubernetes + Wasm** — runwasi, SpinKube의 그림

- 실무 함정과 미래

> 이전 글 [HTTP/3와 QUIC 완전 해부](/blog/culture/2026-04-15-http3-quic-internals-0rtt-stream-multiplexing-connection-migration-bbr-webtransport-deep-dive-guide-2025)에서 전송 계층의 재설계를 봤다. Wasm은 **실행 계층의 재설계**다.

1. 탄생 배경 — asm.js가 남긴 숙제

JavaScript의 불운한 수명

2010년대 초, 브라우저는 사실상 "JavaScript만 돌아가는 운영체제"였다. 하지만 JS는:

- 동적 타입 → JIT이 타입 가정을 끊임없이 재확인

- 가비지 컬렉션 지연

- 고성능 수치 연산에 부적합

asm.js (Mozilla, 2013)

답은 **JavaScript를 "서브셋"으로 쓰는** asm.js였다.

function add(x, y) {

x = x|0; // 비트 OR로 정수 강제 변환

y = y|0;

return (x + y)|0;

}

`|0` 같은 힌트로 타입을 "미리" 확정하면, 브라우저가 기계어 코드로 최적 컴파일 가능. Emscripten이 C/C++ → asm.js로 변환해 **브라우저에서 Unreal Engine**이 돌았다.

하지만 asm.js는 한계가 분명했다:

- 파싱 비용 (여전히 텍스트)

- 가독성 제로

- 명세가 "비공식"

Wasm 1.0 (2017, W3C 표준)

4개 브라우저 벤더(Mozilla, Google, Microsoft, Apple)가 모여 **공식 바이트코드**를 설계. 2019년 W3C 공식 권고. 이름은 **WebAssembly**.

특징:

- 바이너리 포맷 (파싱 빠름)

- 정적 타입 기반 안전성

- 스택 기반 가상 머신

- 선형 메모리 하나 + 호출 스택 분리

2. Wasm 모듈의 해부

최소 예제

(module

(func $add (param $a i32) (param $b i32) (result i32)

local.get $a

local.get $b

i32.add

)

(export "add" (func $add))

)

WAT(WebAssembly Text format)로 작성. 컴파일하면 바이너리 `.wasm`. 브라우저/런타임에서 `add(3, 5)` 호출 가능.

모듈의 Section 구조

`.wasm` 파일은 섹션의 나열이다.

[0] Custom - 메타데이터 (이름, 디버그 정보)

[1] Type - 함수 시그니처 목록

[2] Import - 외부에서 가져오는 항목

[3] Function - 함수 → 타입 인덱스 매핑

[4] Table - 함수 포인터 테이블 (동적 호출용)

[5] Memory - 선형 메모리 정의

[6] Global - 전역 변수

[7] Export - 외부에 노출하는 항목

[8] Start - 모듈 초기화 함수

[9] Element - Table 초기화

[10] Code - 함수 본문 (바이트코드)

[11] Data - Memory 초기화

핵심: 선형 메모리 (Linear Memory)

Wasm 프로그램은 **하나의 큰 연속된 바이트 배열**을 메모리로 본다. 64KB 단위 페이지. 기본 시작 크기와 최대 크기 지정.

(memory 1 10) ;; 최소 1 페이지(64KB), 최대 10 페이지

C/C++/Rust가 "포인터"라고 부르는 것은 이 배열의 **오프셋(i32)**에 불과. 프로그램이 할당/해제를 자체 관리한다 (`malloc`/`free`도 Wasm으로 컴파일된 코드).

**중요**: Wasm은 이 메모리 밖으로 절대 접근할 수 없다. 호스트의 메모리에도, 다른 모듈의 메모리에도. 이게 Sandbox의 1차 방어선이다.

스택 머신

Wasm은 **스택 기반 VM**.

local.get $a ;; 스택 push a

local.get $b ;; 스택 push b

i32.add ;; 스택에서 둘 pop, 더해서 push

레지스터 기반(예: x86)과 달리, 스택 명령은 단순하고 인코딩이 짧다. 런타임이 이를 기계어로 JIT/AOT 컴파일할 때 최적화 여지가 크다.

3. Sandbox의 수학적 보증

왜 Wasm은 안전한가

Wasm이 제공하는 격리:

1. **메모리 격리** — 선형 메모리 밖 접근 불가. 호스트도 다른 Wasm 모듈도 못 건드림

2. **Control Flow 무결성** — 함수 포인터는 Table을 통해서만 호출. 임의 주소로 점프 불가

3. **시스템 콜 없음** — 기본 Wasm은 외부 세계에 직접 접근할 수 없음. Import된 함수만 호출 가능

4. **결정론적** — 동일 입력 → 동일 출력 (부동소수점 NaN 비트 규격화 등으로 결정성 확보)

Capability 기반 보안

컨테이너는 "모든 기본 권한 ON → 취약한 것만 OFF"(deny-list 방식). Wasm은 반대. **"아무 것도 못 함 → 명시적으로 Capability 전달하면 가능"** (allow-list, Capability 기반).

예: 파일 읽기. Wasm 모듈은 기본적으로 파일시스템에 접근 불가. 호스트가 `fd_read` 함수를 import에 제공했다면, **호스트가 어떤 fd를 열어줬는지**까지만 접근 가능. 경로 조작(`../etc/passwd`)도 호스트가 root fd를 어떤 디렉토리로 줬는지에 따라 제한.

컨테이너 vs Wasm 보안 비교

| 측면 | 컨테이너 | Wasm |

|---|---|---|

| 격리 방식 | Linux namespace/cgroup | 가상머신 + 타입 안전 |

| 기본 권한 | 많음 (deny-list) | 없음 (allow-list) |

| 탈출 공격 면 | 커널 CVE (예: Dirty COW) | VM 명세 수학적 검증 |

| 시스템 콜 | 수백 개 | 호스트가 import한 것만 |

| 시작 시간 | 100ms~수초 | 1ms 미만 |

실제로 Fastly는 Wasm에서 발견된 sandbox 탈출을 "다른 Wasm 모듈을 공격" 수준도 아닌 "Wasmtime 버그로 OOM"으로만 경험한다고 보고.

4. WASI — 브라우저 밖으로 나가다

문제: 브라우저 API만으론 서버에서 못 씀

초기 Wasm은 브라우저 API (fetch, DOM) 의존. 서버에서 파일을 읽거나 소켓을 열 표준 방법이 없었다.

WASI 0.1 (2019) — POSIX-ish

Mozilla의 Lin Clark가 주도. POSIX처럼 보이는 시스템 인터페이스를 Wasm용으로.

wasi_snapshot_preview1

- fd_read / fd_write

- fd_seek

- path_open

- clock_time_get

- random_get

- args_get / environ_get

특징: 파일 디스크립터가 호스트가 명시적으로 준 것만 쓸 수 있음 (Capability).

WASI 0.2 (2024) — Component Model 기반

0.1의 한계:

- 오직 "동기 POSIX" 모델

- HTTP/sockets 표준 부재

- 인터페이스가 C 스타일로 고정

0.2는 **Component Model** 위에 재구축.

- `wasi:http` — HTTP 요청/응답 처리 표준

- `wasi:sockets` — TCP/UDP

- `wasi:io/streams` — 비동기 스트림

- `wasi:cli` — CLI 앱용 stdio

언어 중립 인터페이스 정의 (**WIT**).

WIT 예시

package wasi:http@0.2.0;

interface types {

variant method {

get,

post,

put,

delete,

other(string),

}

record request {

method: method,

uri: string,

headers: list<tuple<string, string>>,

body: option<body>,

}

}

이 인터페이스는 Rust, Go, JavaScript, Python 각각의 바인딩으로 생성 가능. **"언어 간 표준 API"**.

5. Component Model — 언어 중립의 비밀

왜 모듈만으론 부족한가

Wasm 1.0 모듈은 숫자와 바이트만 주고받는다. 문자열, 배열, 구조체는 선형 메모리 오프셋으로 직접 조율해야 한다. **언어마다 규칙이 달라** 상호운용이 악몽.

예: Rust → JavaScript로 문자열 전달

- Rust는 UTF-8 길이+포인터

- JS는 UTF-16

- 변환 코드를 손으로 썼다

Component Model (2024 안정화)

Wasm 위에 **"컴포넌트"**라는 상위 단위. 컴포넌트 간:

- 고수준 타입 (string, record, variant, list, option, result)

- 자동 ABI 변환

- import/export를 WIT로 선언

핵심 이점

1. **언어 X로 만든 컴포넌트**를 **언어 Y로 만든 컴포넌트**에 꽂아 사용

2. **버전 관리** — WIT 인터페이스의 semver

3. **Registry** — warg.io 같은 컴포넌트 패키지 레지스트리 출현

실제 사례:

- Rust로 만든 이미지 처리 컴포넌트를

- Go로 만든 HTTP 핸들러에서 import해서

- JavaScript로 만든 오케스트레이터가 조립

**npm + Docker + gRPC의 결합**이라는 평가.

6. Wasm 런타임 3대장

Wasmtime (Bytecode Alliance)

- **기본 엔진**: Cranelift (Rust 컴파일러 프로젝트)

- 언어: Rust

- 특징: **WASI 표준 구현의 레퍼런스**

- 안정성, 보안 감사 품질 최상

- `wasmtime run module.wasm` 한 줄로 실행

- Fastly Compute가 백엔드로 사용

Wasmer

- 언어: Rust

- 컴파일러 **플러그러블**: Singlepass(빠른 JIT), Cranelift, LLVM(최고 성능)

- **wapm** 패키지 매니저

- `wasmer run` 명령, 임베딩 API 다양 (C, Go, Python 등)

- 클라우드 기반 Wasmer Edge 제공

WasmEdge (CNCF Sandbox)

- 언어: C++

- 특징: **AI 워크로드 특화** — Tensor 연산, ONNX 지원

- Docker + Wasm 통합(runwasi)에 활발

- 많은 임베디드/IoT 환경 지원

성능 비교 (2025 Bytecode Alliance 벤치)

| 런타임 | Startup | Throughput | Memory |

|---|---|---|---|

| Wasmtime (Cranelift) | 0.3ms | 100% | 기준 |

| Wasmer (LLVM) | 2s 컴파일 / 0.1ms 재실행 | 115% | 1.3x |

| WasmEdge | 0.5ms | 95% | 0.8x |

선택 기준:

- **표준 준수** → Wasmtime

- **극한 성능** → Wasmer LLVM

- **작은 메모리 + AI** → WasmEdge

7. Cloudflare Workers — Wasm이 아닌 Isolate, 그러나 Wasm도

흔한 오해

"Cloudflare Workers = Wasm"이라고 알고 있는 경우가 많다. 실은:

- 기본은 **V8 Isolate** — JavaScript 런타임을 경량 격리 단위로 분리

- Isolate 하나 = 약 **3MB 메모리, 5ms 콜드 스타트**

- Wasm은 **Isolate 안에서** 실행 가능

왜 Isolate + Wasm인가

- JavaScript 생태계 그대로 씀 (npm)

- 성능 크리티컬 경로는 Wasm으로

- 예: Cloudflare Pages가 Rust → Wasm으로 이미지 변환

Fastly Compute@Edge — 완전 Wasm

Fastly는 전적으로 Wasm 기반.

- 각 요청이 새 Wasm 인스턴스

- 언어: Rust, Go (TinyGo), JS, AssemblyScript

- 콜드 스타트 **35μs** (마이크로초!)

1밀리초 미만 콜드 스타트가 가능한 이유: Wasm **AOT 컴파일 결과를 재사용**하고, Sandbox가 가볍기 때문.

8. Kubernetes에서 Wasm 돌리기

runwasi 프로젝트

containerd의 shim으로 들어가 OCI 이미지로 패키징된 Wasm을 컨테이너처럼 실행. 기존 Kubernetes 생태계에 그대로 얹힘.

apiVersion: v1

kind: Pod

spec:

runtimeClassName: wasmtime

containers:

- image: ghcr.io/example/hello-wasm:latest

name: hello

SpinKube (Fermyon, 2024)

Spin은 Fermyon의 Wasm 프레임워크. SpinKube는 Kubernetes CRD로 Wasm 앱을 관리.

장점:

- Pod이 **수 밀리초**에 뜸

- Deno/Node보다 훨씬 작은 메모리

- Serverless가 "항상 살아있는 것처럼" 반응

단점:

- 디버깅 도구 성숙 부족

- 모든 라이브러리가 Wasm 타겟 빌드되진 않음

- WASI 0.2 네이티브 지원이 아직 과도기

KWasm (Liquid Reply)

DaemonSet으로 Wasm 런타임을 노드에 설치. 기존 Kubernetes 클러스터를 Wasm 지원 환경으로 업그레이드.

9. 언어별 Wasm 여정

Rust — 황금 표준

- `cargo build --target wasm32-wasi`

- `wasm-bindgen`로 브라우저 JS FFI

- `wit-bindgen`로 Component Model

- 크레이트 생태계의 상당 부분이 Wasm 지원

Go — TinyGo가 구원

- 표준 Go 컴파일러는 큰 런타임 포함 (GC, goroutine)

- Go 1.21부터 `GOOS=wasip1 GOARCH=wasm`로 WASI 공식 지원

- 하지만 바이너리 크기가 여전히 몇 MB

- **TinyGo**는 LLVM 기반으로 훨씬 작은 출력 (수십 KB)

- 단점: 표준 라이브러리 일부 미지원

JavaScript/TypeScript — AssemblyScript

- TypeScript 문법으로 Wasm 작성

- 빠른 학습 곡선

- 그러나 풀 JS 생태계는 못 씀

Python — Pyodide, py2wasm

- 브라우저에서 Python 돌리는 Pyodide

- CPython 자체를 Wasm으로 컴파일 (10MB+)

- 서버리스용으론 무겁지만 교육/데이터 시각화 유용

C/C++ — Emscripten, wasi-sdk

- 원래 asm.js 시대부터 지원

- Unity/Unreal 엔진도 Wasm 빌드

10. 성능 — "네이티브의 80%"가 진짜인가

이론

Wasm 바이트코드는 **실행 시 JIT/AOT로 기계어**. 같은 C 소스를:

- Native (gcc) vs Wasm (clang → wasm → Wasmtime AOT)

- 대부분 벤치마크에서 **80~95%** 네이티브 속도

지연 요소들

1. **경계 검사** — 선형 메모리 접근마다 bounds check. 현대 런타임은 OS의 가상 메모리 트릭(mmap + guard pages)으로 **제로 오버헤드**에 근접

2. **SIMD** — Wasm SIMD 128비트 제안이 표준화. Rust의 `std::simd` 일부를 Wasm에 매핑

3. **Atomics + Threads** — 공유 메모리와 아토믹 연산 지원. 웹워커로 병렬 실행

4. **GC 제안** — 호스트 GC에 의존하지 않는 Wasm용 GC. TypeScript/Kotlin/Java를 효율적으로 Wasm으로

현실 — 병목은 FFI

"Wasm이 네이티브보다 느리다"는 벤치마크들, 파고 보면 대부분 **FFI 횟수가 많은 케이스**다. 호스트 함수를 자주 호출하면 boundary 전환 비용. 해결:

- Wasm 내부에서 최대한 처리

- 배치 호출

- 공유 메모리 기반 IPC

11. 실전 사례 — 누가 어떻게 쓰는가

Figma

플러그인 시스템을 **QuickJS + Wasm**으로. 서드파티 코드를 브라우저에서 안전하게 실행. Plugin API 제한된 capability만 노출.

Shopify Functions

서드파티 앱이 체크아웃 로직을 커스터마이즈 가능. Rust → Wasm으로 업로드. 지연이 극도로 민감한 영역이라 Wasm의 빠른 시작이 필수.

Adobe Photoshop Web

Photoshop 데스크톱 코드를 C++에서 Wasm으로 빌드. 브라우저에서 실제 Photoshop 커널이 돌아감. Emscripten 활용.

1Password

브라우저 확장의 암호화 모듈을 Rust → Wasm으로. 보안 감사와 성능을 동시 달성.

Envoy Proxy

WASM 필터로 동적 확장. Lua/Go 필터보다 성능 좋고 언어 선택 자유. Istio의 기본 확장 메커니즘.

12. 함정과 현실적 한계

한계 1: Thread 모델

Wasm의 병렬성은 여전히 **제한적**. 공유 메모리 원자 연산은 가능하지만:

- 웹워커 기반이라 메시지 기반 통신이 기본

- 리턴/재진입 불가한 런타임 많음

- goroutine/async Rust의 풍부함 대비 부족

한계 2: 네트워킹

WASI 0.2가 `wasi:sockets` 도입했지만:

- 브라우저에서는 여전히 fetch API만 (CORS/Same-Origin)

- 서버 Wasm에서도 라이브러리 지원이 제각각

한계 3: 파일시스템 일관성

"Capability 기반 fd"는 안전하지만:

- 경로 기반 코드 대량 포팅 번거로움

- POSIX `fork()`/`exec()` 부재 (의도적)

한계 4: 디버깅

- DWARF 지원 향상 중이지만 gdb/lldb 경험은 아직 네이티브 수준 아님

- Chrome DevTools가 Wasm 디버깅 제공하지만 소스맵 한계

- 스택 트레이스 읽기 난해

한계 5: 바이너리 크기

"경량"이라지만:

- Rust hello world: ~200KB (wasm32-wasi)

- TinyGo: ~50KB

- AssemblyScript: ~10KB

- Python/CPython: ~10MB

엣지에서 콜드 스타트하려면 수십 KB가 목표인데 Rust는 `panic!` 체인만 해도 덩치.

13. 보안 고려사항

1. Wasm 자체는 안전하지만 import는 위험

모듈이 `fd_write`를 imported했다면, 호스트가 안전한 구현을 제공해야 한다. "Wasm이라서 안전"은 틀리고, **"Wasm 런타임과 호스트 바인딩이 안전해야"**가 정확.

2. Denial of Service

- 무한 루프: Fuel 또는 Epoch 기반 인터럽트로 제한

- 메모리 폭주: Memory limit 명시

- 스택 오버플로: Wasm 명세가 제한 지원

3. Side Channel

- Spectre/Meltdown 류 타이밍 공격은 Wasm 내부에서도 가능

- 런타임이 timing-precision 제한 (1ms 단위로 라운딩 등)

4. Supply Chain

- 컴포넌트 레지스트리(warg)의 서명 검증 필수

- WIT 인터페이스 호환성 검증

14. 미래 — 어디로 가는가

임박한 표준들

- **WASI 0.3** — 비동기/스트림 더 풍부

- **Exception Handling** — try/catch 표준화

- **Stack Switching** — 언어별 코루틴/async

- **GC Proposal** — Wasm GC (이미 Chrome 119+/Firefox 120+ 지원)

- **Relaxed SIMD** — 플랫폼별 SIMD 최적화

Trend 예측

1. **서버리스 네이티브** — Lambda처럼 기동이 중요한 영역이 Wasm으로 이동

2. **플러그인 시스템 공통 언어** — Kafka Connect, VSCode 확장 등이 Wasm 기반으로 재탄생 중

3. **Edge AI** — WasmEdge + ONNX로 모델 추론을 엣지로

4. **Kubernetes 포드 대안** — Wasm 워크로드가 Pod의 일부 영역 대체

5. **브라우저 + 서버 통합** — 같은 Wasm 모듈을 양쪽에서 실행

한계의 실명

- JavaScript 생태계의 중력 — 당분간 JS 대체 X

- POSIX 코드베이스의 막대함 — 전부 Wasm으로 못 옮김

- 디버깅/관측성 성숙도 — 2~3년은 더 필요

15. 실무 체크리스트 12가지

1. **사용 사례가 명확할 때만** 도입 — 플러그인, 서버리스, 클라이언트 고성능

2. **컴파일러 옵션으로 바이너리 크기 최적화** — `--release`, `wasm-opt -Os`, `strip`

3. **WASI 0.2 Component Model 기반 선택** — 미래 호환성

4. **Wasmtime을 레퍼런스 런타임**으로 — 표준 구현 확인

5. **Capability 최소 원칙** — 필요한 fd만 열어줌

6. **Fuel / Epoch로 실행 시간 제한** — DoS 방어

7. **`wasm-opt` 체인** — 크기 30%+ 감소

8. **언어 선택은 팀 역량 기반** — Rust가 가장 성숙, TinyGo는 Go 친화

9. **hot path는 Wasm, cold path는 JS/호스트** — 혼합 설계

10. **관측성 — 호스트가 로그/메트릭 제공** — Wasm 내부 자체 로깅은 제한

11. **메모리 한계 + OOM 핸들링** — 호스트에서 감싸기

12. **CI에서 여러 런타임 테스트** — Wasmtime, Wasmer, WasmEdge 호환성

다음 글 예고 — CDN과 엣지 컴퓨팅의 아키텍처

Wasm이 "서버 없는 서버"의 엔진이라면, 그걸 전 세계에 뿌리는 인프라가 **CDN과 엣지 네트워크**다. 다음 글에서는:

- **CDN의 탄생** — Akamai 1998년부터 Cloudflare, Fastly까지

- Anycast 라우팅의 마법

- **캐시 계층 전략** — tiered, pull-through, push

- **엣지 컴퓨팅**의 의미 — Lambda@Edge vs Workers vs Compute@Edge

- Cache Invalidation — "컴퓨터 과학의 두 난제 중 하나"

- **DDoS 방어** — L3/L4/L7, Rate Limiting, Bot Management

- 이미지/비디오 변환의 엣지 처리

- **Zero Trust** 네트워크 — Cloudflare Access, Access Zero Trust

- 상위 CDN 벤더 비교 (Cloudflare/Akamai/Fastly/AWS CloudFront)

"사용자가 1초 안에 페이지를 본다"는 목표 뒤에 얼마나 많은 계층이 있는지, 거기서 Wasm이 어떻게 역할을 하는지 이어서 보자.

> **"인터넷은 평평하지 않다. 지구의 곡률을 따라 CDN이 깔려 있고, 그 위에서 Wasm이 뛰어다닌다."**

현재 단락 (1/283)

2019년 Docker 창시자 Solomon Hykes의 트윗이 화제였다.

작성 글자: 0원문 글자: 9,684작성 단락: 0/283