WASI ile WebAssembly’yi Tarayıcı Dışında Çalıştırma

WebAssembly’nin tarayıcıda çalışması harika bir şey, bunu kabul edelim. Ama asıl heyecan verici olan kısım, WASI (WebAssembly System Interface) ile birlikte bu teknolojinin tarayıcının dışına çıkması. Sunucularda, edge node’larında, hatta küçük IoT cihazlarında bile çalışabilen, güvenli sandbox içinde izole edilmiş, platform bağımsız binary’ler oluşturabilmek… Sistem yöneticisi olarak bunu ilk duyduğumda “bu Docker’ın yerini alabilir mi?” diye düşünmüştüm. Şimdi ise şunu söyleyebilirim: farklı bir araç, ama gerçekten güçlü.

WASI Nedir ve Neden Önemli?

WebAssembly başlangıçta tarayıcı için tasarlanmıştı. C, C++, Rust gibi dillerde yazılmış kodları tarayıcıda hızlıca çalıştırmak için mükemmel bir çözümdü. Ancak tarayıcı dışında kullanmak istediğinizde bir sorun çıkıyordu: WebAssembly modüllerinin dosya sistemi, ağ soketleri, ortam değişkenleri gibi işletim sistemi kaynaklarına erişim standardı yoktu.

İşte WASI tam bu noktada devreye giriyor. WASI (WebAssembly System Interface), WASM modüllerinin işletim sistemi kaynaklarına güvenli ve standart bir şekilde erişmesini sağlayan bir arayüz standardı. Linux’taki POSIX ne ise, WebAssembly için WASI odur diyebiliriz.

WASI’nin temel prensibi capability-based security modelidir. Yani bir WASM modülü, sadece ona açıkça verilmiş izinlere sahiptir. Dosya sistemine erişmek istiyorsa, hangi dizinlere erişebileceği önceden tanımlanmalıdır. Ağa çıkmak istiyorsa o izin de ayrıca verilmelidir. Bu model, Docker container’larından bile daha granüler bir güvenlik katmanı sağlar.

Solomon Hykes (Docker’ın kurucusu) 2019’da şöyle bir tweet atmıştı: “Eğer WASM ve WASI 2008’de var olsaydı, Docker’ı yaratmamıza gerek kalmazdı.” Bu söz konuyu oldukça iyi özetliyor.

WASI Runtime’ları: Hangi Araçla Çalışacaksın?

WASM modüllerini tarayıcı dışında çalıştırmak için bir runtime’a ihtiyacın var. Şu an piyasada birkaç popüler seçenek mevcut:

  • Wasmtime: Bytecode Alliance tarafından geliştirilen, Rust ile yazılmış, en stabil seçeneklerden biri
  • Wasmer: Çoklu backend desteği olan, Python, Go, Ruby gibi dillerde embedding için uygun
  • WasmEdge: Cloud-native ve edge computing senaryoları için optimize edilmiş, CNCF projesi
  • WAMR (WebAssembly Micro Runtime): IoT ve gömülü sistemler için Intel tarafından geliştirilen hafif runtime

Hadi Wasmtime ile başlayalım, zira üretim ortamlarında en yaygın kullanılan bu.

# Wasmtime kurulumu (Linux/macOS)
curl https://wasmtime.dev/install.sh -sSf | bash

# Kurulumu doğrula
wasmtime --version

# Wasmer kurulumu (alternatif)
curl https://get.wasmer.io -sSfL | sh
wasmer --version

# WasmEdge kurulumu
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash

İlk WASM Modülünü Oluşturalım

Rust ile başlamak en mantıklısı çünkü Rust’ın WASI desteği oldukça olgun. Önce Rust toolchain’e WASI target’ı ekleyelim:

# Rust kurulu değilse
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# WASI target'ı ekle
rustup target add wasm32-wasi

# Yeni proje oluştur
cargo new wasi-demo
cd wasi-demo

Basit bir dosya okuma/yazma örneği yapalım. Bu örnek WASI’nin capability modelini görmek için güzel:

