Episode ini membuat service Rust tahan banting: mematikan server dengan bersih lewat signal handling, menyediakan health check dan readiness probes untuk Kubernetes, serta menerapkan retry, timeout, dan circuit breaker dengan tower dan tower-http.

Service yang sehat bukan hanya yang berjalan cepat — melainkan yang mampu gagal dengan anggun. Episode 15 membahas ketahanan: mematikan server tanpa memotong request yang sedang diproses, memberi tahu orkestrator tentang kesehatannya, dan menyerap kegagalan upstream dengan retry, timeout, dan circuit breaker.
Konsep ini semakin penting karena hampir semua deployment modern berjalan di Kubernetes atau platform cloud yang menghentikan instance kapan saja. Resilience adalah perbedaan antara restart yang mulus dan incident.
Saat instance dihentikan, request yang sedang diproses seharusnya diselesaikan, koneksi database ditutup, dan task background diberi waktu menyelesaikan pekerjaannya. Graceful shutdown memberi aplikasi jendela waktu untuk itu sebelum proses benar-benar berakhir.
Tokio menyediakan tokio::signal::ctrl_c dan API untuk signal Unix. Gabungkan dengan axum::serve yang mendukung graceful shutdown:
cat > src/main.rs <<'EOF'
use axum::{routing::get, Router};
use std::time::Duration;
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "resilien" }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("menunggu sinyal shutdown...");
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}
async fn shutdown_signal() {
tokio::signal::ctrl_c().await.unwrap();
println!("menerima sinyal, menutup dengan bersih");
tokio::time::sleep(Duration::from_millis(500)).await;
}
EOF
cargo runwith_graceful_shutdown menunggu semua koneksi aktif selesai sebelum menutup listener. shutdown_signal menunggu Ctrl-C. Di lingkungan cloud, signal SIGTERM dari orchestrator memicu alur yang sama — platform memberi jendela waktu lalu memaksa kill.
Jangan lupa task background: gunakan JoinSet atau TaskTracker (episode 12) dan batasi waktu tunggu keseluruhan. Aturan umum: targetkan shutdown selesai di bawah jendela waktu Kubernetes (biasanya 30 detik) agar tidak ada request yang terpotong.
Kubernetes memakai dua probe: liveness menentukan apakah pod harus di-restart (apakah aplikasi masih hidup), sedangkan readiness menentukan apakah pod menerima traffic (apakah aplikasi siap melayani). Keduanya sering titik akhir HTTP yang berbeda.
cat > src/main.rs <<'EOF'
use axum::{routing::get, Json, Router};
use serde::Serialize;
#[derive(Serialize)]
struct Health {
status: &'static str,
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/healthz", get(|| async { Json(Health { status: "ok" }) }))
.route("/readyz", get(|| async { Json(Health { status: "ok" }) }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
EOF
cargo run/healthz mengembalikan 200 selama proses hidup. /readyz mengembalikan 200 hanya jika dependency siap — misalnya database bisa dikoneksi. Saat dependency down, readyz mengembalikan status error sehingga Kubernetes menghentikan traffic sementara liveness tetap menyala.
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 15readinessProbe menghentikan traffic saat /readyz gagal, livenessProbe me-restart pod saat /healthz gagal. Konfigurasi ini ditulis di spec deployment. kubectl apply menerapkan perubahan; kubectl get pods menampilkan status readiness.
Timeout melindungi kalian dari upstream yang lambat: request dibatalkan setelah batas waktu. tower-http menyediakan layer (cargo add tower-http --features timeout):
use axum::{routing::get, Router};
use std::time::Duration;
use tower_http::timeout::TimeoutLayer;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "cepat" }))
.layer(TimeoutLayer::new(Duration::from_secs(10)));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
EOF
cargo runTimeoutLayer::new(Duration::from_secs(10)) membatasi waktu setiap request. Tanpa timeout, satu upstream lambat bisa menggantung banyak koneksi dan menumpuk ke seluruh service.
Retry mengulangi panggilan yang gagal karena penyebab sementara: timeout, 5xx, atau koneksi terputus. Ekosistem tower menyediakan tower::retry dan tower::reconnect. Untuk panggilan HTTP, crate seperti reqwest-middleware dan reqwest-retry menambah kebijakan retry dengan jitter.
Circuit breaker mencegah request diteruskan ke upstream yang sedang down: saat kegagalan melewati ambang, sirkuit "terbuka" dan request langsung ditolak selama periode cooldown — memberi upstream waktu pulih. tower::limit dan crate seperti failsafe atau boomerang (cargo add failsafe) menyediakan implementasinya:
use std::time::Duration;
use failsafe::{CircuitBreaker, Config};
fn main() {
let cb = Config::new()
.with_failure_rate_threshold(0.5)
.with_volume_threshold(10)
.build();
match cb.call(|| Err::<(), ()>(())) {
Ok(()) => println!("sirkuit tertutup, panggilan ok"),
Err(failsafe::Error::Failure(_)) => println!("panggilan gagal"),
Err(failsafe::Error::Open) => println!("sirkuit terbuka, tolak request"),
}
}
EOF
cargo runwith_failure_rate_threshold(0.5) membuka sirkuit saat lebih dari separuh panggilan gagal. Kombinasi timeout, retry, dan circuit breaker adalah pertahanan berlapis: timeout membatasi durasi, retry menyerap kegagalan sementara, dan circuit breaker menghentikan panggilan sia-sia saat upstream benar-benar down.
Inti yang harus dibawa pulang:
axum::serve dengan with_graceful_shutdown menutup server dengan bersih.readyz dan healthz adalah konvensi probe di ekosistem Kubernetes.Di episode 16 selanjutnya kita akan membahas Rust di cloud, containers, dan WebAssembly — membangun binary statis dengan musl dan distroless image, men-deploy ke Kubernetes, Cloud Run, dan serverless, serta memakai WebAssembly dengan wasm-pack, wasm-bindgen, dan yew. Karya kalian mulai tersebar ke mana-mana.