Rust Binary Derleme ve Linux Sunucuya Yayınlama

Rust ile bir şeyler derlemeye çalışıp da sunucuda “binary yok mu, nerede bu dosya?” diye saçları yolduğunu yaşamamış sysadmin azdır. Go’nun aksine Rust biraz daha karmaşık bir toolchain’e sahip ve özellikle cross-compilation, statik bağlama ve deployment süreçleri ilk bakışta kafa karıştırıcı gelebiliyor. Bu yazıda sıfırdan başlayıp production sunucusuna kadar Rust binary’sini nasıl derleyip yayınlayacağını adım adım anlatacağım.

Rust Toolchain Kurulumu

Rust’ı sistem paket yöneticisiyle kurma. Ciddi söylüyorum. apt install rust veya yum install rust gibi yollarla kurulan versiyonlar genellikle çok eski kalıyor ve rustup ekosistemiyle uyumsuz davranıyor. Resmi yol her zaman rustup üzerinden.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Kurulum sırasında sana üç seçenek sunuyor: default, customize ve cancel. Çoğu durumda default yeterli. Kurulum bittikten sonra PATH’i aktif etmek için ya yeni bir terminal aç ya da şunu çalıştır:

source "$HOME/.cargo/env"

Sonra kontrol et:

rustc --version
cargo --version
rustup --version

Eğer CI/CD pipeline’ında veya sunucu tarafında kurulum yapıyorsan interaktif olmayan mod için şunu kullan:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

-y parametresi bütün sorulara otomatik “evet” diyor.

Toolchain Bileşenlerini Anlamak

Rust’ta birkaç önemli kavram var:

  • stable: Production için kullanılan, kararlı kanal
  • beta: Stable’a gelmeden önce test edilen kanal
  • nightly: Deneysel özellikler içeren, her gece derlenen kanal
  • rustup: Rust toolchain yöneticisi, pyenv veya rbenv gibi düşün
  • cargo: Paket yöneticisi ve build aracı, hem npm hem make görevini yapıyor
  • rustc: Asıl derleyici, direkt nadiren çağırırsın
  • clippy: Linter, kod kalitesi için
  • rustfmt: Kod formatlayıcı

Ek bileşen eklemek istediğinde:

rustup component add clippy rustfmt
rustup target add x86_64-unknown-linux-musl

İlk Proje ve Temel Derleme

Basit bir HTTP health check aracı yazalım. Gerçek dünyada bu tür küçük araçları binary olarak sunucuya atmak çok yaygın.

cargo new health-checker
cd health-checker

Cargo.toml dosyasını düzenle:

cat > Cargo.toml << 'EOF'
[package]
name = "health-checker"
version = "0.1.0"
edition = "2021"

[dependencies]
reqwest = { version = "0.11", features = ["blocking"] }
serde_json = "0.1"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true
EOF

src/main.rs içine basit bir şey koy:

cat > src/main.rs << 'EOF'
use std::env;
use std::process;

fn main() {
    let args: Vec<String> = env::args().collect();
    
    if args.len() < 2 {
        eprintln!("Kullanim: health-checker <url>");
        process::exit(1);
    }
    
    let url = &args[1];
    
    match reqwest::blocking::get(url) {
        Ok(response) => {
            println!("URL: {}", url);
            println!("Status: {}", response.status());
            if response.status().is_success() {
                println!("Sonuc: SAGLIKLI");
                process::exit(0);
            } else {
                println!("Sonuc: SAGLIKSIZ");
                process::exit(1);
            }
        }
        Err(e) => {
            eprintln!("Hata: {}", e);
            process::exit(2);
        }
    }
}
EOF

Debug modunda derle ve test et:

cargo build
./target/debug/health-checker https://example.com

Release modunda derle, bu production için kullanacağın şey:

cargo build --release
ls -lh target/release/health-checker

Release build’de [profile.release] bölümündeki ayarlar devreye giriyor. lto = true link-time optimization yapıyor ve binary boyutunu önemli ölçüde küçültüyor. strip = true debug sembollerini kaldırıyor.

Statik Bağlama ile Taşınabilir Binary

