Spring Boot on Kubernetes

Kubernetes is the standard platform for running containerized microservices. Spring Boot integrates naturally with Kubernetes — Actuator probes map directly to Kubernetes probes, and Spring configuration maps to ConfigMaps and Secrets.

Core Kubernetes Resources

Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  labels:
    app: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
        version: "1.2.3"
    spec:
      containers:
        - name: order-service
          image: devopsmonk/order-service:1.2.3
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 8081
              name: management

          # Resource limits — always set these
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "1000m"

          # Environment from ConfigMap and Secret
          envFrom:
            - configMapRef:
                name: order-service-config
            - secretRef:
                name: order-service-secrets

          # Individual env vars
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: prod
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace

          # Probes
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8081
            failureThreshold: 30
            periodSeconds: 10

          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8081
            initialDelaySeconds: 0
            periodSeconds: 10
            failureThreshold: 3

          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8081
            initialDelaySeconds: 0
            periodSeconds: 5
            failureThreshold: 3

          # Graceful shutdown
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "5"]

      # Allow enough time for graceful shutdown
      terminationGracePeriodSeconds: 60

  # Rolling update — never take down all pods at once
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0      # never have less than desired replicas
      maxSurge: 1            # allow 1 extra pod during update

Service

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: management
      port: 8081
      targetPort: 8081
  type: ClusterIP   # internal only — expose via Ingress for external traffic

ConfigMap and Secrets

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-config
data:
  SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/orders
  SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092
  SPRING_DATA_REDIS_HOST: redis
  MANAGEMENT_SERVER_PORT: "8081"
  SERVER_SHUTDOWN: graceful
  SPRING_LIFECYCLE_TIMEOUT_PER_SHUTDOWN_PHASE: 30s
# secret.yaml — values are base64-encoded
apiVersion: v1
kind: Secret
metadata:
  name: order-service-secrets
type: Opaque
stringData:   # stringData auto-encodes to base64
  SPRING_DATASOURCE_PASSWORD: "${DB_PASSWORD}"
  PAYMENT_API_KEY: "${PAYMENT_API_KEY}"
  JWT_SECRET: "${JWT_SECRET}"

Never commit real secrets to Git. Use:

  • kubectl create secret generic from environment variables
  • External Secrets Operator (syncs from Vault, AWS Secrets Manager)
  • Sealed Secrets (encrypted, safe to commit)

Horizontal Pod Autoscaler

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70   # scale up when avg CPU > 70%
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

Custom metrics-based autoscaling (scale on Kafka consumer lag):

metrics:
  - type: External
    external:
      metric:
        name: kafka_consumer_lag
        selector:
          matchLabels:
            topic: order-events
            consumer_group: inventory-service
      target:
        type: AverageValue
        averageValue: "1000"   # scale if lag > 1000 messages

Ingress

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-service-ingress
  annotations:
    nginx.ingress.kubernetes.io/rate-limit: "100"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.devopsmonk.com
      secretName: devopsmonk-tls
  rules:
    - host: api.devopsmonk.com
      http:
        paths:
          - path: /api/orders
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

Kubernetes-Native Configuration

Enable Spring to read from ConfigMaps and Secrets directly (no Config Server needed):

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring:
  cloud:
    kubernetes:
      config:
        enabled: true
        name: order-service-config     # reads this ConfigMap
        namespace: default
      secrets:
        enabled: true
        name: order-service-secrets
      reload:
        enabled: true
        mode: polling
        period: 15000    # reload every 15s if ConfigMap changes

Now you can update order-service-config ConfigMap, and the application reloads automatically — no restart needed.

RBAC for Spring Cloud Kubernetes

Spring Cloud Kubernetes reads ConfigMaps and Secrets via the Kubernetes API — the pod needs permissions:

# rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: order-service
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: order-service-role
rules:
  - apiGroups: [""]
    resources: ["configmaps", "secrets"]
    verbs: ["get", "list", "watch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: order-service-binding
subjects:
  - kind: ServiceAccount
    name: order-service
roleRef:
  kind: Role
  name: order-service-role
  apiGroup: rbac.authorization.k8s.io
# In Deployment spec:
spec:
  serviceAccountName: order-service

Namespace Isolation

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: order-platform
  labels:
    env: production

Separate namespaces per environment (dev, staging, prod) or per team. Apply NetworkPolicies to prevent cross-namespace communication:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-service-netpol
  namespace: order-platform
spec:
  podSelector:
    matchLabels:
      app: order-service
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: api-gateway
      ports:
        - port: 8080
    - from: []  # allow any for management port from monitoring namespace
      ports:
        - port: 8081

Only the API Gateway namespace can reach port 8080. The management port (8081) is accessible for Prometheus scraping.

PodDisruptionBudget

Prevent accidental outages during node maintenance or rolling updates:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-service-pdb
spec:
  minAvailable: 2    # always keep at least 2 pods running
  selector:
    matchLabels:
      app: order-service

kubectl drain (node maintenance) respects PDBs — it won’t evict pods if it would violate this budget.

Deployment Commands

# Apply all manifests
kubectl apply -f k8s/

# Rolling update — update the image tag
kubectl set image deployment/order-service order-service=devopsmonk/order-service:1.3.0

# Monitor rollout
kubectl rollout status deployment/order-service

# Roll back if something goes wrong
kubectl rollout undo deployment/order-service

# Scale manually
kubectl scale deployment/order-service --replicas=5

# Check pod logs
kubectl logs -l app=order-service -f --tail=100

# Execute into a running pod
kubectl exec -it $(kubectl get pod -l app=order-service -o name | head -1) -- /bin/sh

# Check events (good for debugging probe failures)
kubectl describe pod $(kubectl get pod -l app=order-service -o name | head -1)

What You’ve Learned

  • Kubernetes Deployment manages replicas, rolling updates, and self-healing
  • startupProbe prevents liveness/readiness probes from killing slow-starting apps
  • preStop: sleep 5 gives the load balancer time to deregister before shutdown begins
  • terminationGracePeriodSeconds must exceed Spring Boot’s shutdown timeout
  • ConfigMaps and Secrets inject configuration without baking it into the image
  • HPA scales based on CPU, memory, or custom metrics (Kafka lag, request rate)
  • PodDisruptionBudget ensures minimum availability during maintenance
  • Spring Cloud Kubernetes reads ConfigMaps/Secrets directly from the API — no Config Server needed

Next: Article 54 — Full Observability: Prometheus + Grafana + Tempo + Loki — the complete observability stack for production Spring Boot.