Containerizing Spring Cloud Microservices with Docker and Kubernetes (Part 9)
This article explains why traditional deployment is problematic, then walks through building Docker images, composing services with Docker‑Compose, deploying to a Kubernetes cluster, setting up CI/CD pipelines, and addressing common pitfalls such as slow starts and service discovery failures.
Why Containerize?
Traditional deployment on physical/virtual machines leads to environment inconsistency, dependency conflicts, resource waste, scaling difficulty, and complex releases. Containerization provides consistent environments, isolation, elastic scaling, and fast deployment.
Docker Image Build
Base Dockerfile
# user-service/Dockerfile
# Multi‑stage build
FROM maven:3.8-openjdk-11 AS builder
WORKDIR /app
# Cache dependencies
COPY pom.xml .
COPY common/pom.xml ./common/
COPY user-service/pom.xml ./user-service/
RUN mvn dependency:go-offline
# Copy source and build
COPY common ./common/
COPY user-service ./user-service/
RUN mvn clean package -pl user-service -am -DskipTests
# Runtime stage
FROM openjdk:11-jre-slim
WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Create non‑root user
RUN groupadd -r blog && useradd -r -g blog blog
# Copy JAR
COPY --from=builder /app/user-service/target/user-service-*.jar app.jar
# Optional SkyWalking agent
COPY --from=builder /app/skywalking-agent /skywalking-agent
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8081/actuator/health || exit 1
USER blog
EXPOSE 8081
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC"
ENTRYPOINT ["sh","-c","java $JAVA_OPTS -javaagent:/skywalking-agent/skywalking-agent.jar -jar app.jar"]Build Commands
# Build image
docker build -t blog/user-service:1.0.0 -f user-service/Dockerfile .
# List image
docker images | grep blog
# Run container
docker run -d --name user-service -p 8081:8081 blog/user-service:1.0.0
# Push to registry
docker tag blog/user-service:1.0.0 your-registry/blog/user-service:1.0.0
docker push your-registry/blog/user-service:1.0.0Docker‑Compose Orchestration
A complete docker-compose.yml defines networks, volumes, and services for MySQL, Redis, Nacos, user‑service, article‑service, and gateway, with environment variables such as SPRING_PROFILES_ACTIVE=docker and health checks.
One‑Click Startup
# Build and start all services
docker-compose up -d --build
# View logs
docker-compose logs -f
# Scale user service to 3 instances
docker-compose up -d --scale user-service=3
# Stop all services
docker-compose down
# Remove data volumes
docker-compose down -vKubernetes Deployment
Namespace and ConfigMap
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: blog-system
---
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: blog-config
namespace: blog-system
data:
application.yml: |
spring:
cloud:
nacos:
discovery:
server-addr: nacos:8848Service Manifests
# user-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: blog-system
labels:
app: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: blog/user-service:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8081
env:
- name: SPRING_PROFILES_ACTIVE
value: "k8s"
- name: NACOS_HOST
value: "nacos"
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 30
periodSeconds: 5
---
# user-service-service.yaml
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: blog-system
spec:
selector:
app: user-service
ports:
- port: 8081
targetPort: 8081
type: ClusterIPGateway Deployment and Service
# gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway
namespace: blog-system
spec:
replicas: 2
selector:
matchLabels:
app: gateway
template:
metadata:
labels:
app: gateway
spec:
containers:
- name: gateway
image: blog/gateway:1.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "k8s"
- name: NACOS_HOST
value: "nacos"
---
# gateway-service.yaml
apiVersion: v1
kind: Service
metadata:
name: gateway
namespace: blog-system
spec:
selector:
app: gateway
ports:
- port: 8080
targetPort: 8080
type: LoadBalancerIngress and Autoscaling
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog-ingress
namespace: blog-system
spec:
ingressClassName: nginx
rules:
- host: api.blog.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gateway
port:
number: 8080 # hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: user-service-hpa
namespace: blog-system
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: user-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Deployment Commands
# Create namespace
kubectl create namespace blog-system
# Apply ConfigMap
kubectl apply -f configmap.yaml
# Deploy services
kubectl apply -f user-service-deployment.yaml
kubectl apply -f user-service-service.yaml
kubectl apply -f gateway-deployment.yaml
kubectl apply -f gateway-service.yaml
# Check status
kubectl get pods -n blog-system
kubectl get svc -n blog-system
# Scale
kubectl scale deployment user-service -n blog-system --replicas=5
# Rolling update
kubectl set image deployment/user-service -n blog-system user-service=blog/user-service:1.0.1
kubectl rollout status deployment/user-service -n blog-system
# Rollback
kubectl rollout undo deployment/user-service -n blog-systemCI/CD Pipeline with GitHub Actions
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
strategy:
matrix:
service: [user-service, article-service, gateway]
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Build with Maven
run: mvn clean package -pl ${{ matrix.service }} -am -DskipTests
- name: Build Docker image
run: |
docker build -t blog/${{ matrix.service }}:${{ github.sha }} \
-f ${{ matrix.service }}/Dockerfile .
- name: Push to Docker Hub
run: |
docker push blog/${{ matrix.service }}:${{ github.sha }}
- name: Deploy to K8s
run: |
kubectl set image deployment/${{ matrix.service }} \
${{ matrix.service }}=blog/${{ matrix.service }}:${{ github.sha }} \
-n blog-systemCommon Issues and Solutions
Slow container start‑up : add JVM container‑aware options
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=75".
Service discovery failure : avoid localhost inside containers; use service names (e.g., nacos:8848).
Health‑check failure : give enough start‑up time with initialDelaySeconds: 60 and appropriate periodSeconds.
Log disk exhaustion : configure Logback rolling policy with max-history: 7 and max-size: 100MB.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
