Episode ini membangun service Go yang tangguh: graceful shutdown dengan context dan OS signals, health checks dan readiness probes untuk Kubernetes, serta circuit breaker dan retry policy untuk panggilan eksternal yang tidak stabil.

Aplikasi yang berhenti secara paksa meninggalkan request menggantung, koneksi database bocor, dan transaksi tidak tuntas. Service yang baik harus tahu cara berhenti dengan elegan dan cara bertahan ketika dependensi eksternal bermasalah.
Episode 15 membahas resilience service Go: graceful shutdown dengan context dan OS signals, health checks serta readiness probes untuk Kubernetes, dan pola circuit breaker serta retry untuk panggilan eksternal yang flaky. Ketiganya menentukan apakah service kalian layak dipakai di production.
Ketika Kubernetes atau platform lain mengirim sinyal SIGTERM, service harus menyelesaikan request yang berjalan lalu keluar — bukan langsung diterminasi. signal.NotifyContext mengubah sinyal menjadi context yang dibatalkan.
package main
import (
"context"
"log"
"net/http"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{Addr: ":8080"}
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Println("shutdown error:", err)
}
}()
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}http.Server.Shutdown berhenti menerima request baru, menunggu request aktif selesai, lalu menutup listener. Timeout 10 detik mencegah shutdown menggantung selamanya. Simulasi sinyal bisa dilakukan dengan mengirim kill -TERM <pid> ke proses aplikasi.
Graceful shutdown bukan hanya HTTP server: tutup juga connection pool database, klien Redis, dan worker goroutine. Alur standar: sinyal diterima, seluruh goroutine diberi tahu lewat context, resource ditutup, baru proses keluar.
Kubernetes memakai dua probe berbeda. Liveness probe menjawab "apakah proses masih hidup" — gagal berarti container di-restart. Readiness probe menjawab "apakah service siap menerima traffic" — gagal berarti pod dikeluarkan dari Service dan tidak menerima traffic.
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
if dbHealth := pingDatabase(r.Context()); dbHealth != nil {
http.Error(w, "database tidak siap", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
})/healthz hanya memeriksa proses; /readyz memeriksa dependensi penting seperti database. Jangan pernah menempatkan cek dependensi berat di liveness probe — itu akan memicu restart loop ketika dependensi lambat.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Retry membantu ketika kegagalan bersifat sementara — timeout sesaat atau koneksi terputus. Namun retry tanpa batas justru memperburuk layanan yang sedang down: permintaan terus menumpuk. Circuit breaker memutus panggilan ke layanan yang sedang gagal, memberi waktu pemulihan, lalu mencoba lagi.
Paket github.com/cenkalti/backoff/v4 menangani retry dengan exponential backoff dan jitter:
package main
import (
"time"
"github.com/cenkalti/backoff/v4"
)
func panggilDenganRetry(fn func() error) error {
b := backoff.NewExponentialBackOff()
b.MaxElapsedTime = 30 * time.Second
return backoff.Retry(func() error {
return fn()
}, b)
}Jitter mencegah banyak klien me-retry pada waktu yang sama persis (thundering herd). Batasi juga total waktu retry agar tidak menghabiskan budget waktu request.
Circuit breaker bekerja seperti pemutus arus listrik: kondisi tertutup (normal), terbuka (gagal terus, langsung tolak), dan half-open (uji coba satu request untuk lihat apakah sudah pulih). Paket github.com/sony/gobreaker menyediakan implementasi lengkapnya.
package main
import (
"time"
"github.com/sony/gobreaker/v2"
)
func main() {
cb := gobreaker.NewCircuitBreaker[int](gobreaker.Settings{
Name: "api-eksternal",
MaxRequests: 5,
Timeout: 30 * time.Second,
})
hasil, err := cb.Execute(func() (int, error) {
return panggilApiEksternal()
})
_ = hasil
_ = err
}MaxRequests membatasi request saat half-open, Timeout menentukan berapa lama circuit tetap terbuka. Kombinasikan circuit breaker dengan retry: retry beberapa kali dulu, lalu circuit breaker memutus panggilan untuk periode tertentu.
Episode 15 membuat service Go kalian tangguh: graceful shutdown dengan signal.NotifyContext dan http.Server.Shutdown, health checks liveness dan readiness untuk Kubernetes, serta retry dengan exponential backoff dan circuit breaker dengan gobreaker untuk dependensi eksternal.
Inti yang harus dibawa pulang:
SIGTERM dan matikan server secara elegan.Di episode 16 selanjutnya kita akan membahas Go di cloud, containers, dan Kubernetes — membangun image container efisien dengan multi-stage Dockerfile, mendeploy aplikasi Go ke Kubernetes, Cloud Run, atau platform serverless, serta best practice container runtime dengan minimal image, distroless, dan static binary.