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/cacheile cargo cache’i sakla cargo checkile 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.
