K8s 部署

Kubernetes 是容器编排的事实标准,Spring Boot 应用容器化后可利用 K8s 实现滚动发布、自动扩缩容、健康自愈等能力。本文以常见生产配置为主线,覆盖 Deployment、Service、ConfigMap、Ingress、HPA 等核心资源。


前置:构建镜像

K8s 部署前需先将应用打包为 Docker 镜像并推送到镜像仓库,详见 Docker部署

# 构建并推送
docker build -t registry.example.com/myapp:1.2.0 .
docker push registry.example.com/myapp:1.2.0

一、Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: production
  labels:
    app: myapp
    version: "1.2.0"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # 滚动时最多多创建 1 个 Pod
      maxUnavailable: 0    # 滚动时保持所有旧 Pod 可用(零停机)
  template:
    metadata:
      labels:
        app: myapp
        version: "1.2.0"
    spec:
      containers:
        - name: myapp
          image: registry.example.com/myapp:1.2.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 8081
              name: actuator
 
          # 资源限制
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi
 
          # 环境变量
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: "production"
            - name: JAVA_TOOL_OPTIONS
              value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Dfile.encoding=UTF-8"
            # 从 Secret 注入数据库密码
            - name: SPRING_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: myapp-secrets
                  key: db-password
            # 从 ConfigMap 注入配置
            - name: SPRING_DATASOURCE_URL
              valueFrom:
                configMapKeyRef:
                  name: myapp-config
                  key: db-url
 
          # 存活探针:失败则重启 Pod
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: actuator
            initialDelaySeconds: 60   # 等待 JVM 启动
            periodSeconds: 10
            failureThreshold: 3
 
          # 就绪探针:失败则从 Service 摘除,不接收流量
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: actuator
            initialDelaySeconds: 30
            periodSeconds: 5
            failureThreshold: 3
 
          # 启动探针:应用启动慢时防止存活探针误杀
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: actuator
            initialDelaySeconds: 10
            periodSeconds: 5
            failureThreshold: 30    # 最多等 30*5=150 秒
 
          # 优雅停机:SIGTERM 后等待请求处理完毕
          lifecycle:
            preStop:
              exec:
                command: ["sh", "-c", "sleep 5"]   # 等待 LB 摘除此 Pod
 
      # 终止等待时间(需大于 Spring 优雅停机超时)
      terminationGracePeriodSeconds: 60
 
      imagePullSecrets:
        - name: registry-credentials

二、Service

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: production
spec:
  selector:
    app: myapp
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: actuator
      port: 8081
      targetPort: 8081
  type: ClusterIP    # 集群内部访问;对外暴露用 Ingress

三、ConfigMap 与 Secret

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
  namespace: production
data:
  db-url: "jdbc:postgresql://postgres-service:5432/mydb"
  redis-host: "redis-service"
  # 完整 application.yml(挂载为文件)
  application.yml: |
    spring:
      jpa:
        open-in-view: false
      cache:
        type: redis
    logging:
      level:
        root: INFO
# secret.yaml(值需 base64 编码)
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
  namespace: production
type: Opaque
stringData:             # stringData 无需手动 base64
  db-password: "s3cr3t"
  redis-password: "r3d1s"
  jwt-secret: "very-long-jwt-secret-key"

将 ConfigMap 挂载为配置文件

# deployment.yaml 中添加
spec:
  template:
    spec:
      containers:
        - name: myapp
          volumeMounts:
            - name: config-volume
              mountPath: /app/config
              readOnly: true
          env:
            - name: SPRING_CONFIG_ADDITIONAL_LOCATION
              value: "file:/app/config/"
      volumes:
        - name: config-volume
          configMap:
            name: myapp-config
            items:
              - key: application.yml
                path: application.yml

四、Ingress(对外暴露)

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    # HTTPS 重定向
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    # 限流
    nginx.ingress.kubernetes.io/limit-rps: "100"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls-cert
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp
                port:
                  name: http

五、HPA 自动扩缩容

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 20
  metrics:
    # CPU 使用率超过 60% 触发扩容
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
    # 内存使用率超过 70% 触发扩容
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60    # 扩容冷却 60 秒
      policies:
        - type: Pods
          value: 2
          periodSeconds: 60             # 每 60 秒最多扩 2 个 Pod
    scaleDown:
      stabilizationWindowSeconds: 300   # 缩容冷却 300 秒(防抖)
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60             # 每 60 秒最多缩 10%

六、健康检查端点配置

Spring Boot Actuator 需开放健康探针端点:

# application.yml
server:
  port: 8080
 
management:
  server:
    port: 8081     # Actuator 独立端口,不对外暴露
  endpoint:
    health:
      probes:
        enabled: true    # 启用 liveness/readiness 探针
      show-details: always
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

探针路径:

路径K8s 用途
/actuator/health/livenesslivenessProbe — 失败重启 Pod
/actuator/health/readinessreadinessProbe — 失败摘除流量
/actuator/prometheusPrometheus 指标采集

Actuator 详细配置见 Actuator监控;指标采集见 指标采集


七、优雅滚动发布

滚动发布零停机的关键:maxUnavailable: 0 + Spring 优雅停机 + preStop 延迟。

# application.yml
server:
  shutdown: graceful   # 开启优雅停机
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s   # 等待正在处理的请求最多 30 秒

发布流程:

1. K8s 创建新 Pod(拉取新镜像)
2. 新 Pod startupProbe 通过 → readinessProbe 通过
3. 新 Pod 加入 Service(开始接收流量)
4. 老 Pod 收到 SIGTERM → preStop 执行(sleep 5,等 LB 摘除)
5. Spring 停止接收新请求 → 等待现有请求处理完毕
6. 老 Pod 退出

优雅停机详见 优雅停机


八、常用 kubectl 命令

# 部署 / 更新
kubectl apply -f k8s/
 
# 查看部署状态
kubectl rollout status deployment/myapp -n production
 
# 查看 Pod 日志
kubectl logs -f deployment/myapp -n production --tail=100
 
# 进入 Pod
kubectl exec -it deployment/myapp -n production -- sh
 
# 回滚到上一版本
kubectl rollout undo deployment/myapp -n production
 
# 查看滚动历史
kubectl rollout history deployment/myapp -n production
 
# 手动扩缩容
kubectl scale deployment/myapp --replicas=5 -n production
 
# 强制重启(触发滚动更新)
kubectl rollout restart deployment/myapp -n production
 
# 查看 HPA 状态
kubectl get hpa myapp -n production -w

九、Kustomize 多环境管理

k8s/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    ├── staging/
    │   ├── kustomization.yaml
    │   └── patch-replicas.yaml
    └── production/
        ├── kustomization.yaml
        └── patch-resources.yaml
# k8s/base/kustomization.yaml
resources:
  - deployment.yaml
  - service.yaml
 
commonLabels:
  app: myapp
# k8s/overlays/production/kustomization.yaml
resources:
  - ../../base
 
images:
  - name: registry.example.com/myapp
    newTag: "1.2.0"
 
patches:
  - path: patch-resources.yaml
 
# k8s/overlays/production/patch-resources.yaml
- op: replace
  path: /spec/replicas
  value: 5
# 部署到生产
kubectl apply -k k8s/overlays/production/

相关链接