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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir