Post

Kubernetes Deployments — Getting Your Java App Running

The core Kubernetes concepts and manifests to deploy a Java application and keep it running reliably.

Kubernetes Deployments — Getting Your Java App Running

Kubernetes looks overwhelming from the outside. A lot of new concepts, a lot of YAML, and the documentation has a way of explaining things using other things you haven’t learned yet.

The way it clicked for me was to ignore everything except the four objects you actually need to get a Java service running in production. Everything else is addons and refinements on top of that foundation.

Those four things are: Deployment, Service, ConfigMap, and Secret.

Deployment

The Deployment describes what to run and how many copies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment-service
  template:
    metadata:
      labels:
        app: payment-service
    spec:
      containers:
        - name: payment-service
          image: registry.example.com/payment-service:1.4.2
          ports:
            - containerPort: 8080
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: payment-service-secrets
                  key: db-password
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 15
            failureThreshold: 3

Resourcesrequests is what Kubernetes uses for scheduling. limits is the ceiling. Set the memory limit generously for Java apps — if a pod breaches it, Kubernetes kills it. Combine with MaxRAMPercentage JVM flags so the heap stays within the container limit.

Probes — the readiness probe controls when the pod receives traffic. The liveness probe controls when Kubernetes restarts it. initialDelaySeconds matters for Java apps because the JVM needs time to start. Set it too low and Kubernetes declares the pod unhealthy before it’s had a chance to initialize.

Service

The Service gives your pods a stable network address inside the cluster:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: payment-service
  namespace: production
spec:
  selector:
    app: payment-service
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: ClusterIP

Other pods can now reach payment-service.production.svc.cluster.local:80 and Kubernetes load-balances across the three replicas.

ConfigMap and Secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: ConfigMap
metadata:
  name: payment-service-config
  namespace: production
data:
  db-url: "jdbc:postgresql://db.internal:5432/payments"
  log-level: "INFO"
---
apiVersion: v1
kind: Secret
metadata:
  name: payment-service-secrets
  namespace: production
type: Opaque
data:
  db-password: cGFzc3dvcmQxMjM=   # base64 encoded

Encode values:

1
2
$ echo -n "password123" | base64
cGFzc3dvcmQxMjM=

Note: base64 is not encryption. Secrets are only “secret” in that they’re stored separately and access can be restricted with RBAC. For real secret management, use Vault or External Secrets with a proper secrets manager.

Applying and checking

1
2
3
4
5
6
7
$ kubectl apply -f k8s/
$ kubectl rollout status deployment/payment-service -n production
$ kubectl get pods -n production -l app=payment-service
$ kubectl logs -n production -l app=payment-service --tail=100 -f

# When a pod won't start, this tells you why
$ kubectl describe pod -n production -l app=payment-service

Rolling updates and rollback

1
2
3
4
5
6
7
8
9
10
11
# Update the image — zero downtime by default
$ kubectl set image deployment/payment-service \
    payment-service=registry.example.com/payment-service:1.4.3 \
    -n production

# If something goes wrong
$ kubectl rollout undo deployment/payment-service -n production

# Roll back to a specific revision
$ kubectl rollout history deployment/payment-service -n production
$ kubectl rollout undo deployment/payment-service --to-revision=3 -n production

The rollback is the thing that made me a convert. On a traditional VM deployment, rolling back meant either re-deploying the old jar (fast if you have it handy) or a restore from backup (slow and risky). With Kubernetes, it’s one command and it’s done in seconds.

This post is licensed under CC BY 4.0 by the author.