İşte sysadmin’lerin Rust’ı sevmesinin asıl sebebi bu: statik olarak bağlanmış tek bir binary dosyası. Glibc bağımlılığı olmadan, hiçbir .so dosyasına ihtiyaç duymadan çalışan bir binary.

Bunun için musl libc hedefini kullanıyoruz:

rustup target add x86_64-unknown-linux-musl

Ubuntu/Debian’da musl araçlarını kur:

sudo apt-get install -y musl-tools musl-dev

RHEL/CentOS/Rocky’de:

sudo yum install -y musl-gcc
# veya EPEL üzerinden
sudo dnf install -y musl-gcc musl-libc-static

Şimdi statik binary derle:

cargo build --release --target x86_64-unknown-linux-musl

Kontrol et, hiçbir dinamik bağımlılık olmamalı:

ldd target/x86_64-unknown-linux-musl/release/health-checker
# "not a dynamic executable" çıktısını görmelisin
file target/x86_64-unknown-linux-musl/release/health-checker
# "statically linked" yazısını görmelisin

Bu binary artık Alpine Linux’ta da, CentOS 6’da da, Debian 12’de de çalışır. Glibc versiyonu uyumsuzluğu gibi dertlerin sonu.

.cargo/config.toml ile Varsayılan Hedef Belirleme

Her seferinde --target yazmak can sıkıcı. Proje dizininde ya da ~/.cargo/config.toml dosyasında varsayılan hedef belirleyebilirsin:

mkdir -p .cargo
cat > .cargo/config.toml << 'EOF'
[build]
target = "x86_64-unknown-linux-musl"

