بازی با WASM در Docker

ایده بایت کدی که می تواند در هر جایی اجرا شود به زمان شروع JVM برمی گردد (تا جایی که من می دانم). WebAssembly اجرای جدید یک ایده قدیمی است. در حالی که WebAssembly قرار است در مرورگر اجرا شود، Docker اخیراً توانایی خود را برای اجرای کد WASM بدون نیاز به کانتینر اعلام کرده است. در این پست، میخواهم نحوه عملکرد آن را بررسی کنم.
پيش نياز
اجرای WebAssembly یک است بتا ویژگی و نیاز به استفاده دارد containerd
. برای فعال کردن containerd
، به داشبورد Docker Desktop بروید، سپس به تنظیمات > ویژگی های در حال توسعه > ویژگی های بتا > استفاده از ظرف برای ذخیره و کشیدن تصویر بروید.
هشدار داده شود که فعال کردن containerd
قبلا یکی از دموهای Kubernetes من را شکست. تا دلتان بخواهد با WASM بازی کنید، اما به یاد داشته باشید که پیکربندی را بلافاصله پس از آن برگردانید، در غیر این صورت این احتمال وجود دارد که کانتینرهای دانلود شده دیگر اجرا نشوند.
من می خواهم تصاویر معمولی را با WebAssembly مقایسه کنم. از این رو، من به پروژه ای نیاز دارم که بتواند به کد بومی و WASM کامپایل شود. به همین دلیل استفاده از زبان Rust را انتخاب کردم. من یک پروژه ساده با دو داکرفایل خواهم داشت: یکی که به بومی کامپایل می شود و دیگری در WASM کامپایل می شود.
ساخت و ساز به صورت محلی
در اینجا Rust مورد انتظار Hello World است:
fn main() {
println!("Hello, world!");
}
ما می توانیم هدف Webassembly را نصب کرده و به صورت محلی برای مقاصد مقایسه بسازیم:
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release
فایل نسبتا کوچک است:
-rwxr-xr-x 1 nico staff 2.0M Jun 4 15:44 wasm-native.wasm
ساخت تصاویر پایه داکر
را Dockerfile
که تصویر Webassembly را می سازد به صورت زیر است:
FROM rust:1.70-slim-bullseye as build #1
COPY Cargo.toml .
COPY Cargo.lock .
COPY src src
RUN rustup target add wasm32-wasi #2
RUN cargo build --target wasm32-wasi --release #3
FROM scratch #4
COPY --from=build /target/wasm32-wasi/release/wasm-native.wasm wasm.wasm #5
ENTRYPOINT [ "/wasm.wasm" ]
- از آخرین تصویر Rust Docker شروع کنید
- هدف WASM را اضافه کنید
- ساخت، وب اسمبلی را هدف قرار دهید
- از ساخت چند مرحله ای استفاده کنید. از صفر شروع کنید
- فایل Webassembly تولید شده در مرحله قبل را کپی کنید
مواد مرجع از --platform wasi/wasm32
آرگومان هنگام ساخت تصویر داکر. روی دستگاه من کار نمیکنه ممکن است به این دلیل باشد که من از M1 Mac استفاده میکنم یا اسناد باید بهروزرسانی شوند. در هر صورت، من “به طور معمول” را می سازم:
docker build -f Dockerfile-wasm -t docker-wasm:1.0 .
اکنون میتوانیم آن را اجرا کنیم و زمان اجرای WASM پشتیبانی شده را مشخص کنیم:
docker run --runtime=io.containerd.wasmedge.v1 docker-wasm:1.0
برای مقایسه، می توانیم یک تصویر بومی ایجاد کنیم با همین کد:
FROM rust:1.70-slim-bullseye as build
COPY Cargo.toml .
COPY Cargo.lock .
COPY src src
RUN RUSTFLAGS='-C target-feature=+crt-static' cargo build --release #1
FROM scratch #2
COPY --from=build /target/release/wasm-native native
- باینری را خودکفا کنید
- می تواند از صفر شروع شود
اکنون می توانیم اندازه تصاویر را با هم مقایسه کنیم:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-native 1.0 0c227194910a 7 weeks ago 7.09MB
docker-wasm 1.0 f9a88747f798 4 weeks ago 2.61MB
تصویر Webassembly حدود یک سوم بسته باینری بومی است.
ما کمی تقلب می کنیم زیرا زمان اجرا WASM را … در زمان اجرا اضافه می کنیم.
ساخت تصاویر پیچیده تر
بیایید ببینیم چگونه میتوانیم پارامترهایی را به باینری اضافه کنیم و کد را مطابق با آن بهروزرسانی کنیم:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
println!("Hello, world!");
} else {
println!("Hello, {}!", args[1]);
}
}
بیایید تصاویر را دوباره بسازیم و دوباره مقایسه کنیم:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-native 1.0 0c227194910a 7 weeks ago 7.09MB
docker-native 1.1 3ae029030e83 39 minutes ago 7.1MB
docker-wasm 1.0 f9a88747f798 4 weeks ago 2.61MB
docker-wasm 1.1 41e38b68f4e4 39 minutes ago 2.63MB
تماس های HTTP را اجرا می کنید؟
با این کار، به راحتی می توان از خود دور شد و شروع به فکر کردن کرد: اگر بتوانیم تماس های HTTP را اجرا کنیم، چه؟
من از جعبه reqwest استفاده خواهم کرد زیرا با آن آشنا هستم. reqwest
به توکیو متکی است.
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.28", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
اکنون میتوانیم کد را بهروزرسانی کنیم تا درخواستی برای آن ارسال کنیم و نتیجه را چاپ کنیم:
#[tokio::main]
async fn main() {
match get("http://httpbin.org/get").await {
Ok(response) => {
let result = response.json::<GetBody>().await;
match result {
Ok(json) => {
println!("{:#?}", json);
}
Err(err) => {
println!("{:#?}", err)
}
}
}
Err (err) => {
println!("{:#?}", err)
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct GetBody {
args: HashMap<String, String>,
headers: HashMap<String, String>,
origin: String,
url: String,
}
کامپایل این کد محدودیت های WASM را آشکار می کند، هرچند:
#0 12.40 error: Only features sync,macros,io-util,rt,time are supported on wasm.
#0 12.40 --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/lib.rs:488:1
#0 12.40 |
#0 12.40 488 | compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm.");
#0 12.40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WASM چند رشته ای نیست، در حالی که توکیو است به صورت پیش فرض. با این حال، میتوانیم توکیو را برای کار در یک محیط تک رشتهای پیکربندی کنیم. بیایید با استفاده از ویژگی هایی که نیاز داریم شروع کنیم: macros
برای main
عملکرد و rt
برای زمان اجرا توکیو
tokio = { version = "1.28", features = ["rt", "macros"] }
اکنون میتوانیم توکیو را به رشته منحصربفرد محدود کنیم:
#[tokio::main(flavor = "current_thread")]
async fn main() {}
اکنون کامپایل کار می کند. با این حال، هنگام اجرا با مشکلاتی مواجه می شوم:
[2023-06-05 12:22:11.986] [error] instantiation failed: unknown import, Code: 0x62
[2023-06-05 12:22:11.986] [error] When linking module: "__wbindgen_placeholder__" , function name: "__wbindgen_object_drop_ref"
[2023-06-05 12:22:11.986] [error] At AST node: import description
[2023-06-05 12:22:11.986] [error] At AST node: import section
[2023-06-05 12:22:11.986] [error] At AST node: module
docker: Error response from daemon: Others("unknown import"): unknown.
را reqwest
جعبه با محیط WASI کار نمی کند. تا زمانی که این کار انجام نشود، یک چنگال به درستی به نام reqwest_wasi وجود دارد. tokio_wasi جعبه سازگار با WASI است tokio
. توجه داشته باشید که نسخه دومی نیاز به جبران دارد. بیایید جعبه ها را تعویض کنیم:
[dependencies]
reqwest_wasi = { version = "0.11", features = ["json"] }
tokio_wasi = { version = "1.25", features = ["rt", "macros"] }
با جعبه های جدید، کار تالیف و همچنین اجرا می شود. از طرف دیگر، تصویر بومی بدون نقص و با تغییرات جزئی برای Dockerfile کار می کند:
#docker build -f Dockerfile-native -t docker-native:1.2 .
FROM rust:1.70-slim-bullseye as build
COPY Cargo.toml .
COPY Cargo.lock .
COPY src src
RUN apt-get update && apt-get install -y pkg-config libssl-dev #1
RUN cargo build --release
FROM debian:bullseye-slim #2
COPY --from=build /target/release/wasm-native native
ENTRYPOINT [ "/native" ]
- کتابخانه های مورد نیاز برای SSL را نصب کنید
- برای جلوگیری از نصب کتابخانه های اضافی، به یک تصویر پایه کامل تر تغییر دهید
اینم مقایسه نهایی:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-native 1.0 0c227194910a 7 weeks ago 7.09MB
docker-native 1.1 3ae029030e83 22 hours ago 7.1MB
docker-native 1.2 4ff64cf9de46 7 hours ago 123MB
docker-wasm 1.0 1cc78a392477 23 hours ago 2.61MB
docker-wasm 1.1 41e38b68f4e4 22 hours ago 2.63MB
docker-wasm 1.2 6026f5bd789c 18 seconds ago 5.34MB
من با بهینه سازی تصویر بومی سر و کار نداشتم. با این حال، شکست دادن تصویر WASM سخت خواهد بود، زیرا کمتر از 6 مگابایت است!
با این حال، هیچ شانسی برای پیاده سازی سرور Axum وجود ندارد.
نتیجه
من چند تصویر WASM Docker را در این پست پیادهسازی کردم، از سادهترین Hello World تا یک کلاینت HTTP.
در حالی که اکوسیستم فضایی برای بهبود دارد، در حال حاضر می توان از پشتیبانی WASM Docker بهره مند شد. اندازه کوچک تصاویر WASM یک حرفه ای بزرگ است.
کد منبع کامل این پست را می توانید در GitHub بیابید:
فراتر رفتن:
در ابتدا در A Java Geek در 11 ژوئن منتشر شدهفتم، 2023