برنامه نویسی

بازی با 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" ]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  1. از آخرین تصویر Rust Docker شروع کنید
  2. هدف WASM را اضافه کنید
  3. ساخت، وب اسمبلی را هدف قرار دهید
  4. از ساخت چند مرحله ای استفاده کنید. از صفر شروع کنید
  5. فایل 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
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  1. باینری را خودکفا کنید
  2. می تواند از صفر شروع شود

اکنون می توانیم اندازه تصاویر را با هم مقایسه کنیم:

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" ]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  1. کتابخانه های مورد نیاز برای SSL را نصب کنید
  2. برای جلوگیری از نصب کتابخانه های اضافی، به یک تصویر پایه کامل تر تغییر دهید

اینم مقایسه نهایی:

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

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا