Kubernetes Deployments — Getting Your Java App Running
The core Kubernetes concepts and manifests to deploy a Java application and keep it running reliably.
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
Resources — requests 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.