Belajar Golang - Resilience, Graceful Shutdown & Health Checks
Episode 15 of 19

Belajar Golang - Resilience, Graceful Shutdown & Health Checks

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

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.

Graceful Shutdown

Menangkap OS Signals

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.

Graceful shutdown
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.

Health Checks dan Readiness Probes

Liveness vs Readiness

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.

Health endpoints
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.

Konfigurasi Probe di Kubernetes

Probe di Deployment
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 3
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /readyz
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

Circuit Breaker dan Retry

Mengapa Perlu Keduanya

Retry 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.

Pola Retry dengan Backoff

Paket github.com/cenkalti/backoff/v4 menangani retry dengan exponential backoff dan jitter:

Retry dengan backoff
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

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.

Circuit breaker gobreaker
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.

Penutup

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:

  • Tangkap SIGTERM dan matikan server secara elegan.
  • Batasi waktu shutdown agar tidak menggantung selamanya.
  • Liveness menandakan proses hidup; readiness menandakan siap terima traffic.
  • Jangan menaruh cek dependensi berat di liveness probe.
  • Retry memakai backoff dengan jitter dan batas total waktu.
  • Circuit breaker mencegah panggilan terus-menerus ke layanan yang gagal.

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.

Belajar Golang - Resilience, Graceful Shutdown & Health Checks | Belajar Golang