Chuyển đến nội dung chính

Lesson 22: Kubernetes Deployment & CI/CD

Deploy microservices to Kubernetes, Helm charts, ConfigMaps/Secrets, HPA autoscaling, GitHub Actions CI/CD, monitoring with Prometheus + Grafana on K8s.

💻 Programming — Lesson 21 Lesson 22: Kubernetes Deployment & CI/CD

Quarkus Microservices: From Basics to Production

xdev.asia

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

CategoryItemStatus
SecuritySecrets encrypted (Sealed Secrets)☐
SecurityNetwork Policies configured☐
SecurityRBAC for service accounts☐
ReliabilityHealth probes configured☐
ReliabilityHPA configured☐
ReliabilityPodDisruptionBudget set☐
ObservabilityDistributed tracing (Jaeger)☐
ObservabilityMetrics (Prometheus + Grafana)☐
ObservabilityCentralized logging (EFK/Loki)☐
CI/CDAutomated tests☐
CI/CDContract tests☐
CI/CDAutomated deployment☐
CI/CDRollback strategy☐

Exercises

  1. Generate Kubernetes manifests with quarkus-kubernetes extension
  2. Create Helm chart for E-Commerce Platform
  3. Setup GitHub Actions CI/CD pipeline
  4. Deploy to local Kubernetes (minikube/kind)
  5. Configure HPA and check autoscaling
  6. Setup Prometheus + Grafana monitoring

Series Summary

Through 22 lessons, you have completely built:

  1. Quarkus Platform — REST API, PostgreSQL, Validation
  2. Microservices Design — DDD, Database per Service, 4 practical services
  3. Security — Keycloak OIDC, RBAC, Token Propagation
  4. Communication — REST Client, gRPC, Kafka Event-Driven
  5. Resilience & Observability — Fault Tolerance, OpenTelemetry, Caching
  6. Testing — @QuarkusTest, Contract Testing
  7. 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