Deploying Applications on Kubernetes

Kubernetes provides powerful abstractions to deploy and manage containerized applications at scale. Understanding these building blocks is essential for effectively running applications in a Kubernetes cluster.

Goal: learn how to deploy a Java Spring (Spring Boot) microservice on Kubernetes, step by step. This post is organized in three blocks: Pods, Deployments & ReplicaSets, and Services & Networking.

1. Prerequisites & Sample Spring Boot App

  • JDK 17+ and Maven or Gradle
  • Docker or another OCI image builder
  • A Kubernetes cluster (Kind/Minikube/K3d/AKS/EKS/GKE) and kubectl

Minimal REST app (Maven)

// src/main/java/com/example/demo/DemoApplication.java
@SpringBootApplication
@RestController
public class DemoApplication {
  @GetMapping("/healthz") public String health() { return "ok"; }
  @GetMapping("/hello") public Map<String,String> hello() {
    return Map.of("message", "Hello, Kubernetes!");
  }
  public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
}

Dockerfile (multi-stage, small image):

# Dockerfile
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -q -e -DskipTests dependency:go-offline
COPY src ./src
RUN mvn -q -DskipTests package

FROM eclipse-temurin:17-jre
ENV JAVA\_OPTS="-XX\:MaxRAMPercentage=75 -XX:+UseG1GC"
WORKDIR /app
COPY --from=build /app/target/\*SNAPSHOT.jar app.jar
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=2s --retries=3 CMD curl -fs [http://localhost:8080/healthz](http://localhost:8080/healthz) || exit 1
ENTRYPOINT \["sh", "-c", "java \$JAVA\_OPTS -jar app.jar"] 

Build & (optionally) push:

docker build -t demo-svc:1.0.0 .
# If using a remote registry:
# docker tag demo-svc:1.0.0 <REGISTRY>/demo-svc:1.0.0
# docker push <REGISTRY>/demo-svc:1.0.0

2. Creating & Managing Pods

A Pod is the smallest deployable unit in Kubernetes. It usually runs one container (your app), optionally sidecars (like an agent).

Pod manifest (for learning only; in production use Deployments):

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  labels:
    app: demo
spec:
  containers:
    - name: app
      image: demo-svc:1.0.0   # or <REGISTRY>/demo-svc:1.0.0
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
      readinessProbe:
        httpGet: { path: /healthz, port: 8080 }
        initialDelaySeconds: 5
        periodSeconds: 10
      livenessProbe:
        httpGet: { path: /healthz, port: 8080 }
        initialDelaySeconds: 15
        periodSeconds: 20

Create & inspect:

kubectl apply -f pod.yaml
kubectl get pods -o wide
kubectl logs -f demo-pod
kubectl exec -it demo-pod -- curl -s http://localhost:8080/hello

Why not Pods alone? Pods are ephemeral and not self-healing. For rollouts and replicas, use Deployments.

3. Deployments & ReplicaSets

A Deployment manages desired state, rollouts, and rollback via underlying ReplicaSets.

ConfigMap & Secret (externalize config):

apiVersion: v1
kind: ConfigMap
metadata: { name: demo-config }
data:
  SPRING_PROFILES_ACTIVE: "prod"
  WELCOME_MESSAGE: "Hello from ConfigMap"

---

apiVersion: v1
kind: Secret
metadata: { name: demo-secret }
type: Opaque
stringData:
DB\_USERNAME: "appuser"
DB\_PASSWORD: "s3cr3t" 

Deployment (3 replicas, rolling updates, resource limits, probes):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-deployment
  labels: { app: demo }
spec:
  replicas: 3
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels: { app: demo }
  template:
    metadata:
      labels: { app: demo }
    spec:
      serviceAccountName: demo-sa
      containers:
        - name: app
          image: demo-svc:1.0.0      # bump version to roll out
          ports: [{ containerPort: 8080 }]
          envFrom:
            - configMapRef: { name: demo-config }
            - secretRef:    { name: demo-secret }
          readinessProbe:
            httpGet: { path: /healthz, port: 8080 }
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet: { path: /healthz, port: 8080 }
            initialDelaySeconds: 15
            periodSeconds: 20
          resources:
            requests: { cpu: "200m", memory: "256Mi" }
            limits:   { cpu: "500m", memory: "512Mi" }
      # Optional tolerations/affinity can be added here

Basic rollout operations:

# Create / update
kubectl apply -f config.yaml -f secret.yaml -f deployment.yaml

# Watch status

kubectl rollout status deployment/demo-deployment

# History & rollback

kubectl rollout history deployment/demo-deployment
kubectl rollout undo deployment/demo-deployment --to-revision=1 

4. Services & Networking

A Service provides a stable virtual IP and DNS for a set of Pods (selected by labels). Common types:

  • ClusterIP (default): internal-only
  • NodePort: exposes a port on each node (dev/test)
  • LoadBalancer: requests a cloud LB (prod on managed clouds)

ClusterIP Service for the app:

apiVersion: v1
kind: Service
metadata:
  name: demo-svc
spec:
  type: ClusterIP
  selector: { app: demo }
  ports:
    - name: http
      port: 80
      targetPort: 8080

Ingress (optional; requires an Ingress Controller like NGINX):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: demo.local   # map in DNS or /etc/hosts for local
      http:
        paths:
          - path: /(.*)
            pathType: Prefix
            backend:
              service:
                name: demo-svc
                port: { number: 80 }

Local testing without Ingress:

# Port-forward to your laptop
kubectl port-forward svc/demo-svc 8080:80
curl -s http://localhost:8080/hello

5. Probes, Resources & Autoscaling

  • Liveness restarts crashed or stuck containers.
  • Readiness gates traffic until the app is ready.
  • Resource requests/limits enable fair scheduling and stability.
  • Horizontal Pod Autoscaler (HPA) adjusts replicas based on metrics.

HPA (CPU-based autoscaling 1–5 replicas):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: demo-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: demo-deployment
  minReplicas: 1
  maxReplicas: 5
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

6. Apply & Verify

Put the above manifests in one file (separated by ---) or multiple files, then:

# Create a dedicated namespace (recommended)
kubectl create namespace demo
kubectl config set-context --current --namespace=demo

# (Optional) service account and simple RBAC

kubectl apply -f - <\

7. Troubleshooting Cheatsheet

  • Image pull issues: check image name, registry auth (imagePullSecrets), and kubectl describe pod events.
  • CrashLoopBackOff: view kubectl logs POD -p (previous), inspect probes and env vars.
  • Readiness failing: hit the pod directly (kubectl exec + curl), confirm port/path.
  • Service not routing: labels must match spec.selector; verify with kubectl get endpoints demo-svc.
  • Ingress 404: ensure controller is installed and rule host/path matches, check kubectl logs of the ingress controller.
  • Resources: too low memory causes OOMKill; tune requests/limits and JVM heap (JAVA_OPTS).

Takeaways: Start with Pods to understand the basics, then use Deployments for rollouts and resilience, and wire traffic through Services (and optional Ingress). Add probes, resource limits, and HPA for production-grade behavior.

8. Architecture Diagram

The following PlantUML diagram summarizes the full deployment workflow of our Java Spring Boot microservice on Kubernetes. It shows how developers push code, CI/CD builds and publishes container images, and how Kubernetes orchestrates Pods, Deployments, Services, and Ingress with support from ConfigMaps, Secrets, Autoscaling, Observability, and Logging.



Post a Comment

0 Comments