Stage 7 · Master
Phase 7 — Resident Service
Deployment
Deploying a service that depends on another service being reachable
Adopt the Reviewed Image Without Re-teaching It
-COPY services/flat-service ./services/flat-service
+COPY services/resident-service ./services/resident-service
-RUN go work init ./services/flat-service
+RUN go work init ./services/resident-service
-WORKDIR /src/services/flat-service
+WORKDIR /src/services/resident-service
-RUN go build -o /out/flat-service ./cmd/api
+RUN go build -o /out/resident-service ./cmd/api
-RUN adduser -D -u 10001 appuser
+RUN adduser -D -u 10002 appuser
-COPY --from=build /out/flat-service /usr/local/bin/flat-service
+COPY --from=build /out/resident-service /usr/local/bin/resident-service
-EXPOSE 8081
+EXPOSE 8082
-ENTRYPOINT ["/usr/local/bin/flat-service"]
+ENTRYPOINT ["/usr/local/bin/resident-service"]Configuring the flat-service Dependency
resident-service needs FLAT_SERVICE_URL at runtime to build its FlatClient. In cluster, that is flat-service's internal ClusterIP DNS name; locally, it is whatever port flat-service is bound to. Getting this wrong does not fail loudly at startup — it fails on the first ownership or occupancy request, which is exactly why the health check below verifies it eagerly.
import (
+ "github.com/hoa-platform/backend/services/resident-service/internal/client"
)
-func NewReadinessHandler(pool *pgxpool.Pool) gin.HandlerFunc {
+func NewReadinessHandler(pool *pgxpool.Pool, flatClient *client.FlatClient) gin.HandlerFunc {
return func(c *gin.Context) {
// Existing two-second context and pool.Ping check stay unchanged.
+ if err := flatClient.Ping(ctx); err != nil {
+ c.JSON(http.StatusServiceUnavailable, gin.H{
+ "status": "down", "dependency": "flat-service",
+ })
+ return
+ }
c.JSON(http.StatusOK, gin.H{"status": "up"})
}
}Flat Service already supplied the database-aware check. Resident adds only downstream readiness. Its separate liveness handler performs no network calls, so a Flat outage removes Resident from traffic without restarting the process.
package http
import (
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/hoa-platform/backend/services/resident-service/internal/client"
)
func RegisterHealthRoutes(r *gin.Engine, pool *pgxpool.Pool, flatClient *client.FlatClient) {
r.GET("/health/live", NewLivenessHandler())
r.GET("/health/ready", NewReadinessHandler(pool, flatClient))
} router := httphandler.NewRouter(residentHandler, familyHandler, ownershipHandler)
+httphandler.RegisterHealthRoutes(router, pool, flatClient)Kubernetes: Explicit Dependency Ordering
spec:
template:
spec:
containers:
- name: resident-service
+ env:
+ - name: FLAT_SERVICE_URL
+ value: "http://flat-service.default.svc.cluster.local"
+ readinessProbe:
+ httpGet:
+ path: /health/ready
+ port: 8082
+ failureThreshold: 3
+ periodSeconds: 5
+ livenessProbe:
+ httpGet:
+ path: /health/live
+ port: 8082
+ failureThreshold: 3
+ periodSeconds: 15Do not copy Flat's entire Deployment. Start from the service manifest pattern already established there and review only Resident's new operational contract: readiness depends on Flat, while liveness proves only that the Resident process can continue running.
Simulating a Downstream Outage
Applied exercise
Add a startup probe so slow flat-service recovery doesn't kill resident-service
During a flat-service redeploy, resident-service's liveness probe (which also checks flat-service via the shared health handler) starts failing and Kubernetes restarts resident-service unnecessarily — a working process gets killed because a dependency was briefly restarting.
- Split the health handler into a liveness variant that only checks resident-service's own Postgres, and a readiness variant that also checks flat-service.
- Update the Deployment manifest: livenessProbe hits the liveness-only path, readinessProbe hits the full dependency-check path.
- Document in a comment why liveness must never depend on a downstream service's health.
Deliverable
Two health handler functions (or one parameterized), and an updated deployment.yaml.
Completion checks
- Liveness failing no longer depends on flat-service's state.
- Readiness still correctly reports not-ready when flat-service is unreachable.
- A working resident-service process is never restarted just because flat-service redeployed.
Why does resident-service's readiness probe check flat-service's reachability, while a well-designed liveness probe should not?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.