十、CI/CD深度实战

10.1 GitHub Actions高级特性

# .github/workflows/advanced-deploy.yml
# 高级CI/CD流水线:矩阵构建、缓存优化、并行测试

name: Advanced CI/CD Pipeline

on:
  push:
    branches: [main, develop, 'release/**']
  pull_request:
    branches: [main]

# 并发控制:同一分支只运行最新的流水线
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  JAVA_VERSION: '17'
  REGISTRY: registry.example.com
  IMAGE_NAME: shop/backend

jobs:
  # ===== 代码质量检查 =====
  code-quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # SonarQube需要完整历史

      - uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: temurin
          cache: maven

      - name: SonarQube代码扫描
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        run: |
          mvn verify sonar:sonar \
            -Dsonar.projectKey=shop-backend \
            -Dsonar.host.url=$SONAR_HOST_URL \
            -Dsonar.login=$SONAR_TOKEN

  # ===== 并行测试(矩阵策略)=====
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        # 并行运行不同模块的测试
        module: [user-service, order-service, payment-service]
      fail-fast: false  # 一个失败不影响其他

    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: test123
          MYSQL_DATABASE: shop_test
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: temurin
          cache: maven

      - name: 运行 ${{ matrix.module }} 测试
        run: |
          mvn test -pl ${{ matrix.module }} \
            -Dspring.datasource.url=jdbc:mysql://localhost:3306/shop_test \
            -Dspring.data.redis.host=localhost

      - name: 上传测试覆盖率报告
        uses: codecov/codecov-action@v3
        with:
          flags: ${{ matrix.module }}

  # ===== 构建和推送镜像 =====
  build-push:
    needs: [code-quality, test]
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
      image-tag: ${{ steps.meta.outputs.version }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: temurin
          cache: maven

      - name: 构建JAR
        run: mvn clean package -DskipTests -B

      - name: 设置Docker Buildx(多平台构建)
        uses: docker/setup-buildx-action@v3

      - name: 生成镜像元数据
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=sha-
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

      - name: 登录镜像仓库
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: 构建并推送(多平台)
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64  # 支持ARM(如Apple M1服务器)
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: true  # 生成构建来源证明(供应链安全)
          sbom: true        # 生成软件物料清单

  # ===== 部署到不同环境 =====
  deploy-staging:
    needs: build-push
    runs-on: ubuntu-latest
    environment: staging
    if: github.ref == 'refs/heads/develop'

    steps:
      - uses: actions/checkout@v4

      - name: 部署到Staging
        uses: azure/k8s-deploy@v4
        with:
          namespace: shop-staging
          manifests: k8s/staging/
          images: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}

  deploy-production:
    needs: [build-push, deploy-staging]
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://api.example.com
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: 部署到生产(金丝雀)
        run: |
          # 先部署金丝雀版本(10%流量)
          kubectl set image deployment/shop-backend-canary \
            backend=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} \
            -n shop

          # 等待金丝雀稳定(5分钟)
          sleep 300

          # 检查错误率
          ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query" \
            --data-urlencode 'query=rate(http_server_requests_seconds_count{status=~"5.."}[5m])/rate(http_server_requests_seconds_count[5m])*100' \
            | jq '.data.result[0].value[1]' -r)

          if (( $(echo "$ERROR_RATE > 1" | bc -l) )); then
            echo "错误率过高(${ERROR_RATE}%),回滚金丝雀"
            kubectl rollout undo deployment/shop-backend-canary -n shop
            exit 1
          fi

          # 全量发布
          kubectl set image deployment/shop-backend \
            backend=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} \
            -n shop

10.2 Jenkins高级实践:共享库与多分支流水线

// vars/deployToK8s.groovy - Jenkins共享库
// 将部署逻辑封装为可复用的函数

def call(Map config) {
    def namespace = config.namespace ?: 'default'
    def deployment = config.deployment
    def image = config.image
    def timeout = config.timeout ?: 300

    echo "部署 ${deployment}${namespace},镜像:${image}"

    withKubeConfig([credentialsId: 'kubeconfig']) {
        sh """
            kubectl set image deployment/${deployment} \
                app=${image} \
                -n ${namespace}

            kubectl rollout status deployment/${deployment} \
                -n ${namespace} \
                --timeout=${timeout}s
        """
    }

    // 部署后验证
    def healthUrl = config.healthUrl ?: "http://${deployment}-svc.${namespace}/actuator/health"
    retry(3) {
        sleep(10)
        sh "curl -f ${healthUrl} || exit 1"
    }

    echo "部署成功!"
}
// Jenkinsfile - 使用共享库的简洁流水线
@Library('jenkins-shared-lib') _

pipeline {
    agent any

    parameters {
        string(name: 'IMAGE_TAG', defaultValue: '', description: '镜像标签')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '跳过测试')
        choice(name: 'DEPLOY_ENV', choices: ['staging', 'production'], description: '部署环境')
    }

    stages {
        stage('构建') {
            steps {
                sh "mvn clean package ${params.SKIP_TESTS ? '-DskipTests' : ''} -B"
            }
        }

        stage('Docker构建推送') {
            steps {
                script {
                    def tag = params.IMAGE_TAG ?: env.GIT_COMMIT[0..7]
                    docker.withRegistry('https://registry.example.com', 'registry-creds') {
                        def image = docker.build("shop/backend:${tag}")
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }

        stage('部署') {
            steps {
                script {
                    def tag = params.IMAGE_TAG ?: env.GIT_COMMIT[0..7]
                    // 调用共享库函数
                    deployToK8s(
                        namespace: params.DEPLOY_ENV == 'production' ? 'shop' : 'shop-staging',
                        deployment: 'shop-backend',
                        image: "registry.example.com/shop/backend:${tag}",
                        healthUrl: "https://api.example.com/actuator/health"
                    )
                }
            }
        }
    }

    post {
        success {
            // 更新Jira工单状态
            jiraComment(
                issueKey: env.JIRA_ISSUE,
                body: "✅ 部署成功:${env.BUILD_URL}"
            )
        }
        failure {
            // 自动创建故障工单
            jiraNewIssue(
                site: 'JIRA',
                issue: [
                    fields: [
                        project: [key: 'OPS'],
                        summary: "部署失败:${env.JOB_NAME} #${env.BUILD_NUMBER}",
                        issuetype: [name: 'Bug']
                    ]
                ]
            )
        }
    }
}

10.3 ArgoCD:GitOps持续部署

GitOps核心理念:
- Git仓库是唯一的真实来源(Single Source of Truth)
- 所有基础设施变更通过Git提交触发
- 自动同步:Git状态 = 集群状态

传统CI/CD vs GitOps:
传统:代码提交 → CI构建 → CD推送部署(Push模式)
GitOps:代码提交 → CI构建 → 更新Git配置 → ArgoCD拉取同步(Pull模式)

GitOps优势:
- 完整的变更审计(Git历史)
- 轻松回滚(git revert)
- 集群状态可重现
- 安全(ArgoCD在集群内,无需外部访问K8s API)
# argocd-app.yaml - ArgoCD应用配置
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: shop-backend
  namespace: argocd
spec:
  project: default

  # 源:Git仓库中的K8s配置
  source:
    repoURL: https://github.com/example/shop-k8s-config
    targetRevision: main
    path: environments/production/shop-backend

  # 目标:部署到哪个集群和namespace
  destination:
    server: https://kubernetes.default.svc
    namespace: shop

  # 同步策略
  syncPolicy:
    automated:
      prune: true      # 自动删除Git中已移除的资源
      selfHeal: true   # 自动修复手动修改(保持Git为真实来源)
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground

  # 健康检查
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas  # 忽略副本数差异(HPA会修改)
# ArgoCD常用命令
# 安装ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 获取初始密码
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

# 登录
argocd login localhost:8080 --username admin --password <password>

# 创建应用
argocd app create shop-backend \
  --repo https://github.com/example/shop-k8s-config \
  --path environments/production/shop-backend \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace shop \
  --sync-policy automated

# 查看应用状态
argocd app get shop-backend
argocd app list

# 手动同步
argocd app sync shop-backend

# 回滚到上一版本
argocd app rollback shop-backend

# 查看同步历史
argocd app history shop-backend

十一、监控体系深度实战

11.1 Spring Boot自定义监控指标实战

// 完整的业务监控示例:电商订单监控

@Configuration
public class MetricsConfig {

    @Bean
    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(
            @Value("${spring.application.name}") String appName) {
        // 给所有指标添加公共标签
        return registry -> registry.config()
            .commonTags("application", appName,
                        "env", System.getenv().getOrDefault("SPRING_PROFILES_ACTIVE", "unknown"));
    }
}

@Service
@Slf4j
public class OrderMetricsService {

    private final MeterRegistry registry;

    // 订单创建计数器(按状态分类)
    private final Counter orderSuccessCounter;
    private final Counter orderFailCounter;

    // 订单金额分布(直方图)
    private final DistributionSummary orderAmountSummary;

    // 订单处理耗时(计时器)
    private final Timer orderProcessTimer;

    // 待支付订单数(实时仪表盘)
    private final AtomicLong pendingPaymentCount = new AtomicLong(0);

    public OrderMetricsService(MeterRegistry registry) {
        this.registry = registry;

        this.orderSuccessCounter = Counter.builder("order.created")
            .tag("result", "success")
            .description("成功创建的订单数")
            .register(registry);

        this.orderFailCounter = Counter.builder("order.created")
            .tag("result", "failure")
            .description("创建失败的订单数")
            .register(registry);

        // 金额分布:统计订单金额的分布情况
        this.orderAmountSummary = DistributionSummary.builder("order.amount")
            .description("订单金额分布(元)")
            .baseUnit("yuan")
            .publishPercentiles(0.5, 0.75, 0.95, 0.99)
            .publishPercentileHistogram()
            .minimumExpectedValue(1.0)
            .maximumExpectedValue(100000.0)
            .register(registry);

        this.orderProcessTimer = Timer.builder("order.process.duration")
            .description("订单处理耗时")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);

        // 注册实时指标
        Gauge.builder("order.pending.payment.count", pendingPaymentCount, AtomicLong::get)
            .description("待支付订单数")
            .register(registry);
    }

    public Order createOrder(OrderDTO dto) {
        return orderProcessTimer.record(() -> {
            try {
                Order order = doCreateOrder(dto);
                orderSuccessCounter.increment();
                orderAmountSummary.record(order.getTotalAmount().doubleValue());
                pendingPaymentCount.incrementAndGet();
                return order;
            } catch (Exception e) {
                orderFailCounter.increment();
                // 记录失败原因标签
                registry.counter("order.error",
                    "type", e.getClass().getSimpleName()).increment();
                throw e;
            }
        });
    }

    public void onOrderPaid(Long orderId) {
        pendingPaymentCount.decrementAndGet();
        registry.counter("order.paid").increment();
    }
}
// 自定义健康检查指标
@Component
public class ExternalServiceHealthIndicator implements HealthIndicator {

    @Autowired
    private PaymentServiceClient paymentClient;

    @Override
    public Health health() {
        try {
            long start = System.currentTimeMillis();
            boolean available = paymentClient.ping();
            long duration = System.currentTimeMillis() - start;

            if (available) {
                return Health.up()
                    .withDetail("responseTime", duration + "ms")
                    .withDetail("service", "payment-service")
                    .build();
            } else {
                return Health.down()
                    .withDetail("reason", "支付服务不可用")
                    .build();
            }
        } catch (Exception e) {
            return Health.down()
                .withException(e)
                .build();
        }
    }
}

11.2 Grafana仪表盘实战:从零搭建Spring Boot监控面板

Spring Boot监控面板应包含的核心面板:

第一行:关键指标概览(单值面板)
┌──────────┬──────────┬──────────┬──────────┐
│  QPS     │ 错误率   │ P99延迟  │ 在线实例 │
│  1234/s  │  0.1%    │  45ms    │    3     │
└──────────┴──────────┴──────────┴──────────┘

第二行:趋势图
┌──────────────────────┬──────────────────────┐
│  QPS趋势(折线图)    │  响应时间分位数       │
│  P50/P95/P99         │  P50/P95/P99         │
└──────────────────────┴──────────────────────┘

第三行:JVM监控
┌──────────────────────┬──────────────────────┐
│  JVM堆内存使用        │  GC暂停时间           │
│  Used/Max            │  Young GC/Full GC    │
└──────────────────────┴──────────────────────┘

第四行:业务指标
┌──────────────────────┬──────────────────────┐
│  订单创建趋势         │  支付成功率           │
│  成功/失败           │  实时待支付订单数      │
└──────────────────────┴──────────────────────┘
// Grafana面板配置示例(QPS单值面板)
{
  "title": "当前QPS",
  "type": "stat",
  "fieldConfig": {
    "defaults": {
      "unit": "reqps",
      "thresholds": {
        "mode": "absolute",
        "steps": [
          {"color": "green", "value": null},
          {"color": "yellow", "value": 500},
          {"color": "red", "value": 1000}
        ]
      }
    }
  },
  "options": {
    "reduceOptions": {
      "calcs": ["lastNotNull"]
    },
    "orientation": "auto",
    "colorMode": "background"
  },
  "targets": [
    {
      "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\"}[1m]))",
      "legendFormat": "QPS"
    }
  ]
}
# Grafana告警配置(通过API)

# 创建告警规则(Grafana 8.x+统一告警)
curl -X POST http://admin:admin@localhost:3000/api/ruler/grafana/api/v1/rules/shop \
  -H "Content-Type: application/json" \
  -d '{
    "name": "shop-alerts",
    "interval": "1m",
    "rules": [
      {
        "alert": "HighErrorRate",
        "expr": "sum(rate(http_server_requests_seconds_count{status=~\"5..\"}[5m])) / sum(rate(http_server_requests_seconds_count[5m])) * 100 > 1",
        "for": "3m",
        "labels": {
          "severity": "warning",
          "team": "backend"
        },
        "annotations": {
          "summary": "HTTP错误率过高",
          "description": "错误率 {{ $value | printf \"%.2f\" }}% 超过1%阈值"
        }
      }
    ]
  }'

11.3 ELK Stack完整搭建与使用

# docker-compose-elk.yml - ELK Stack本地搭建

version: '3.8'

services:
  elasticsearch:
    image: elasticsearch:8.11.0
    container_name: elk-es
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
      - bootstrap.memory_lock=true
    ulimits:
      memlock:
        soft: -1
        hard: -1
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data
    networks:
      - elk-net

  logstash:
    image: logstash:8.11.0
    container_name: elk-logstash
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
      - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml
    ports:
      - "5044:5044"   # Beats输入
      - "5000:5000"   # TCP输入
      - "9600:9600"   # API
    environment:
      LS_JAVA_OPTS: "-Xmx512m -Xms512m"
    depends_on:
      - elasticsearch
    networks:
      - elk-net

  kibana:
    image: kibana:8.11.0
    container_name: elk-kibana
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch
    networks:
      - elk-net

  # Filebeat:采集容器日志
  filebeat:
    image: elastic/filebeat:8.11.0
    container_name: elk-filebeat
    user: root
    volumes:
      - ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - logstash
    networks:
      - elk-net

volumes:
  es_data:

networks:
  elk-net:
    driver: bridge
