Introduction
Final lesson: deploy the entire E-Commerce Platform to Kubernetes, automate with GitHub Actions CI/CD, monitor production with Prometheus & Grafana. Quarkus has extensions quarkus-kubernetes self-generate manifests.
Architecture on Kubernetes
┌─── Ingress (Nginx) ───┐
│ ecommerce.xdev.asia │
└──────┬────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌───────────┐ ┌────────────┐
│ Product │ │ Order │ │ Payment │
│ Service │ │ Service │ │ Service │
│ (3 pods) │ │ (3 pods) │ │ (2 pods) │
└──────┬─────┘ └─────┬─────┘ └──────┬─────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────┐
│ PostgreSQL (StatefulSet) │
│ Kafka (Strimzi Operator) │
│ Keycloak (Operator) │
│ Redis (StatefulSet) │
└────────────────────────────────────┘
│
┌──────┴────────────────────────────┐
│ Monitoring Namespace │
│ Prometheus + Grafana + Jaeger │
└──────────────────────────────────┘
Quarkus Kubernetes Extension
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
application.properties
# Kubernetes deployment config
quarkus.kubernetes.deployment-target=kubernetes
quarkus.kubernetes.namespace=ecommerce
# Container image
quarkus.container-image.group=xdev
quarkus.container-image.name=product-service
quarkus.container-image.tag=${quarkus.application.version}
quarkus.container-image.registry=ghcr.io
# Deployment settings
quarkus.kubernetes.replicas=3
quarkus.kubernetes.image-pull-policy=always
# Resources
quarkus.kubernetes.resources.requests.cpu=100m
quarkus.kubernetes.resources.requests.memory=64Mi
quarkus.kubernetes.resources.limits.cpu=500m
quarkus.kubernetes.resources.limits.memory=128Mi
# Health probes (auto-configured từ SmallRye Health)
quarkus.kubernetes.liveness-probe.http-action-path=/q/health/live
quarkus.kubernetes.readiness-probe.http-action-path=/q/health/ready
quarkus.kubernetes.startup-probe.http-action-path=/q/health/started
# Service
quarkus.kubernetes.service-type=cluster-ip
quarkus.kubernetes.ports.http.container-port=8080
# Labels
quarkus.kubernetes.labels."app.kubernetes.io/part-of"=ecommerce
quarkus.kubernetes.labels."app.kubernetes.io/managed-by"=quarkus
# Env from ConfigMap & Secret
quarkus.kubernetes.env.configmaps=product-service-config
quarkus.kubernetes.env.secrets=product-service-secret
Build generates target/kubernetes/kubernetes.yml:
./mvnw package -Dnative \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.push=true
Kubernetes Manifests
Namespace & ConfigMap
# k8s/base/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ecommerce
labels:
app.kubernetes.io/part-of: ecommerce
---
# k8s/base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: product-service-config
namespace: ecommerce
data:
QUARKUS_DATASOURCE_JDBC_URL: >-
jdbc:postgresql://postgres:5432/productdb
QUARKUS_OIDC_AUTH_SERVER_URL: >-
http://keycloak:8080/realms/ecommerce
QUARKUS_LOG_LEVEL: INFO
QUARKUS_OTEL_EXPORTER_OTLP_ENDPOINT: >-
http://jaeger-collector:4317
Secrets (sealed)
# k8s/base/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: product-service-secret
namespace: ecommerce
type: Opaque
stringData:
QUARKUS_DATASOURCE_USERNAME: product
QUARKUS_DATASOURCE_PASSWORD: "${DB_PASSWORD}"
Production: use Sealed Secrets or External Secrets Operator instead of plain Secret.
HPA — Horizontal Pod Autoscaler
# k8s/base/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: product-service
namespace: ecommerce
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: product-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 120
Ingress
# k8s/base/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ecommerce-ingress
namespace: ecommerce
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- api.ecommerce.xdev.asia
secretName: ecommerce-tls
rules:
- host: api.ecommerce.xdev.asia
http:
paths:
- path: /products(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: product-service
port:
number: 8080
- path: /orders(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: order-service
port:
number: 8080
- path: /payments(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: payment-service
port:
number: 8080
Helm Chart
ecommerce-chart/
├── Chart.yaml
├── values.yaml
├── values-staging.yaml
├── values-production.yaml
└── templates/
├── _helpers.tpl
├── deployment.yaml
├── service.yaml
├── configmap.yaml
├── secret.yaml
├── hpa.yaml
└── ingress.yaml
values.yaml
# values.yaml
global:
namespace: ecommerce
imageRegistry: ghcr.io/xdev
services:
product:
name: product-service
image:
tag: latest
replicas: 2
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
hpa:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPU: 70
order:
name: order-service
image:
tag: latest
replicas: 2
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
payment:
name: payment-service
image:
tag: latest
replicas: 2
notification:
name: notification-service
image:
tag: latest
replicas: 1
ingress:
enabled: true
host: api.ecommerce.xdev.asia
tls: true
values-production.yaml
# values-production.yaml
services:
product:
replicas: 3
resources:
limits:
cpu: "1"
memory: 256Mi
hpa:
minReplicas: 3
maxReplicas: 20
order:
replicas: 3
payment:
replicas: 2
Deploy
# Install
helm install ecommerce ./ecommerce-chart \
-n ecommerce --create-namespace \
-f values-production.yaml
# Upgrade
helm upgrade ecommerce ./ecommerce-chart \
-n ecommerce \
-f values-production.yaml \
--set services.product.image.tag=v1.2.0
# Rollback
helm rollback ecommerce 1 -n ecommerce
GitHub Actions CI/CD
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
service:
- product-service
- order-service
- payment-service
- notification-service
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Run tests
run: |
cd ${{ matrix.service }}
./mvnw verify
contract-test:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Consumer contract tests
run: |
cd order-service
./mvnw test -Dtest="*ContractTest"
- name: Provider verification
run: |
cd product-service
./mvnw test -Dtest="*ContractVerificationTest"
build-and-push:
needs: [test, contract-test]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
service:
- product-service
- order-service
- payment-service
- notification-service
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build native image & push
run: |
cd ${{ matrix.service }}
./mvnw package -Dnative \
-Dquarkus.native.container-build=true \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.push=true \
-Dquarkus.container-image.registry=ghcr.io \
-Dquarkus.container-image.group=\
${{ github.repository_owner }} \
-Dquarkus.container-image.tag=\
${{ github.sha }} \
-DskipTests
deploy-staging:
needs: build-and-push
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v4
- name: Set K8s context
uses: azure/k8s-set-context@v4
with:
kubeconfig: ${{ secrets.KUBE_CONFIG_STAGING }}
- name: Deploy to staging
run: |
helm upgrade --install ecommerce \
./ecommerce-chart \
-n ecommerce-staging \
--create-namespace \
-f ecommerce-chart/values-staging.yaml \
--set global.imageTag=${{ github.sha }}
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v4
- name: Set K8s context
uses: azure/k8s-set-context@v4
with:
kubeconfig: ${{ secrets.KUBE_CONFIG_PRODUCTION }}
- name: Deploy to production
run: |
helm upgrade --install ecommerce \
./ecommerce-chart \
-n ecommerce \
--create-namespace \
-f ecommerce-chart/values-production.yaml \
--set global.imageTag=${{ github.sha }}
- name: Verify deployment
run: |
kubectl rollout status deployment/product-service \
-n ecommerce --timeout=300s
kubectl rollout status deployment/order-service \
-n ecommerce --timeout=300s
Monitoring on Kubernetes
ServiceMonitor for Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: quarkus-services
namespace: ecommerce
labels:
release: prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/part-of: ecommerce
endpoints:
- port: http
path: /q/metrics
interval: 15s
Grafana Dashboard
Import dashboard ID 14370 (Quarkus Microprofile Metrics) or create custom:
{
"dashboard": {
"title": "E-Commerce Platform",
"panels": [
{
"title": "Request Rate",
"targets": [{
"expr": "rate(http_server_requests_seconds_count{namespace='ecommerce'}[5m])"
}]
},
{
"title": "P99 Latency",
"targets": [{
"expr": "histogram_quantile(0.99, rate(http_server_requests_seconds_bucket{namespace='ecommerce'}[5m]))"
}]
},
{
"title": "Error Rate",
"targets": [{
"expr": "rate(http_server_requests_seconds_count{namespace='ecommerce',status=~'5..'}[5m])"
}]
}
]
}
}
Production Checklist
| Category | Item | Status |
|---|---|---|
| Security | Secrets encrypted (Sealed Secrets) | ☐ |
| Security | Network Policies configured | ☐ |
| Security | RBAC for service accounts | ☐ |
| Reliability | Health probes configured | ☐ |
| Reliability | HPA configured | ☐ |
| Reliability | PodDisruptionBudget set | ☐ |
| Observability | Distributed tracing (Jaeger) | ☐ |
| Observability | Metrics (Prometheus + Grafana) | ☐ |
| Observability | Centralized logging (EFK/Loki) | ☐ |
| CI/CD | Automated tests | ☐ |
| CI/CD | Contract tests | ☐ |
| CI/CD | Automated deployment | ☐ |
| CI/CD | Rollback strategy | ☐ |
Exercises
- Generate Kubernetes manifests with
quarkus-kubernetesextension - Create Helm chart for E-Commerce Platform
- Setup GitHub Actions CI/CD pipeline
- Deploy to local Kubernetes (minikube/kind)
- Configure HPA and check autoscaling
- Setup Prometheus + Grafana monitoring
Series Summary
Through 22 lessons, you have completely built:
- Quarkus Platform — REST API, PostgreSQL, Validation
- Microservices Design — DDD, Database per Service, 4 practical services
- Security — Keycloak OIDC, RBAC, Token Propagation
- Communication — REST Client, gRPC, Kafka Event-Driven
- Resilience & Observability — Fault Tolerance, OpenTelemetry, Caching
- Testing — @QuarkusTest, Contract Testing
- Production — GraalVM Native, Kubernetes, CI/CD
Next steps:
- Add Saga Pattern for distributed transactions
- GraphQL API Gateway (quarkus-smallrye-graphql)
- Service Mesh (Istio) for traffic management
- GitOps with ArgoCD
- Quarkus Virtual Threads (Project Loom) for reactive workloads