Containers & Kubernetes — Architect-Level Interview Guide
Target: Senior Engineer · Engineering Lead · Pre-Architect Focus: Docker, Kubernetes, probes, secrets management, service mesh
Q: How do you containerize a Spring Boot application with Docker? What are best practices?
Why interviewers ask this: Image size, security, and layer caching directly impact build speed, deployment time, and attack surface.
Answer
Naive Dockerfile (avoid):
Problems: 600MB+ image, full JDK in production, rebuilds everything on any change.Best practice — multi-stage layered build:
# Stage 1: Extract Spring Boot layers
FROM eclipse-temurin:21-jre-alpine AS builder
WORKDIR /app
COPY target/app.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
# Stage 2: Production image
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Run as non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Copy layers — only app code changes frequently
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
EXPOSE 8080
# Use exec form to receive signals correctly (graceful shutdown)
ENTRYPOINT ["java", \
"-XX:MaxRAMPercentage=75.0", \
"-XX:+UseContainerSupport", \
"org.springframework.boot.loader.launch.JarLauncher"]
Why layers matter: Docker caches each layer. If only application code changes, the dependencies layer (unchanged) is reused — build goes from 3 min to 15 sec.
Layer rebuild frequency:
dependencies/ ← Rarely changes → cached aggressively
spring-boot-loader/ ← Rarely changes → cached
snapshot-dependencies/ ← Sometimes
application/ ← Every commit → always rebuilt
Image best practices:
| Practice | Why |
|---|---|
Use alpine or distroless base |
Smaller attack surface, smaller image |
| Non-root user | Limits damage if container is compromised |
| No JDK in production | JRE only — eclipse-temurin:21-jre |
| Pin image tags | eclipse-temurin:21.0.3_9-jre-alpine not latest |
| Scan image for CVEs | Use docker scout, Trivy, or Snyk in CI |
| Set memory limits | -XX:MaxRAMPercentage=75.0 respects container limits |
Q: Describe the key components of a Kubernetes deployment for a Java microservice.
Answer
Complete production-ready deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: production
labels:
app: order-service
version: "1.2.0"
spec:
replicas: 3
selector:
matchLabels:
app: order-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Allow 1 extra pod during update
maxUnavailable: 0 # Never reduce below desired count
template:
metadata:
labels:
app: order-service
spec:
serviceAccountName: order-service-sa # Least privilege SA
containers:
- name: order-service
image: myrepo/order-service:1.2.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: order-db-secret
key: password
resources:
requests:
memory: "256Mi" # Guaranteed allocation
cpu: "250m"
limits:
memory: "512Mi" # OOM kill threshold
cpu: "500m"
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: production
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Key components explained:
graph LR
HPA["HPA · Auto-scale 3-10 pods"]
Deploy["Deployment · 3 replicas"]
Pod1["Pod 1"]
Pod2["Pod 2"]
Pod3["Pod 3"]
Svc["Service · ClusterIP"]
Secret["Secret · DB password"]
SA["ServiceAccount · least-priv"]
HPA -->|Scale| Deploy
Deploy -->|Creates| Pod1
Deploy -->|Creates| Pod2
Deploy -->|Creates| Pod3
Svc -->|Load balance| Pod1
Svc -->|Load balance| Pod2
Svc -->|Load balance| Pod3
Secret -.->|Mounted| Pod1
SA -.->|Identity| Deploy
Architect Insight
Always set both requests and limits. Without requests, the scheduler can't place pods correctly. Without limits, one runaway pod can starve other services on the node. Set memory limit = 2x request and cpu limit = 2x request as a starting point.
Q: How do you securely manage secrets in Kubernetes?
Why interviewers ask this: Secret sprawl is a compliance and security nightmare. Tests understanding of secret lifecycle management.
Answer
Kubernetes Secrets (built-in) — limitations:
- Base64 encoded, not encrypted at rest by default
- Visible to anyone with cluster access
- No rotation, no audit trail
Better approaches:
1. Kubernetes + Sealed Secrets (GitOps-safe):
# Encrypt secret for Git storage
kubeseal --scope namespace-wide -o yaml < secret.yaml > sealed-secret.yaml
# Sealed secret committed to Git — only the controller in cluster can decrypt
2. HashiCorp Vault + Vault Agent Injector:
# Pod annotation — Vault injects secrets as files
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "order-service"
vault.hashicorp.com/agent-inject-secret-db: "secret/data/order-service/db"
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "secret/data/order-service/db" -}}
spring.datasource.password={{ .Data.data.password }}
{{- end -}}
3. External Secrets Operator (AWS, GCP, Azure):
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: order-db-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: order-db-secret # Creates K8s Secret
data:
- secretKey: password
remoteRef:
key: prod/order-service/db
property: password
Comparison:
| Approach | Encryption at rest | Rotation | Audit log | Complexity |
|---|---|---|---|---|
| Plain K8s Secret | ❌ (unless KMS) | Manual | ❌ | Low |
| Sealed Secrets | ✅ | Manual | ❌ | Medium |
| Vault | ✅ | ✅ Auto | ✅ | High |
| External Secrets | ✅ (cloud KMS) | ✅ Auto | ✅ (cloud) | Medium |
Common Mistake
Never use environment variables for secrets in Kubernetes. They're visible in kubectl describe pod, process listings, and crash dumps. Mount secrets as files with restrictive permissions (0400) and read them at startup.
Q: What are the best practices for container networking in microservices?
Answer
Network policy — default deny everything, allow explicitly:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: order-service-netpol
namespace: production
spec:
podSelector:
matchLabels:
app: order-service
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api-gateway # Only gateway can call order-service
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: inventory-service # order-service can call inventory
ports:
- port: 8080
- to: # Allow DNS
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP
Key networking concepts for microservices:
| Concept | Implementation | Purpose |
|---|---|---|
| Service discovery | Kubernetes DNS (order-service.production.svc) |
Find services by name |
| Load balancing | Kubernetes Service (ClusterIP) |
Distribute traffic across pods |
| Ingress | NGINX Ingress / Istio Gateway | External traffic → cluster |
| mTLS | Istio / Linkerd | Encrypt and authenticate service-to-service |
| Network isolation | NetworkPolicy | Prevent lateral movement |
Q: What is a service mesh? When should you use one?
Answer
A service mesh is an infrastructure layer that handles service-to-service communication, providing:
- Traffic management — retries, circuit breaking, timeouts, traffic splitting
- Observability — automatic distributed tracing, metrics per service pair
- Security — mTLS between all services, certificate rotation
- Policy enforcement — rate limiting, authorization policies
Without service mesh: Each microservice implements these itself (Resilience4j, Spring Cloud). With service mesh: Handled by sidecar proxy (Envoy) — zero code changes needed.
graph LR
SvcA["Service A"]
ProxyA["Envoy Sidecar"]
SvcB["Service B"]
ProxyB["Envoy Sidecar"]
ControlPlane["Istio Control Plane · Istiod"]
SvcA -->|localhost| ProxyA
ProxyA -->|mTLS · with policy| ProxyB
ProxyB -->|localhost| SvcB
ControlPlane -.->|Config · certs| ProxyA
ControlPlane -.->|Config · certs| ProxyB
When to use a service mesh:
✅ Use when:
- You have 10+ services and maintaining resilience code in each is costly
- You need zero-trust security (mTLS everywhere) as a compliance requirement
- You need fine-grained traffic control (canary per service pair, fault injection for testing)
- You want automatic observability without code instrumentation
❌ Skip when:
- Small service count (< 10) — operational overhead outweighs benefits
- Team unfamiliar with service mesh — steep learning curve (Istio especially)
- Simple deployments — start with library-based resilience (Resilience4j) first
Cloud-Native Patterns
Q: When would you use serverless functions over containerised microservices? What are the trade-offs?
Why interviewers ask this: Serverless is widely adopted but often misapplied. Tests understanding of cold start penalties, statelessness constraints, and cost models.
Answer
Serverless (FaaS): Functions are deployed as individual units. The platform manages scaling, infrastructure, and lifecycle. You pay per invocation — not for idle capacity.
| Dimension | Serverless (Lambda · Azure Functions) | Containerised Microservice (k8s) |
|---|---|---|
| Scaling | Automatic, to zero | Manual HPA config, min replicas > 0 |
| Cold start | 100ms–2s latency on first invocation | No cold start (always-on) |
| Cost | Pay per invocation (cheap for sporadic) | Pay per running instance (wasteful if idle) |
| Execution limit | 15 min max (AWS Lambda) | Unlimited |
| State | Stateless only | Can be stateful with volumes |
| Vendor lock-in | High (platform-specific runtimes) | Low (portable OCI images) |
| Observability | Platform-provided, less control | Full control with custom tooling |
Good serverless candidates:
✅ Use serverless for:
- Event-driven processing: S3 upload triggers image resize
- Scheduled jobs: nightly report generation, data cleanup
- Sporadic webhooks: payment provider callbacks
- Lightweight data transformations in data pipelines
- Glue code between managed services
❌ Avoid serverless for:
- Low-latency user-facing APIs (cold start is unacceptable)
- Long-running workloads (>15 min processing)
- Stateful services (session, in-memory cache)
- High-throughput consistent traffic (containers are cheaper at scale)
Spring Cloud Function — portable serverless:
// Same function runs on AWS Lambda, Azure Functions, or as a Spring Boot endpoint
@SpringBootApplication
public class OrderProcessorApplication {
@Bean
public Function<OrderEvent, ProcessedOrder> processOrder() {
return event -> {
// Stateless transformation
return orderProcessor.process(event);
};
}
}
graph LR
S3["S3 Upload\nEvent"]
Func["Lambda Function\nImage Resize"]
DDB["DynamoDB\nStore Metadata"]
SNS["SNS Topic\nNotify User"]
S3 -->|Trigger| Func
Func -->|Save| DDB
Func -->|Publish| SNS
style Func fill:#ffe066
style S3 fill:#4ecdc4
Q: How do you design a multi-cloud strategy for a microservices platform?
Why interviewers ask this: Tests strategic thinking about vendor independence, latency, compliance, and the real cost of multi-cloud complexity.
Answer
Why multi-cloud?
| Driver | Explanation |
|---|---|
| Vendor lock-in avoidance | No dependency on a single provider's SLAs or pricing changes |
| Regulatory compliance | Data residency laws may require specific regions or providers |
| Resilience | Cloud provider outages (rare but costly) — failover to another cloud |
| Best-of-breed services | GCP BigQuery for analytics, AWS S3 for storage, Azure AD for identity |
| Cost optimization | Leverage pricing differences across providers |
Architecture strategies:
1. Active-Active Multi-Cloud:
Traffic split across AWS + GCP simultaneously
✅ Maximum availability ❌ Data synchronisation complexity
Use: global platforms, financial services, > 99.99% SLA requirements
2. Active-Passive (DR) Multi-Cloud:
Primary on AWS, failover to Azure
✅ Simpler data sync (one-way replication) ❌ Failover has RTO cost
Use: disaster recovery, most enterprise workloads
3. Services Split by Cloud:
Auth on Azure AD, compute on AWS, ML on GCP
✅ Use each cloud's strengths ❌ Cross-cloud latency and egress costs
Use: data engineering, ML pipelines
Portability best practices:
# Use cloud-agnostic abstractions:
# 1. Kubernetes everywhere — same manifests, different cloud k8s managed service
# AWS EKS | GCP GKE | Azure AKS
# 2. Terraform for IaC — provider-abstracted modules
provider "aws" { region = "us-east-1" }
# Swap provider block to replicate infra on Azure
# 3. Abstract cloud SDK calls behind interfaces
public interface ObjectStorage {
void upload(String bucket, String key, byte[] data);
byte[] download(String bucket, String key);
}
// AWS implementation: AmazonS3Client
// GCP implementation: Storage client
// Azure implementation: BlobServiceClient
# 4. Use portable message brokers (Confluent Kafka, Pulsar) over cloud-native (SQS, Pub/Sub)
Common Mistake
Multi-cloud sounds strategically sound but has high operational cost. Data egress fees between clouds are significant (often $0.08/GB). Latency between clouds adds 20–80ms. Start with multi-region within one cloud (much simpler), then consider multi-cloud only if you have a specific compelling driver — vendor lock-in risk, regulatory requirement, or a specific best-of-breed service need.