WASM ile Sunucu Tarafı Uygulama: WASI Rehberi
Sunucu tarafında WebAssembly kullanmak, birkaç yıl önce kulağa fütüristik gelirdi. Ama artık production ortamlarında WASI çalıştıran ekipler var, edge node’larda saniyede milyonlarca istek işleyen WASM modülleri var ve ben de bu yazıda size bunun nasıl çalıştığını, nasıl kurulacağını ve gerçek hayatta ne işe yaradığını anlatacağım.
WASI Nedir ve Neden Önemli?
WebAssembly System Interface, yani WASI, WASM modüllerinin tarayıcı dışında çalışabilmesi için geliştirilmiş bir sistem arayüzü standardıdır. Klasik WebAssembly tarayıcıya özgüydü; dosya sistemi, ağ, çevre değişkenleri gibi OS kaynaklarına erişemiyordu. WASI bu boşluğu kapatıyor.
Düşünün: Rust ile yazdığınız bir uygulama, WASM’a derleniyor. Bu WASM modülü Linux’ta da çalışıyor, Windows’ta da, ARM tabanlı edge cihazında da. Docker container’larına gerek yok, JVM overhead’i yok, native binary’nin platform bağımlılığı yok. Bir kez yaz, her yerde çalıştır sloganını Java’dan çok daha sert bir şekilde hayata geçiriyor.
WASI’nin sağladığı temel yetenekler:
- wasi_snapshot_preview1: Dosya I/O, stdin/stdout/stderr erişimi
- wasi_nn: Makine öğrenmesi inference için nöral ağ arayüzü
- wasi_crypto: Kriptografik operasyonlar
- wasi_http: HTTP istemci/sunucu yetenekleri (component model ile geliyor)
- wasi_sockets: TCP/UDP soket erişimi
Güvenlik modeli de son derece önemli. WASI capability-based security kullanıyor. Modüle açıkça izin vermediğiniz sürece hiçbir sistem kaynağına erişemiyor. Bu, supply chain saldırılarına karşı inanılmaz güçlü bir savunma katmanı oluşturuyor.
Geliştirme Ortamı Kurulumu
Önce araçları yerleştirelim. Ben bu yazıda Ubuntu 22.04 üzerinde çalışıyorum ama adımlar Fedora veya Debian’da da aynı.
# Rust kurulumu (zaten varsa atlayın)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# WASM WASI target'ı ekle
rustup target add wasm32-wasi
# Wasmtime runtime kurulumu
curl https://wasmtime.dev/install.sh -sSf | bash
source ~/.bashrc
# WasmEdge kurulumu (alternatif runtime)
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
source $HOME/.wasmedge/env
# wasm-pack kurulumu
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# Versiyonları doğrula
wasmtime --version
wasmedge --version
Ayrıca Node.js ekosistemiyle çalışacaksanız @bytecodealliance/wasmtime-js paketine de ihtiyacınız olacak. Ama bu yazıda ağırlıklı olarak Rust + Wasmtime kombinasyonunu kullanacağız.
İlk WASI Uygulaması: Dosya İşlemleri
Basit bir şeyle başlayalım. Bir CSV dosyasını okuyup işleyen bir WASM modülü yazacağız.
// src/main.rs
use std::fs;
use std::io::{self, BufRead, Write};
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Kullanım: program <dosya_yolu>");
std::process::exit(1);
}
let dosya_yolu = &args[1];
match fs::File::open(dosya_yolu) {
Ok(dosya) => {
let reader = io::BufReader::new(dosya);
let mut toplam_satir = 0;
let mut toplam_deger: f64 = 0.0;
for satir in reader.lines().skip(1) {
if let Ok(icerik) = satir {
let parcalar: Vec<&str> = icerik.split(',').collect();
if parcalar.len() >= 2 {
if let Ok(deger) = parcalar[1].trim().parse::<f64>() {
toplam_deger += deger;
toplam_satir += 1;
}
}
}
}
println!("İşlenen satır: {}", toplam_satir);
println!("Toplam değer: {:.2}", toplam_deger);
println!("Ortalama: {:.2}", toplam_deger / toplam_satir as f64);
// Sonucu yaz
let mut cikti = fs::File::create("/output/sonuc.txt")
.expect("Çıktı dosyası oluşturulamadı");
writeln!(cikti, "Satır: {}, Ortalama: {:.2}", toplam_satir, toplam_deger / toplam_satir as f64)
.expect("Yazma başarısız");
}
Err(e) => {
eprintln!("Dosya açılamadı: {}", e);
std::process::exit(1);
}
}
}
Derleme ve çalıştırma:
# Cargo.toml'da proje adını csv-processor olarak ayarlayın
cargo build --target wasm32-wasi --release
# Binary'nin nerede olduğunu kontrol et
ls -lh target/wasm32-wasi/release/csv-processor.wasm
# Wasmtime ile çalıştır
# --dir ile dizin erişim izni veriyoruz (capability-based security)
wasmtime run
--dir /data/input::/input
--dir /data/output::/output
target/wasm32-wasi/release/csv-processor.wasm
-- /input/veriler.csv
# Daha kısıtlı: sadece tek dosyaya erişim
wasmtime run
--dir /data/input/veriler.csv::/input/veriler.csv
target/wasm32-wasi/release/csv-processor.wasm
-- /input/veriler.csv
Buradaki --dir parametresi kritik. Sol taraf host’taki gerçek yol, sağ taraf WASM modülünün göreceği sanal yol. Modüle sadece ihtiyacı olan dizini açıyorsunuz.
HTTP Sunucusu ile Gerçek Dünya Senaryosu
Şimdi daha pratik bir şeye geçelim. Bir microservice yazacağız. Cloudflare Workers veya Fastly Compute@Edge gibi platformlara deploy edilebilecek bir HTTP handler.
// Cargo.toml
// [dependencies]
// warp = "0.3"
// tokio = { version = "1", features = ["full"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
use std::collections::HashMap;
use std::io::{self, Read};
#[derive(serde::Deserialize)]
struct Istek {
kullanici_id: u64,
islem: String,
miktar: f64,
}
#[derive(serde::Serialize)]
struct Yanit {
basari: bool,
mesaj: String,
islem_id: String,
}
fn main() {
// WASI HTTP için stdin'den istek okuyoruz
let mut istek_body = String::new();
io::stdin().read_to_string(&mut istek_body)
.expect("Stdin okunamadı");
// Çevre değişkenlerinden konfigürasyon al
let env_vars: HashMap<String, String> = std::env::vars().collect();
let max_miktar: f64 = env_vars
.get("MAX_ISLEM_MIKTARI")
.and_then(|v| v.parse().ok())
.unwrap_or(10000.0);
let yanit = match serde_json::from_str::<Istek>(&istek_body) {
Ok(istek) => {
if istek.miktar > max_miktar {
Yanit {
basari: false,
mesaj: format!("Miktar limiti aşıldı: max {}", max_miktar),
islem_id: String::new(),
}
} else {
// Gerçekte burada DB işlemi, validasyon vs. olurdu
let islem_id = format!("TXN-{}-{}", istek.kullanici_id,
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis());
Yanit {
basari: true,
mesaj: format!("{} işlemi başarılı", istek.islem),
islem_id,
}
}
}
Err(e) => Yanit {
basari: false,
mesaj: format!("Geçersiz istek formatı: {}", e),
islem_id: String::new(),
}
};
// stdout'a JSON yanıt yaz
println!("{}", serde_json::to_string(&yanit).unwrap());
}
# Test edelim
echo '{"kullanici_id": 42, "islem": "para_transferi", "miktar": 500.0}' |
wasmtime run
--env MAX_ISLEM_MIKTARI=1000
target/wasm32-wasi/release/payment-handler.wasm
# Yük testi için hey kullanabiliriz
# Önce bir wrapper script yazalım
cat > /usr/local/bin/wasm-handler.sh << 'EOF'
#!/bin/bash
INPUT=$(cat)
echo "$INPUT" | wasmtime run
--env MAX_ISLEM_MIKTARI=10000
/opt/wasm/payment-handler.wasm
EOF
chmod +x /usr/local/bin/wasm-handler.sh
Wasmtime’ı Embedding: Rust Host Uygulaması
Gerçek production senaryolarında WASM modüllerini bir host uygulamaya gömersiniz. Bu sayede başlatma maliyetini sıfıra indirebilirsiniz.
// host/src/main.rs - WASM modülünü çalıştıran host uygulama
use wasmtime::*;
use wasmtime_wasi::WasiCtxBuilder;
use std::time::Instant;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Engine bir kez oluşturulur, thread-safe
let engine = Engine::new(Config::new().async_support(false))?;
// Modül de bir kez derlenir ve cache'lenir
let module = Module::from_file(&engine, "target/wasm32-wasi/release/islemci.wasm")?;
println!("Modül yüklendi, istekler bekleniyor...");
// Her istek için yeni bir instance oluşturulur (milisaniyeler içinde)
for i in 0..5 {
let baslangic = Instant::now();
let wasi = WasiCtxBuilder::new()
.inherit_stdio()
.env("ISTEK_ID", &i.to_string())?
.env("ORTAM", "production")?
// Sadece gerekli dizine erişim
.preopened_dir(
wasmtime_wasi::sync::Dir::open_ambient_dir(
"/tmp/wasm-sandbox",
wasmtime_wasi::ambient_authority()
)?,
"/sandbox"
)?
.build();
let mut store = Store::new(&engine, wasi);
let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
let instance = linker.instantiate(&mut store, &module)?;
let run = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
run.call(&mut store, ())?;
println!("İstek {} tamamlandı: {:?}", i, baslangic.elapsed());
}
Ok(())
}
# Host uygulamayı derle
cd host
cargo build --release
# Sandbox dizinini oluştur
mkdir -p /tmp/wasm-sandbox
# Çalıştır
./target/release/wasm-host
Buradaki kritik nokta: modül bir kez derleniyor, her istek için sadece yeni bir Store ve Instance oluşturuluyor. Bu, cold start problemini büyük ölçüde çözüyor.
Nginx ile WASM Entegrasyonu
Nginx Unit, WASM modüllerini doğrudan HTTP handler olarak çalıştırabiliyor. Kurulumu yapalım:
# Nginx Unit kurulumu (Ubuntu)
curl --output /usr/share/keyrings/nginx-keyring.gpg
https://unit.nginx.org/keys/nginx-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg]
https://packages.nginx.org/unit/ubuntu/ jammy unit"
> /etc/apt/sources.list.d/unit.list
apt-get update
apt-get install unit unit-wasm
# WASM modülünü kopyala
mkdir -p /opt/unit/wasm
cp target/wasm32-wasi/release/web-handler.wasm /opt/unit/wasm/
# Nginx Unit konfigürasyonu
cat > /etc/unit/config.json << 'EOF'
{
"listeners": {
"*:8080": {
"pass": "applications/wasm-app"
}
},
"applications": {
"wasm-app": {
"type": "wasm",
"module": "/opt/unit/wasm/web-handler.wasm",
"request_handler": "handle_request",
"malloc_handler": "luw_malloc_handler",
"free_handler": "luw_free_handler",
"module_init_handler": "luw_module_init_handler",
"module_end_handler": "luw_module_end_handler"
}
}
}
EOF
# Konfigürasyonu uygula
curl -X PUT --data-binary @/etc/unit/config.json
--unix-socket /var/run/control.unit.sock
http://localhost/config
# Test
curl http://localhost:8080/api/test
Performans Karşılaştırması ve Profiling
WASM’ın ne kadar hızlı olduğunu görmek için basit bir benchmark yapalım:
# hyperfine kurulumu
cargo install hyperfine
# Native binary derleme
cargo build --release
cargo build --target wasm32-wasi --release
# Karşılaştırmalı benchmark
hyperfine
--warmup 10
--runs 100
'target/release/islemci /data/test.csv'
'wasmtime run --dir /data::/data target/wasm32-wasi/release/islemci.wasm -- /data/test.csv'
# Wasmtime cache'i aktifleştir (tekrar eden çalıştırmalar için)
mkdir -p ~/.cache/wasmtime
export WASMTIME_CACHE_CONFIG=~/.config/wasmtime/config.toml
cat > ~/.config/wasmtime/config.toml << 'EOF'
[cache]
enabled = true
directory = "/home/user/.cache/wasmtime"
cleanup-interval = "1d"
files-total-size-soft-limit = "1Gi"
EOF
# Cache aktifken tekrar test
hyperfine
--warmup 5
'wasmtime run --config ~/.config/wasmtime/config.toml
--dir /data::/data
target/wasm32-wasi/release/islemci.wasm -- /data/test.csv'
# perf ile profiling (Linux)
perf stat wasmtime run
--dir /data::/data
target/wasm32-wasi/release/islemci.wasm
-- /data/test.csv
Gerçek sonuçlara bakıldığında, native binary ile WASM arasındaki fark genellikle yüzde 5-20 arasında kalıyor. Bu, JVM veya Python yorumlayıcısına kıyasla inanılmaz küçük bir overhead.
Docker ile WASM: Konteyner Alternatifleri
Docker artık WASM runtime’larını destekliyor. Bu, container image boyutunu dramatik şekilde düşürüyor:
# Docker Desktop'ta WASM desteğini aktifleştir
# Settings > Features in development > Use containerd for pulling and storing images
# containerd shim kurulumu
wget https://github.com/containerd/runwasi/releases/download/v0.3.0/
containerd-shim-wasmtime-v1-linux-amd64.tar.gz
tar -xzf containerd-shim-wasmtime-v1-linux-amd64.tar.gz
mv containerd-shim-wasmtime-v1 /usr/local/bin/
# WASM için minimal Dockerfile
cat > Dockerfile.wasm << 'EOF'
FROM scratch
COPY target/wasm32-wasi/release/web-handler.wasm /app.wasm
ENTRYPOINT ["/app.wasm"]
EOF
# Image boyutunu karşılaştır
docker build -f Dockerfile.wasm -t myapp:wasm .
docker build -f Dockerfile -t myapp:normal .
docker images | grep myapp
# myapp:wasm 1.2MB
# myapp:normal 180MB
# WASM container'ı çalıştır
docker run --runtime=io.containerd.wasmtime.v1
--platform=wasi/wasm
-e MAX_ISLEM=1000
myapp:wasm
Kubernetes’te WASM Workload’ları
SpinKube veya Kwasm operatörü ile Kubernetes’te WASM çalıştırabilirsiniz:
# Kwasm operatörünü kur
helm repo add kwasm http://kwasm.sh/kwasm-operator/
helm install -n kwasm --create-namespace kwasm-operator kwasm/kwasm-operator
# Node'ları WASM için hazırla
kubectl annotate node mynode kwasm.sh/kwasm-node=true
# WASM workload deploy et
cat > wasm-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: wasm-islemci
spec:
replicas: 3
selector:
matchLabels:
app: wasm-islemci
template:
metadata:
labels:
app: wasm-islemci
annotations:
module.wasm.image/variant: compat-smart
spec:
runtimeClassName: wasmtime
containers:
- name: islemci
image: myrepo/wasm-islemci:latest
resources:
limits:
memory: "64Mi"
cpu: "200m"
env:
- name: ORTAM
value: "production"
- name: MAX_ISLEM_MIKTARI
value: "50000"
EOF
kubectl apply -f wasm-deployment.yaml
kubectl get pods -w
Güvenlik Hardening
WASI’nin sandbox modelini maksimum düzeyde kullanmak için:
# Sadece gerekli izinleri ver
wasmtime run
--deny-unknown-imports
--dir /data/readonly::/input:readonly
--dir /tmp/output::/output
--env APP_ENV=production
--env LOG_LEVEL=warn
--fuel 1000000000
--max-wasm-stack 1048576
target/wasm32-wasi/release/app.wasm
# --fuel: WASM instruction sayısını sınırla (DoS koruması)
# --max-wasm-stack: Stack overflow koruması
# --deny-unknown-imports: Tanımsız importları reddet
# readonly: Dizine sadece okuma erişimi ver
# seccomp ile ek katman (systemd service örneği)
cat > /etc/systemd/system/wasm-app.service << 'EOF'
[Unit]
Description=WASM Uygulama Servisi
After=network.target
[Service]
Type=simple
User=wasm-runner
Group=wasm-runner
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/wasm-app/output
CapabilityBoundingSet=
SystemCallFilter=@basic-io @file-system @process
ExecStart=/usr/bin/wasmtime run
--dir /var/lib/wasm-app/input::/input:readonly
--dir /var/lib/wasm-app/output::/output
/opt/wasm/app.wasm
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now wasm-app
Sonuç
WASI ile sunucu tarafı WebAssembly, artık deneysel bir teknoloji olmaktan çıkmış durumda. Cloudflare Workers zaten milyarlarca isteği WASM ile işliyor, Fastly Compute@Edge production’da, Fermyon Spin ile full-stack WASM uygulamaları yazılıyor.
Sysadmin perspektifinden değerlendirdiğimde şu avantajlar özellikle çarpıcı:
- Güvenlik izolasyonu: Her modül kendi sandbox’ında çalışıyor, capability-based access control ile sistem kaynakları en az ayrıcalık prensibiyle paylaşılıyor
- Kaynak verimliliği: 64MB memory limit ile çalışan bir WASM servisi, 512MB bekleyen bir Docker container’ının çok önünde
- Soğuk start: Modüller cache’lendiğinde yeni instance başlatma süresi mikrosaniye mertebesinde
- Taşınabilirlik: Bir kez derlenen binary, x86, ARM, RISC-V üzerinde değişiklik gerektirmeden çalışıyor
Elbinde eksikler de var. WASI preview2 hala aktif geliştirme altında, component model ekosistemi olgunlaşıyor ama henüz tam değil, async I/O desteği karmaşıklaşabiliyor. Ancak trajectory çok net: önümüzdeki 2-3 yıl içinde edge computing ve microservice alanında WASM/WASI, Docker’ın bugün oynadığı rolü oynamaya başlayacak.
Şu an için en iyi başlangıç noktası: CPU-yoğun işlemlerinizi (veri dönüştürme, parsing, encoding) WASM modülleri olarak yazıp mevcut altyapınıza embedding yöntemiyle entegre etmek. Riski minimal, kazancı somut ve öğrenme eğrisi sandığınızdan çok daha düz.
