Wippy에서 Rust 실행
Rust WebAssembly 컴포넌트를 빌드하고 Wippy에 등록한 뒤 함수, CLI, HTTP 엔트리로 노출합니다.
분류: 외부 Rust 컴포넌트 도구 체인을 사용하는 실행 가능한 튜토리얼. WIT, Rust 구현, Wippy 레지스트리, 무결성 해시 워크플로, 명령, 예상 결과, 실패 확인 절차를 제공합니다.
만들 컴포넌트
내보낸 함수 네 개를 갖는 Rust 컴포넌트를 만듭니다.
- greet — 이름을 받아 인사말을 반환합니다.
- add — 두 정수를 더합니다.
- fibonacci — n번째 피보나치 수를 계산합니다.
- list-files — 마운트된 디렉터리의 파일을 나열합니다.
Wippy 애플리케이션은 이 내보내기를 호출 가능한 함수, CLI 명령, HTTP 엔드포인트로 등록합니다.
사전 요구 사항
- Wippy 런타임
v0.3.32a wasm32-wasip1대상이 설치된 Rust 도구 체인- 작동하는 C 도구 체인. Linux에서
cargo-component는 OpenSSL 개발 라이브러리도 필요로 합니다. - 이 튜토리얼에서 사용하는 cargo-component 0.21.1
rustup target add wasm32-wasip1
cargo install cargo-component --version 0.21.1 --locked
생성되는 컴포넌트 골격과 Wippy 디렉터리를 만듭니다.
mkdir rust-wasm-demo
cd rust-wasm-demo
cargo component new --lib demo
mkdir -p app/src/demo/wasm
PowerShell에서는 다음 명령을 사용합니다.
New-Item -ItemType Directory -Path rust-wasm-demo
Set-Location rust-wasm-demo
cargo component new --lib demo
New-Item -ItemType Directory -Path app\src\demo\wasm -Force
cargo component new는 호환되는 Cargo.toml, src/lib.rs, WIT 파일을 만들고 이후 src/bindings.rs를 다시 생성합니다. 생성된 wit-bindgen-rt 버전을 설치된 cargo-component와 짝지어 유지하세요. 도구는 이 인터페이스를 실험적이라고 설명하며 버전 간 생성 코드 호환성을 보장하지 않습니다.
프로젝트 구조
rust-wasm-demo/
├── demo/ # Rust component
│ ├── Cargo.toml
│ ├── wit/
│ │ └── world.wit # WIT interface
│ └── src/
│ ├── bindings.rs # generated by cargo-component
│ └── lib.rs # implementation
└── app/ # Wippy application
├── wippy.lock
└── src/
├── _index.yaml # Infrastructure
└── demo/
├── _index.yaml # CLI processes
└── wasm/
├── _index.yaml # WASM entries
└── demo_component.wasm # Compiled binary
1단계: WIT 인터페이스 생성
WebAssembly Interface Types(WIT)는 호스트와 게스트 컴포넌트 사이의 계약을 정의합니다.
demo/wit/world.wit를 만듭니다.
package component:demo;
world demo {
export greet: func(name: string) -> string;
export add: func(a: s32, b: s32) -> s32;
export fibonacci: func(n: u32) -> u64;
export list-files: func(path: string) -> string;
}
각 내보내기는 Wippy에서 호출할 수 있는 함수가 됩니다.
2단계: Rust로 구현
생성된 demo/Cargo.toml을 유지합니다. 패키지 메타데이터는 WIT 패키지와 일치하는 component:demo를 대상으로 해야 하며, 라이브러리 크레이트 유형은 cdylib로 유지해야 합니다.
demo/src/lib.rs를 만듭니다.
#[allow(warnings)]
mod bindings;
use bindings::Guest;
struct Component;
impl Guest for Component {
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let next = a + b;
a = b;
b = next;
}
b
}
fn list_files(path: String) -> String {
let mut result = String::new();
match std::fs::read_dir(&path) {
Ok(entries) => {
for entry in entries {
match entry {
Ok(e) => {
let name = e.file_name().to_string_lossy().to_string();
let meta = e.metadata();
let (kind, size) = match meta {
Ok(m) => {
let kind = if m.is_dir() { "dir" } else { "file" };
(kind, m.len())
}
Err(_) => ("?", 0),
};
let line = format!("{:<6} {:>8} {}", kind, size, name);
println!("{}", line);
result.push_str(&line);
result.push('\n');
}
Err(e) => {
let line = format!("error: {}", e);
eprintln!("{}", line);
result.push_str(&line);
result.push('\n');
}
}
}
}
Err(e) => {
let line = format!("cannot read {}: {}", path, e);
eprintln!("{}", line);
result.push_str(&line);
result.push('\n');
}
}
result
}
}
bindings::export!(Component with_types_in bindings);
bindings 모듈은 WIT 정의에서 cargo-component가 생성합니다.
3단계: 컴포넌트 빌드
cd demo
cargo component build --release
이 명령은 target/wasm32-wasip1/release/demo.wasm을 만듭니다. Wippy 애플리케이션으로 복사합니다.
mkdir -p ../app/src/demo/wasm
cp target/wasm32-wasip1/release/demo.wasm ../app/src/demo/wasm/demo_component.wasm
PowerShell에서는 다음 명령을 사용합니다.
New-Item -ItemType Directory -Path ..\app\src\demo\wasm -Force
Copy-Item -LiteralPath target\wasm32-wasip1\release\demo.wasm `
-Destination ..\app\src\demo\wasm\demo_component.wasm
무결성 검증에 사용할 SHA-256 해시를 가져옵니다.
sha256sum ../app/src/demo/wasm/demo_component.wasm
PowerShell에서는 다음 명령을 사용합니다.
(Get-FileHash ..\app\src\demo\wasm\demo_component.wasm -Algorithm SHA256).Hash.ToLowerInvariant()
아래의 모든 YOUR_HASH_HERE에 소문자 16진수 문자 64개를 복사합니다. 최종 필드는 sha256:<64-hex-characters> 형식이어야 합니다. Rust 소스나 원래 빌드 경로가 아니라 복사된 바이너리의 해시입니다.
4단계: Wippy 애플리케이션
인프라
app/src/_index.yaml을 만듭니다.
version: "1.0"
namespace: demo
entries:
- name: gateway
kind: http.service
meta:
comment: HTTP server
addr: ":8090"
lifecycle:
auto_start: true
- name: api
kind: http.router
meta:
comment: Public API router
server: demo:gateway
prefix: /
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: terminal
kind: terminal.host
lifecycle:
auto_start: true
- name: policy
kind: security.policy
meta:
comment: Grants access to mounted filesystems and WASM functions
policy:
actions:
- fs.get
- funcs.call
resources: "*"
effect: allow
파일시스템을 WASM 모듈에 마운트하는 것과 WASM 함수를 호출하는 것은 모두 보호되는 액션입니다. 이 정책이 두 액션을 허용하며, 이를 필요로 하는 엔트리가 정책을 참조합니다.
WASM 함수
app/src/demo/wasm/_index.yaml을 만듭니다.
version: "1.0"
namespace: demo.wasm
entries:
- name: assets
kind: fs.directory
meta:
comment: Filesystem with WASM binaries
directory: ./src/demo/wasm
- name: greet_function
kind: function.wasm
meta:
comment: Greet function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: greet
pool:
type: inline
- name: add_function
kind: function.wasm
meta:
comment: Add function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: add
pool:
type: inline
- name: fibonacci_function
kind: function.wasm
meta:
comment: Fibonacci function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: fibonacci
pool:
type: inline
핵심 사항:
- 하나의
fs.directory엔트리가 WASM 바이너리를 제공합니다. - 여러 함수가 서로 다른
method값으로 같은 바이너리를 참조합니다. hash필드는 로드 시 바이너리 무결성을 확인합니다.inline풀은 하나의 준비된 인스턴스를 통해 호출을 직렬화합니다. 동기 호출 사이에 호출별 실행 상태를 초기화합니다. 동시 워커가 필요하면 다른 풀 유형을 사용하세요.
WASI를 사용하는 함수
list-files 함수는 파일 시스템에 접근하므로 WASI 가져오기가 필요합니다.
- name: list_files_function
kind: function.wasm
meta:
comment: Filesystem listing with WASI mounts
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: list-files
imports:
- wasi:cli
- wasi:io
- wasi:clocks
- wasi:filesystem
wasi:
mounts:
- fs: demo.wasm:assets
guest: /data
pool:
type: inline
wasi.mounts 섹션은 Wippy 파일 시스템 엔트리를 게스트 경로에 매핑합니다. WASM 모듈 안에서 /data는 demo.wasm:assets 디렉터리를 가리킵니다.
CLI 명령
app/src/demo/_index.yaml을 만듭니다.
version: "1.0"
namespace: demo.cli
entries:
- name: wasm_cli_policy
kind: security.policy
policy:
actions:
- fs.get
resources:
- demo.wasm:assets
effect: allow
- name: ls
kind: process.wasm
meta:
comment: List files from mounted WASI filesystem
command:
name: ls
short: List files from mounted directory
security:
actor: {id: demo.cli:ls}
policies: [demo:policy]
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: list-files
imports:
- wasi:cli
- wasi:io
- wasi:clocks
- wasi:filesystem
wasi:
mounts:
- fs: demo.wasm:assets
guest: /data
meta.command 블록은 프로세스를 이름이 있는 CLI 명령으로 등록합니다. greet 명령은 문자열 연산만 사용하므로 WASI 임포트가 필요 없습니다. ls 명령은 파일시스템 접근이 필요하므로, 마운트를 허용하는 보안 컨텍스트도 함께 지닙니다.
HTTP 엔드포인트
app/src/demo/wasm/_index.yaml에 다음을 추가합니다.
- name: http_greet
kind: function.wasm
meta:
comment: Greet exposed via wasi-http transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: greet
transport: wasi-http
pool:
type: inline
- name: http_greet_endpoint
kind: http.endpoint
meta:
comment: HTTP POST endpoint for WASM greet
router: demo:api
method: POST
path: /greet
func: http_greet
wasi-http 전송은 HTTP 요청/응답 컨텍스트를 WASM 인수와 결과에 매핑합니다.
5단계: 초기화 및 실행
cd app
wippy init
CLI 명령 실행
# List available commands
wippy run list
Available commands:
greet Greet someone via WASM (demo.cli:greet)
ls List files from mounted directory (demo.cli:ls)
Run with: wippy run <command>
명령 이름 뒤의 인자는 익스포트된 함수에 문자열 파라미터로 전달되므로, 각 명령은 WIT 시그니처가 선언한 인자를 정확히 받습니다:
# Run greet
wippy run greet World
Hello, World!
# Run ls to list mounted directory
wippy run ls /data
명령은 최소한 demo_component.wasm과 파일 크기를 출력하고 상태 0으로 종료되어야 합니다. Wippy는 임의의 process.wasm 반환 페이로드를 출력하지 않으므로 CLI 예제에서는 WASI stdout에 쓰는 Rust 함수를 사용합니다.
서비스로 실행
wippy run
포트 8090에서 HTTP 서버가 시작됩니다. wasi-http 트랜스포트는 요청 본문을 함수의 단일 문자열 인자로 전달합니다:
curl -X POST http://localhost:8090/greet -d 'World'
Hello, World!
Lua에서 호출
WASM 함수는 Lua 함수와 동일한 방식으로 호출됩니다. 호출하는 프로세스에는 대상에 대한 funcs.call 권한이 필요하며, demo:policy가 이를 부여합니다:
local funcs = require("funcs")
local greeting, err = funcs.call("demo.wasm:greet_function", "World")
-- greeting: "Hello, World!"
local sum, err = funcs.call("demo.wasm:add_function", 6, 7)
-- sum: 13
local fib, err = funcs.call("demo.wasm:fibonacci_function", 10)
-- fib: 55
문제 해결과 정리
cargo component를 알 수 없다는 오류가 나오면 설치한 뒤cargo component build를 다시 실행하세요. 이 구성에서 일반cargo build는 같은 바인딩이나 컴포넌트 출력을 생성하지 않습니다.- 첫 빌드 전에
src/bindings.rs가 없는 것은 정상입니다.cargo component build후에도 파일이 없다면 WIT 패키지 또는 컴포넌트 메타데이터를 해석하지 못한 것입니다. 바이너리를 복사하기 전에 해당 빌드 오류를 해결하세요. WASM hash mismatch는 문서화된 다이제스트를 계산한 뒤 바이너리가 변경되었거나 자리표시자가 남아 있다는 뜻입니다. 릴리스 바이너리를 다시 복사하고 다이제스트를 다시 계산한 뒤 참조하는 모든 엔트리를 업데이트하세요.- 가져오기 인스턴스화 오류는 컴포넌트가 엔트리에서 생략한 호스트 프로필을 가져온다는 뜻입니다. 파일 시스템 예제에는 문서화된
wasi:cli,wasi:io,wasi:clocks,wasi:filesystem가져오기를 유지하세요. cannot read /data는wasi.mounts게스트 경로나 해당 파일 시스템 엔트리가 레지스트리와 일치하지 않는다는 뜻입니다.- Ctrl+C로 HTTP 런타임을 중지합니다. Rust 빌드 출력은
demo/target/아래에 남습니다. 생성된 산출물을 정리하려면 이 디렉터리와 복사된.wasm파일을 제거하세요.