# filebeat/filebeat.yml - 采集Docker容器日志
filebeat.inputs:
  - type: container
    paths:
      - /var/lib/docker/containers/*/*.log
    processors:
      - add_docker_metadata:
          host: "unix:///var/run/docker.sock"
      # 只采集shop相关容器的日志
      - drop_event:
          when:
            not:
              contains:
                docker.container.labels.com.docker.compose.service: "shop"

processors:
  - decode_json_fields:
      fields: ["message"]
      target: ""
      overwrite_keys: true

output.logstash:
  hosts: ["logstash:5044"]
# logstash/pipeline/shop.conf
input {
  beats {
    port => 5044
  }
}

filter {
  # 解析Spring Boot JSON日志
  if [fields][app_type] == "spring-boot" {
    json {
      source => "message"
      target => "log"
    }

    # 提取关键字段到顶层
    mutate {
      rename => {
        "[log][level]" => "log_level"
        "[log][logger_name]" => "logger"
        "[log][message]" => "log_message"
        "[log][stack_trace]" => "stack_trace"
      }
    }

    # 解析时间戳
    date {
      match => ["[log][@timestamp]", "ISO8601"]
      target => "@timestamp"
    }

    # 提取TraceId(链路追踪关联)
    if [log][mdc][traceId] {
      mutate {
        add_field => { "trace_id" => "%{[log][mdc][traceId]}" }
      }
    }

    # 标记ERROR日志
    if [log_level] == "ERROR" {
      mutate {
        add_tag => ["error"]
      }
    }
  }

  # 过滤健康检查日志(减少噪音)
  if [log_message] =~ "actuator/health" {
    drop {}
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    # 按应用和日期分索引
    index => "shop-logs-%{[docker][container][labels][com.docker.compose.service]}-%{+YYYY.MM.dd}"
  }

  # 同时输出到标准输出(调试用)
  # stdout { codec => rubydebug }
}
Kibana常用操作:

1. 创建索引模式
   Management → Stack Management → Index Patterns
   → Create index pattern → shop-logs-* → @timestamp

2. 日志搜索(Discover)
   常用KQL查询:
   log_level: "ERROR"                    # 查看所有ERROR日志
   log_level: "ERROR" and logger: "com.example.shop.order"  # 特定类的ERROR
   trace_id: "abc123"                    # 按TraceId查询完整链路
   log_message: "OutOfMemoryError"       # 搜索OOM错误
   @timestamp >= "2024-01-01T10:00:00"  # 时间范围

3. 创建可视化
   Visualize → Create visualization
   - 折线图:按时间统计ERROR数量
   - 饼图:各服务日志级别分布
   - 数据表:TOP 10错误类型

4. 创建告警(Kibana Alerting)
   Management → Rules and Connectors → Create rule
   - 类型:Elasticsearch query
   - 条件:ERROR日志数量 > 10/分钟
   - 通知:钉钉Webhook

11.4 SkyWalking分布式链路追踪实战

分布式系统中,一个请求可能经过多个服务,出问题时很难定位是哪个环节出了故障。SkyWalking通过在每个服务中注入Agent,自动采集调用链数据,无需修改业务代码。

链路追踪解决的问题:
┌─────────────────────────────────────────────────────────────────┐
│  用户请求 → 网关 → 用户服务 → 订单服务 → 库存服务 → 支付服务    │
│                                                                  │
│  问题:响应慢,但不知道是哪个服务慢                              │
│  解决:SkyWalking记录每个服务的耗时,生成完整调用链              │
│                                                                  │
│  TraceId: abc-123                                                │
│  ├── 网关          5ms                                           │
│  ├── 用户服务      12ms                                          │
│  ├── 订单服务      ████████ 350ms  ← 瓶颈在这里!               │
│  │   ├── MySQL查询 280ms                                         │
│  │   └── Redis     8ms                                           │
│  └── 支付服务      20ms                                          │
└─────────────────────────────────────────────────────────────────┘
# docker-compose-skywalking.yml
version: '3.8'

services:
  # OAP Server:链路数据收集和分析
  oap:
    image: apache/skywalking-oap-server:9.7.0
    container_name: skywalking-oap
    environment:
      SW_STORAGE: elasticsearch
      SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200
      SW_HEALTH_CHECKER: default
      JAVA_OPTS: "-Xms512m -Xmx1g"
    ports:
      - "11800:11800"   # gRPC端口(Agent上报)
      - "12800:12800"   # HTTP端口(UI查询)
    depends_on:
      - elasticsearch
    networks:
      - sw-net

  # UI:可视化界面
  ui:
    image: apache/skywalking-ui:9.7.0
    container_name: skywalking-ui
    environment:
      SW_OAP_ADDRESS: http://oap:12800
    ports:
      - "8080:8080"
    depends_on:
      - oap
    networks:
      - sw-net

  elasticsearch:
    image: elasticsearch:8.11.0
    container_name: sw-es
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms512m -Xmx1g
    networks:
      - sw-net

networks:
  sw-net:
    driver: bridge
# Dockerfile - 集成SkyWalking Agent
FROM eclipse-temurin:17-jre AS base

# 下载SkyWalking Agent
ADD https://archive.apache.org/dist/skywalking/java-agent/9.1.0/apache-skywalking-java-agent-9.1.0.tgz /tmp/
RUN tar -xzf /tmp/apache-skywalking-java-agent-9.1.0.tgz -C /opt/ \
    && rm /tmp/apache-skywalking-java-agent-9.1.0.tgz

WORKDIR /app
COPY target/*.jar app.jar

# 通过JVM参数挂载Agent(无需修改代码)
ENTRYPOINT ["java", \
  "-javaagent:/opt/skywalking-agent/skywalking-agent.jar", \
  "-Dskywalking.agent.service_name=${SW_SERVICE_NAME:-shop-service}", \
  "-Dskywalking.collector.backend_service=${SW_OAP_ADDRESS:-oap:11800}", \
  "-jar", "app.jar"]
# K8s Deployment中注入SkyWalking Agent(推荐方式:initContainer)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-order-service
spec:
  template:
    spec:
      initContainers:
        # 用initContainer将Agent复制到共享Volume
        - name: skywalking-agent-init
          image: apache/skywalking-java-agent:9.1.0-java17
          command: ["sh", "-c", "cp -r /skywalking/agent /agent"]
          volumeMounts:
            - name: sw-agent
              mountPath: /agent

      containers:
        - name: app
          image: registry.example.com/shop/order-service:1.0.0
          env:
            - name: JAVA_TOOL_OPTIONS
              value: >-
                -javaagent:/agent/skywalking-agent.jar
                -Dskywalking.agent.service_name=order-service
                -Dskywalking.collector.backend_service=skywalking-oap.monitoring:11800
          volumeMounts:
            - name: sw-agent
              mountPath: /agent

      volumes:
        - name: sw-agent
          emptyDir: {}
// SkyWalking自定义Span(标注关键业务操作)
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.Tag;
import org.apache.skywalking.apm.toolkit.trace.Tags;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;

@Service
public class OrderServiceImpl implements OrderService {

    // @Trace注解:将此方法加入链路追踪
    @Trace(operationName = "createOrder")
    @Tags({
        @Tag(key = "userId", value = "arg[0]"),      // 记录参数
        @Tag(key = "productId", value = "arg[1]")
    })
    public Order createOrder(Long userId, Long productId, Integer quantity) {
        try {
            // 手动添加业务标签到当前Span
            ActiveSpan.tag("orderAmount", calculateAmount(productId, quantity).toString());

            Order order = doCreateOrder(userId, productId, quantity);

            ActiveSpan.tag("orderId", order.getId().toString());
            return order;
        } catch (InsufficientStockException e) {
            // 标记Span为错误状态
            ActiveSpan.error(e);
            throw e;
        }
    }

    // 跨线程传递TraceId(异步场景)
    public void asyncNotify(Order order) {
        // 获取当前TraceId,传递给异步线程
        String traceId = TraceContext.traceId();

        CompletableFuture.runAsync(() -> {
            // 在异步线程中继续链路
            try (TraceContext.CloseableContext ctx = TraceContext.capture()) {
                log.info("异步通知, traceId={}, orderId={}", traceId, order.getId());
                notificationService.sendOrderCreated(order);
            }
        });
    }
}
SkyWalking核心功能使用指南:

1. 服务拓扑图(Topology)
   - 自动生成服务间调用关系图
   - 显示每条链路的调用次数、成功率、平均响应时间
   - 快速识别服务依赖和瓶颈

2. 链路追踪(Trace)
   - 按TraceId搜索完整调用链
   - 查看每个Span的耗时和参数
   - 定位慢查询、慢接口

3. 性能剖析(Profile)
   - 对指定端点进行CPU采样
   - 生成火焰图,定位热点代码
   - 无需重启应用,动态开启

4. 告警规则配置(alarm-settings.yml)
   rules:
     service_resp_time_rule:
       metrics-name: service_resp_time
       threshold: 1000        # 响应时间超过1秒
       period: 10             # 最近10分钟
       count: 3               # 触发3次
       message: "服务 {name} 响应时间超过1秒"

     service_sla_rule:
       metrics-name: service_sla
       threshold: 8000        # 成功率低于80%(8000/10000)
       period: 10
       count: 2
       message: "服务 {name} 成功率低于80%"

十二、生产故障排查实战手册

12.1 故障排查方法论

生产环境出现问题时,需要快速、系统地定位根因。遵循以下排查流程可以大幅缩短MTTR(平均恢复时间)。

生产故障排查流程:
┌─────────────────────────────────────────────────────────────────┐
│                     故障发生                                     │
│                        ↓                                        │
│              1. 确认影响范围                                     │
│              (哪些服务?哪些用户?多少比例?)                   │
│                        ↓                                        │
│              2. 快速止血(优先恢复服务)                         │
│              (回滚?限流?降级?扩容?)                         │
│                        ↓                                        │
│              3. 收集现场信息                                     │
│              (日志、监控、链路追踪)                             │
│                        ↓                                        │
│              4. 定位根因                                         │
│              (代码?配置?资源?外部依赖?)                     │
│                        ↓                                        │
│              5. 修复并验证                                       │
│                        ↓                                        │
│              6. 复盘总结(故障报告)                             │
└─────────────────────────────────────────────────────────────────┘

12.2 CPU飙高排查实战

# 场景:线上服务CPU突然飙到90%+,需要快速定位

# Step 1:找到CPU最高的Java进程
top -c
# 记录PID,例如:PID=12345

# Step 2:找到该进程中CPU最高的线程
top -H -p 12345
# 记录线程PID(十进制),例如:TID=12389

# Step 3:将线程PID转为十六进制
printf '%x\n' 12389
# 输出:3065

# Step 4:导出线程堆栈
jstack 12345 > /tmp/thread_dump.txt

# Step 5:在堆栈中搜索该线程(用十六进制nid)
grep -A 30 "nid=0x3065" /tmp/thread_dump.txt
常见CPU飙高原因及对应堆栈特征:

┌──────────────────┬────────────────────────────────────────────┐
│ 原因             │ 堆栈特征                                    │
├──────────────────┼────────────────────────────────────────────┤
│ 死循环           │ 同一个方法反复出现,无阻塞                   │
│ 频繁GC           │ GC线程占用高,堆内存接近上限                 │
│ 正则表达式回溯   │ java.util.regex.Pattern 相关方法             │
│ JSON序列化       │ Jackson/Fastjson 相关方法                   │
│ 加密解密         │ javax.crypto 相关方法                       │
│ 日志同步写入     │ FileOutputStream.write 阻塞                 │
└──────────────────┴────────────────────────────────────────────┘
# 自动化CPU排查脚本(保存为 cpu_diagnose.sh)
#!/bin/bash

PID=$1
if [ -z "$PID" ]; then
    echo "用法: ./cpu_diagnose.sh <PID>"
    exit 1
fi

echo "=== CPU诊断报告 ==="
echo "时间: $(date)"
echo "进程: $PID"
echo ""

# 获取TOP 5 CPU线程
echo "=== TOP 5 CPU线程 ==="
top -H -b -n 1 -p $PID | head -20

# 导出堆栈
DUMP_FILE="/tmp/jstack_${PID}_$(date +%Y%m%d%H%M%S).txt"
jstack $PID > $DUMP_FILE
echo "堆栈已保存到: $DUMP_FILE"

# 找出RUNNABLE线程
echo ""
echo "=== RUNNABLE线程数量 ==="
grep -c "RUNNABLE" $DUMP_FILE

echo ""
echo "=== RUNNABLE线程堆栈(前50行)==="
grep -A 10 "RUNNABLE" $DUMP_FILE | head -50

12.3 内存溢出(OOM)排查实战

# JVM启动参数:OOM时自动导出堆转储文件
JAVA_OPTS="-Xms2g -Xmx2g \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/data/dumps/heap_$(date +%Y%m%d%H%M%S).hprof \
  -XX:+PrintGCDetails \
  -XX:+PrintGCDateStamps \
  -Xloggc:/data/logs/gc.log"

# 手动触发堆转储(不重启服务)
jmap -dump:format=b,file=/tmp/heap.hprof <PID>

# 查看堆内存使用情况
jmap -heap <PID>

# 查看对象统计(找出占用内存最多的类)
jmap -histo <PID> | head -30
// 常见OOM场景及代码示例

// 场景1:内存泄漏 - 静态集合持有对象引用
@Component
public class CacheManager {
    // ❌ 错误:静态Map无限增长,GC无法回收
    private static final Map<String, Object> cache = new HashMap<>();

    public void put(String key, Object value) {
        cache.put(key, value);  // 只进不出,迟早OOM
    }

    // ✅ 正确:使用有界缓存,设置最大容量和过期时间
    private final Cache<String, Object> boundedCache = Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(30, TimeUnit.MINUTES)
        .build();
}

// 场景2:大查询导致OOM
@Service
public class ReportService {
    // ❌ 错误:一次性加载百万数据到内存
    public void exportAllOrders() {
        List<Order> allOrders = orderDao.findAll();  // 可能返回100万条
        // 处理...
    }

    // ✅ 正确:分页流式处理
    public void exportAllOrdersStream() {
        int pageSize = 1000;
        int pageNum = 0;
        List<Order> page;
        do {
            page = orderDao.findByPage(pageNum++, pageSize);
            page.forEach(this::processOrder);
            // 每批处理完后,page对象可被GC回收
        } while (page.size() == pageSize);
    }
}
MAT(Memory Analyzer Tool)分析堆转储文件:

1. 下载MAT:https://eclipse.dev/mat/downloads.php

2. 打开堆转储文件:File → Open Heap Dump → 选择.hprof文件

3. 关键分析视图:
   - Leak Suspects Report:自动分析内存泄漏嫌疑
   - Dominator Tree:按内存占用排序,找出最大对象
   - Histogram:按类统计实例数量和内存占用
   - Thread Overview:查看各线程的栈和局部变量

4. 常见分析步骤:
   a. 先看 Leak Suspects,通常能直接定位问题
   b. 在 Dominator Tree 中找占用最多的对象
   c. 右键 → List objects → with incoming references(谁持有它)
   d. 追溯引用链,找到GC Root

12.4 接口响应慢排查实战

# 场景:某接口P99延迟从50ms突然升到2000ms

# Step 1:确认是哪个接口慢(通过Grafana/SkyWalking)
# 找到慢接口:POST /api/orders

# Step 2:查看该接口的慢日志
# 在ELK中搜索:
# log_message: "/api/orders" AND duration > 1000

# Step 3:查看数据库慢查询
# MySQL慢查询日志
tail -f /var/log/mysql/slow.log

# 或通过SQL查询当前慢查询
SELECT * FROM information_schema.PROCESSLIST
WHERE TIME > 5
ORDER BY TIME DESC;

# Step 4:查看Redis慢日志
redis-cli SLOWLOG GET 10

# Step 5:查看JVM GC情况(GC停顿会导致所有请求变慢)
jstat -gcutil <PID> 1000 10
# 关注FGC(Full GC次数)和FGCT(Full GC总耗时)
// 接口慢的常见原因和解决方案

// 原因1:N+1查询问题
// ❌ 错误:查询订单列表,再逐个查询用户信息
public List<OrderVO> listOrders() {
    List<Order> orders = orderDao.findAll();
    return orders.stream().map(order -> {
        OrderVO vo = new OrderVO(order);
        // 每个订单都发一次SQL查询用户,100个订单 = 101次SQL
        User user = userDao.findById(order.getUserId());
        vo.setUserName(user.getName());
        return vo;
    }).collect(Collectors.toList());
}

// ✅ 正确:批量查询,一次SQL解决
public List<OrderVO> listOrdersOptimized() {
    List<Order> orders = orderDao.findAll();
    // 收集所有userId
    Set<Long> userIds = orders.stream()
        .map(Order::getUserId).collect(Collectors.toSet());
    // 一次批量查询
    Map<Long, User> userMap = userDao.findByIds(userIds).stream()
        .collect(Collectors.toMap(User::getId, u -> u));
    // 组装结果
    return orders.stream().map(order -> {
        OrderVO vo = new OrderVO(order);
        vo.setUserName(userMap.get(order.getUserId()).getName());
        return vo;
    }).collect(Collectors.toList());
}

12.5 K8s Pod故障排查实战

# Pod常见故障排查命令速查

# 查看Pod状态
kubectl get pods -n shop -o wide

# 查看Pod详细信息(重点看Events部分)
kubectl describe pod <pod-name> -n shop

# 查看Pod日志
kubectl logs <pod-name> -n shop
kubectl logs <pod-name> -n shop --previous    # 查看上一次崩溃的日志
kubectl logs <pod-name> -n shop -f            # 实时跟踪
kubectl logs <pod-name> -n shop --tail=100    # 最后100行

# 进入Pod容器调试
kubectl exec -it <pod-name> -n shop -- /bin/sh

# 查看Pod资源使用
kubectl top pod <pod-name> -n shop
Pod常见状态及排查方向:

┌─────────────────────┬──────────────────────────────────────────────┐
│ Pod状态             │ 排查方向                                      │
├─────────────────────┼──────────────────────────────────────────────┤
│ Pending             │ 资源不足?节点选择器不匹配?PVC未绑定?        │
│ CrashLoopBackOff    │ 应用启动失败,看 logs --previous              │
│ OOMKilled           │ 内存超出limits,调大limits或优化内存           │
│ ImagePullBackOff    │ 镜像不存在?仓库认证失败?网络问题?           │
│ Evicted             │ 节点资源不足被驱逐,检查节点磁盘/内存          │
│ Terminating(卡住) │ Finalizer未清除,强制删除:--force --grace-period=0 │
└─────────────────────┴──────────────────────────────────────────────┘
# 实战:排查CrashLoopBackOff

# 1. 查看崩溃原因
kubectl logs shop-order-7d9f8b-xxx -n shop --previous
# 输出:java.lang.IllegalStateException: Failed to load ApplicationContext
#       Caused by: Cannot connect to MySQL: Connection refused

# 2. 检查ConfigMap中的数据库配置
kubectl get configmap shop-config -n shop -o yaml

# 3. 检查MySQL Service是否正常
kubectl get svc mysql-svc -n shop
kubectl get endpoints mysql-svc -n shop  # 确认有Endpoint

# 4. 在Pod内测试连通性
kubectl run debug --image=busybox -it --rm -n shop -- sh
# 在容器内:
wget -qO- http://mysql-svc:3306  # 测试TCP连通

# 实战:排查OOMKilled
kubectl describe pod shop-order-7d9f8b-xxx -n shop
# 关注:
# Limits: memory: 512Mi
# Last State: Terminated  Reason: OOMKilled

# 查看内存使用趋势(Prometheus查询)
# container_memory_working_set_bytes{pod="shop-order-7d9f8b-xxx"}

十三、成本优化与资源规划

13.1 K8s资源规划最佳实践

合理设置 requests 和 limits 是K8s资源管理的核心,设置不当会导致资源浪费或服务不稳定。

requests vs limits 的区别:
┌─────────────────────────────────────────────────────────────────┐
│  requests:调度依据,K8s保证Pod能获得的最低资源                  │
│  limits:使用上限,超出后CPU被限速,内存超出则OOMKill            │
│                                                                  │
│  节点资源:8核16G                                                │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ Pod A: requests=2C/4G  limits=4C/8G                      │   │
│  │ Pod B: requests=2C/4G  limits=4C/8G                      │   │
│  │ Pod C: requests=2C/4G  limits=4C/8G                      │   │
│  │ Pod D: requests=2C/4G  limits=4C/8G  ← 调度失败!        │   │
│  │        requests总和=8C/16G,已满                          │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘
# 生产环境资源配置参考(Spring Boot服务)
resources:
  requests:
    cpu: "500m"       # 0.5核,保证最低CPU
    memory: "512Mi"   # 512MB,保证最低内存
  limits:
    cpu: "2000m"      # 2核,突发流量时可用
    memory: "1Gi"     # 1GB,超出则OOMKill

# 资源配置原则:
# 1. requests设为正常负载下的实际使用量(通过监控获取)
# 2. limits设为requests的2-4倍(应对流量突发)
# 3. 内存limits不要设太高,防止单Pod内存泄漏影响整个节点
# 4. CPU可以超卖(limits > requests),内存不建议超卖
# 使用VPA(Vertical Pod Autoscaler)自动推荐资源配置
# 安装VPA
kubectl apply -f https://github.com/kubernetes/autoscaler/releases/latest/download/vertical-pod-autoscaler.yaml

# 创建VPA对象(推荐模式,只给建议不自动修改)
cat <<EOF | kubectl apply -f -
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: shop-order-vpa
  namespace: shop
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: shop-order-service
  updatePolicy:
    updateMode: "Off"   # Off=只推荐,Auto=自动更新
EOF

# 查看VPA推荐值
kubectl describe vpa shop-order-vpa -n shop
# 输出示例:
# Recommendation:
#   Container Recommendations:
#     Container Name: app
#     Lower Bound:    cpu: 100m, memory: 256Mi
#     Target:         cpu: 300m, memory: 512Mi   ← 推荐值
#     Upper Bound:    cpu: 1000m, memory: 2Gi

13.2 多环境成本优化策略

# 开发环境:最小化资源,节省成本
# dev-values.yaml
replicaCount: 1

resources:
  requests:
    cpu: "100m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

# 关闭不必要的组件
monitoring:
  enabled: false

autoscaling:
  enabled: false

# 使用低规格数据库
mysql:
  resources:
    requests:
      cpu: "100m"
      memory: "256Mi"
# 生产环境:高可用配置
# prod-values.yaml
replicaCount: 3

resources:
  requests:
    cpu: "500m"
    memory: "512Mi"
  limits:
    cpu: "2000m"
    memory: "1Gi"

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

# 反亲和性:Pod分散到不同节点,避免单点故障
affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app
                operator: In
                values: ["shop-order-service"]
          topologyKey: kubernetes.io/hostname

13.3 镜像体积优化实战

镜像体积对比(Spring Boot应用):

┌──────────────────────────────┬──────────┬──────────────────────┐
│ 基础镜像                      │ 大小     │ 说明                  │
├──────────────────────────────┼──────────┼──────────────────────┤
│ openjdk:17                   │ 471MB    │ 包含完整JDK,不推荐    │
│ eclipse-temurin:17-jdk       │ 456MB    │ 包含完整JDK,不推荐    │
│ eclipse-temurin:17-jre       │ 249MB    │ 只含JRE,推荐          │
│ eclipse-temurin:17-jre-alpine│ 185MB    │ Alpine版,更小         │
│ eclipse-temurin:17-jre-jammy │ 228MB    │ Ubuntu版,兼容性好     │
│ gcr.io/distroless/java17     │ 218MB    │ 无Shell,最安全        │
└──────────────────────────────┴──────────┴──────────────────────┘
# 极致优化的Dockerfile:利用Spring Boot分层特性
FROM eclipse-temurin:17-jre-alpine AS builder
WORKDIR /app
COPY target/*.jar app.jar
# Spring Boot 2.3+ 支持分层提取
RUN java -Djarmode=layertools -jar app.jar extract

# 最终镜像:利用Docker层缓存,依赖层几乎不变
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

# 按变化频率从低到高分层(低频层在前,充分利用缓存)
COPY --from=builder /app/dependencies/ ./          # 第三方依赖(最少变化)
COPY --from=builder /app/spring-boot-loader/ ./    # Spring Boot Loader
COPY --from=builder /app/snapshot-dependencies/ ./ # SNAPSHOT依赖
COPY --from=builder /app/application/ ./           # 业务代码(最常变化)

# 安全加固:非root用户运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# JVM调优参数
ENV JAVA_OPTS="-XX:+UseContainerSupport \
               -XX:MaxRAMPercentage=75.0 \
               -XX:+UseG1GC \
               -XX:+HeapDumpOnOutOfMemoryError \
               -XX:HeapDumpPath=/tmp/heap.hprof"

EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]
# 镜像安全扫描(使用Trivy)
# 安装Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

# 扫描镜像漏洞
trivy image registry.example.com/shop/order-service:1.0.0

# 只显示HIGH和CRITICAL级别漏洞
trivy image --severity HIGH,CRITICAL registry.example.com/shop/order-service:1.0.0

# 在CI/CD中集成(发现CRITICAL漏洞则构建失败)
trivy image --exit-code 1 --severity CRITICAL registry.example.com/shop/order-service:1.0.0

十四、DevOps工具链进阶面试题

14.1 Docker高频面试题

Q1:Docker容器和虚拟机的区别?

虚拟机架构:
┌──────────┬──────────┬──────────┐
│  App A   │  App B   │  App C   │
├──────────┼──────────┼──────────┤
│  Guest   │  Guest   │  Guest   │
│   OS     │   OS     │   OS     │
├──────────┴──────────┴──────────┤
│         Hypervisor             │
├────────────────────────────────┤
│         Host OS                │
├────────────────────────────────┤
│         Hardware               │
└────────────────────────────────┘

Docker容器架构:
┌──────────┬──────────┬──────────┐
│  App A   │  App B   │  App C   │
├──────────┼──────────┼──────────┤
│Container │Container │Container │
│ Runtime  │ Runtime  │ Runtime  │
├──────────┴──────────┴──────────┤
│         Docker Engine          │
├────────────────────────────────┤
│         Host OS                │
├────────────────────────────────┤
│         Hardware               │
└────────────────────────────────┘

核心区别:
- 虚拟机:完整OS隔离,启动慢(分钟级),资源占用大
- 容器:共享Host OS内核,启动快(秒级),资源占用小
- 隔离性:虚拟机 > 容器(容器共享内核,存在逃逸风险)

Q2:Docker镜像分层原理是什么?

镜像分层结构(以Spring Boot镜像为例):

Layer 5: 业务代码 (application/)          ← 最常变化
Layer 4: SNAPSHOT依赖                     ← 偶尔变化
Layer 3: Spring Boot Loader              ← 很少变化
Layer 2: 第三方依赖 (dependencies/)       ← 很少变化
Layer 1: eclipse-temurin:17-jre-alpine   ← 基础镜像

原理:
- 每个RUN/COPY/ADD指令创建一个新层
- 层是只读的,通过Union FS叠加
- 容器运行时在最上层添加可写层(Container Layer)
- 相同的层在多个镜像间共享,节省存储空间

实际意义:
- 构建时:未变化的层直接使用缓存,加速构建
- 推送时:只推送变化的层,节省带宽
- 拉取时:已有的层不重复下载

Q3:如何减小Docker镜像体积?

1. 选择小体积基础镜像
   - 用 eclipse-temurin:17-jre-alpine 替代 openjdk:17
   - 用 distroless 镜像(无Shell,更安全)

2. 多阶段构建
   - 构建阶段用完整JDK+Maven
   - 运行阶段只保留JRE+JAR

3. 利用Spring Boot分层
   - 依赖层和代码层分离,充分利用缓存

4. 清理构建缓存
   RUN mvn package -B && rm -rf ~/.m2/repository

5. 使用.dockerignore
   target/
   .git/
   *.md
   src/test/

Q4:Docker网络模式有哪些?

网络模式 说明 适用场景
bridge 默认模式,容器通过虚拟网桥通信 单机多容器
host 容器直接使用宿主机网络 高性能场景
none 无网络,完全隔离 安全敏感场景
overlay 跨主机容器通信 Docker Swarm/K8s
macvlan 容器拥有独立MAC地址 需要直接接入物理网络

14.2 Kubernetes高频面试题

Q5:K8s中Pod、Deployment、Service的关系?

关系图:

                    ┌─────────────────────────────┐
                    │         Deployment           │
                    │  (管理Pod的期望状态)         │
                    │  replicas: 3                 │
                    └──────────────┬──────────────┘
                                   │ 创建/管理
                    ┌──────────────▼──────────────┐
                    │          ReplicaSet          │
                    │  (确保Pod数量符合期望)       │
                    └──────────────┬──────────────┘
                                   │ 创建/管理
              ┌────────────────────┼────────────────────┐
              ▼                    ▼                    ▼
         ┌─────────┐         ┌─────────┐         ┌─────────┐
         │  Pod 1  │         │  Pod 2  │         │  Pod 3  │
         │app:shop │         │app:shop │         │app:shop │
         └────┬────┘         └────┬────┘         └────┬────┘
              └──────────────────┬┘──────────────────┘
                                 │ 通过Label Selector选择
                    ┌────────────▼────────────┐
                    │         Service          │
                    │  (稳定的访问入口)        │
                    │  selector: app=shop      │
                    │  ClusterIP: 10.96.0.100  │
                    └─────────────────────────┘

Q6:K8s如何实现零停机滚动更新?

滚动更新过程(replicas=3,maxSurge=1,maxUnavailable=0):

初始状态:
[v1] [v1] [v1]

Step 1:创建1个新Pod(maxSurge=1,最多多1个)
[v1] [v1] [v1] [v2-启动中]

Step 2:v2就绪后,删除1个旧Pod
[v1] [v1] [v2]

Step 3:再创建1个新Pod
[v1] [v1] [v2] [v2-启动中]

Step 4:v2就绪后,删除1个旧Pod
[v1] [v2] [v2]

Step 5:再创建1个新Pod
[v1] [v2] [v2] [v2-启动中]

Step 6:v2就绪后,删除最后1个旧Pod
[v2] [v2] [v2]

关键配置:
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1        # 最多多出1个Pod
    maxUnavailable: 0  # 始终保持3个可用(零停机关键)

Q7:K8s的健康检查探针有什么区别?

探针类型 触发时机 失败后果 适用场景
livenessProbe 持续检查 重启容器 检测死锁、无响应
readinessProbe 持续检查 从Service摘除 检测是否准备好接流量
startupProbe 启动阶段 重启容器 慢启动应用(替代liveness初始延迟)
# 三种探针配合使用示例
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 60   # 等待应用启动
  periodSeconds: 10
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 5
  failureThreshold: 3        # 连续3次失败才摘除

startupProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  failureThreshold: 30       # 最多等待30*10=300秒
  periodSeconds: 10

Q8:ConfigMap和Secret的区别及使用场景?

ConfigMap:存储非敏感配置
- 数据库URL、Redis地址、应用参数
- 明文存储,kubectl get configmap 可直接查看
- 支持热更新(挂载为文件时,修改ConfigMap后Pod内文件自动更新)

Secret:存储敏感信息
- 数据库密码、API密钥、TLS证书
- Base64编码存储(注意:不是加密!)
- 生产环境建议配合Vault或KMS加密存储

使用方式对比:
# ConfigMap → 环境变量
env:
  - name: DB_URL
    valueFrom:
      configMapKeyRef:
        name: shop-config
        key: db-url

# Secret → 环境变量
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: shop-secret
        key: db-password

14.3 CI/CD高频面试题

Q9:说说你们团队的CI/CD流程?

标准回答模板(结合实际项目):

"我们团队使用GitHub Actions实现CI/CD,整体流程如下:

1. 开发阶段:
   - 开发在feature分支开发,提交PR到develop分支
   - PR触发CI流水线:代码检查 → 单元测试 → 集成测试
   - 测试通过后,代码审查,合并到develop

2. 测试环境部署:
   - develop分支合并触发自动部署到测试环境
   - 运行自动化测试(接口测试、E2E测试)
   - 测试通过后,发起PR到main分支

3. 生产环境部署:
   - main分支合并触发生产部署流水线
   - 构建Docker镜像,推送到Harbor私有仓库
   - 通过Helm更新K8s Deployment
   - 滚动更新,自动健康检查
   - 部署完成后发送钉钉通知

4. 回滚机制:
   - 每次部署前记录当前版本
   - 发现问题可一键回滚到上一版本
   - helm rollback shop-order 0  # 回滚到上一版本"

Q10:蓝绿部署和金丝雀发布的区别?

蓝绿部署:
┌─────────────────────────────────────────────────────────────┐
│  当前状态:蓝色环境(v1)接收100%流量                         │
│                                                              │
│  [蓝 v1] [蓝 v1] [蓝 v1]  ← 100%流量                       │
│  [绿 v2] [绿 v2] [绿 v2]  ← 0%流量(已部署,待切换)         │
│                                                              │
│  切换:修改Service selector,瞬间将100%流量切到绿色           │
│  回滚:再切回蓝色,秒级回滚                                   │
│                                                              │
│  优点:切换快,回滚快                                         │
│  缺点:需要双倍资源                                           │
└─────────────────────────────────────────────────────────────┘

金丝雀发布:
┌─────────────────────────────────────────────────────────────┐
│  阶段1:5%流量到v2,观察指标                                  │
│  [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v2]        │
│                                                              │
│  阶段2:指标正常,扩大到20%                                   │
│  [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v1] [v2] [v2]        │
│                                                              │
│  阶段3:继续扩大到50%、100%                                   │
│                                                              │
│  优点:风险可控,逐步验证                                     │
│  缺点:需要流量控制能力(Istio/Nginx),过程较长              │
└─────────────────────────────────────────────────────────────┘

选择建议:
- 重大版本变更、不确定性高 → 金丝雀发布
- 紧急修复、需要快速切换 → 蓝绿部署
- 普通迭代 → 滚动更新

14.4 监控告警高频面试题

Q11:Prometheus的数据模型是什么?

Prometheus数据模型:时序数据库

每条数据由以下部分组成:
metric_name{label1="value1", label2="value2"} value timestamp

示例:
http_requests_total{method="POST", uri="/api/orders", status="200"} 1234 1704067200000

四种指标类型:
┌──────────────┬────────────────────────────────────────────┐
│ Counter      │ 只增不减的计数器                             │
│              │ 示例:请求总数、错误总数                      │
│              │ 常用函数:rate()、increase()                 │
├──────────────┼────────────────────────────────────────────┤
│ Gauge        │ 可增可减的仪表盘                             │
│              │ 示例:当前连接数、内存使用量                  │
│              │ 常用函数:直接使用当前值                      │
├──────────────┼────────────────────────────────────────────┤
│ Histogram    │ 分布统计(分桶)                             │
│              │ 示例:请求延迟分布                           │
│              │ 常用函数:histogram_quantile()               │
├──────────────┼────────────────────────────────────────────┤
│ Summary      │ 客户端计算分位数                             │
│              │ 示例:P50/P95/P99延迟                       │
│              │ 注意:不支持跨实例聚合                        │
└──────────────┴────────────────────────────────────────────┘

Q12:如何设计一套完整的告警体系?

告警体系设计原则:

1. 告警分级
   P0(紧急):服务不可用,立即电话通知
   P1(严重):核心功能异常,5分钟内响应
   P2(警告):性能下降,30分钟内响应
   P3(提示):潜在风险,工作时间处理

2. 告警内容要素(5W1H)
   - What:什么指标异常(错误率超过5%)
   - Where:哪个服务/实例(shop-order-service Pod-1)
   - When:什么时间发生(2024-01-01 10:00:00)
   - Why:可能的原因(数据库连接池耗尽)
   - How:如何处理(查看日志/重启Pod/扩容)
   - Value:当前值是多少(错误率:8.5%,阈值:5%)

3. 避免告警疲劳
   - 设置合理的持续时间(for: 5m,避免抖动触发)
   - 相关告警合并(同一服务的多个告警聚合)
   - 定期review告警规则,删除无效告警
   - 告警静默(维护窗口期间)

4. 告警闭环
   - 告警触发 → 通知到人 → 确认处理 → 解决 → 复盘
   - 记录每次告警的处理过程
   - 定期分析告警趋势,优化系统

十五、生产运维最佳实践

15.1 变更管理规范

生产变更三原则:

1. 可回滚(Rollback)
   - 每次变更前确认回滚方案
   - 数据库变更使用可逆的DDL(先加列,不删列)
   - 代码变更通过Helm版本管理,一键回滚

2. 可观测(Observable)
   - 变更后立即观察关键指标(错误率、延迟、CPU)
   - 设置变更观察期(至少15分钟)
   - 准备好监控大屏,变更时盯屏

3. 最小化影响(Minimal Impact)
   - 避免在业务高峰期变更
   - 优先使用滚动更新,避免全量重启
   - 数据库变更在低峰期执行
生产变更检查清单:

变更前:
□ 变更方案已评审
□ 回滚方案已准备
□ 测试环境已验证
□ 通知相关团队
□ 确认当前系统状态正常(无告警)

变更中:
□ 按步骤执行,不跳步
□ 每步执行后观察指标
□ 保持通讯畅通
□ 记录执行时间和结果

变更后:
□ 观察15分钟,确认指标正常
□ 验证核心功能可用
□ 更新变更记录
□ 通知相关团队变更完成

15.2 数据库变更最佳实践

-- 生产数据库变更规范

-- ✅ 正确:先加列(允许NULL或有默认值),不影响现有数据
ALTER TABLE `order`
ADD COLUMN `remark` VARCHAR(500) NULL COMMENT '备注' AFTER `status`;

-- ✅ 正确:加索引使用 ALGORITHM=INPLACE(不锁表)
ALTER TABLE `order`
ADD INDEX `idx_create_time` (`create_time`),
ALGORITHM=INPLACE, LOCK=NONE;

-- ❌ 错误:直接删列(可能导致代码报错,应先废弃再删)
ALTER TABLE `order` DROP COLUMN `old_field`;

-- ✅ 正确的删列流程:
-- Step 1:代码中移除对该列的引用,发布
-- Step 2:等待一个发布周期,确认无引用
-- Step 3:再执行 DROP COLUMN

-- ✅ 大表数据迁移:使用pt-online-schema-change(不锁表)
-- pt-online-schema-change --alter "ADD COLUMN remark VARCHAR(500)" \
--   --execute D=shop,t=order,h=localhost,u=root,p=password

15.3 日志规范与最佳实践

// 生产环境日志最佳实践

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public Order createOrder(OrderDTO dto) {
        // 1. 关键业务操作记录INFO日志
        log.info("开始创建订单, userId={}, productId={}, quantity={}",
            dto.getUserId(), dto.getProductId(), dto.getQuantity());

        try {
            // 2. 外部调用记录耗时
            long start = System.currentTimeMillis();
            boolean stockOk = inventoryService.deductStock(
                dto.getProductId(), dto.getQuantity());
            log.info("库存扣减完成, productId={}, cost={}ms",
                dto.getProductId(), System.currentTimeMillis() - start);

            if (!stockOk) {
                // 3. 业务异常记录WARN(不是ERROR,是预期内的情况)
                log.warn("库存不足, productId={}, quantity={}",
                    dto.getProductId(), dto.getQuantity());
                throw new InsufficientStockException("库存不足");
            }

            Order order = buildOrder(dto);
            orderDao.insert(order);

            log.info("订单创建成功, orderId={}, amount={}",
                order.getId(), order.getTotalAmount());
            return order;

        } catch (InsufficientStockException e) {
            throw e;  // 业务异常直接抛出,不记录ERROR
        } catch (Exception e) {
            // 4. 非预期异常记录ERROR,必须包含异常堆栈
            log.error("订单创建失败, userId={}, productId={}",
                dto.getUserId(), dto.getProductId(), e);
            throw new OrderCreateException("订单创建失败", e);
        }
    }
}
# logback-spring.xml - 生产环境日志配置
# 输出JSON格式,方便ELK采集

<configuration>
    <springProperty scope="context" name="appName" source="spring.application.name"/>

    <!-- 生产环境:JSON格式输出到文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/data/logs/${appName}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/data/logs/${appName}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>500MB</maxFileSize>
            <maxHistory>7</maxHistory>
            <totalSizeCap>5GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <!-- 添加自定义字段 -->
            <customFields>{"app":"${appName}","env":"${SPRING_PROFILES_ACTIVE:-unknown}"}</customFields>
            <!-- 包含MDC字段(TraceId等) -->
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
        </encoder>
    </appender>

    <!-- 异步写入,避免日志IO阻塞业务线程 -->
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>1024</queueSize>
        <appender-ref ref="FILE"/>
    </appender>

    <root level="INFO">
        <appender-ref ref="ASYNC_FILE"/>
    </root>

    <!-- 降低框架日志级别,减少噪音 -->
    <logger name="org.springframework" level="WARN"/>
    <logger name="org.hibernate" level="WARN"/>
    <logger name="com.zaxxer.hikari" level="WARN"/>
</configuration>

十六、综合实战:从零搭建完整DevOps环境

16.1 环境规划

完整DevOps环境架构:

开发者本地
    ↓ git push
GitHub/GitLab
    ↓ 触发Webhook
CI服务器(Jenkins/GitHub Actions)
    ↓ 构建 → 测试 → 打包
Harbor私有镜像仓库
    ↓ 镜像推送完成
K8s集群(通过Helm部署)
    ├── 开发环境(dev namespace)
    ├── 测试环境(staging namespace)
    └── 生产环境(prod namespace)
         ↓ 运行时
监控体系
    ├── Prometheus(指标采集)
    ├── Grafana(可视化)
    ├── ELK(日志管理)
    └── SkyWalking(链路追踪)

16.2 一键搭建本地开发环境脚本

#!/bin/bash
# setup-dev-env.sh - 一键搭建本地开发环境

set -e  # 遇到错误立即退出

echo "=========================================="
echo "  电商系统本地开发环境搭建脚本"
echo "=========================================="

# 检查依赖
check_dependency() {
    if ! command -v $1 &> /dev/null; then
        echo "❌ 缺少依赖: $1,请先安装"
        exit 1
    fi
    echo "✅ $1 已安装"
}

echo "检查依赖..."
check_dependency docker
check_dependency docker-compose
check_dependency java
check_dependency mvn

# 创建必要目录
mkdir -p data/{mysql,redis,elasticsearch}
mkdir -p logs/{app,nginx}
mkdir -p config/{nginx,logstash}

echo "启动基础服务..."
docker-compose -f docker-compose-dev.yml up -d mysql redis

echo "等待MySQL启动..."
until docker exec shop-mysql mysqladmin ping -h localhost --silent; do
    echo "  等待MySQL..."
    sleep 2
done
echo "✅ MySQL已就绪"

echo "初始化数据库..."
docker exec -i shop-mysql mysql -uroot -p123456 < sql/init.sql
echo "✅ 数据库初始化完成"

echo "启动其他服务..."
docker-compose -f docker-compose-dev.yml up -d

echo ""
echo "=========================================="
echo "  环境搭建完成!"
echo "=========================================="
echo "  MySQL:     localhost:3306"
echo "  Redis:     localhost:6379"
echo "  Nginx:     http://localhost:80"
echo "  Kibana:    http://localhost:5601"
echo "  Grafana:   http://localhost:3000 (admin/admin)"
echo "=========================================="

16.3 生产部署完整流程演练

# 完整的生产部署流程(手动执行版)

# ===== Step 1:构建镜像 =====
APP_VERSION=$(git describe --tags --always)
IMAGE_NAME="registry.example.com/shop/order-service:${APP_VERSION}"

echo "构建镜像: ${IMAGE_NAME}"
docker build -t ${IMAGE_NAME} .
docker push ${IMAGE_NAME}

# ===== Step 2:更新K8s配置 =====
# 方式A:直接更新镜像(快速)
kubectl set image deployment/shop-order-service \
    app=${IMAGE_NAME} -n shop

# 方式B:通过Helm更新(推荐,有版本记录)
helm upgrade shop-order ./helm/shop-order \
    --namespace shop \
    --set image.tag=${APP_VERSION} \
    --set image.repository=registry.example.com/shop/order-service \
    --atomic \          # 部署失败自动回滚
    --timeout 5m \      # 超时时间
    --wait              # 等待所有Pod就绪

# ===== Step 3:验证部署 =====
echo "等待部署完成..."
kubectl rollout status deployment/shop-order-service -n shop --timeout=5m

echo "验证Pod状态..."
kubectl get pods -n shop -l app=shop-order-service

echo "验证服务健康..."
kubectl exec -n shop deploy/shop-order-service -- \
    wget -qO- http://localhost:8080/actuator/health

# ===== Step 4:冒烟测试 =====
echo "执行冒烟测试..."
INGRESS_IP=$(kubectl get ingress shop-ingress -n shop -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -f "http://${INGRESS_IP}/api/health" || {
    echo "❌ 冒烟测试失败,开始回滚..."
    helm rollback shop-order -n shop
    exit 1
}

echo "✅ 部署成功!版本: ${APP_VERSION}"

16.4 故障演练(Chaos Engineering)

# 使用chaos-mesh进行故障注入演练

# 安装chaos-mesh
helm repo add chaos-mesh https://charts.chaos-mesh.org
helm install chaos-mesh chaos-mesh/chaos-mesh \
    --namespace=chaos-testing --create-namespace

# 演练1:Pod随机重启(验证服务高可用)
cat <<EOF | kubectl apply -f -
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: pod-kill-test
  namespace: shop
spec:
  action: pod-kill
  mode: one              # 随机杀死1个Pod
  selector:
    namespaces: [shop]
    labelSelectors:
      app: shop-order-service
  scheduler:
    cron: "@every 2m"    # 每2分钟执行一次
EOF

# 演练2:网络延迟(验证超时处理)
cat <<EOF | kubectl apply -f -
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: network-delay-test
  namespace: shop
spec:
  action: delay
  mode: all
  selector:
    namespaces: [shop]
    labelSelectors:
      app: shop-order-service
  delay:
    latency: "500ms"     # 注入500ms延迟
    correlation: "25"
    jitter: "100ms"
  duration: "5m"         # 持续5分钟
EOF

# 演练3:CPU压力(验证HPA自动扩容)
cat <<EOF | kubectl apply -f -
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
  name: cpu-stress-test
  namespace: shop
spec:
  mode: one
  selector:
    namespaces: [shop]
    labelSelectors:
      app: shop-order-service
  stressors:
    cpu:
      workers: 2         # 2个CPU压力线程
      load: 80           # 80% CPU负载
  duration: "3m"
EOF

十七、工具链速查手册

17.1 Docker常用命令速查

┌────────────────────────────────────────────────────────────────────┐
│                      Docker 命令速查表                              │
├──────────────────────┬─────────────────────────────────────────────┤
│ 镜像操作             │                                              │
│ docker build         │ docker build -t name:tag .                  │
│ docker push/pull     │ docker push/pull registry/name:tag          │
│ docker images        │ 列出本地镜像                                 │
│ docker rmi           │ docker rmi image_id                         │
│ docker image prune   │ 清理未使用的镜像                             │
├──────────────────────┼─────────────────────────────────────────────┤
│ 容器操作             │                                              │
│ docker run           │ docker run -d -p 8080:8080 --name app img   │
│ docker ps            │ docker ps -a(含已停止)                     │
│ docker logs          │ docker logs -f --tail=100 container         │
│ docker exec          │ docker exec -it container /bin/sh           │
│ docker stop/rm       │ 停止/删除容器                                │
│ docker stats         │ 实时查看容器资源使用                          │
├──────────────────────┼─────────────────────────────────────────────┤
│ 网络/存储            │                                              │
│ docker network ls    │ 列出网络                                     │
│ docker volume ls     │ 列出数据卷                                   │
│ docker inspect       │ 查看容器/镜像详细信息                         │
├──────────────────────┼─────────────────────────────────────────────┤
│ Compose操作          │                                              │
│ docker-compose up -d │ 后台启动所有服务                             │
│ docker-compose down  │ 停止并删除容器                               │
│ docker-compose logs  │ 查看服务日志                                 │
│ docker-compose ps    │ 查看服务状态                                 │
│ docker-compose scale │ 扩缩容:--scale service=3                   │
└──────────────────────┴─────────────────────────────────────────────┘

17.2 kubectl常用命令速查

┌────────────────────────────────────────────────────────────────────┐
│                      kubectl 命令速查表                             │
├──────────────────────┬─────────────────────────────────────────────┤
│ 查看资源             │                                              │
│ get pods             │ kubectl get pods -n ns -o wide              │
│ get all              │ kubectl get all -n ns                       │
│ describe             │ kubectl describe pod name -n ns             │
│ logs                 │ kubectl logs pod -n ns -f --tail=100        │
│ top                  │ kubectl top pod/node                        │
├──────────────────────┼─────────────────────────────────────────────┤
│ 操作资源             │                                              │
│ apply                │ kubectl apply -f manifest.yaml              │
│ delete               │ kubectl delete -f manifest.yaml             │
│ scale                │ kubectl scale deploy name --replicas=3      │
│ rollout              │ kubectl rollout status/history/undo deploy  │
│ set image            │ kubectl set image deploy/name c=img:tag     │
├──────────────────────┼─────────────────────────────────────────────┤
│ 调试                 │                                              │
│ exec                 │ kubectl exec -it pod -n ns -- /bin/sh       │
│ port-forward         │ kubectl port-forward pod 8080:8080 -n ns    │
│ cp                   │ kubectl cp pod:/path /local/path -n ns      │
│ debug                │ kubectl debug pod -it --image=busybox       │
├──────────────────────┼─────────────────────────────────────────────┤
│ 配置管理             │                                              │
│ config get-contexts  │ 查看所有集群上下文                           │
│ config use-context   │ 切换集群                                     │
│ create secret        │ kubectl create secret generic name --from-literal=k=v │
└──────────────────────┴─────────────────────────────────────────────┘

17.3 Helm常用命令速查

┌────────────────────────────────────────────────────────────────────┐
│                       Helm 命令速查表                               │
├──────────────────────┬─────────────────────────────────────────────┤
│ 仓库管理             │                                              │
│ repo add             │ helm repo add name url                      │
│ repo update          │ helm repo update                            │
│ search repo          │ helm search repo keyword                    │
├──────────────────────┼─────────────────────────────────────────────┤
│ 发布管理             │                                              │
│ install              │ helm install release chart -n ns -f vals.yaml│
│ upgrade              │ helm upgrade release chart --set key=val    │
│ rollback             │ helm rollback release [revision]            │
│ uninstall            │ helm uninstall release -n ns                │
├──────────────────────┼─────────────────────────────────────────────┤
│ 查看状态             │                                              │
│ list                 │ helm list -n ns                             │
│ status               │ helm status release -n ns                   │
│ history              │ helm history release -n ns                  │
│ get values           │ helm get values release -n ns               │
├──────────────────────┼─────────────────────────────────────────────┤
│ 调试                 │                                              │
│ template             │ helm template release chart -f vals.yaml    │
│ lint                 │ helm lint ./chart                           │
│ dry-run              │ helm install --dry-run --debug              │
└──────────────────────┴─────────────────────────────────────────────┘

17.4 常用PromQL速查

┌────────────────────────────────────────────────────────────────────┐
│                      PromQL 速查表                                  │
├──────────────────────────────────────────────────────────────────┤
│ HTTP指标                                                           │
│ QPS:                                                               │
│   sum(rate(http_server_requests_seconds_count[1m]))                │
│                                                                    │
│ 错误率:                                                             │
│   sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))│
│   / sum(rate(http_server_requests_seconds_count[5m])) * 100        │
│                                                                    │
│ P99延迟:                                                            │
│   histogram_quantile(0.99,                                         │
│     sum(rate(http_server_requests_seconds_bucket[5m])) by (le))    │
├──────────────────────────────────────────────────────────────────┤
│ JVM指标                                                            │
│ 堆内存使用率:                                                        │
│   jvm_memory_used_bytes{area="heap"}                               │
│   / jvm_memory_max_bytes{area="heap"} * 100                        │
│                                                                    │
│ GC频率:                                                             │
│   rate(jvm_gc_pause_seconds_count[5m])                             │
│                                                                    │
│ 线程数:                                                             │
│   jvm_threads_live_threads                                         │
├──────────────────────────────────────────────────────────────────┤
│ 数据库连接池                                                         │
│ 连接池使用率:                                                        │
│   hikaricp_connections_active / hikaricp_connections_max * 100     │
│                                                                    │
│ 等待连接数:                                                          │
│   hikaricp_connections_pending                                     │
└────────────────────────────────────────────────────────────────────┘

补充练习题

综合实战练习

练习1:完整CI/CD流水线搭建

要求:

  1. 在GitHub上创建一个Spring Boot项目仓库
  2. 编写GitHub Actions工作流,实现:
    • push到main分支时触发
    • 执行 mvn test 单元测试
    • 构建Docker镜像并推送到Docker Hub
    • 通过 kubectl set image 更新K8s Deployment
  3. 验证:提交代码后,K8s中的Pod自动更新到新版本

练习2:监控告警配置

要求:

  1. 在本地搭建 Prometheus + Grafana
  2. 接入Spring Boot Actuator指标
  3. 配置以下告警规则:
    • HTTP错误率超过5%持续3分钟
    • JVM堆内存使用率超过85%
    • 数据库连接池等待数超过10
  4. 配置告警通知到钉钉机器人

练习3:故障排查演练

场景:模拟以下故障并排查:

  1. 将应用的数据库连接配置改错,观察Pod状态变化,排查CrashLoopBackOff
  2. 将内存limits设置为128Mi(不够用),触发OOMKilled,排查并修复
  3. 在应用中加入一个死循环接口,调用后CPU飙高,用jstack定位问题线程

练习4:零停机部署演练

要求:

  1. 部署一个有3个副本的Spring Boot应用
  2. 在持续发送请求的同时(用ab或wrk压测),执行滚动更新
  3. 验证整个更新过程中没有请求失败(错误率为0)
  4. 尝试蓝绿部署:部署新版本后,通过修改Service selector切换流量

补充学习检查清单

  • 能够编写多阶段Dockerfile,将Spring Boot镜像体积控制在200MB以内
  • 能够用Docker Compose搭建包含MySQL、Redis、Nginx的完整本地环境
  • 能够编写K8s Deployment/Service/Ingress/ConfigMap/Secret的YAML
  • 能够通过Helm管理K8s应用的多环境部署
  • 能够编写GitHub Actions流水线,实现代码提交后自动部署
  • 能够搭建Prometheus + Grafana,配置Spring Boot监控面板
  • 能够配置ELK采集Spring Boot日志,在Kibana中搜索和分析
  • 能够集成SkyWalking,查看服务调用链路
  • 能够排查CPU飙高、OOM、接口慢等常见生产问题
  • 能够执行零停机滚动更新,并在出问题时快速回滚
  • 熟悉Docker、K8s、CI/CD相关的大厂面试题
  • 能够在面试中清晰描述团队的DevOps工具链和实践经验

十八、服务网格(Service Mesh)入门

18.1 为什么需要Service Mesh?

随着微服务数量增多,服务间通信的治理变得复杂:超时重试、熔断限流、链路追踪、mTLS加密……如果每个服务都自己实现,代码重复且难以统一管理。Service Mesh将这些能力下沉到基础设施层(Sidecar代理),业务代码完全无感知。

传统微服务 vs Service Mesh:

传统方式(每个服务自己处理):
┌─────────────────────────────────────────────────────────────┐
│  服务A                          服务B                        │
│  ┌──────────────────────┐      ┌──────────────────────┐    │
│  │ 业务代码              │      │ 业务代码              │    │
│  │ + Ribbon负载均衡      │ ───► │ + Hystrix熔断         │    │
│  │ + Sleuth链路追踪      │      │ + Sleuth链路追踪      │    │
│  │ + 重试逻辑            │      │ + 限流逻辑            │    │
│  └──────────────────────┘      └──────────────────────┘    │
│  问题:治理逻辑分散在各服务,升级困难,多语言难统一           │
└─────────────────────────────────────────────────────────────┘

Service Mesh方式(Sidecar代理统一处理):
┌─────────────────────────────────────────────────────────────┐
│  服务A Pod                      服务B Pod                    │
│  ┌──────────┬──────────┐       ┌──────────┬──────────┐     │
│  │ 业务代码  │ Envoy    │ ────► │ Envoy    │ 业务代码  │     │
│  │ (只关注  │ Sidecar  │       │ Sidecar  │ (只关注  │     │
│  │  业务)   │ 负责所有  │       │ 负责所有  │  业务)   │     │
│  │          │ 治理能力  │       │ 治理能力  │          │     │
│  └──────────┴──────────┘       └──────────┴──────────┘     │
│                    ↕ 统一上报                                │
│              Istio Control Plane                             │
│         (统一配置、策略、可观测性)                           │
└─────────────────────────────────────────────────────────────┘

18.2 Istio核心功能实战

# 安装Istio(使用istioctl)
curl -L https://istio.io/downloadIstio | sh -
export PATH=$PWD/istio-1.20.0/bin:$PATH

# 安装到K8s集群(demo配置,适合学习)
istioctl install --set profile=demo -y

# 为命名空间开启自动注入Sidecar
kubectl label namespace shop istio-injection=enabled

# 验证注入(Pod应该有2个容器:app + istio-proxy)
kubectl get pods -n shop
# NAME                                READY   STATUS
# shop-order-7d9f8b-xxx               2/2     Running  ← 2/2表示有Sidecar
# 流量管理:VirtualService + DestinationRule

# DestinationRule:定义服务的子集(版本)
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: shop-order-dr
  namespace: shop
spec:
  host: shop-order-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 50
        maxRequestsPerConnection: 10
    # 熔断配置
    outlierDetection:
      consecutive5xxErrors: 5      # 连续5次5xx错误
      interval: 30s                # 检测间隔
      baseEjectionTime: 30s        # 熔断持续时间
      maxEjectionPercent: 50       # 最多熔断50%实例
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
---
# VirtualService:定义路由规则(金丝雀发布)
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: shop-order-vs
  namespace: shop
spec:
  hosts:
    - shop-order-service
  http:
    - match:
        # 特定用户(测试人员)路由到v2
        - headers:
            x-canary-user:
              exact: "true"
      route:
        - destination:
            host: shop-order-service
            subset: v2
    # 其他用户:90%到v1,10%到v2
    - route:
        - destination:
            host: shop-order-service
            subset: v1
          weight: 90
        - destination:
            host: shop-order-service
            subset: v2
          weight: 10
# 超时和重试配置
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: shop-order-vs
  namespace: shop
spec:
  hosts:
    - shop-order-service
  http:
    - route:
        - destination:
            host: shop-order-service
      timeout: 3s          # 请求超时3秒
      retries:
        attempts: 3        # 最多重试3次
        perTryTimeout: 1s  # 每次重试超时1秒
        # 只对网络错误和5xx重试(幂等操作才能重试)
        retryOn: "gateway-error,connect-failure,retriable-4xx"

18.3 mTLS双向认证

# 开启严格mTLS(服务间通信必须加密)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: shop
spec:
  mtls:
    mode: STRICT   # 严格模式:只允许mTLS流量

---
# 授权策略:只允许特定服务访问订单服务
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: order-service-authz
  namespace: shop
spec:
  selector:
    matchLabels:
      app: shop-order-service
  rules:
    - from:
        # 只允许来自网关和用户服务的请求
        - source:
            principals:
              - "cluster.local/ns/shop/sa/shop-gateway"
              - "cluster.local/ns/shop/sa/shop-user-service"
      to:
        - operation:
            methods: ["GET", "POST"]
            paths: ["/api/orders*"]

十九、安全加固实战

19.1 容器安全最佳实践

# 安全加固的Dockerfile

FROM eclipse-temurin:17-jre-alpine

# 1. 创建非root用户(最小权限原则)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 2. 设置工作目录权限
WORKDIR /app
RUN chown appuser:appgroup /app

# 3. 复制文件并设置只读权限
COPY --chown=appuser:appgroup target/*.jar app.jar
RUN chmod 444 app.jar

# 4. 切换到非root用户
USER appuser

# 5. 不暴露特权端口(使用8080而非80)
EXPOSE 8080

# 6. 使用exec格式(避免shell注入)
ENTRYPOINT ["java", "-jar", "app.jar"]
# K8s安全上下文配置
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      # Pod级别安全上下文
      securityContext:
        runAsNonRoot: true          # 禁止root运行
        runAsUser: 1000             # 指定UID
        runAsGroup: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault      # 启用seccomp过滤系统调用

      containers:
        - name: app
          # 容器级别安全上下文
          securityContext:
            allowPrivilegeEscalation: false   # 禁止提权
            readOnlyRootFilesystem: true       # 根文件系统只读
            capabilities:
              drop: ["ALL"]                    # 删除所有Linux能力
              add: ["NET_BIND_SERVICE"]        # 只保留必要能力

          # 挂载可写目录(根文件系统只读时需要)
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: logs
              mountPath: /app/logs

      volumes:
        - name: tmp
          emptyDir: {}
        - name: logs
          emptyDir: {}

19.2 Secret安全管理:集成HashiCorp Vault

为什么不能直接用K8s Secret存密码?

K8s Secret的问题:
- Base64编码不是加密,kubectl get secret 可直接解码
- Secret存储在etcd中,etcd被攻破则所有密码泄露
- 无法做到密码轮换(更换密码需要重新部署)
- 无审计日志(谁在什么时间访问了哪个密码)

Vault的优势:
- 真正的加密存储
- 动态密码(每次申请都生成新密码,用完即废)
- 细粒度访问控制
- 完整的审计日志
- 支持密码自动轮换
# 安装Vault(K8s方式)
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault \
    --namespace vault --create-namespace \
    --set "server.dev.enabled=true"   # 开发模式,生产用HA模式

# 初始化Vault(生产环境)
kubectl exec -n vault vault-0 -- vault operator init \
    -key-shares=5 \        # 5个解封密钥
    -key-threshold=3       # 需要3个才能解封

# 存储数据库密码
kubectl exec -n vault vault-0 -- vault kv put \
    secret/shop/database \
    username=shop_user \
    password=SuperSecret123!

# 读取密码(验证)
kubectl exec -n vault vault-0 -- vault kv get secret/shop/database
# Spring Boot集成Vault(通过Vault Agent Injector)
# 在Pod注解中声明需要的Secret
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:
      annotations:
        # 开启Vault注入
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "shop-order-service"
        # 将数据库密码注入为文件
        vault.hashicorp.com/agent-inject-secret-database: "secret/shop/database"
        # 自定义文件格式(生成Spring Boot可读的properties)
        vault.hashicorp.com/agent-inject-template-database: |
          {{- with secret "secret/shop/database" -}}
          spring.datasource.username={{ .Data.data.username }}
          spring.datasource.password={{ .Data.data.password }}
          {{- end }}
    spec:
      serviceAccountName: shop-order-service
      containers:
        - name: app
          env:
            # 告诉Spring Boot从Vault注入的文件加载配置
            - name: SPRING_CONFIG_ADDITIONAL_LOCATION
              value: "file:/vault/secrets/database"

19.3 网络安全:Nginx防护配置

# nginx-security.conf - 生产安全配置

# 隐藏Nginx版本信息
server_tokens off;

# 防止点击劫持
add_header X-Frame-Options "SAMEORIGIN" always;

# 防止MIME类型嗅探
add_header X-Content-Type-Options "nosniff" always;

# XSS防护
add_header X-XSS-Protection "1; mode=block" always;

# HSTS(强制HTTPS,1年)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# CSP(内容安全策略)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" always;

server {
    listen 443 ssl http2;
    server_name api.example.com;

    # SSL配置
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # 限制请求体大小(防止大文件上传攻击)
    client_max_body_size 10m;

    # 全局限流(每个IP每秒最多20个请求)
    limit_req zone=api_limit burst=50 nodelay;
    limit_req_status 429;

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        return 404;
    }

    # API代理
    location /api/ {
        # 接口级别更严格的限流
        limit_req zone=api_strict burst=10 nodelay;

        proxy_pass http://backend_pool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 超时配置
        proxy_connect_timeout 5s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

# 限流区域定义(在http块中)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=api_strict:10m rate=5r/s;

二十、性能压测与容量规划

20.1 压测工具对比与选择

常用压测工具对比:

┌──────────┬──────────┬──────────────────────────────────────────┐
│ 工具     │ 特点     │ 适用场景                                  │
├──────────┼──────────┼──────────────────────────────────────────┤
│ ab       │ 简单易用 │ 快速验证单接口性能,不适合复杂场景         │
│ wrk      │ 高性能   │ 单接口高并发压测,Lua脚本扩展              │
│ JMeter   │ 功能全面 │ 复杂业务场景,GUI操作,支持分布式压测       │
│ Gatling  │ 代码驱动 │ 场景复杂,需要版本控制,Scala DSL          │
│ k6       │ 现代化   │ JavaScript编写场景,CI/CD集成友好          │
└──────────┴──────────┴──────────────────────────────────────────┘
# wrk压测示例

# 基础压测:4线程,100并发,持续30秒
wrk -t4 -c100 -d30s http://localhost:8080/api/orders

# 输出解读:
# Running 30s test @ http://localhost:8080/api/orders
#   4 threads and 100 connections
#   Thread Stats   Avg      Stdev     Max   +/- Stdev
#     Latency    45.23ms   12.34ms  234.56ms   89.12%
#     Req/Sec   543.21     45.67   678.00     72.34%
#   65123 requests in 30.05s, 45.23MB read
# Requests/sec:   2167.45   ← QPS
# Transfer/sec:      1.50MB

# 带Lua脚本的POST请求压测
cat > post_order.lua << 'EOF'
wrk.method = "POST"
wrk.body   = '{"userId":1,"productId":100,"quantity":1}'
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Authorization"] = "Bearer test-token"
EOF

wrk -t4 -c100 -d60s -s post_order.lua http://localhost:8080/api/orders
// k6压测脚本(更贴近真实场景)
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// 自定义指标:错误率
const errorRate = new Rate('errors');

// 压测配置:阶梯式加压
export const options = {
    stages: [
        { duration: '1m', target: 50 },   // 1分钟内从0升到50并发
        { duration: '3m', target: 50 },   // 保持50并发3分钟
        { duration: '1m', target: 200 },  // 1分钟内升到200并发
        { duration: '3m', target: 200 },  // 保持200并发3分钟
        { duration: '1m', target: 0 },    // 1分钟内降到0
    ],
    thresholds: {
        http_req_duration: ['p(99)<500'],  // 99%请求在500ms内
        errors: ['rate<0.01'],             // 错误率低于1%
    },
};

export default function () {
    // 模拟真实用户行为:登录 → 查询商品 → 下单
    const loginRes = http.post('http://localhost:8080/api/auth/login', JSON.stringify({
        username: 'testuser',
        password: 'password123'
    }), { headers: { 'Content-Type': 'application/json' } });

    check(loginRes, { '登录成功': (r) => r.status === 200 });
    const token = loginRes.json('data.token');

    sleep(1);  // 模拟用户思考时间

    // 查询商品
    const productRes = http.get('http://localhost:8080/api/products/100', {
        headers: { 'Authorization': `Bearer ${token}` }
    });
    check(productRes, { '查询商品成功': (r) => r.status === 200 });

    sleep(0.5);

    // 下单
    const orderRes = http.post('http://localhost:8080/api/orders', JSON.stringify({
        productId: 100,
        quantity: 1
    }), {
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`
        }
    });

    const success = check(orderRes, { '下单成功': (r) => r.status === 200 });
    errorRate.add(!success);

    sleep(2);
}

20.2 压测结果分析与容量规划

压测结果分析框架:

1. 确定系统瓶颈
   ┌─────────────────────────────────────────────────────────┐
   │  压测时同时观察:                                         │
   │  - 应用层:QPS、延迟、错误率(Grafana)                   │
   │  - JVM层:GC频率、堆内存、线程数                          │
   │  - 数据库:慢查询、连接池使用率、CPU                       │
   │  - 系统层:CPU、内存、网络IO、磁盘IO                       │
   │                                                          │
   │  瓶颈判断:                                               │
   │  CPU高 → 计算密集,考虑优化算法或扩容CPU                  │
   │  内存高 → 内存泄漏或缓存不足,分析堆转储                   │
   │  DB连接池满 → 慢查询或连接数不足,优化SQL或增加连接数       │
   │  GC频繁 → 对象创建过多,优化对象复用                       │
   └─────────────────────────────────────────────────────────┘

2. 容量规划公式
   目标QPS = 峰值QPS × 安全系数(通常1.5-2倍)
   所需实例数 = 目标QPS / 单实例QPS
   
   示例:
   - 单实例压测QPS = 500
   - 预期峰值QPS = 1000
   - 安全系数 = 1.5
   - 目标QPS = 1000 × 1.5 = 1500
   - 所需实例数 = 1500 / 500 = 3个实例

3. HPA配置建议
   - CPU触发阈值:70%(留30%余量应对突发)
   - 内存触发阈值:80%
   - 最小实例数:2(保证高可用)
   - 最大实例数:根据容量规划结果 × 2

20.3 JVM调优实战

# 生产环境JVM参数模板(Spring Boot + G1GC)

JAVA_OPTS="\
  # 堆内存:容器内存的75%(留25%给堆外内存)
  -XX:+UseContainerSupport \
  -XX:MaxRAMPercentage=75.0 \
  -XX:InitialRAMPercentage=50.0 \

  # 使用G1GC(JDK9+默认,适合大堆)
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \      # 目标GC停顿时间200ms
  -XX:G1HeapRegionSize=16m \      # Region大小(堆/2048)
  -XX:G1NewSizePercent=20 \       # 新生代最小占比
  -XX:G1MaxNewSizePercent=40 \    # 新生代最大占比

  # OOM时自动导出堆转储
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/data/dumps/ \

  # GC日志(用于事后分析)
  -Xlog:gc*:file=/data/logs/gc.log:time,uptime:filecount=5,filesize=50m \

  # 元空间(避免频繁Full GC)
  -XX:MetaspaceSize=256m \
  -XX:MaxMetaspaceSize=512m \

  # 字符串去重(减少内存占用)
  -XX:+UseStringDeduplication"
// 常见JVM调优场景

// 场景1:频繁Full GC
// 原因:老年代空间不足,通常是内存泄漏或堆设置太小
// 排查:
// jstat -gcutil <PID> 1000 10
// 观察 OU(老年代使用率)是否持续增长
// 如果持续增长 → 内存泄漏,用MAT分析堆转储
// 如果稳定在高位 → 堆设置太小,增大-Xmx

// 场景2:Young GC频繁(每秒多次)
// 原因:新生代太小,对象晋升太快
// 解决:增大新生代比例
// -XX:G1NewSizePercent=30
// -XX:G1MaxNewSizePercent=50

// 场景3:大对象直接进老年代
// 原因:对象大小超过G1 Region的50%
// 解决:增大Region大小,或避免创建大对象
// -XX:G1HeapRegionSize=32m

// 场景4:元空间OOM
// java.lang.OutOfMemoryError: Metaspace
// 原因:动态生成类过多(反射、动态代理、Groovy脚本)
// 解决:增大元空间上限
// -XX:MaxMetaspaceSize=512m

二十一、多云与混合云部署

21.1 多云部署策略

多云部署的常见原因:
- 避免云厂商锁定
- 不同地区使用不同云(合规要求)
- 灾备:主云故障时切换到备云
- 成本优化:不同业务选择最优价格的云

多云架构示意:
┌─────────────────────────────────────────────────────────────┐
│                    全局流量调度(DNS/GTM)                    │
│                           ↓                                 │
│         ┌─────────────────┴─────────────────┐               │
│         ▼                                   ▼               │
│   阿里云(主)                          腾讯云(备)          │
│   ┌─────────────────┐             ┌─────────────────┐       │
│   │ K8s集群          │             │ K8s集群          │       │
│   │ 应用服务          │             │ 应用服务          │       │
│   │ MySQL主库        │ ──同步──►   │ MySQL从库        │       │
│   │ Redis主          │ ──同步──►   │ Redis从          │       │
│   └─────────────────┘             └─────────────────┘       │
└─────────────────────────────────────────────────────────────┘
# 使用Terraform管理多云基础设施(基础示例)
# main.tf

terraform {
  required_providers {
    alicloud = {
      source  = "aliyun/alicloud"
      version = "~> 1.200"
    }
  }
}

# 阿里云K8s集群
resource "alicloud_cs_managed_kubernetes" "shop_cluster" {
  name                 = "shop-prod-cluster"
  cluster_spec         = "ack.pro.small"
  kubernetes_version   = "1.28.3-aliyun.1"

  worker_vswitch_ids   = [alicloud_vswitch.worker.id]
  pod_cidr             = "172.20.0.0/16"
  service_cidr         = "172.21.0.0/20"

  worker_instance_types = ["ecs.c7.xlarge"]
  worker_number         = 3

  # 开启自动扩缩容
  addons {
    name = "cluster-autoscaler"
  }
}

# 输出kubeconfig
output "kubeconfig" {
  value     = alicloud_cs_managed_kubernetes.shop_cluster.certificate_authority
  sensitive = true
}

21.2 GitOps实践:ArgoCD

GitOps核心理念:
- Git仓库是唯一的事实来源(Single Source of Truth)
- 所有变更通过Git提交触发,而非手动kubectl
- 系统自动将实际状态同步到Git中声明的期望状态

传统部署 vs GitOps:

传统:
开发者 → kubectl apply → K8s集群
(谁改了什么?什么时候改的?无法追溯)

GitOps:
开发者 → git push → Git仓库 → ArgoCD自动同步 → K8s集群
(所有变更有完整的Git历史,可审计、可回滚)
# 安装ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f \
    https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 获取初始密码
kubectl -n argocd get secret argocd-initial-admin-secret \
    -o jsonpath="{.data.password}" | base64 -d

# 访问UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# 浏览器访问:https://localhost:8080
# ArgoCD Application:声明要同步的应用
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: shop-order-service
  namespace: argocd
spec:
  project: default

  # 源:Git仓库中的Helm Chart
  source:
    repoURL: https://github.com/example/shop-k8s-configs
    targetRevision: main
    path: helm/shop-order-service
    helm:
      valueFiles:
        - values-prod.yaml

  # 目标:K8s集群和命名空间
  destination:
    server: https://kubernetes.default.svc
    namespace: shop

  # 同步策略
  syncPolicy:
    automated:
      prune: true      # 自动删除Git中已移除的资源
      selfHeal: true   # 手动修改K8s资源后自动恢复到Git状态
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

二十二、大厂DevOps实践总结

22.1 大厂DevOps成熟度模型

DevOps成熟度五个阶段:

Level 1 - 初始阶段
  - 手动部署,依赖个人经验
  - 没有自动化测试
  - 发布频率:月/季度

Level 2 - 基础自动化
  - 有CI流水线(自动构建+测试)
  - 手动触发部署
  - 发布频率:周

Level 3 - 持续交付
  - CI/CD全自动化
  - 多环境自动晋级
  - 发布频率:天

Level 4 - 持续部署
  - 代码合并即自动部署到生产
  - 完善的监控和告警
  - 发布频率:小时

Level 5 - 优化阶段(大厂水平)
  - 混沌工程,主动发现问题
  - 自动扩缩容,弹性应对流量
  - 全链路压测,容量精准规划
  - 发布频率:分钟级

22.2 大厂面试中的DevOps加分项

面试官最想听到的DevOps实践经验:

1. 解决了什么实际问题
   ✅ "我们之前部署需要30分钟,经常出错,我搭建了CI/CD流水线后
      部署时间缩短到5分钟,错误率降低了90%"

2. 遇到了什么挑战,如何解决
   ✅ "在做蓝绿部署时,发现数据库Schema变更无法做到零停机,
      我们通过'扩展-收缩'模式解决:先加新列(兼容新旧代码),
      部署新版本,再删旧列"

3. 有量化的数据支撑
   ✅ "通过优化Docker镜像(多阶段构建+分层),镜像从800MB降到180MB,
      CI/CD时间从15分钟降到8分钟"

4. 主动思考和改进
   ✅ "我发现告警太多导致告警疲劳,主动梳理了告警规则,
      将无效告警从200条减少到30条,响应速度提升了3倍"

22.3 工具链选型决策树

如何选择合适的工具?

容器化:
  单机开发 → Docker + Docker Compose
  多机生产 → Kubernetes

CI/CD:
  开源项目/小团队 → GitHub Actions(免费额度够用)
  企业内网/复杂流水线 → Jenkins
  云原生/GitOps → ArgoCD + GitHub Actions

监控:
  指标监控 → Prometheus + Grafana(开源标准)
  日志管理 → ELK Stack 或 Loki + Grafana
  链路追踪 → SkyWalking(Java友好)或 Jaeger

镜像仓库:
  公开项目 → Docker Hub
  企业私有 → Harbor(功能全面,支持镜像扫描)
  云厂商 → 阿里云ACR / 腾讯云TCR

服务网格:
  流量治理需求强 → Istio(功能最全,但复杂)
  轻量级需求 → Linkerd(简单易用)
  暂不需要 → 用Spring Cloud自带的治理能力

知识总结思维导图

企业级工具链总结

容器化

Docker多阶段构建

镜像体积优化

分层缓存策略

安全加固

Docker Compose

本地开发环境

服务依赖管理

镜像安全

Trivy扫描

非root运行

只读文件系统

K8s生产运维

资源管理

requests/limits

VPA自动推荐

HPA自动扩缩容

故障排查

CrashLoopBackOff

OOMKilled

Pending调度失败

安全加固

RBAC权限控制

NetworkPolicy

SecurityContext

CI/CD

GitHub Actions

多环境部署

矩阵构建

缓存优化

Jenkins

Jenkinsfile

共享库

制品管理

GitOps

ArgoCD

声明式配置

自动同步

可观测性

监控

Prometheus指标

Grafana面板

告警规则

日志

ELK Stack

结构化日志

日志告警

链路追踪

SkyWalking

TraceId传递

性能剖析

安全

Secret管理

Vault动态密码

K8s Secret加密

网络安全

mTLS

NetworkPolicy

Nginx防护

性能

压测

wrk/k6

阶梯加压

瓶颈定位

JVM调优

G1GC参数

堆内存规划

GC日志分析

容量规划

单实例基准

安全系数

HPA阈值


二十三、云原生应用开发最佳实践

23.1 十二要素应用(12-Factor App)

云原生应用应该遵循的12条原则,确保应用可以在任何云平台上运行。

12-Factor App 原则详解:

1. 基准代码(Codebase)
   一份代码,多处部署
   ✅ 正确:一个Git仓库,通过配置区分dev/staging/prod
   ❌ 错误:每个环境一份代码副本

2. 依赖(Dependencies)
   显式声明依赖,不依赖系统工具
   ✅ 正确:pom.xml声明所有依赖,Maven自动下载
   ❌ 错误:依赖系统预装的库(如ImageMagick)

3. 配置(Config)
   配置存储在环境变量中,不硬编码
   ✅ 正确:数据库URL从环境变量读取
   ❌ 错误:配置写死在application.properties

4. 后端服务(Backing Services)
   把数据库、缓存等视为附加资源,通过URL访问
   ✅ 正确:通过环境变量切换MySQL实例
   ❌ 错误:代码中硬编码数据库地址

5. 构建、发布、运行(Build, Release, Run)
   严格分离构建和运行阶段
   ✅ 正确:CI构建镜像 → 推送仓库 → K8s拉取运行
   ❌ 错误:在生产服务器上编译代码

6. 进程(Processes)
   应用作为无状态进程运行
   ✅ 正确:Session存Redis,任意Pod都能处理请求
   ❌ 错误:Session存本地内存,Pod重启丢失

7. 端口绑定(Port Binding)
   通过端口绑定提供服务,不依赖外部Web服务器
   ✅ 正确:Spring Boot内嵌Tomcat,监听8080端口
   ❌ 错误:依赖外部Tomcat容器

8. 并发(Concurrency)
   通过进程模型扩展(水平扩展)
   ✅ 正确:增加Pod副本数应对流量
   ❌ 错误:单实例垂直扩容(加CPU/内存)

9. 易处理(Disposability)
   快速启动和优雅终止
   ✅ 正确:应用启动<30秒,收到SIGTERM后优雅关闭
   ❌ 错误:启动需要5分钟,强制kill导致数据丢失

10. 开发环境与生产环境等价(Dev/Prod Parity)
    开发、测试、生产环境尽量一致
    ✅ 正确:都用Docker + K8s,只是规格不同
    ❌ 错误:开发用H2,生产用MySQL

11. 日志(Logs)
    日志作为事件流输出到stdout
    ✅ 正确:应用输出到stdout,由容器运行时收集
    ❌ 错误:应用直接写文件,需要挂载Volume

12. 管理进程(Admin Processes)
    管理任务作为一次性进程运行
    ✅ 正确:数据迁移用K8s Job,执行完自动退出
    ❌ 错误:在应用启动时执行数据迁移

23.2 Spring Boot云原生配置

// 云原生Spring Boot应用配置示例

// 1. 优雅关闭(响应K8s的SIGTERM信号)
// application.yml
server:
  shutdown: graceful   # 优雅关闭
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s   # 最多等待30// 2. 健康检查端点
@Configuration
public class HealthConfig {
    
    @Bean
    public HealthIndicator customHealthIndicator() {
        return () -> {
            // 检查关键依赖是否可用
            boolean dbOk = checkDatabase();
            boolean redisOk = checkRedis();
            
            if (dbOk && redisOk) {
                return Health.up()
                    .withDetail("database", "ok")
                    .withDetail("redis", "ok")
                    .build();
            } else {
                return Health.down()
                    .withDetail("database", dbOk ? "ok" : "down")
                    .withDetail("redis", redisOk ? "ok" : "down")
                    .build();
            }
        };
    }
}

// 3. 配置外部化(从环境变量读取)
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    
    // 从环境变量 APP_DATABASE_URL 读取
    private String databaseUrl;
    
    // 从环境变量 APP_REDIS_HOST 读取
    private String redisHost;
    
    // 从环境变量 APP_MAX_CONNECTIONS 读取,默认100
    private int maxConnections = 100;
    
    // getters and setters...
}
# K8s Deployment中注入环境变量
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          env:
            # 从ConfigMap读取
            - name: APP_DATABASE_URL
              valueFrom:
                configMapKeyRef:
                  name: shop-config
                  key: database-url
            
            # 从Secret读取
            - name: APP_DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: shop-secret
                  key: database-password
            
            # 直接设置
            - name: SPRING_PROFILES_ACTIVE
              value: "prod"
            
            # 从Pod信息读取
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP

23.3 应用可观测性增强

// 使用Micrometer增强可观测性

@RestController
@RequestMapping("/api/orders")
@Slf4j
public class OrderController {

    private final OrderService orderService;
    private final MeterRegistry meterRegistry;

    @PostMapping
    @Timed(value = "order.create", description = "订单创建耗时")
    public Result<Order> createOrder(@RequestBody OrderDTO dto) {
        // 记录业务指标
        meterRegistry.counter("order.create.attempt",
            "product_id", dto.getProductId().toString()).increment();

        try {
            Order order = orderService.createOrder(dto);
            
            // 记录订单金额分布
            meterRegistry.summary("order.amount",
                "status", "success").record(order.getTotalAmount().doubleValue());
            
            return Result.success(order);
        } catch (InsufficientStockException e) {
            // 记录失败原因
            meterRegistry.counter("order.create.failure",
                "reason", "insufficient_stock").increment();
            throw e;
        }
    }
}

// 自定义Actuator端点
@Component
@Endpoint(id = "business-metrics")
public class BusinessMetricsEndpoint {

    @Autowired
    private OrderService orderService;

    @ReadOperation
    public Map<String, Object> businessMetrics() {
        Map<String, Object> metrics = new HashMap<>();
        
        // 实时业务指标
        metrics.put("todayOrderCount", orderService.getTodayOrderCount());
        metrics.put("todayRevenue", orderService.getTodayRevenue());
        metrics.put("pendingPaymentCount", orderService.getPendingPaymentCount());
        
        return metrics;
    }
}

二十四、灾难恢复与业务连续性

24.1 备份策略

3-2-1备份原则:
- 3份副本:生产数据 + 2份备份
- 2种介质:本地磁盘 + 云存储
- 1份异地:防止机房级灾难

备份类型对比:
┌──────────────┬──────────┬──────────┬──────────────────┐
│ 备份类型     │ 恢复速度 │ 存储空间 │ 适用场景          │
├──────────────┼──────────┼──────────┼──────────────────┤
│ 全量备份     │ 快       │ 大       │ 每周一次          │
│ 增量备份     │ 慢       │ 小       │ 每天一次          │
│ 差异备份     │ 中       │ 中       │ 折中方案          │
│ 快照         │ 极快     │ 小       │ 云盘快照,秒级    │
└──────────────┴──────────┴──────────┴──────────────────┘
# MySQL自动备份脚本
#!/bin/bash
# mysql-backup.sh

BACKUP_DIR="/data/backups/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7

# 全量备份
mysqldump -h localhost -u backup_user -p'password' \
    --single-transaction \
    --routines \
    --triggers \
    --events \
    --all-databases \
    | gzip > ${BACKUP_DIR}/full_backup_${DATE}.sql.gz

# 上传到云存储(阿里云OSS)
ossutil cp ${BACKUP_DIR}/full_backup_${DATE}.sql.gz \
    oss://my-backup-bucket/mysql/

# 删除7天前的本地备份
find ${BACKUP_DIR} -name "full_backup_*.sql.gz" -mtime +${RETENTION_DAYS} -delete

# 验证备份完整性
gunzip -t ${BACKUP_DIR}/full_backup_${DATE}.sql.gz
if [ $? -eq 0 ]; then
    echo "备份成功: full_backup_${DATE}.sql.gz"
else
    echo "备份失败!" | mail -s "MySQL备份失败" admin@example.com
fi
# K8s CronJob定时备份
apiVersion: batch/v1
kind: CronJob
metadata:
  name: mysql-backup
  namespace: shop
spec:
  schedule: "0 2 * * *"   # 每天凌晨2点
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: backup
              image: mysql:8.0
              command:
                - /bin/sh
                - -c
                - |
                  mysqldump -h mysql-svc -u root -p${MYSQL_ROOT_PASSWORD} \
                    --single-transaction --all-databases \
                    | gzip > /backup/backup_$(date +%Y%m%d_%H%M%S).sql.gz
              env:
                - name: MYSQL_ROOT_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: mysql-secret
                      key: root-password
              volumeMounts:
                - name: backup-volume
                  mountPath: /backup
          
          volumes:
            - name: backup-volume
              persistentVolumeClaim:
                claimName: mysql-backup-pvc
          
          restartPolicy: OnFailure

24.2 灾难恢复演练

灾难恢复演练流程:

目标:验证在主数据中心完全不可用时,能否在30分钟内切换到备用数据中心

演练步骤:
1. 准备阶段(T-1天)
   - 通知所有相关人员演练时间
   - 准备演练脚本和检查清单
   - 确认备用数据中心状态正常

2. 执行阶段(T0)
   T+0分钟:宣布演练开始,模拟主数据中心故障
   T+5分钟:切换DNS,将流量指向备用数据中心
   T+10分钟:验证应用服务可访问
   T+15分钟:验证数据库主从切换成功
   T+20分钟:执行冒烟测试,验证核心功能
   T+30分钟:宣布演练成功,恢复正常

3. 复盘阶段(T+1天)
   - 记录实际耗时 vs 目标耗时
   - 分析遇到的问题和改进点
   - 更新灾难恢复手册
# 灾难恢复自动化脚本
#!/bin/bash
# disaster-recovery.sh

set -e

echo "=========================================="
echo "  灾难恢复流程启动"
echo "=========================================="

# Step 1:切换数据库主从
echo "[1/5] 切换MySQL主从..."
mysql -h backup-mysql -u admin -p'password' -e "STOP SLAVE; RESET SLAVE ALL;"
mysql -h backup-mysql -u admin -p'password' -e "SET GLOBAL read_only = OFF;"
echo "✅ MySQL已切换为主库"

# Step 2:更新K8s ConfigMap(指向新的数据库)
echo "[2/5] 更新K8s配置..."
kubectl patch configmap shop-config -n shop \
    --type merge \
    -p '{"data":{"database-host":"backup-mysql.shop.svc.cluster.local"}}'
echo "✅ ConfigMap已更新"

# Step 3:滚动重启应用(加载新配置)
echo "[3/5] 重启应用..."
kubectl rollout restart deployment/shop-order-service -n shop
kubectl rollout status deployment/shop-order-service -n shop --timeout=5m
echo "✅ 应用已重启"

# Step 4:更新DNS(切换流量)
echo "[4/5] 更新DNS..."
# 调用云厂商API更新DNS记录
aliyun alidns UpdateDomainRecord \
    --RecordId 123456 \
    --RR api \
    --Type A \
    --Value 47.100.200.100   # 备用数据中心IP
echo "✅ DNS已更新(生效需要5-10分钟)"

# Step 5:验证服务可用性
echo "[5/5] 验证服务..."
sleep 60   # 等待DNS生效
for i in {1..10}; do
    if curl -f -s http://api.example.com/health > /dev/null; then
        echo "✅ 服务验证成功"
        break
    fi
    echo "等待服务就绪... ($i/10)"
    sleep 10
done

echo "=========================================="
echo "  灾难恢复完成!"
echo "=========================================="

二十五、团队协作与DevOps文化

25.1 DevOps团队组织结构

传统组织 vs DevOps组织:

传统瀑布式:
开发团队 → 测试团队 → 运维团队
(各自为政,交接环节多,责任不清)

DevOps全功能团队:
┌─────────────────────────────────────────────────────────┐
│              产品特性团队(Feature Team)                 │
│  ┌──────────┬──────────┬──────────┬──────────┐          │
│  │ 后端开发  │ 前端开发  │ 测试     │ 运维     │          │
│  │ (2-3人)  │ (1-2人)  │ (1人)    │ (1人)    │          │
│  └──────────┴──────────┴──────────┴──────────┘          │
│  共同负责:需求 → 开发 → 测试 → 部署 → 运维              │
└─────────────────────────────────────────────────────────┘

优势:
- 端到端负责,减少交接
- 快速反馈,问题及时解决
- 团队自主性强,效率高

25.2 代码审查最佳实践

代码审查检查清单:

功能性:
□ 代码实现了需求的功能
□ 边界条件处理正确
□ 错误处理完善

可读性:
□ 命名清晰,见名知意
□ 逻辑清晰,易于理解
□ 注释恰当,解释了"为什么"

性能:
□ 无明显性能问题(N+1查询、死循环)
□ 数据库查询有索引
□ 大数据量处理考虑分页

安全:
□ 无SQL注入风险
□ 敏感信息不硬编码
□ 权限校验完善

测试:
□ 有单元测试覆盖核心逻辑
□ 测试用例充分

DevOps:
□ 日志记录充分
□ 监控指标完善
□ 配置外部化
// 代码审查示例:优化前 vs 优化后

// ❌ 审查意见:存在N+1查询问题
public List<OrderVO> listOrders() {
    List<Order> orders = orderDao.findAll();
    return orders.stream().map(order -> {
        OrderVO vo = new OrderVO(order);
        // 每个订单都查一次用户,100个订单 = 101次SQL
        User user = userDao.findById(order.getUserId());
        vo.setUserName(user.getName());
        return vo;
    }).collect(Collectors.toList());
}

// ✅ 修改后:批量查询,性能提升100倍
public List<OrderVO> listOrders() {
    List<Order> orders = orderDao.findAll();
    
    // 收集所有userId,批量查询
    Set<Long> userIds = orders.stream()
        .map(Order::getUserId)
        .collect(Collectors.toSet());
    Map<Long, User> userMap = userDao.findByIds(userIds).stream()
        .collect(Collectors.toMap(User::getId, u -> u));
    
    // 组装结果
    return orders.stream().map(order -> {
        OrderVO vo = new OrderVO(order);
        User user = userMap.get(order.getUserId());
        vo.setUserName(user != null ? user.getName() : "未知用户");
        return vo;
    }).collect(Collectors.toList());
}

25.3 事故复盘(Postmortem)

事故复盘模板:

1. 事故概述
   - 发生时间:2024-01-15 10:30 - 11:15
   - 影响范围:订单服务不可用,影响100%用户
   - 严重程度:P0(最高级)
   - 根本原因:数据库连接池耗尽

2. 时间线
   10:30 - 监控告警:订单服务错误率100%
   10:32 - 值班人员确认,开始排查
   10:35 - 发现数据库连接池满,所有请求等待连接超时
   10:40 - 紧急重启应用,临时恢复
   10:45 - 分析日志,发现慢查询导致连接长时间占用
   11:00 - 优化慢查询SQL,添加索引
   11:15 - 服务完全恢复,监控正常

3. 根本原因分析(5 Whys)
   Q1: 为什么服务不可用?
   A1: 数据库连接池耗尽

   Q2: 为什么连接池耗尽?
   A2: 有慢查询占用连接时间过长

   Q3: 为什么有慢查询?
   A3: 订单表缺少create_time索引

   Q4: 为什么缺少索引?
   A4: 新增查询条件时未评审SQL性能

   Q5: 为什么未评审?
   A5: 缺少SQL审查流程

4. 改进措施
   短期(1周内):
   - [已完成] 为订单表添加create_time索引
   - [进行中] 增大连接池上限(20 → 50)
   - [进行中] 添加慢查询告警(>1秒)

   长期(1个月内):
   - [计划中] 建立SQL审查流程,所有SQL需DBA审核
   - [计划中] 引入数据库连接池监控,提前预警
   - [计划中] 定期进行压测,发现性能瓶颈

5. 经验教训
   - 数据库变更必须评估性能影响
   - 监控要覆盖连接池等关键资源
   - 事故演练要包含数据库故障场景

二十六、DevOps工具链终极总结

26.1 从零到一的DevOps演进路径

DevOps成熟度演进路线图:

阶段1:手动部署(1-2周)
目标:建立基础CI
工具:GitHub + GitHub Actions
产出:代码提交自动触发构建和测试

阶段2:容器化(2-3周)
目标:应用容器化,本地环境标准化
工具:Docker + Docker Compose
产出:一键启动完整开发环境

阶段3:自动化部署(3-4周)
目标:实现CD,自动部署到测试环境
工具:K8s + Helm
产出:代码合并自动部署到测试环境

阶段4:监控可观测(2-3周)
目标:建立监控体系,快速发现问题
工具:Prometheus + Grafana + ELK
产出:完整的监控大屏和告警规则

阶段5:生产部署(2-3周)
目标:安全地部署到生产环境
工具:蓝绿部署 + 金丝雀发布
产出:零停机部署,快速回滚能力

阶段6:持续优化(持续进行)
目标:提升系统稳定性和效率
工具:混沌工程 + 全链路压测
产出:系统容量规划,故障自愈能力

26.2 工具选型决策矩阵

根据团队规模和需求选择工具:

┌─────────────┬──────────────┬──────────────┬──────────────┐
│ 团队规模    │ 小团队(5人内) │ 中型(5-20人)  │ 大型(20+人)   │
├─────────────┼──────────────┼──────────────┼──────────────┤
│ 代码托管    │ GitHub       │ GitLab       │ GitLab EE    │
│ CI/CD       │ GitHub Actions│ GitLab CI   │ Jenkins      │
│ 容器编排    │ Docker Compose│ K8s         │ K8s + Istio  │
│ 镜像仓库    │ Docker Hub   │ Harbor       │ Harbor HA    │
│ 配置管理    │ ConfigMap    │ ConfigMap   │ Vault        │
│ 监控        │ Prometheus   │ Prometheus  │ Prometheus HA│
│ 日志        │ 文件日志      │ ELK Stack   │ ELK + Kafka  │
│ 链路追踪    │ 不需要        │ SkyWalking  │ SkyWalking   │
└─────────────┴──────────────┴──────────────┴──────────────┘

26.3 DevOps关键指标(DORA Metrics)

DORA四大关键指标(衡量DevOps成熟度):

1. 部署频率(Deployment Frequency)
   精英团队:按需部署(每天多次)
   高效团队:每周一次到每月一次
   中等团队:每月一次到每半年一次
   低效团队:少于每半年一次

2. 变更前置时间(Lead Time for Changes)
   从代码提交到生产部署的时间
   精英团队:< 1小时
   高效团队:1天 - 1周
   中等团队:1周 - 1个月
   低效团队:> 1个月

3. 变更失败率(Change Failure Rate)
   导致生产故障的部署比例
   精英团队:0-15%
   高效团队:16-30%
   中等团队:31-45%
   低效团队:> 45%

4. 服务恢复时间(Time to Restore Service)
   从故障发生到恢复的时间
   精英团队:< 1小时
   高效团队:< 1天
   中等团队:1天 - 1周
   低效团队:> 1周
# 在CI/CD中收集DORA指标
# .github/workflows/metrics.yml

name: Collect DORA Metrics

on:
  push:
    branches: [main]
  deployment_status:

jobs:
  metrics:
    runs-on: ubuntu-latest
    steps:
      - name: Record Deployment
        run: |
          # 记录部署时间
          curl -X POST https://metrics.example.com/api/deployments \
            -H "Content-Type: application/json" \
            -d '{
              "service": "shop-order-service",
              "commit": "${{ github.sha }}",
              "deployed_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
              "lead_time_seconds": ${{ github.event.deployment.created_at - github.event.head_commit.timestamp }}
            }'

      - name: Check Deployment Status
        if: github.event.deployment_status.state == 'failure'
        run: |
          # 记录部署失败
          curl -X POST https://metrics.example.com/api/failures \
            -H "Content-Type: application/json" \
            -d '{
              "service": "shop-order-service",
              "commit": "${{ github.sha }}",
              "failed_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
            }'

最终总结:成为DevOps工程师的学习路径

学习路线图

第1个月:基础能力
□ 熟练使用Linux命令行
□ 掌握Git版本控制
□ 学习Docker基础(镜像、容器、网络、存储)
□ 编写Dockerfile和docker-compose.yml

第2个月:容器编排
□ 学习K8s核心概念(Pod、Deployment、Service)
□ 掌握kubectl常用命令
□ 编写K8s YAML配置文件
□ 理解K8s网络和存储

第3个月:CI/CD
□ 搭建GitHub Actions流水线
□ 学习Jenkins Pipeline
□ 掌握Helm包管理
□ 实现自动化部署

第4个月:监控可观测
□ 搭建Prometheus + Grafana
□ 配置告警规则
□ 学习PromQL查询语言
□ 集成ELK日志系统

第5个月:高级特性
□ 学习服务网格(Istio)
□ 掌握蓝绿部署、金丝雀发布
□ 学习混沌工程
□ 性能压测和容量规划

第6个月:生产实践
□ 参与真实项目的DevOps实践
□ 处理生产环境故障
□ 优化CI/CD流水线
□ 编写运维文档和SOP

推荐学习资源

书籍:
1. 《凤凰项目》- DevOps理念入门
2. 《持续交付》- CI/CD经典著作
3. 《Kubernetes权威指南》- K8s深入学习
4. 《SRE:Google运维解密》- 大厂运维实践

在线课程:
1. Kubernetes官方文档和教程
2. Docker官方文档
3. Prometheus官方文档
4. CNCF(云原生计算基金会)项目

实践平台:
1. Katacoda - 在线K8s实验环境
2. Play with Docker - 在线Docker环境
3. GitHub - 开源项目学习
4. 自己搭建本地K8s集群(Minikube/Kind)

认证考试:
1. CKA(Certified Kubernetes Administrator)
2. CKAD(Certified Kubernetes Application Developer)
3. Docker Certified Associate

面试准备建议

技术深度:
- 不要只会用工具,要理解原理
- Docker:镜像分层、网络模式、存储驱动
- K8s:调度原理、网络模型、控制器模式
- CI/CD:流水线设计、制品管理、环境晋级

实战经验:
- 准备3-5个DevOps实践案例
- 每个案例包含:背景、方案、结果、收获
- 量化成果:部署时间缩短X%,故障率降低Y%

问题解决:
- 准备故障排查案例
- 展示排查思路和解决过程
- 体现主动思考和持续改进

软技能:
- 跨团队协作能力
- 文档编写能力
- 自动化思维
- 持续学习能力

恭喜你完成了《企业级生产工具链实战》的学习!这份文档涵盖了从Docker基础到K8s生产运维、从CI/CD到监控告警、从故障排查到性能优化的完整DevOps知识体系。

记住:DevOps不仅是工具的使用,更是一种文化和思维方式。持续学习、自动化一切、快速反馈、持续改进,这些理念将伴随你的整个职业生涯。

祝你在DevOps的道路上越走越远,成为企业不可或缺的技术骨干!


二十三、云原生应用开发最佳实践

23.1 十二要素应用(12-Factor App)

云原生应用设计原则(12-Factor):

1. 代码库(Codebase)
   一个代码库,多个部署环境
   ✅ Git仓库统一管理,通过分支/标签区分环境

2. 依赖(Dependencies)
   显式声明依赖,不依赖系统工具
   ✅ Maven/Gradle管理依赖,Docker镜像包含所有依赖

3. 配置(Config)
   配置与代码分离,通过环境变量注入
   ✅ K8s ConfigMap/Secret,不要硬编码配置

4. 后端服务(Backing Services)
   数据库、缓存等作为附加资源,可随时替换
   ✅ 通过环境变量配置连接信息,不绑定特定实例

5. 构建、发布、运行(Build, Release, Run)
   严格分离构建和运行阶段
   ✅ CI构建镜像 → CD发布到K8s → 运行时不可变

6. 进程(Processes)
   应用作为无状态进程运行
   ✅ 不在本地存储会话,使用Redis等外部存储

7. 端口绑定(Port Binding)
   通过端口对外提供服务
   ✅ Spring Boot内嵌Tomcat,监听8080端口

8. 并发(Concurrency)
   通过进程模型扩展
   ✅ K8s HPA水平扩展Pod数量

9. 易处理(Disposability)
   快速启动和优雅关闭
   ✅ 健康检查 + PreStop Hook

10. 开发环境与生产环境等价(Dev/Prod Parity)
    开发、测试、生产环境尽量一致
    ✅ Docker保证环境一致性

11. 日志(Logs)
    日志作为事件流输出到stdout
    ✅ 不写本地文件,由日志收集器统一采集

12. 管理进程(Admin Processes)
    管理任务作为一次性进程运行
    ✅ K8s Job/CronJob执行数据迁移、定时任务

23.2 Spring Boot云原生配置

// application.yml - 云原生配置示例

spring:
  application:
    name: shop-order-service

  # 配置从环境变量读取(12-Factor原则)
  datasource:
    url: ${DB_URL:jdbc:mysql://localhost:3306/shop}
    username: ${DB_USERNAME:root}
    password: ${DB_PASSWORD:password}
    hikari:
      maximum-pool-size: ${DB_POOL_SIZE:20}
      minimum-idle: ${DB_POOL_MIN_IDLE:5}

  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}

# Actuator健康检查(K8s探针使用)
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
  endpoint:
    health:
      probes:
        enabled: true   # 开启liveness和readiness端点
      show-details: always
  health:
    livenessState:
      enabled: true
    readinessState:
      enabled: true

# 优雅关闭(K8s PreStop Hook配合)
server:
  shutdown: graceful   # 优雅关闭
  port: ${SERVER_PORT:8080}

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s   # 最多等待30
// 优雅关闭实现:确保请求处理完再退出

@Component
@Slf4j
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        log.info("收到关闭信号,开始优雅关闭...");

        // 1. 停止接收新请求(通过readiness探针实现)
        log.info("停止接收新请求");

        // 2. 等待现有请求处理完成
        log.info("等待现有请求处理完成...");
        try {
            Thread.sleep(5000);  // 等待5秒,让现有请求处理完
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        // 3. 关闭线程池
        log.info("关闭线程池...");
        taskExecutor.shutdown();
        try {
            if (!taskExecutor.getThreadPoolExecutor().awaitTermination(20, TimeUnit.SECONDS)) {
                log.warn("线程池未能在20秒内关闭,强制关闭");
                taskExecutor.getThreadPoolExecutor().shutdownNow();
            }
        } catch (InterruptedException e) {
            taskExecutor.getThreadPoolExecutor().shutdownNow();
            Thread.currentThread().interrupt();
        }

        log.info("优雅关闭完成");
    }
}
# K8s Deployment配合优雅关闭
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          lifecycle:
            preStop:
              exec:
                # PreStop Hook:K8s发送SIGTERM前先执行
                # 等待10秒,让负载均衡器摘除此Pod
                command: ["/bin/sh", "-c", "sleep 10"]
          
          # 健康检查
          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摘除
            failureThreshold: 1

      # 优雅关闭超时时间(必须大于preStop + shutdown timeout)
      terminationGracePeriodSeconds: 60

二十四、大规模集群管理实战

24.1 多集群管理策略

多集群场景:
- 多地域部署(北京、上海、广州)
- 多环境隔离(dev、staging、prod)
- 灾备集群(主集群故障时切换)

多集群管理工具对比:

┌──────────────┬────────────────────────────────────────┐
│ 工具         │ 特点                                    │
├──────────────┼────────────────────────────────────────┤
│ kubectl      │ 手动切换context,适合少量集群            │
│ kubectx      │ 快速切换context,命令行友好              │
│ Rancher      │ Web UI统一管理,适合运维团队             │
│ ArgoCD       │ GitOps多集群部署,声明式管理             │
│ Flux         │ GitOps,更轻量级                        │
└──────────────┴────────────────────────────────────────┘
# kubectl多集群管理

# 查看所有集群配置
kubectl config get-contexts

# 切换集群
kubectl config use-context prod-beijing

# 同时操作多个集群(使用kubectx + kubens)
# 安装kubectx
brew install kubectx

# 快速切换集群
kubectx prod-beijing
kubectx prod-shanghai

# 快速切换命名空间
kubens shop

# 在所有集群执行命令(自定义脚本)
for ctx in prod-beijing prod-shanghai prod-guangzhou; do
    echo "=== $ctx ==="
    kubectl --context=$ctx get pods -n shop
done

24.2 大规模节点管理

# 节点管理常用操作

# 查看节点资源使用情况
kubectl top nodes

# 标记节点(用于调度)
kubectl label nodes node-1 disktype=ssd
kubectl label nodes node-2 zone=beijing

# 节点污点(Taint):阻止Pod调度到特定节点
# 场景:节点维护、专用节点(GPU节点只跑AI任务)
kubectl taint nodes node-1 maintenance=true:NoSchedule

# 容忍(Toleration):允许Pod调度到有污点的节点
# 在Deployment中配置:
spec:
  template:
    spec:
      tolerations:
        - key: "maintenance"
          operator: "Equal"
          value: "true"
          effect: "NoSchedule"

# 驱逐节点上的所有Pod(节点维护前)
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data

# 恢复节点调度
kubectl uncordon node-1

# 节点亲和性:指定Pod调度到特定节点
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disktype
                operator: In
                values: ["ssd"]

24.3 资源配额与限制

# ResourceQuota:限制命名空间的总资源使用
apiVersion: v1
kind: ResourceQuota
metadata:
  name: shop-quota
  namespace: shop
spec:
  hard:
    requests.cpu: "50"           # 最多申请50核CPU
    requests.memory: 100Gi       # 最多申请100GB内存
    limits.cpu: "100"            # 最多使用100核CPU
    limits.memory: 200Gi         # 最多使用200GB内存
    persistentvolumeclaims: "10" # 最多10个PVC
    pods: "100"                  # 最多100个Pod
    services.loadbalancers: "5"  # 最多5个LoadBalancer

---
# LimitRange:限制单个Pod/Container的资源范围
apiVersion: v1
kind: LimitRange
metadata:
  name: shop-limit-range
  namespace: shop
spec:
  limits:
    # Container级别限制
    - type: Container
      max:
        cpu: "4"          # 单容器最多4核
        memory: 8Gi       # 单容器最多8GB
      min:
        cpu: "100m"       # 单容器最少100m
        memory: 128Mi     # 单容器最少128MB
      default:
        cpu: "500m"       # 默认limits
        memory: 512Mi
      defaultRequest:
        cpu: "200m"       # 默认requests
        memory: 256Mi

    # Pod级别限制
    - type: Pod
      max:
        cpu: "8"
        memory: 16Gi

二十五、DevOps文化与团队协作

25.1 DevOps文化核心理念

DevOps不仅是工具,更是文化和理念:

CALMS模型:

C - Culture(文化)
  - 打破Dev和Ops的壁垒
  - 共同对产品质量负责
  - 失败是学习机会,不是追责

A - Automation(自动化)
  - 自动化一切可自动化的流程
  - 减少人为错误
  - 提高效率和一致性

L - Lean(精益)
  - 小批量、高频率发布
  - 快速反馈,持续改进
  - 消除浪费(等待、返工)

M - Measurement(度量)
  - 用数据驱动决策
  - 关键指标:部署频率、变更前置时间、MTTR、变更失败率
  - 持续监控和优化

S - Sharing(分享)
  - 知识共享,避免信息孤岛
  - 复盘总结,沉淀最佳实践
  - 跨团队协作

25.2 DevOps关键指标(DORA Metrics)

DORA四大关键指标(衡量DevOps成熟度):

1. 部署频率(Deployment Frequency)
   精英团队:每天多次
   高水平:每周一次到每月一次
   中等水平:每月一次到每半年一次
   低水平:少于每半年一次

2. 变更前置时间(Lead Time for Changes)
   从代码提交到生产部署的时间
   精英团队:少于1小时
   高水平:1天到1周
   中等水平:1周到1个月
   低水平:1个月到6个月

3. 平均恢复时间(MTTR - Mean Time To Recovery)
   服务故障后恢复的平均时间
   精英团队:少于1小时
   高水平:少于1天
   中等水平:1天到1周
   低水平:1周到1个月

4. 变更失败率(Change Failure Rate)
   导致服务降级或需要回滚的变更比例
   精英团队:0-15%
   高水平:16-30%
   中等水平:31-45%
   低水平:46-60%

25.3 故障复盘模板(Postmortem)

# 故障复盘报告模板

## 基本信息
- 故障时间:2024-01-15 10:30 - 11:15(45分钟)
- 影响范围:订单服务不可用,影响100%用户
- 严重级别:P0(服务完全不可用)
- 负责人:张三

## 故障现象
- 用户反馈:下单失败,提示"系统繁忙"
- 监控告警:订单服务错误率100%,所有Pod处于CrashLoopBackOff状态
- 业务影响:预计损失订单约500单,金额约10万元

## 根因分析
直接原因:
- 数据库连接池耗尽,所有请求超时

根本原因:
- 凌晨发布时,误将数据库连接池配置从50改为5
- 配置变更未经过Code Review
- 缺少配置变更的自动化测试

## 时间线
- 10:30 - 监控告警:订单服务错误率突增
- 10:32 - 值班人员确认故障,拉群通知
- 10:35 - 查看日志,发现大量数据库连接超时
- 10:40 - 检查数据库,连接数正常,怀疑应用配置问题
- 10:45 - 对比配置,发现连接池配置异常
- 10:50 - 决定回滚到上一版本
- 11:00 - 回滚完成,服务恢复
- 11:15 - 确认服务稳定,解除告警

## 做得好的地方
- 监控告警及时,2分钟内发现问题
- 团队响应迅速,5分钟内拉群协作
- 决策果断,15分钟内决定回滚
- 回滚流程顺畅,10分钟完成回滚

## 需要改进的地方
- 配置变更未经Review,缺少卡点
- 缺少配置合理性校验(连接池不应小于10)
- 缺少灰度发布,一次性全量发布风险大
- 缺少自动化回滚机制

## 行动计划
| 行动项 | 负责人 | 截止日期 | 状态 |
|-------|-------|---------|------|
| 配置变更强制Code Review | 张三 | 2024-01-20 | 进行中 |
| 添加配置合理性校验 | 李四 | 2024-01-22 | 待开始 |
| 实施金丝雀发布策略 | 王五 | 2024-01-25 | 待开始 |
| 配置监控告警(连接池使用率) | 赵六 | 2024-01-18 | 已完成 |

## 经验教训
1. 配置即代码,必须经过Review和测试
2. 关键配置要有合理性校验
3. 生产变更必须灰度发布,不能一次性全量
4. 完善的监控是快速定位问题的关键

最终总结

通过本文档的学习,你已经掌握了企业级生产工具链的完整体系:

从容器化(Docker多阶段构建、镜像优化)到编排(Kubernetes资源管理、故障排查),从CI/CD流水线(GitHub Actions、Jenkins、GitOps)到可观测性(Prometheus监控、ELK日志、SkyWalking链路追踪),从安全加固(容器安全、Vault密钥管理)到性能优化(压测、JVM调优、容量规划)。

这些技能不仅是大厂面试的必考点,更是实际工作中每天都会用到的核心能力。建议你:

  1. 动手实践:在本地搭建完整的DevOps环境,亲自操作每个工具
  2. 模拟故障:通过Chaos Engineering主动制造故障,锻炼排查能力
  3. 持续学习:关注云原生社区(CNCF),了解最新技术趋势
  4. 总结沉淀:将实践经验整理成文档,形成自己的知识库

记住:DevOps不是一蹴而就的,而是持续改进的过程。从自动化构建开始,逐步完善监控、告警、日志、链路追踪,最终形成完整的可观测性体系。

祝你在DevOps的道路上越走越远,早日成为大厂认可的全栈工程师!

Logo

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

更多推荐