9.1 容器化概述
9.1.1 容器化架构
graph TB
A[应用代码] --> B[Quarkus 应用]
B --> C[容器镜像]
C --> D[容器运行时]
D --> E[Kubernetes 集群]
F[Docker] --> C
G[Podman] --> C
H[Buildah] --> C
E --> I[Pod]
E --> J[Service]
E --> K[Ingress]
E --> L[ConfigMap]
E --> M[Secret]
9.1.2 Quarkus 容器化优势
- 快速启动时间:毫秒级启动
- 低内存占用:原生镜像内存使用极低
- 小镜像体积:优化的镜像层
- 云原生支持:完美适配 Kubernetes
9.2 Docker 容器化
9.2.1 Dockerfile 配置
JVM 模式 Dockerfile
# 多阶段构建 - JVM 模式
FROM registry.access.redhat.com/ubi8/openjdk-17:1.15 AS builder
# 复制源代码
COPY --chown=185 . /app
WORKDIR /app
# 构建应用
RUN ./mvnw clean package -DskipTests
# 运行时镜像
FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15
# 设置环境变量
ENV LANGUAGE='en_US:en'
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
# 复制应用 JAR
COPY --from=builder --chown=185 /app/target/quarkus-app/lib/ /deployments/lib/
COPY --from=builder --chown=185 /app/target/quarkus-app/*.jar /deployments/
COPY --from=builder --chown=185 /app/target/quarkus-app/app/ /deployments/app/
COPY --from=builder --chown=185 /app/target/quarkus-app/quarkus/ /deployments/quarkus/
# 暴露端口
EXPOSE 8080
# 设置用户
USER 185
# 启动命令
ENTRYPOINT ["java", "-jar", "/deployments/quarkus-run.jar"]
原生镜像 Dockerfile
# 多阶段构建 - 原生模式
FROM quay.io/quarkus/ubi-quarkus-graalvm-builder-image:22.3-java17 AS builder
# 复制源代码
COPY --chown=quarkus:quarkus . /app
WORKDIR /app
# 构建原生镜像
RUN ./mvnw clean package -Pnative -DskipTests
# 运行时镜像
FROM quay.io/quarkus/quarkus-micro-image:2.0
# 复制原生可执行文件
COPY --from=builder --chown=1001 /app/target/*-runner /application
# 暴露端口
EXPOSE 8080
# 设置用户
USER 1001
# 启动命令
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
9.2.2 Docker Compose 配置
# docker-compose.yml
version: '3.8'
services:
# Quarkus 应用
quarkus-app:
build:
context: .
dockerfile: src/main/docker/Dockerfile.jvm
ports:
- "8080:8080"
environment:
- QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://postgres:5432/quarkus
- QUARKUS_DATASOURCE_USERNAME=quarkus
- QUARKUS_DATASOURCE_PASSWORD=quarkus
- QUARKUS_REDIS_HOSTS=redis://redis:6379
depends_on:
- postgres
- redis
networks:
- quarkus-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/q/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=quarkus
- POSTGRES_USER=quarkus
- POSTGRES_PASSWORD=quarkus
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- quarkus-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U quarkus"]
interval: 10s
timeout: 5s
retries: 5
# Redis 缓存
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- quarkus-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# Prometheus 监控
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
networks:
- quarkus-network
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--web.enable-lifecycle'
# Grafana 可视化
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards
- ./grafana/datasources:/etc/grafana/provisioning/datasources
networks:
- quarkus-network
volumes:
postgres_data:
redis_data:
prometheus_data:
grafana_data:
networks:
quarkus-network:
driver: bridge
9.2.3 容器构建脚本
#!/bin/bash
# build-container.sh
set -e
# 配置变量
APP_NAME="quarkus-demo"
VERSION="1.0.0"
REGISTRY="your-registry.com"
NAMESPACE="your-namespace"
# 构建模式:jvm 或 native
BUILD_MODE=${1:-jvm}
echo "构建 Quarkus 应用容器镜像..."
echo "应用名称: $APP_NAME"
echo "版本: $VERSION"
echo "构建模式: $BUILD_MODE"
# 清理之前的构建
echo "清理之前的构建..."
./mvnw clean
# 根据构建模式选择 Dockerfile
if [ "$BUILD_MODE" = "native" ]; then
DOCKERFILE="src/main/docker/Dockerfile.native"
IMAGE_TAG="$REGISTRY/$NAMESPACE/$APP_NAME:$VERSION-native"
echo "构建原生镜像..."
else
DOCKERFILE="src/main/docker/Dockerfile.jvm"
IMAGE_TAG="$REGISTRY/$NAMESPACE/$APP_NAME:$VERSION-jvm"
echo "构建 JVM 镜像..."
fi
# 构建 Docker 镜像
echo "构建 Docker 镜像: $IMAGE_TAG"
docker build -f $DOCKERFILE -t $IMAGE_TAG .
# 标记为 latest
LATEST_TAG="$REGISTRY/$NAMESPACE/$APP_NAME:latest-$BUILD_MODE"
docker tag $IMAGE_TAG $LATEST_TAG
echo "镜像构建完成:"
echo " - $IMAGE_TAG"
echo " - $LATEST_TAG"
# 推送到镜像仓库(可选)
read -p "是否推送到镜像仓库? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "推送镜像到仓库..."
docker push $IMAGE_TAG
docker push $LATEST_TAG
echo "镜像推送完成"
fi
echo "容器构建流程完成"
9.3 Kubernetes 部署
9.3.1 基础部署配置
Deployment 配置
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: quarkus-demo
namespace: default
labels:
app: quarkus-demo
version: v1
spec:
replicas: 3
selector:
matchLabels:
app: quarkus-demo
template:
metadata:
labels:
app: quarkus-demo
version: v1
spec:
containers:
- name: quarkus-demo
image: your-registry.com/your-namespace/quarkus-demo:1.0.0-jvm
ports:
- containerPort: 8080
name: http
env:
- name: QUARKUS_DATASOURCE_JDBC_URL
valueFrom:
configMapKeyRef:
name: quarkus-config
key: database.url
- name: QUARKUS_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: quarkus-secret
key: database.username
- name: QUARKUS_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: quarkus-secret
key: database.password
- name: QUARKUS_LOG_LEVEL
value: "INFO"
- name: JAVA_OPTS_APPEND
value: "-Dquarkus.http.host=0.0.0.0 -Xmx512m -Xms256m"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /q/health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /q/health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /q/health/started
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
restartPolicy: Always
terminationGracePeriodSeconds: 30
Service 配置
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: quarkus-demo-service
namespace: default
labels:
app: quarkus-demo
spec:
selector:
app: quarkus-demo
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
Ingress 配置
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: quarkus-demo-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.yourdomain.com
secretName: quarkus-demo-tls
rules:
- host: api.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: quarkus-demo-service
port:
number: 80
9.3.2 配置管理
ConfigMap 配置
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: quarkus-config
namespace: default
data:
application.properties: |
# 数据库配置
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://postgres-service:5432/quarkus
quarkus.datasource.jdbc.max-size=20
quarkus.datasource.jdbc.min-size=5
# Redis 配置
quarkus.redis.hosts=redis://redis-service:6379
quarkus.redis.timeout=10s
# 日志配置
quarkus.log.console.enable=true
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.console.level=INFO
# 监控配置
quarkus.micrometer.export.prometheus.enabled=true
quarkus.micrometer.export.prometheus.path=/q/metrics
# 健康检查配置
quarkus.health.extensions.enabled=true
quarkus.health.openapi.included=true
database.url: "jdbc:postgresql://postgres-service:5432/quarkus"
redis.url: "redis://redis-service:6379"
log.level: "INFO"
Secret 配置
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: quarkus-secret
namespace: default
type: Opaque
data:
database.username: cXVhcmt1cw== # base64 encoded 'quarkus'
database.password: cXVhcmt1cw== # base64 encoded 'quarkus'
jwt.secret: bXlfc3VwZXJfc2VjcmV0X2tleQ== # base64 encoded 'my_super_secret_key'
redis.password: "" # 空密码
9.3.3 部署脚本
#!/bin/bash
# deploy.sh
set -e
# 配置变量
NAMESPACE="default"
APP_NAME="quarkus-demo"
KUBECONFIG=${KUBECONFIG:-~/.kube/config}
echo "部署 Quarkus 应用到 Kubernetes..."
echo "命名空间: $NAMESPACE"
echo "应用名称: $APP_NAME"
# 检查 kubectl 连接
echo "检查 Kubernetes 连接..."
kubectl cluster-info --kubeconfig=$KUBECONFIG
# 创建命名空间(如果不存在)
echo "确保命名空间存在..."
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
# 应用配置
echo "应用 ConfigMap 和 Secret..."
kubectl apply -f k8s/configmap.yaml -n $NAMESPACE
kubectl apply -f k8s/secret.yaml -n $NAMESPACE
# 部署应用
echo "部署应用..."
kubectl apply -f k8s/deployment.yaml -n $NAMESPACE
kubectl apply -f k8s/service.yaml -n $NAMESPACE
kubectl apply -f k8s/ingress.yaml -n $NAMESPACE
# 等待部署完成
echo "等待部署完成..."
kubectl rollout status deployment/$APP_NAME -n $NAMESPACE --timeout=300s
# 检查部署状态
echo "检查部署状态..."
kubectl get pods -l app=$APP_NAME -n $NAMESPACE
kubectl get services -l app=$APP_NAME -n $NAMESPACE
kubectl get ingress -n $NAMESPACE
echo "部署完成!"
# 获取访问信息
echo "\n访问信息:"
echo "内部访问: http://$APP_NAME-service.$NAMESPACE.svc.cluster.local"
echo "外部访问: https://api.yourdomain.com"
echo "\n健康检查:"
echo "kubectl port-forward service/$APP_NAME-service 8080:80 -n $NAMESPACE"
echo "curl http://localhost:8080/q/health"
9.4 云原生特性
9.4.1 自动扩缩容配置
HorizontalPodAutoscaler
# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: quarkus-demo-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: quarkus-demo
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:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 60
- type: Pods
value: 2
periodSeconds: 60
selectPolicy: Max
VerticalPodAutoscaler
# k8s/vpa.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: quarkus-demo-vpa
namespace: default
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: quarkus-demo
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: quarkus-demo
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 1
memory: 1Gi
controlledResources: ["cpu", "memory"]
9.4.2 服务网格集成
Istio 配置
# istio/virtual-service.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: quarkus-demo-vs
namespace: default
spec:
hosts:
- api.yourdomain.com
gateways:
- quarkus-demo-gateway
http:
- match:
- uri:
prefix: /api/v1
route:
- destination:
host: quarkus-demo-service
port:
number: 80
weight: 90
- destination:
host: quarkus-demo-service-canary
port:
number: 80
weight: 10
fault:
delay:
percentage:
value: 0.1
fixedDelay: 5s
retries:
attempts: 3
perTryTimeout: 2s
timeout: 10s
---
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: quarkus-demo-gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: quarkus-demo-tls
hosts:
- api.yourdomain.com
目标规则
# istio/destination-rule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: quarkus-demo-dr
namespace: default
spec:
host: quarkus-demo-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 10
loadBalancer:
simple: LEAST_CONN
outlierDetection:
consecutiveErrors: 3
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
subsets:
- name: v1
labels:
version: v1
- name: canary
labels:
version: canary
9.4.3 监控和可观测性
ServiceMonitor 配置
# monitoring/service-monitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: quarkus-demo-metrics
namespace: default
labels:
app: quarkus-demo
spec:
selector:
matchLabels:
app: quarkus-demo
endpoints:
- port: http
path: /q/metrics
interval: 30s
scrapeTimeout: 10s
Grafana Dashboard 配置
{
"dashboard": {
"id": null,
"title": "Quarkus Application Metrics",
"tags": ["quarkus", "microservices"],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "HTTP Requests Rate",
"type": "graph",
"targets": [
{
"expr": "rate(http_server_requests_seconds_count{job=\"quarkus-demo\"}[5m])",
"legendFormat": "{{method}} {{uri}}"
}
]
},
{
"id": 2,
"title": "Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(http_server_requests_seconds_bucket{job=\"quarkus-demo\"}[5m]))",
"legendFormat": "95th percentile"
}
]
}
]
}
}
9.5 CI/CD 集成
9.5.1 GitHub Actions 工作流
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Run tests
run: ./mvnw clean test
- name: Generate test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Maven Tests
path: target/surefire-reports/*.xml
reporter: java-junit
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: src/main/docker/Dockerfile.jvm
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
deploy:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'
- name: Configure kubectl
run: |
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy to Kubernetes
run: |
export KUBECONFIG=kubeconfig
sed -i "s|IMAGE_TAG|${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}|g" k8s/deployment.yaml
kubectl apply -f k8s/
kubectl rollout status deployment/quarkus-demo -n default
9.5.2 GitLab CI/CD 配置
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
cache:
paths:
- .m2/repository/
- target/
test:
stage: test
image: maven:3.8.6-openjdk-17
script:
- ./mvnw $MAVEN_CLI_OPTS clean test
artifacts:
reports:
junit:
- target/surefire-reports/TEST-*.xml
paths:
- target/
coverage: '/Total.*?([0-9]{1,3})%/'
build-image:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
before_script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- docker build -f src/main/docker/Dockerfile.jvm -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
only:
- main
deploy-staging:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT
- sed -i "s|IMAGE_TAG|$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA|g" k8s/deployment.yaml
- kubectl apply -f k8s/ -n staging
- kubectl rollout status deployment/quarkus-demo -n staging
environment:
name: staging
url: https://staging-api.yourdomain.com
only:
- main
deploy-production:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT
- sed -i "s|IMAGE_TAG|$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA|g" k8s/deployment.yaml
- kubectl apply -f k8s/ -n production
- kubectl rollout status deployment/quarkus-demo -n production
environment:
name: production
url: https://api.yourdomain.com
when: manual
only:
- main
9.6 本章小结
9.6.1 核心概念回顾
- 容器化优势:快速启动、低资源占用、一致性部署
- Kubernetes 部署:声明式配置、自动化管理
- 云原生特性:弹性扩缩容、服务网格、可观测性
- CI/CD 集成:自动化构建、测试、部署流程
9.6.2 技术要点总结
- 多阶段构建:优化镜像大小和安全性
- 健康检查:确保应用可用性和可靠性
- 配置管理:分离配置和代码,支持多环境
- 监控集成:全面的应用和基础设施监控
- 自动扩缩容:根据负载自动调整资源
9.6.3 最佳实践
- 使用非 root 用户运行容器
- 实施资源限制和请求
- 配置适当的健康检查
- 使用 Secret 管理敏感信息
- 实施滚动更新策略
- 监控应用性能和资源使用
9.6.4 下一章预告
下一章将深入探讨 性能优化与生产实践,包括: - JVM 调优策略 - 原生镜像优化 - 缓存策略 - 数据库优化 - 生产环境最佳实践
通过本章学习,你已经掌握了 Quarkus 应用的容器化和云原生部署技能,能够构建可扩展、高可用的微服务应用。