// src/main.rs
use std::fs;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    
    if args.len() < 2 {
        eprintln!("Kullanim: program <dosya_adi>");
        std::process::exit(1);
    }
    
    let dosya_adi = &args[1];
    
    match fs::read_to_string(dosya_adi) {
        Ok(icerik) => {
            println!("Dosya basariyla okundu: {} karakter", icerik.len());
            println!("Icerik:n{}", icerik);
        },
        Err(e) => {
            eprintln!("Hata: {}", e);
            std::process::exit(1);
        }
    }
}
# WASI hedefi için derle
cargo build --target wasm32-wasi --release

# Oluşan dosyanın boyutuna bak
ls -lh target/wasm32-wasi/release/wasi-demo.wasm

# Çalıştır - dikkat et, dizin izni vermeden dosyaya erişemez
echo "Merhaba WASI!" > test.txt

# Bu hata verir (dosya sistemine erişim yok)
wasmtime target/wasm32-wasi/release/wasi-demo.wasm test.txt

# Bu çalışır (mevcut dizine erişim izni verdik)
wasmtime --dir=. target/wasm32-wasi/release/wasi-demo.wasm test.txt

Fark ettiysen --dir=. parametresi olmadan modül dosyaya erişemiyor. İşte bu WASI’nin capability modeli. Sadece açıkça verilen kaynaklara erişim var.

Gerçek Dünya Senaryosu 1: Log İşleme Servisi

Diyelim ki farklı müşterilerimiz için log analiz scriptleri çalıştırıyoruz. Her müşterinin kendi log dizini var ve birbirlerinin loglarına erişmemesi lazım. Normalde bu için ayrı container’lar veya VM’ler kullanırdık. WASI ile çok daha hafif bir çözüm mümkün:

# Dizin yapısını oluştur
mkdir -p /opt/log-processor/{modules,logs/{musteri-a,musteri-b},configs}

# Müşteri A logları
echo "2024-01-15 10:23:45 ERROR Database connection failed" > /opt/log-processor/logs/musteri-a/app.log
echo "2024-01-15 10:24:12 INFO Retry successful" >> /opt/log-processor/logs/musteri-a/app.log

# Müşteri A için WASM modülünü çalıştır - sadece kendi log dizinine erişimi var
wasmtime 
  --dir=/opt/log-processor/logs/musteri-a 
  --env LOG_LEVEL=ERROR 
  /opt/log-processor/modules/log-analyzer.wasm 
  app.log

# Müşteri B'nin log dizinine erişim deneyi - başarısız olur
wasmtime 
  --dir=/opt/log-processor/logs/musteri-a 
  /opt/log-processor/modules/log-analyzer.wasm 
  /opt/log-processor/logs/musteri-b/app.log

Bu yapıyla her müşteri için ayrı container ayağa kaldırmaya gerek kalmıyor. Modül saniyenin altında başlıyor ve izole kalıyor.

Gerçek Dünya Senaryosu 2: Edge Computing ile CDN Cache Mantığı

Edge computing’de WASM’ın parladığı alan burası. Cloudflare Workers, Fastly Compute@Edge gibi platformlar WASM/WASI kullanıyor. Kendi nginx veya Caddy tabanlı edge node’unuzda özel cache mantığı çalıştırabilirsiniz.

WasmEdge ile HTTP isteklerini işleyen bir örnek:

# WasmEdge ile HTTP sunucu modülü çalıştırma
# wasmedge_wasi_socket feature'ı ile ağ erişimi mümkün

# Önce WasmEdge'i ağ yetenekleriyle kur
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | 
  bash -s -- --plugins wasmedge_httpsreq

# Edge modülünü çalıştır (ağ erişimi açıkça verilmeli)
wasmedge --nn-preload default:GGML:AUTO:model.gguf 
  edge-handler.wasm

Nginx ile entegrasyon için proxy_pass kullanabilirsin:

# /etc/nginx/sites-available/wasm-edge.conf
cat > /etc/nginx/sites-available/wasm-edge.conf << 'EOF'
upstream wasm_backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 80;
    server_name edge.sirketim.com;
    
    location /api/ {
        proxy_pass http://wasm_backend;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # WASM işlemi hızlı, timeout düşük tutabilirsin
        proxy_connect_timeout 5s;
        proxy_read_timeout 10s;
    }
}
EOF

nginx -t && systemctl reload nginx