[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
EOF

Artık sadece cargo build --release yazmak yeterli.

Cross-Compilation: Farklı Mimari için Derleme

Geliştirme makineniz macOS ARM (M1/M2) ama sunucularınız x86_64 Linux. Ya da tersine. Cross-compilation Rust’ta göreceli kolay ama birkaç kurulum adımı var.

Mac’te x86_64 Linux binary’si derlemek için:

# Mac'te
rustup target add x86_64-unknown-linux-musl

# macOS'ta cross-compilation toolchain
brew install filosottile/musl-cross/musl-cross

# .cargo/config.toml
cat > .cargo/config.toml << 'EOF'
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
EOF

# Derle
cargo build --release --target x86_64-unknown-linux-musl

Docker ile cross-compilation ise en güvenilir yöntem. Platform bağımlılıklarından kurtulursun:

docker run --rm 
  -v "$(pwd)":/app 
  -w /app 
  rust:alpine 
  sh -c "apk add --no-cache musl-dev && cargo build --release --target x86_64-unknown-linux-musl"

Veya cross aracını kullan, hayatı çok kolaylaştırıyor:

cargo install cross

# Artık normal cargo gibi kullanıyorsun
cross build --release --target x86_64-unknown-linux-musl
cross build --release --target aarch64-unknown-linux-musl  # ARM64 için

cross arka planda Docker kullanıyor ve hedef mimari için hazırlanmış container’ları otomatik indiriyor.

Binary’yi Sunucuya Yayınlama

Binary hazır, şimdi sunucuya atma vakti. Birkaç farklı yöntem var, ortamına göre seçeceksin.

SCP ile Manuel Yayınlama

En basit yöntem, küçük takımlar veya tek sunucu senaryoları için yeterli:

# Binary'yi kopyala
scp target/x86_64-unknown-linux-musl/release/health-checker user@sunucu:/tmp/

# Sunucuda yerleştir
ssh user@sunucu << 'ENDSSH'
sudo mv /tmp/health-checker /usr/local/bin/
sudo chmod 755 /usr/local/bin/health-checker
sudo chown root:root /usr/local/bin/health-checker
health-checker https://example.com
ENDSSH

rsync ile Daha Akıllı Transfer

Binary büyükse ve sık güncelliyorsan rsync daha mantıklı:

rsync -avz --progress 
  target/x86_64-unknown-linux-musl/release/health-checker 
  user@sunucu:/usr/local/bin/health-checker

Systemd Servisi Olarak Çalıştırma

Binary’yi bir servis haline getirmek için systemd unit dosyası yaz. Örneğin health-checker’ı periyodik bir daemon olarak değil, sürekli çalışan bir HTTP endpoint olarak hayal edelim:

sudo tee /etc/systemd/system/health-checker.service << 'EOF'
[Unit]
Description=Health Checker Servisi
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=healthchecker
Group=healthchecker
ExecStart=/usr/local/bin/health-checker
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=health-checker

# Guvenlik ayarlari
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/health-checker

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable health-checker
sudo systemctl start health-checker
sudo systemctl status health-checker

Servis için ayrı bir kullanıcı oluşturmayı unutma:

sudo useradd -r -s /bin/false -d /var/lib/health-checker -m healthchecker

GitHub Actions ile CI/CD Pipeline

Manuel süreç zahmetli, haydi otomatize edelim. .github/workflows/release.yml dosyası:

mkdir -p .github/workflows
cat > .github/workflows/release.yml << 'EOF'
name: Build and Deploy

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Rust toolchain kur
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: x86_64-unknown-linux-musl
      
      - name: musl araclari kur
        run: sudo apt-get install -y musl-tools
      
      - name: Cache cargo
        uses: actions/cache@v3
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
      
      - name: Release binary derle
        run: cargo build --release --target x86_64-unknown-linux-musl
      
      - name: Binary boyutunu kontrol et
        run: ls -lh target/x86_64-unknown-linux-musl/release/health-checker
      
      - name: Sunucuya deploy et
        env:
          SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
          SERVER_HOST: ${{ secrets.SERVER_HOST }}
          SERVER_USER: ${{ secrets.SERVER_USER }}
        run: |
          echo "$SSH_PRIVATE_KEY" > /tmp/deploy_key
          chmod 600 /tmp/deploy_key
          scp -i /tmp/deploy_key 
            -o StrictHostKeyChecking=no 
            target/x86_64-unknown-linux-musl/release/health-checker 
            $SERVER_USER@$SERVER_HOST:/tmp/health-checker-new
          ssh -i /tmp/deploy_key 
            -o StrictHostKeyChecking=no 
            $SERVER_USER@$SERVER_HOST 
            'sudo mv /tmp/health-checker-new /usr/local/bin/health-checker && sudo systemctl restart health-checker'
          rm -f /tmp/deploy_key
EOF

GitHub repository’nin Secrets bölümüne DEPLOY_SSH_KEY, SERVER_HOST ve SERVER_USER ekle. Tag push’ladığında otomatik deploy başlıyor.

Binary Boyutu Optimizasyonu

Büyük projelerde binary boyutu önemli olabiliyor. Özellikle container image’larında veya sık deploy edilen servislerde. Cargo.toml’da ekstra optimizasyonlar:

cat >> Cargo.toml << 'EOF'

[profile.release]
opt-level = "z"     # Boyut icin optimize et (hiz yerine)
lto = "fat"         # Agresif link-time optimization
codegen-units = 1   # Daha iyi optimizasyon, uzun derleme suresi
panic = "abort"     # Panic handler boyutunu azalt
strip = "symbols"   # Debug sembollerini kaldir
EOF

upx ile binary’yi sıkıştır, statik binary’lerde iyi sonuç veriyor:

sudo apt-get install -y upx

upx --best --lzma target/x86_64-unknown-linux-musl/release/health-checker

# Boyut karsilastirmasi
ls -lh target/x86_64-unknown-linux-musl/release/health-checker

Genellikle %60-70 oranında boyut azalması görürsün. Dezavantajı: başlangıç süresi biraz uzuyor çünkü bellekte açılması gerekiyor. Uzun süre çalışan servisler için sorun değil.

Sık Karşılaşılan Sorunlar ve Çözümleri

OpenSSL bağımlılığı sorunu: Eğer projen OpenSSL kullanıyorsa statik derleme karmaşıklaşıyor. Cargo.toml‘a şunu ekle:

# Cargo.toml'da
[dependencies]
openssl = { version = "0.10", features = ["vendored"] }
# veya daha iyi: native-tls yerine rustls kullan
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "blocking"] }

