Deployment — Cheat Sheet
Microservices · 5 topics. Download the PDF or the Instagram carousel and share it.
Containerisation with Docker
Each microservice is packaged as a Docker image; multi-stage builds keep images small, and images are tagged with the commit SHA for traceability.
- ✓Multi-stage Dockerfile: build in JDK stage, copy only the JAR to a minimal JRE runtime stage — keeps images small and secure.
- ✓Layered JARs (Spring Boot 2.3+) split the fat JAR into layers ordered by change frequency, maximising Docker layer cache reuse.
- ✓Tag images with commit SHA and semantic version; never use `latest` in production — it prevents reliable rollbacks.
- ✓Always run containers as non-root users — set runAsNonRoot: true and a specific UID in the K8s securityContext.
- ✓Set JVM flag -XX:MaxRAMPercentage=75.0 so the JVM respects container memory limits instead of using the host RAM.
- ✓Always set K8s resource requests and limits — requests are used for scheduling; limits cap CPU/memory consumption.
# Stage 1: Build — uses full JDK + Maven, not included in final image FROM eclipse-temurin:21-jdk AS builder WORKDIR /build # Cache dependency layer separately (only invalidated when pom.xml changes) COPY pom.xml . COPY .mvn .mvn COPY mvnw . RUN ./mvnw dependency:go-offline -q # Copy source and build COPY src ./src RUN ./mvnw package -DskipTests -q # Extract layered JAR (Spring Boot 2.3+ layered JAR support) RUN java -Djarmode=layertools -jar target/*.jar extract --destination extracted # Stage 2: Runtime — minimal JRE image, no build tools FROM eclipse-temurin:21-jre AS runtime WORKDIR /app RUN addgroup --system app && adduser --system --group app USER app # run as non-root # Copy layers in order of change frequency (stable layers first for cache) COPY --from=builder /build/extracted/dependencies/ ./ COPY --from=builder /build/extracted/spring-boot-loader/ ./ COPY --from=builder /build/extracted/snapshot-dependencies/ ./ COPY --from=builder /build/extracted/application/ ./ EXPOSE 8080 ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
Kubernetes for Microservices
Kubernetes orchestrates containers across nodes; Deployments, Services, ConfigMaps, and HorizontalPodAutoscalers provide self-healing, scaling, and traffic routing.
- ✓Deployment manages rolling updates (maxUnavailable=0, maxSurge=1) for zero-downtime deploys across pod replicas
- ✓Service provides stable cluster-internal DNS (<name>.<namespace>.svc.cluster.local) and load balancing
- ✓readinessProbe removes a pod from Service endpoints during rolling update until it is ready to serve traffic
- ✓startupProbe replaces liveness during slow startup, preventing premature restarts of slow-starting JVM applications
- ✓HPA scales replicas based on CPU utilisation or custom Prometheus metrics (via Prometheus Adapter)
- ✓Graceful shutdown (server.shutdown=graceful) combined with terminationGracePeriodSeconds prevents request drops on pod termination
# Deployment — manages pod replicas with rolling updates
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: backend
spec:
replicas: 3
selector:
matchLabels:
app: order-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # zero-downtime rolling update
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: registry.example.com/order-service:1.2.0
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: order-service-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: order-service-secrets
key: db-password
resources:
requests: { memory: "256Mi", cpu: "100m" }
limits: { memory: "512Mi", cpu: "500m" }
---
# Service — stable DNS: order-service.backend.svc.cluster.local
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: backend
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
---
# ConfigMap — non-sensitive config
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
namespace: backend
data:
SPRING_PROFILES_ACTIVE: "prod"
KAFKA_BOOTSTRAP_SERVERS: "kafka.infra:9092"Blue-Green Deployment
Run two identical production environments; switch the load-balancer to the new (green) release instantly, with the old (blue) kept on standby for instant rollback.
- ✓Blue-green provides instant rollback by switching the load-balancer/Service selector — no gradual traffic shifting.
- ✓Both environments must remain compatible with the same database schema during the transition window.
- ✓Use the expand-contract pattern for schema changes: add new columns first, deploy, then remove old columns later.
- ✓Cold-start latency: warm up green (health checks, cache population) before cutting over to avoid error spikes.
- ✓Blue-green costs 2× infrastructure; use deployment slots (Azure) or Kubernetes inactive Deployments to manage cost.
- ✓Blue-green is better than canary for large atomic changes that cannot be partial (e.g. dependent service contracts).
# blue deployment (currently live)
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service-blue
labels:
app: order-service
version: blue
spec:
replicas: 5
selector:
matchLabels:
app: order-service
version: blue
template:
metadata:
labels:
app: order-service
version: blue
spec:
containers:
- name: order-service
image: order-service:v1.2.0
# Service — currently pointing to blue
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
version: blue # ← switch this to "green" for cutover
ports:
- port: 80
targetPort: 8080Canary Deployment
Route a small percentage of traffic to the new version, observe error rate and latency, then gradually increase to 100% or roll back based on signals.
- ✓Start with a small canary slice (1–5%) and use real production traffic — synthetic testing misses real user behavior.
- ✓Define success criteria before deploying: p99 latency, error rate, and business metrics (conversion, revenue).
- ✓Always have an automated rollback trigger — manual monitoring of canaries at 2 AM is not a strategy.
- ✓Canary differs from blue-green: canary is gradual traffic shifting; blue-green is an instant full cutover.
- ✓Stateful services (databases) require extra care — canary may run a newer schema against shared state.
- ✓Header-based canary routing (X-Canary: true) is useful for internal testing before enabling percentage-based routing.
# rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: order-service
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 5 # 5% canary traffic
- pause: {duration: 2m}
- setWeight: 20
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 100 # full promotion
analysis:
templates:
- templateName: http-error-rate
startingStep: 1
canaryService: order-service-canary
stableService: order-service-stable
selector:
matchLabels:
app: order-serviceService Mesh (Istio)
A service mesh injects sidecar proxies (Envoy) alongside every pod; mTLS, traffic policies, retries, and telemetry are handled by the mesh without application changes.
- ✓Istio injects Envoy sidecars into every pod — all traffic flows through the sidecar transparently to the application
- ✓PeerAuthentication STRICT enforces mTLS between all pods in the namespace — no plaintext service-to-service traffic
- ✓VirtualService defines routing rules (canary weights, header-based routing); DestinationRule defines load balancing and circuit breaker
- ✓Retries, timeouts, and circuit breakers are configured in VirtualService/DestinationRule — no Resilience4j library needed
- ✓Sidecars emit automatic telemetry (metrics, traces, access logs) to Prometheus/Jaeger without application instrumentation
- ✓AuthorizationPolicy implements RBAC at the mesh level — restricts which services can call which endpoints
# Enable Istio sidecar injection for a namespace
kubectl label namespace backend istio-injection=enabled
# Verify sidecars are injected
kubectl get pod -n backend order-service-xyz -o jsonpath='{.spec.containers[*].name}'
# order-service istio-proxy ← two containers: app + sidecar
# PeerAuthentication — enable mTLS for all services in namespace
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: backend
spec:
mtls:
mode: STRICT # reject all non-mTLS connections (PERMISSIVE=allow both)
# Check mTLS status
istioctl x describe pod order-service-xyz.backend
# Shows: mTLS STRICT, policy, incoming/outgoing port rules
# Mesh-wide mTLS (all namespaces)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system # root namespace = mesh-wide
spec:
mtls:
mode: STRICT