Kubernetes ile WASM: Kraken Runtime

Kubernetes üzerinde WASM modüllerini doğrudan çalıştırmak artık mümkün. containerd-shim-spin ve runwasi projeleri bunu sağlıyor. Bu yaklaşımda her pod bir container yerine bir WASM modülü çalıştırıyor:

# containerd için runwasi kurulumu
wget https://github.com/containerd/runwasi/releases/latest/download/containerd-shim-wasmtime-v1
chmod +x containerd-shim-wasmtime-v1
mv containerd-shim-wasmtime-v1 /usr/local/bin/

# containerd config'ini güncelle
cat >> /etc/containerd/config.toml << 'EOF'
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmtime]
  runtime_type = "io.containerd.wasmtime.v1"
EOF

systemctl restart containerd

# WASM workload için RuntimeClass oluştur
kubectl apply -f - << 'EOF'
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: wasmtime
handler: wasmtime
EOF

# WASM pod'u deploy et
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: wasm-demo
  annotations:
    module.wasm.image/variant: compat-smart
spec:
  runtimeClassName: wasmtime
  containers:
  - name: wasm-app
    image: ghcr.io/sirketim/wasm-app:latest
    resources:
      limits:
        memory: "32Mi"
        cpu: "100m"
EOF

Dikkat ettin mi? Memory limit 32Mi. Aynı işi yapan bir Node.js container’ı için 256Mi-512Mi gerekirdi. Bu fark production’da gerçekten hissediliyor.

Performans İzleme ve Debug

WASM modüllerini production’da çalıştırıyorsak izleme şart. Wasmtime’ın built-in profiling desteği var:

# CPU profiling ile çalıştır
wasmtime --profile=guest 
  --dir=/data 
  /opt/modules/analiz.wasm 
  input.csv

# Perf entegrasyonu (Linux)
wasmtime --profile=perf 
  /opt/modules/analiz.wasm

# Üretilen perf map ile analiz
perf report --input=perf.data

# Fuel mekanizması ile çalışma süresini sınırla
# Bu özellik sonsuz döngüleri önler
wasmtime --fuel=1000000 
  /opt/modules/user-plugin.wasm

# WASM modülünün ne export ettiğini gör
wasmtime inspect /opt/modules/analiz.wasm

Wasmtime fuel mekanizması sysadmin’ler için çok değerli. Kullanıcıların yüklediği plugin’leri çalıştırıyorsanız, sonsuz döngü veya CPU tüketimi gibi durumları bu şekilde sınırlayabilirsiniz. Belirlediğin fuel bitince modül otomatik durur.

Systemd ile WASM Servis Olarak Çalıştırma

WASM modülünü bir systemd servisi olarak ayağa kaldırmak oldukça temiz bir yaklaşım:

# Servis dosyası oluştur
cat > /etc/systemd/system/wasm-api.service << 'EOF'
[Unit]
Description=WASM API Servisi
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=wasm-runner
Group=wasm-runner

# Sadece gerekli dizine erişim ver
ExecStart=/usr/local/bin/wasmtime 
  --dir=/opt/wasm-api/data 
  --env=PORT=8080 
  --env=LOG_LEVEL=info 
  /opt/wasm-api/modules/api.wasm

# Ekstra sistem güvenliği (WASI'nin üstüne systemd katmanı)
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
CapabilityBoundingSet=

Restart=on-failure
RestartSec=5s

StandardOutput=journal
StandardError=journal
SyslogIdentifier=wasm-api

[Install]
WantedBy=multi-user.target
EOF

# Kullanıcı oluştur
useradd --system --no-create-home --shell /sbin/nologin wasm-runner

# Dizin izinleri
chown -R wasm-runner:wasm-runner /opt/wasm-api/data

# Servisi aktifleştir
systemctl daemon-reload
systemctl enable --now wasm-api
systemctl status wasm-api

# Logları izle
journalctl -u wasm-api -f

Bu yapıda hem WASI’nin capability modeli hem de systemd’nin güvenlik direktifleri çalışıyor. Çift katman izolasyon diyebiliriz buna.

WASI Component Model: Geleceğe Bakış