vendored özelliği OpenSSL’i kaynak koddan derleyip statik olarak bağlıyor. rustls ise tamamen Rust ile yazılmış TLS implementasyonu, daha temiz çözüm.

GLIBC version hatası: Sunucuda GLIBC_2.29 not found gibi hatalar alıyorsan musl hedefine geçmedin demektir. Ya da dinamik binary gönderdin. Kontrol et:

ldd /usr/local/bin/health-checker
# "not a dynamic executable" olmalı

Derleme süresi uzunluğu: Büyük projelerde cargo build ilk seferinde dakikalarca sürebiliyor. Çözümler:

  • sccache kullan: derlenmiş artifact’ları cache’liyor
  • CI’da actions/cache ile cargo cache’i sakla
  • cargo check ile derleme yapmadan hata kontrolü yap
cargo install sccache
export RUSTC_WRAPPER=sccache
cargo build --release
sccache --show-stats

Çok büyük binary: Bağımlılık ağacını kontrol et:

cargo tree
cargo bloat --release --crates  # cargo-bloat'u kur: cargo install cargo-bloat

cargo-bloat hangi crate’in en çok yer kapladığını gösteriyor, fazladan çektiğin bağımlılıkları temizlemene yardımcı oluyor.

Versiyonlama ve Rollback Stratejisi

Production’da versiyonlama olmadan binary yönetimi kaos oluyor. Basit ama etkili bir strateji:

# Deploy scripti - /usr/local/bin/deploy-rust-binary.sh
#!/bin/bash

BINARY_NAME=$1
BINARY_PATH=$2
INSTALL_DIR="/usr/local/bin"
BACKUP_DIR="/usr/local/bin/backups"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

# Mevcut binary'yi yedekle
if [ -f "$INSTALL_DIR/$BINARY_NAME" ]; then
    cp "$INSTALL_DIR/$BINARY_NAME" "$BACKUP_DIR/${BINARY_NAME}_${DATE}"
    echo "Yedek alindi: ${BINARY_NAME}_${DATE}"
fi

# Yeni binary'yi kur
cp "$BINARY_PATH" "$INSTALL_DIR/$BINARY_NAME"
chmod 755 "$INSTALL_DIR/$BINARY_NAME"
echo "Yeni binary kuruldu: $BINARY_NAME"

# Servisi yeniden basla
systemctl restart "$BINARY_NAME" 2>/dev/null || true

# Eski yedekleri temizle (son 5'i tut)
ls -t "$BACKUP_DIR/${BINARY_NAME}_"* 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null
echo "Eski yedekler temizlendi"

Rollback yapmak gerektiğinde:

ls /usr/local/bin/backups/health-checker_*
cp /usr/local/bin/backups/health-checker_20240315_143022 /usr/local/bin/health-checker
sudo systemctl restart health-checker

Sonuç

Rust binary deployment süreci ilk bakışta karmaşık görünüyor ama temelde şu kadar: rustup ile toolchain kur, musl hedefi ekle, cargo build --release --target x86_64-unknown-linux-musl çalıştır, tek binary’yi sunucuya kopyala. Glibc bağımlılığı yok, runtime yok, yorumlayıcı yok.

Benim önerim şu: yeni bir Rust projesi başlarken en başından .cargo/config.toml‘da musl hedefini varsayılan yap. Sonradan değiştirmek bazen sürprizler çıkarıyor. Cross-compilation için cross aracını kullan, Docker’ın gücünü kullanarak neredeyse her hedef için kolayca binary üretebiliyorsun. CI/CD için GitHub Actions çok iş görüyor ve ücretsiz.

Production’da çalışan Rust binary’leri gerçekten keyifli: küçük, hızlı, bağımlılıksız. Go ile kıyaslandığında binary boyutu biraz daha büyük olabiliyor ama optimizasyonla farkı kapatabilirsin. Önemli olan statik derleme alışkanlığını edinmek ve deployment pipeline’ını en başından otomatize etmek. Manuel scp ile binary atmak belli bir noktaya kadar idare eder, ama ekip büyüyüp sunucu sayısı arttığında CI/CD’ye geçmek zorunlu hale geliyor.

Bir yanıt yazın

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