──────────────────────────────────────────────────

目录

  1. [章节概述](#章节概述)

2. [Docker多阶段构建详解](#docker多阶段构建详解)

3. [Docker Compose本地开发环境](#docker-compose本地开发环境)

4. [Kubernetes集群部署架构](#kubernetes集群部署架构)

5. [HPA弹性伸缩配置](#hpa弹性伸缩配置)

6. [实际代码示例](#实际代码示例)

7. [章节总结](#章节总结)

──────────────────────────────────────────────────

章节概述

19.1 学习目标

本章节将深入讲解合同审核系统的容器化部署与Kubernetes编排技术栈,包括:

  • 掌握Docker多阶段构建技术,减小镜像体积
  • 熟练使用Docker Compose搭建本地开发环境
  • 理解Kubernetes核心资源对象及部署策略
  • 掌握HPA弹性伸缩配置,实现自动扩缩容

19.2 章节背景

合同智能审核系统涉及大模型推理,需要稳定的容器化部署环境支持。通过Docker和Kubernetes的组合,我们可以实现:

  • 环境一致性:开发、测试、生产环境统一
  • 弹性伸缩:根据负载自动调整实例数量
  • 高可用:多副本部署,自动故障恢复
  • 资源隔离:不同服务独立运行,互不影响

──────────────────────────────────────────────────

Docker多阶段构建详解

19.2.1 传统构建的问题

传统的Dockerfile通常存在以下问题:

# 传统方式 - 问题多
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y \
    openjdk-11-jdk \
    maven \
    git \
    && rm -rf /var/lib/apt/lists/*
COPY . /app
WORKDIR /app
RUN mvn package
CMD ["java", "-jar", "/app/target/contract-api.jar"]

**问题分析:**

  • 镜像体积过大(可能超过2GB)
  • 包含构建工具,安全性降低
  • 层次结构不清晰,缓存利用率低

19.2.2 多阶段构建解决方案

# contract-api/Dockerfile
# ===================================================================
# 第一阶段:构建阶段
# ===================================================================
FROM maven:3.8.6-openjdk-11 AS builder

# 设置工作目录
WORKDIR /build

# 复制pom.xml以利用Docker缓存
COPY pom.xml .

# 下载依赖(单独层,便于缓存)
RUN mvn dependency:go-offline -B

# 复制源代码
COPY src ./src

# 构建应用(跳过测试)
RUN mvn package -DskipTests

# ===================================================================
# 第二阶段:运行时阶段
# ===================================================================
FROM openjdk:11-jre-slim

# 创建非root用户
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

# 设置工作目录
WORKDIR /app

# 从构建阶段复制产物
COPY --from=builder /build/target/*.jar app.jar
COPY --from=builder /build/src/main/resources/config ./config

# 修改文件所有者
RUN chown -R appuser:appgroup /app

# 切换到非root用户
USER appuser

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD "curl -f http://localhost:8080/actuator/health || exit 1"

# JVM参数优化
ENV JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"

# 启动命令
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

19.2.3 构建实战

# 构建镜像
docker build -t contract-api:1.0.0 .

# 查看镜像大小对比
docker images | grep contract-api

# 多阶段构建日志示例
Step 1/15 : FROM maven:3.8.6-openjdk-11 AS builder
 ---> a1b2c3d4e5f6
Step 2/15 : WORKDIR /build
 ---> Using cache
Step 3/15 : COPY pom.xml .
 ---> Using cache
Step 4/15 : RUN mvn dependency:go-offline -B
 ---> Running in abc123...
Downloading dependencies...
Completed dependency resolution
 ---> def456...
...
Successfully built abc789
Successfully tagged contract-api:1.0.0

# 运行容器测试
docker run -d -p 8080:8080 --name contract-api contract-api:1.0.0

# 查看容器日志
docker logs -f contract-api

# 查看容器状态
docker inspect contract-api | grep -A 10 "Status"

19.2.4 镜像优化技巧

| 优化项 | 优化前 | 优化后 | 节省 |

|--------|--------|--------|------|

| 基础镜像 | ubuntu:20.04 (800MB) | openjdk:11-jre-slim (200MB) | 75% |

| 包含工具 | Maven、JDK完整版 | 仅JRE运行时 | 60% |

| 最终大小 | ~2.5GB | ~400MB | 84% |

──────────────────────────────────────────────────

Docker Compose本地开发环境

19.3.1 docker-compose.yml配置

# docker-compose.yml
version: '3.8'

services:
  # ===================================================================
  # 合同审核API服务
  # ===================================================================
  contract-api:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: contract-api
    ports:
      - "8080:8080"
      - "5005:5005"  # 远程调试端口
    environment:
      - SPRING_PROFILES_ACTIVE=dev
      - JAVA_OPTS=-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=n
      - DB_HOST=mysql
      - DB_PORT=3306
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    volumes:
      - ./logs:/app/logs
      - ./config/dev:/app/config:ro
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # MySQL数据库服务
  # ===================================================================
  mysql:
    image: mysql:8.0
    container_name: contract-mysql
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root_secret
      - MYSQL_DATABASE=contract_db
      - MYSQL_USER=contract_user
      - MYSQL_PASSWORD=contract_pass
    volumes:
      - mysql-data:/var/lib/mysql
      - ./init-scripts:/docker-entrypoint-initdb.d:ro
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --default-authentication-plugin=mysql_native_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot_secret"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # Redis缓存服务
  # ===================================================================
  redis:
    image: redis:7-alpine
    container_name: contract-redis
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # Elasticsearch日志存储
  # ===================================================================
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    container_name: contract-elasticsearch
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    volumes:
      - es-data:/usr/share/elasticsearch/data
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # Logstash日志处理
  # ===================================================================
  logstash:
    image: docker.elastic.co/logstash/logstash:8.11.0
    container_name: contract-logstash
    ports:
      - "5044:5044"
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline:ro
      - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
    depends_on:
      - elasticsearch
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # Kibana可视化
  # ===================================================================
  kibana:
    image: docker.elastic.co/kibana/kibana:8.11.0
    container_name: contract-kibana
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # Prometheus监控
  # ===================================================================
  prometheus:
    image: prom/prometheus:v2.47.0
    container_name: contract-prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # Grafana可视化监控
  # ===================================================================
  grafana:
    image: grafana/grafana:10.2.0
    container_name: contract-grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin123
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    depends_on:
      - prometheus
    networks:
      - contract-network
    restart: unless-stopped

  # ===================================================================
  # MinIO对象存储
  # ===================================================================
  minio:
    image: minio/minio:latest
    container_name: contract-minio
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin123
    volumes:
      - minio-data:/data
    command: server /data --console-address ":9001"
    networks:
      - contract-network
    restart: unless-stopped

# ===================================================================
# 网络配置
# ===================================================================
networks:
  contract-network:
    driver: bridge
    name: contract_dev_network

# ===================================================================
# 卷配置
# ===================================================================
volumes:
  mysql-data:
    name: contract_mysql_data
  redis-data:
    name: contract_redis_data
  es-data:
    name: contract_es_data
  prometheus-data:
    name: contract_prometheus_data
  grafana-data:
    name: contract_grafana_data
  minio-data:
    name: contract_minio_data

19.3.2 环境启动与验证

# 启动所有服务(后台运行)
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看服务日志
docker-compose logs -f contract-api
docker-compose logs -f mysql
docker-compose logs -f redis

# 等待服务健康检查通过
docker-compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"

# 测试API接口
curl -s http://localhost:8080/actuator/health | jq .

# 验证数据库连接
docker-compose exec mysql mysql -ucontract_user -pcontract_pass -e "SHOW DATABASES;"

# 验证Redis连接
docker-compose exec redis redis-cli ping

# 访问监控界面
# Prometheus: http://localhost:9090
# Grafana: http://localhost:3000 (admin/admin123)
# Kibana: http://localhost:5601
# MinIO: http://localhost:9001 (minioadmin/minioadmin123)

# 停止所有服务
docker-compose down

# 停止服务并清除数据卷
docker-compose down -v

──────────────────────────────────────────────────

Kubernetes集群部署架构

19.4.1 命名空间配置

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    name: production
    environment: production
    team: contract-team

# 创建命名空间
kubectl apply -f namespace.yaml

# 查看命名空间
kubectl get namespaces

19.4.2 ConfigMap配置

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: contract-api-config
  namespace: production
  labels:
    app: contract-api
    version: v1
data:
  # 应用配置
  application.yml: |
    spring:
      application:
        name: contract-api
      profiles:
        active: production
      servlet:
        multipart:
          max-file-size: 100MB
          max-request-size: 100MB

    # 数据库配置
    datasource:
      url: jdbc:mysql://mysql.production.svc.cluster.local:3306/contract_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
      username: contract_user
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        maximum-pool-size: 20
        minimum-idle: 5
        idle-timeout: 300000
        connection-timeout: 20000

    # Redis配置
    redis:
      host: redis.production.svc.cluster.local
      port: 6379
      timeout: 5000
      lettuce:
        pool:
          max-active: 50
          max-idle: 20
          min-idle: 5

    # 文件存储配置
    minio:
      endpoint: http://minio.production.svc.cluster.local:9000
      access-key: ${MINIO_ACCESS_KEY}
      secret-key: ${MINIO_SECRET_KEY}
      bucket: contracts

    # AI模型配置
    ai:
      model:
        provider: openai
        api-key: ${AI_API_KEY}
        base-url: https://api.openai.com/v1
        completion-model: gpt-4
        embedding-model: text-embedding-ada-002
        max-tokens: 4096
        temperature: 0.7

    # 日志配置
    logging:
      level:
        root: INFO
        com.contract: DEBUG
      pattern:
        console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

  # logback配置
  logback-spring.xml: |
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/contract-api}"/>
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
        <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
                <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
                    <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSSX</timestampFormat>
                    <timestampFormatTimezoneId>Asia/Shanghai</timestampFormatTimezoneId>
                    <appendLineSeparator>true</appendLineSeparator>
                </layout>
            </encoder>
        </appender>
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </configuration>

# 应用ConfigMap
kubectl apply -f configmap.yaml

# 查看ConfigMap
kubectl get configmap -n production
kubectl describe configmap contract-api-config -n production

19.4.3 Secret配置

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: contract-api-secret
  namespace: production
type: Opaque
stringData:
  # 数据库密码
  DB_PASSWORD: contract_db_secret_pass_2024
  # Redis密码(无密码时留空)
  REDIS_PASSWORD: ""
  # AI API密钥
  AI_API_KEY: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  # MinIO凭证
  MINIO_ACCESS_KEY: minioadmin
  MINIO_SECRET_KEY: minioadmin123
  # JWT密钥
  JWT_SECRET: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
  # 加密密钥
  ENCRYPTION_KEY: 0123456789abcdef0123456789abcdef

# 创建Secret
kubectl apply -f secret.yaml

# 查看Secret(加密显示)
kubectl get secret contract-api-secret -n production -o yaml

# 解码特定字段
kubectl get secret contract-api-secret -n production -o jsonpath="{.data.AI_API_KEY}" | base64 -d

19.4.4 Deployment配置

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: contract-api
  namespace: production
  labels:
    app: contract-api
    version: v1
    component: backend
spec:
  # 副本数
  replicas: 3

  # 选择器
  selector:
    matchLabels:
      app: contract-api

  # 滚动更新策略
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

  # Pod模板
  template:
    metadata:
      labels:
        app: contract-api
        version: v1
        component: backend
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/actuator/prometheus"
    spec:
      # 服务账户
      serviceAccountName: contract-api-sa

      # 安全上下文
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000

      # 初始化容器 - 等待数据库就绪
      initContainers:
        - name: wait-for-mysql
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              echo "Waiting for MySQL to be ready..."
              until nc -z mysql.production.svc.cluster.local 3306; do
                echo "MySQL is not ready, waiting..."
                sleep 5
              done
              echo "MySQL is ready!"
        - name: wait-for-redis
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              echo "Waiting for Redis to be ready..."
              until nc -z redis.production.svc.cluster.local 6379; do
                echo "Redis is not ready, waiting..."
                sleep 3
              done
              echo "Redis is ready!"

      # 容器配置
      containers:
        - name: contract-api
          image: contract-api:1.0.0
          imagePullPolicy: Always

          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
            - name: ajp
              containerPort: 8009
              protocol: TCP

          # 环境变量
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: "production"
            - name: JAVA_OPTS
              value: "-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof"
            - name: DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: contract-api-config
                  key: DB_HOST
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: contract-api-secret
                  key: DB_PASSWORD
            - name: AI_API_KEY
              valueFrom:
                secretKeyRef:
                  name: contract-api-secret
                  key: AI_API_KEY

          # 资源配置
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: 2000m
              memory: 4Gi

          # 存活探针
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            initialDelaySeconds: 60
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
            successThreshold: 1

          # 就绪探针
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            initialDelaySeconds: 30
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
            successThreshold: 1

          # 启动探针
          startupProbe:
            httpGet:
              path: /actuator/health
              port: http
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 30

          # 卷挂载
          volumeMounts:
            - name: app-logs
              mountPath: /app/logs
            - name: tmp-volume
              mountPath: /tmp
            - name: heapdump-volume
              mountPath: /tmp

      # 卷配置
      volumes:
        - name: app-logs
          emptyDir: {}
        - name: tmp-volume
          emptyDir: {}
        - name: heapdump-volume
          emptyDir: {}

      # 亲和性配置
      affinity:
        # Pod反亲和性 - 分散到不同节点
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: contract-api
                topologyKey: kubernetes.io/hostname
        # 节点亲和性
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 10
              preference:
                matchExpressions:
                  - key: node-type
                    operator: In
                    values:
                      - compute-optimized

      # 容忍调度
      tolerations:
        - key: "node-type"
          operator: "Equal"
          value: "compute-optimized"
          effect: "NoSchedule"

# 部署应用
kubectl apply -f deployment.yaml

# 查看部署状态
kubectl get deployment -n production
kubectl describe deployment contract-api -n production

# 查看Pods
kubectl get pods -n production -l app=contract-api

# 查看Pod日志
kubectl logs -f deployment/contract-api -n production

# 查看资源使用
kubectl top pods -n production -l app=contract-api

19.4.5 Service配置

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: contract-api
  namespace: production
  labels:
    app: contract-api
spec:
  # Service类型
  type: ClusterIP

  # 会话亲和性
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

  # 端口配置
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP
    - name: ajp
      port: 8009
      targetPort: 8009
      protocol: TCP

  # 选择器
  selector:
    app: contract-api

---
# Headless Service - 用于有状态服务
apiVersion: v1
kind: Service
metadata:
  name: contract-api-headless
  namespace: production
  labels:
    app: contract-api
spec:
  clusterIP: None
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app: contract-api

# 应用Service
kubectl apply -f service.yaml

# 查看Service
kubectl get service -n production

# 测试Service连通性(临时Pod)
kubectl run -it --rm debug-pod --image=busybox:1.36 --restart=Never -- sh
# 在Pod内执行
nslookup contract-api.production.svc.cluster.local
curl http://contract-api.production.svc.cluster.local/actuator/health
exit

19.4.6 Ingress配置

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: contract-api-ingress
  namespace: production
  labels:
    app: contract-api
  annotations:
    # 重写规则
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    # CORS配置
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, DELETE, PATCH, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization,Content-Type,Accept,Origin,User-Agent,Cache-Control,Keep-Alive,X-Requested-With"
    # 连接超时
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    # 速率限制
    nginx.ingress.kubernetes.io/limit-rps: "100"
    nginx.ingress.kubernetes.io/limit-connections: "50"
    # 代理头
    nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - contract-api.example.com
      secretName: contract-api-tls
  rules:
    - host: contract-api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: contract-api
                port:
                  number: 80

──────────────────────────────────────────────────

HPA弹性伸缩配置

19.5.1 HPA资源清单

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: contract-api-hpa
  namespace: production
  labels:
    app: contract-api
spec:
  # 缩放目标
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: contract-api

  # 副本数范围
  minReplicas: 2
  maxReplicas: 20

  # metrics配置
  metrics:
    # CPU指标
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

    # 内存指标
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

    # 自定义指标 - HTTP请求率
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"

  # 行为策略
  behavior:
    # 扩容策略
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        # 基于百分比扩容 - 每次最多增加100%副本
        - type: Percent
          value: 100
          periodSeconds: 15
        # 基于Pod数量扩容 - 每次最多增加4个Pod
        - type: Pods
          value: 4
          periodSeconds: 15
      selectPolicy: Max

    # 缩容策略
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        # 每次最多删除1个Pod
        - type: Pods
          value: 1
          periodSeconds: 60
      selectPolicy: Min

19.5.2 垂直 Pod 自动扩缩容 (VPA)

# vpa.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: contract-api-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: contract-api
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
      - containerName: contract-api
        minAllowed:
          cpu: 250m
          memory: 512Mi
        maxAllowed:
          cpu: 4000m
          memory: 8Gi
        controlledResources: ["cpu", "memory"]

19.5.3 HPA验证命令

# 创建HPA
kubectl apply -f hpa.yaml

# 查看HPA状态
kubectl get hpa -n production
kubectl describe hpa contract-api-hpa -n production

# 查看HPA推荐值
kubectl get hpa contract-api-hpa -n production -o jsonpath="{.status}" | jq .

# 手动触发扩容测试
kubectl scale deployment contract-api --replicas=10 -n production

# 查看当前副本数
kubectl get pods -n production -l app=contract-api --watch

# 删除HPA
kubectl delete hpa contract-api-hpa -n production

──────────────────────────────────────────────────

实际代码示例

19.6.1 完整的Kubernetes资源清单

# k8s-resources.yaml
# ===================================================================
# 命名空间
# ===================================================================
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    name: production
---
# ===================================================================
# ConfigMap
# ===================================================================
apiVersion: v1
kind: ConfigMap
metadata:
  name: contract-api-config
  namespace: production
data:
  application.yml: |
    spring:
      application:
        name: contract-api
      servlet:
        multipart:
          max-file-size: 100MB
          max-request-size: 100MB
    server:
      port: 8080
      tomcat:
        threads:
          max: 200
          min-spare: 10
        accept-count: 100
        max-connections: 10000
---
# ===================================================================
# Secret
# ===================================================================
apiVersion: v1
kind: Secret
metadata:
  name: contract-api-secret
  namespace: production
type: Opaque
stringData:
  DB_PASSWORD: production_db_password
  AI_API_KEY: sk-production-key
---
# ===================================================================
# Deployment
# ===================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: contract-api
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: contract-api
  template:
    metadata:
      labels:
        app: contract-api
    spec:
      containers:
        - name: contract-api
          image: contract-api:1.0.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: 2000m
              memory: 4Gi
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 5
---
# ===================================================================
# Service
# ===================================================================
apiVersion: v1
kind: Service
metadata:
  name: contract-api
  namespace: production
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: contract-api
---
# ===================================================================
# HPA
# ===================================================================
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: contract-api-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: contract-api
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

# 一键部署所有资源
kubectl apply -f k8s-resources.yaml

# 验证部署
kubectl get all -n production

# 查看Pod事件
kubectl describe pods -n production | grep -A 10 "Events:"

# 清理所有资源
kubectl delete -f k8s-resources.yaml

19.6.2 Helm Chart打包

# Chart.yaml
apiVersion: v2
name: contract-api
description: A Helm chart for Contract API Service
type: application
version: 1.0.0
appVersion: "1.0.0"
keywords:
  - contract
  - api
  - spring-boot
maintainers:
  - name: Contract Team
    email: team@example.com

# values.yaml
replicaCount: 3

image:
  repository: contract-api
  tag: "1.0.0"
  pullPolicy: Always

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

ingress:
  enabled: true
  className: nginx
  host: contract-api.example.com
  tls:
    enabled: true
    secretName: contract-api-tls

resources:
  requests:
    cpu: 500m
    memory: 1Gi
  limits:
    cpu: 2000m
    memory: 4Gi

hpa:
  enabled: true
  minReplicas: 2
  maxReplicas: 20
  targetCPUUtilizationPercentage: 70

config:
  springProfiles: production

# 打包Chart
helm package ./chart

# 安装Release
helm install contract-api ./contract-api-1.0.0.tgz -n production

# 升级Release
helm upgrade contract-api ./contract-api-1.0.0.tgz -n production

# 回滚Release
helm rollback contract-api 1 -n production

# 卸载Release
helm uninstall contract-api -n production

──────────────────────────────────────────────────

章节总结

19.7 核心知识点回顾

| 知识点 | 关键内容 |

|--------|----------|

| Docker多阶段构建 | Builder阶段构建 + Runtime阶段运行,减小80%镜像体积 |

| Docker Compose | 一键启动完整开发环境,依赖管理,健康检查 |

| Kubernetes资源 | Deployment/Service/ConfigMap/Secret/HPA |

| HPA弹性伸缩 | CPU/内存/自定义指标,自动扩缩容策略 |

| Helm Chart | 应用打包,版本管理,一键部署升级回滚 |

19.8 最佳实践建议

  1. **镜像优化**:始终使用多阶段构建,仅包含运行时必需的组件

2. **资源配置**:合理设置requests和limits,避免资源浪费

3. **健康检查**:配置livenessProbe和readinessProbe,确保服务可用性

4. **弹性伸缩**:根据实际负载调整HPA阈值,建议CPU 70%,内存80%

5. **日志收集**:统一日志格式,便于ELK收集和分析

19.9 扩展学习方向

  • 学习Kubernetes Operator开发,实现自定义资源控制器
  • 研究Service Mesh(Istio)实现更精细的流量管理
  • 探索Knative实现serverless化的容器部署
  • 学习ArgoCD实现GitOps方式的持续部署

──────────────────────────────────────────────────

下章预告

第20篇将介绍**生产上线与持续迭代**相关内容:

  • 生产环境验收测试清单
  • 灰度发布与回滚策略
  • Prometheus + Grafana监控告警配置
  • ELK日志收集与分析
  • Jenkins/GitHub Actions持续集成部署

──────────────────────────────────────────────────

*版权声明:本文为洛水石原创文章,版权所有,侵权必究。*

图: docker multi stage build.png

图: hpa scaling diagram.png

图: kubernetes deployment architecture.png

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