WASI’nin şu an geçiş sürecinde olduğu bir konu var: Component Model. Eski WASI (preview1) ile yeni WASI (preview2) arasındaki fark oldukça büyük. Component Model ile birlikte WASM modülleri birbirleriyle güvenli şekilde iletişim kurabilecek, interface tanımları için WIT (WebAssembly Interface Types) kullanılacak.

# wit-bindgen aracını kur
cargo install wit-bindgen-cli

# Basit bir WIT interface tanımı
mkdir -p wit
cat > wit/hesap.wit << 'EOF'
package sirketim:hesap;

world hesapla {
  export topla: func(a: f64, b: f64) -> f64;
  export carp: func(a: f64, b: f64) -> f64;
  export ortalama: func(sayilar: list<f64>) -> f64;
}
EOF

# Rust binding'leri oluştur
wit-bindgen rust wit/ --out-dir src/bindings

# Component'i derle ve dönüştür
cargo build --target wasm32-wasi --release
wasm-tools component new target/wasm32-wasi/release/hesap.wasm 
  -o hesap-component.wasm

# Component bilgilerini görüntüle
wasm-tools component wit hesap-component.wasm

Component Model’in güzel tarafı şu: Farklı dillerde yazılmış WASM modülleri birbirleriyle konuşabilecek. Rust’ta yazdığın bir modül, Python’da yazdığın bir modülle sorunsuz entegre olacak. Ortak bir interface standardı sayesinde.

Yaygın Sorunlar ve Çözümleri

Üretimde karşılaşacağın tipik durumlar:

  • Dosya sistemi erişim hatası: --dir parametresini doğru verdiğinden emin ol. Wasmtime, verilen dizinin dışına çıkmana izin vermez. Bu özellik, güvenlik açısından bir nimet.
  • Modül boyutu: Rust ile derlenen WASM dosyaları büyük olabilir. wasm-opt aracı ile boyutu %30-50 azaltabilirsin.
  • Soket/ağ erişimi: Default olarak ağa çıkış kapalıdır. WasmEdge veya Wasmtime’ın --tcplisten ve --addr parametreleri ile açabilirsin.
  • Ortam değişkenleri: --env KEY=VALUE şeklinde açıkça belirtmen gerekiyor. Modül host’un tüm env’lerine erişemez.
  • Thread desteği: WASI’de thread desteği hala gelişim aşamasında. CPU-bound workload’lar için şimdilik birden fazla modül instance’ı çalıştırmak daha güvenli.
# wasm-opt ile optimizasyon
cargo install wasm-opt

wasm-opt -Oz 
  target/wasm32-wasi/release/uygulama.wasm 
  -o uygulama-optimized.wasm

# Boyut karşılaştırması
ls -lh target/wasm32-wasi/release/uygulama.wasm uygulama-optimized.wasm

# WASM dosyasını doğrula
wasmtime validate uygulama-optimized.wasm

Sonuç

WASI ile WebAssembly’yi tarayıcı dışında çalıştırmak, sistem yönetimi açısından gerçekten ilginç kapılar açıyor. Container’ların sağladığı izolasyonu çok daha düşük overhead ile elde edebiliyorsun. Başlangıç süreleri milisaniyeler mertebesinde, bellek tüketimi bir container’ın çok altında ve güvenlik modeli son derece katı.

Tabii henüz olgunlaşmakta olan bir ekosistem. Bazı library’lerin WASI desteği eksik, networking API’leri hala standartlaşıyor, Component Model geçişi devam ediyor. Bu yüzden her şeyi WASM’a taşımak yerine uygun use case’leri seçmek önemli.

Benim önerim: Plugin sistemleri, güvenilmeyen kod çalıştırma, edge computing ve serverless fonksiyonlar için WASM/WASI mükemmel bir seçim. Ana uygulama altyapısı için Container’lar hala daha olgun ve geniş ekosistem desteğine sahip.

Sonuç olarak Docker’ın kurucusunun dediği gibi, eğer WASI 2008’de olsaydı Docker’a belki gerek kalmazdı. Ama 2024’teyiz ve her ikisi de elimizde mevcut. Doğru araç, doğru iş için prensibini aklında tutarak WASI’yi araç kutuna ekle. Pişman olmayacaksın.

Bir yanıt yazın

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