feat(T1.8): implement health checks for Kubernetes deployment

- Add internal/health package with health checker
- Implement three endpoints: /health, /health/live, /health/ready
- /health returns full JSON report with component status, latency, timestamp
- /health/live for K8s liveness probe (service running)
- /health/ready for K8s readiness probe (ready to accept traffic)
- Temporal connectivity check via GetWorkflow call with timeout
- Health check caching (30s interval) to prevent excessive checks
- Graceful shutdown: health server stops on SIGINT/SIGTERM
- Add --health flag to starter command to run health check
- Worker runs health server on port 8081 alongside task queue worker
- 10/10 unit tests passing
- All verification criteria met

Closes T1.8
This commit is contained in:
Test
2026-08-23 16:31:33 -07:00
parent e3a5e571bf
commit 90fcd6a9df
7 changed files with 612 additions and 10 deletions
+54 -3
View File
@@ -1,13 +1,20 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
"github.com/rockliang/poimen/workflows/action"
"github.com/rockliang/poimen/workflows/internal/config"
"github.com/rockliang/poimen/workflows/internal/health"
"github.com/rockliang/poimen/workflows/statemachine"
)
@@ -55,9 +62,53 @@ func main() {
// w.RegisterActivity(action.UpdateLessonsActivity)
// w.RegisterActivity(action.ReadLessonsActivity)
// Run worker
fmt.Println("Starting worker on queue 'poimen-taskqueue'...")
if err := w.Run(worker.InterruptCh()); err != nil {
// Initialize health checker
healthChecker := health.NewChecker(c)
healthHandler := health.NewHandler(healthChecker)
// Set up HTTP server for health checks
mux := http.NewServeMux()
healthHandler.RegisterRoutes(mux)
healthServer := &http.Server{
Addr: ":8081",
Handler: mux,
}
// Start health check server in a goroutine
go func() {
log.Printf("Health check server listening on %s", healthServer.Addr)
if err := healthServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("health check server error: %v", err)
}
}()
// Set up signal handling for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Run worker in a goroutine
workerErrChan := make(chan error, 1)
go func() {
fmt.Println("Starting worker on queue 'poimen-taskqueue'...")
if err := w.Run(worker.InterruptCh()); err != nil {
workerErrChan <- err
}
}()
// Wait for either worker error or signal
select {
case err := <-workerErrChan:
log.Fatalf("worker failed: %v", err)
case sig := <-sigChan:
log.Printf("received signal: %v, shutting down gracefully", sig)
w.Stop()
// Shutdown health check server
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := healthServer.Shutdown(ctx); err != nil {
log.Printf("health check server shutdown error: %v", err)
}
}
}