DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

From docker-compose to Kubernetes: 8 Spring Boot services, probes that gate rollout, and a test that reads the manifests

Day 43 of building OrderHub in public takes yesterday's docker compose up and turns it into a real Kubernetes deployment — 8 Spring Boot services, each with liveness and readiness probes wired to Spring Boot Actuator, plus a ConfigMap, a Secret, and a JUnit test that parses every manifest so the topology can't silently drift.

One Deployment + Service per app

Each of the 8 runnable services (config-server, eureka, gateway, order, inventory, payment, shipping, notification) gets a Deployment and a ClusterIP Service. The images are the ones built on Day 42 (orderhub/<svc>:0.1.0), the container port matches the EXPOSE, and the Service port lines up with it. Shared config comes from a ConfigMap (orderhub-config) and secrets from a Secret (orderhub-secrets) — consumed via envFrom, so no config is baked into an image.

Probes wired to Actuator — the important part

Kubernetes has three probes and they do different jobs:

  • readiness/actuator/health/readiness — gates whether the Service sends traffic to the pod. Fails during startup or when a dependency is down, so no request hits a half-ready app.
  • liveness/actuator/health/liveness — gates restarts. If the app deadlocks, Kubernetes kills and replaces it.
  • startup → shields a slow JVM cold start so liveness doesn't kill the pod before it's up.

Spring Boot exposes those two health groups only when you turn them on:

# activated ONLY under the k8s profile — local/compose behaviour is unchanged
spring:
  config:
    activate:
      on-profile: k8s
management:
  endpoint:
    health:
      probes:
        enabled: true
      group:
        readiness:
          include: readinessState,db
        liveness:
          include: livenessState
Enter fullscreen mode Exit fullscreen mode

The whole k8s wiring is profile-gated (SPRING_PROFILES_ACTIVE=k8s set in the ConfigMap), so the local Docker Compose and the test suite stay byte-for-byte the same.

readinessProbe:
  httpGet: { path: /actuator/health/readiness, port: 8082 }
  initialDelaySeconds: 10
  periodSeconds: 5
livenessProbe:
  httpGet: { path: /actuator/health/liveness, port: 8082 }
  periodSeconds: 10
Enter fullscreen mode Exit fullscreen mode

Test the manifests, don't hope

Day 42 shipped a test that parsed docker-compose.yml; Day 43 does the same for Kubernetes. KubernetesManifestTest loads every YAML with SnakeYAML (no cluster needed) and asserts: each app has a Deployment + a Service, images and ports match, every Deployment declares liveness and readiness probes pointing at the actuator paths, and each one envFroms the ConfigMap and Secret. If someone adds a service and forgets a probe, the build goes red.

mvn clean test across all 8 modules: BUILD SUCCESS, 155 tests (up from 152 — the +3 are the manifest test).

The Secret holds only clearly-labelled base64 placeholders — real values come from the platform (a sealed secret / vault) at deploy time, never the repo.

Full manifests + the kill-a-pod self-heal walkthrough:

Next: Helm + Ingress, so the whole stack installs with one helm install.

Top comments (0)