DevOps 面试题大全(四·二):CI/CD 进阶实战 17 题详解

前言

CI/CD 面试题第二篇(18-34 题),涵盖进阶实战内容,包括多环境部署、蓝绿发布、金丝雀发布、凭据管理、SonarQube 集成、构建优化等。


二、进阶实战(18-34 题)

18. 如何设计多环境部署流程?请给出完整方案

【难度】 ⭐⭐⭐    【高频指数】 ⭐⭐⭐⭐⭐    【建议掌握时间】 60 分钟


【核心答案】

多环境架构:开发 → 测试 → 预发 → 生产,不同环境使用不同配置,生产部署需要审批。

核心原则:环境隔离、配置分离、自动化部署、生产审批。


【详细解析】

1. 多环境架构设计

┌─────────────────────────────────────────────────────────────────────┐
│                        多环境部署架构                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  开发环境        测试环境        预发环境        生产环境            │
│  (Dev)          (Test)         (Staging)       (Prod)              │
│     │              │               │              │                 │
│     ▼              ▼               ▼              ▼                 │
│  ┌─────┐      ┌─────┐       ┌─────┐       ┌─────┐                 │
│  │自动  │      │自动  │       │自动  │       │手动  │                 │
│  │部署  │      │部署  │       │部署  │       │审批  │                 │
│  └─────┘      └─────┘       └─────┘       └─────┘                 │
│     │              │               │              │                 │
│  develop       测试通过         main 分支      release            │
│  分支          的分支           合并后         标签                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

环境说明

环境 用途 触发条件 审批要求
开发环境 开发自测,功能验证 develop 分支提交 无需审批
测试环境 测试团队验证,集成测试 测试分支合并 无需审批
预发环境 生产环境模拟,回归测试 main 分支合并 无需审批
生产环境 真实用户访问 release 标签 需要审批

2. 环境隔离策略

方案一:命名空间隔离(推荐起点)

# Kubernetes Namespace 隔离
kubectl create namespace dev
kubectl create namespace test
kubectl create namespace staging
kubectl create namespace prod

# 部署到不同环境
kubectl apply -f deployment.yaml -n dev
kubectl apply -f deployment.yaml -n test
  • 优点:简单,成本低,适合中小规模
  • 缺点:隔离性有限,单个集群故障影响所有环境

方案二:集群隔离(推荐生产)

# 不同环境使用不同 K8s 集群
# dev/test/staging: 共享集群(不同 namespace)
# prod: 独立集群

# kubeconfig 配置
export KUBECONFIG_DEV=~/.kube/config-dev
export KUBECONFIG_TEST=~/.kube/config-test
export KUBECONFIG_PROD=~/.kube/config-prod
  • 优点:隔离性好,生产环境独立,安全性高
  • 缺点:成本高,管理复杂

方案三:云账号隔离(大型企业)

  • 不同环境使用不同云账号或项目
  • 完全隔离,安全性最高
  • 成本和管理复杂度最高

3. 配置管理策略

方案一:环境变量

# Jenkins Pipeline
environment {
    DB_HOST = params.ENV == 'prod' ? 'prod-db.example.com' : 'dev-db.example.com'
    DB_PORT = '5432'
    LOG_LEVEL = params.ENV == 'prod' ? 'INFO' : 'DEBUG'
}

方案二:ConfigMap/Secret

# 不同环境的 ConfigMap
kubectl create configmap app-config-dev --from-env-file=config.dev.env -n dev
kubectl create configmap app-config-prod --from-env-file=config.prod.env -n prod

# Deployment 引用
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        envFrom:
        - configMapRef:
            name: app-config

方案三:Helm Values

# values-dev.yaml
replicaCount: 1
resources:
  limits:
    cpu: 500m
    memory: 512Mi

# values-prod.yaml
replicaCount: 3
resources:
  limits:
    cpu: 2000m
    memory: 4Gi

# 部署命令
helm upgrade --install myapp ./chart -f values-dev.yaml -n dev
helm upgrade --install myapp ./chart -f values-prod.yaml -n prod

4. 完整 Jenkins Pipeline 示例

pipeline {
    agent any
    
    parameters {
        choice(name: 'DEPLOY_ENV', choices: ['dev', 'test', 'staging', 'prod'], description: '部署环境')
        booleanParam(name: 'SKIP_TEST', defaultValue: false, description: '跳过测试')
    }
    
    environment {
        DOCKER_REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
        IMAGE_NAME = 'mycompany/myapp'
        KUBECONFIG_DEV = credentials('kubeconfig-dev')
        KUBECONFIG_TEST = credentials('kubeconfig-test')
        KUBECONFIG_STAGING = credentials('kubeconfig-staging')
        KUBECONFIG_PROD = credentials('kubeconfig-prod')
    }
    
    stages {
        stage('拉取代码') {
            steps {
                checkout scm
            }
        }
        
        stage('构建镜像') {
            steps {
                script {
                    docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}")
                }
            }
        }
        
        stage('运行测试') {
            when {
                expression { return !params.SKIP_TEST }
            }
            steps {
                script {
                    sh 'docker run ${IMAGE_NAME}:${BUILD_NUMBER} npm test'
                }
            }
        }
        
        stage('推送镜像') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
                        docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}").push()
                    }
                }
            }
        }
        
        stage('部署到开发环境') {
            when {
                expression { return params.DEPLOY_ENV == 'dev' }
            }
            steps {
                script {
                    deployToEnvironment('dev', KUBECONFIG_DEV)
                }
            }
        }
        
        stage('部署到测试环境') {
            when {
                expression { return params.DEPLOY_ENV == 'test' }
            }
            steps {
                script {
                    deployToEnvironment('test', KUBECONFIG_TEST)
                }
            }
        }
        
        stage('部署到预发环境') {
            when {
                expression { return params.DEPLOY_ENV == 'staging' }
            }
            steps {
                script {
                    deployToEnvironment('staging', KUBECONFIG_STAGING)
                }
            }
        }
        
        stage('部署到生产环境') {
            when {
                expression { return params.DEPLOY_ENV == 'prod' }
            }
            input {
                message '确认部署到生产环境?'
                ok '确认部署'
                submitter 'admin,release-manager'
            }
            steps {
                script {
                    deployToEnvironment('prod', KUBECONFIG_PROD)
                }
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            sendNotification('SUCCESS')
        }
        failure {
            sendNotification('FAILURE')
        }
    }
}

// 部署函数
def deployToEnvironment(String env, String kubeconfig) {
    echo "开始部署到 ${env} 环境"
    
    withKubeConfig([credentialsId: kubeconfig]) {
        sh """
            kubectl set image deployment/myapp \\
                myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} \\
                -n ${env}
            
            kubectl rollout status deployment/myapp -n ${env} --timeout=300s
            
            // 健康检查
            kubectl get pods -n ${env} -l app=myapp
            
            // 验证服务
            curl -f http://myapp.${env}.internal/health || exit 1
        """
    }
    
    echo "${env} 环境部署完成"
}

// 通知函数
def sendNotification(String status) {
    // 发送钉钉/企业微信通知
}

【实战场景】

某 SaaS 公司多环境部署实践

背景:50 人开发团队,20+ 微服务,需要支持多环境部署

实施方案

  • 开发环境:开发提交 develop 分支后自动部署,用于开发自测
  • 测试环境:测试分支合并后自动部署,用于测试团队验证
  • 预发环境:main 分支合并后自动部署,用于回归测试
  • 生产环境:创建 release 标签,手动触发,需要技术负责人审批

实施效果

  • ✅ 部署频率从每周 1 次提升到每天 10+ 次
  • ✅ 部署失败率从 20% 降低到 5%
  • ✅ 平均部署时间从 30 分钟降低到 5 分钟
  • ✅ 团队满意度大幅提升

【面试官考察点】

  1. 架构设计:能否设计合理的多环境架构
  2. 实践经验:是否有实际的多环境部署经验
  3. 安全意识:是否理解生产环境需要特殊保护
  4. 工具熟悉度:是否熟悉 K8s、Helm 等部署工具

💡 加分回答

  • 提到环境配置的秘密信息管理(Vault、Secrets Manager)
  • 说明如何实现环境间的一致性(IaC、GitOps)
  • 分享处理数据库迁移的经验
  • 提到蓝绿部署、金丝雀发布等高级策略

【常见误区】

  • 误区 1:所有环境使用相同配置
    • 正解:不同环境应该有独立的配置(数据库、API 密钥等)
  • 误区 2:生产环境不需要测试
    • 正解:生产环境部署前必须经过充分测试
  • 误区 3:手动部署更可靠
    • 正解:自动化部署减少人为错误,更可靠
  • 误区 4:环境越多越好
    • 正解:环境数量根据团队规模和需求平衡,避免过度复杂

【延伸知识】

  • GitOps:使用 Git 作为环境配置的唯一事实来源
  • ArgoCD:声明式 GitOps 持续交付工具
  • Feature Flag:特性开关,控制功能发布
  • Database Migration:数据库迁移工具(Flyway、Liquibase)


19. 蓝绿部署的原理是什么?如何实现?

【难度】 ⭐⭐⭐    【高频指数】 ⭐⭐⭐⭐    【建议掌握时间】 45 分钟


【核心答案】

蓝绿部署是一种零停机部署策略:

  • 维护两套完全相同的环境:蓝环境(当前版本)和绿环境(新版本)
  • 新版本部署到空闲环境(绿)
  • 测试验证通过后,切换流量到绿环境
  • 出现问题时,快速切回蓝环境

【详细解析】

1. 蓝绿部署工作流程

┌─────────────────────────────────────────────────────────────────────┐
│                        蓝绿部署流程                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  步骤 1: 蓝环境运行 v1.0(当前生产)                                 │
│  ┌─────────────┐                                                   │
│  │ 蓝环境      │  ← 所有流量                                       │
│  │ v1.0        │                                                   │
│  └─────────────┘                                                   │
│                                                                     │
│  步骤 2: 部署 v2.0 到绿环境                                         │
│  ┌─────────────┐      ┌─────────────┐                             │
│  │ 蓝环境      │      │ 绿环境      │  ← 测试验证                   │
│  │ v1.0        │      │ v2.0        │                             │
│  └─────────────┘      └─────────────┘                             │
│                                                                     │
│  步骤 3: 切换流量到绿环境                                           │
│  ┌─────────────┐      ┌─────────────┐                             │
│  │ 蓝环境      │      │ 绿环境      │  ← 所有流量                   │
│  │ v1.0        │      │ v2.0        │                             │
│  └─────────────┘      └─────────────┘                             │
│                                                                     │
│  步骤 4: 绿环境成为新的生产,蓝环境空闲(保留快速回滚)               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2. Kubernetes 实现

步骤 1:创建两个 Deployment

# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
      - name: myapp
        image: myapp:v1.0
        ports:
        - containerPort: 8080

---
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
      - name: myapp
        image: myapp:v2.0
        ports:
        - containerPort: 8080

步骤 2:创建 Service,初始指向蓝环境

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
    version: blue  # 初始指向蓝环境
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer

步骤 3:部署并测试绿环境

# 部署绿环境
kubectl apply -f green-deployment.yaml

# 测试绿环境(不通过 Service,直接访问 Pod)
kubectl get pods -l version=green
kubectl port-forward pod/myapp-green-xxx 8081:8080
curl http://localhost:8081/health

步骤 4:切换流量到绿环境

# 切换 Service 指向
kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'

# 验证切换
kubectl get service myapp-service -o yaml

# 测试新环境
curl http://myapp-service.example.com/health

步骤 5:缩容蓝环境(保留快速回滚能力)

# 缩容蓝环境(不删除)
kubectl scale deployment myapp-blue --replicas=0

# 如果需要回滚,只需:
# 1. 切换 Service 回蓝环境
# 2. 扩容蓝环境

3. Jenkins Pipeline 实现

pipeline {
    agent any
    
    environment {
        CURRENT_VERSION = 'blue'
        NEW_VERSION = 'green'
        IMAGE = 'myapp'
    }
    
    stages {
        stage('部署新版本到空闲环境') {
            steps {
                script {
                    // 部署新版本
                    sh """
                        kubectl set image deployment/myapp-\${NEW_VERSION} \\
                            myapp=\${IMAGE}:\${BUILD_NUMBER}
                        kubectl rollout status deployment/myapp-\${NEW_VERSION}
                    """
                    
                    // 等待 Pod 就绪
                    sh """
                        kubectl wait --for=condition=ready pod \\
                            -l version=\${NEW_VERSION} \\
                            --timeout=120s
                    """
                }
            }
        }
        
        stage('健康检查') {
            steps {
                script {
                    // 多次健康检查
                    def maxRetries = 5
                    def success = false
                    
                    for (int i = 0; i < maxRetries; i++) {
                        def status = sh(
                            script: 'curl -f http://myapp-green/health',
                            returnStatus: true
                        )
                        
                        if (status == 0) {
                            echo '健康检查通过'
                            success = true
                            break
                        }
                        
                        if (i == maxRetries - 1) {
                            error '健康检查失败,回滚'
                        }
                        
                        sleep 10
                    }
                }
            }
        }
        
        stage('切换流量') {
            input {
                message '确认切换流量到新版本?'
                ok '切换'
            }
            steps {
                script {
                    // 切换 Service 指向
                    sh """
                        kubectl patch service myapp-service \\
                            -p '{\\"spec\\":{\\"selector\\":{\\"version\\":\\"${NEW_VERSION}\\"}}}'
                    """
                    
                    // 等待切换生效
                    sleep 30
                    
                    // 验证新环境
                    sh 'curl -f http://myapp-service/health'
                    
                    // 记录当前版本
                    CURRENT_VERSION = NEW_VERSION
                }
            }
        }
        
        stage('清理旧环境') {
            steps {
                script {
                    // 缩容旧环境(不删除,保留快速回滚能力)
                    def oldVersion = CURRENT_VERSION == 'blue' ? 'green' : 'blue'
                    sh "kubectl scale deployment/myapp-\${oldVersion} --replicas=0"
                }
            }
        }
    }
    
    post {
        failure {
            // 自动回滚
            script {
                echo '部署失败,执行回滚'
                sh """
                    kubectl patch service myapp-service \\
                        -p '{\\"spec\\":{\\"selector\\":{\\"version\\":\\"blue\\"}}}'
                    kubectl scale deployment/myapp-blue --replicas=3
                """
            }
        }
    }
}

4. 优缺点对比

优点 缺点
✅ 零停机部署 ❌ 资源成本翻倍(需要两套环境)
✅ 快速回滚(秒级) ❌ 数据库迁移需要额外处理
✅ 降低部署风险 ❌ 有状态服务处理复杂
✅ 测试环境与生产环境一致 ❌ 需要额外的负载均衡配置

【适用场景】

  • ✅ 关键业务系统,要求零停机
  • ✅ 需要快速回滚能力
  • ✅ 资源充足的环境
  • ✅ 无状态服务(或数据库向后兼容)

【不适用场景】

  • ❌ 资源受限的环境
  • ❌ 数据库 schema 不兼容的变更
  • ❌ 有状态服务(需要特殊处理)

【面试官考察点】

  1. 原理理解:是否真正理解蓝绿部署的工作流程
  2. 实践能力:能否给出具体实现方案
  3. 问题分析:是否了解优缺点和适用场景
  4. 风险意识:是否考虑回滚和异常情况

💡 加分回答

  • 提到数据库迁移的处理策略(向后兼容、双写等)
  • 说明与金丝雀发布的区别和选择依据
  • 分享实际项目中遇到的问题和解决方案

【延伸知识】

  • 金丝雀发布:渐进式发布,风险更小
  • 滚动更新:K8s 默认部署策略,资源利用率高
  • A/B 测试:基于用户特征的流量分发
  • Feature Flag:特性开关,控制功能发布

(第 20-34 题继续...)

20. 金丝雀发布的原理是什么?如何实现?

【难度】 ⭐⭐⭐⭐    【高频指数】 ⭐⭐⭐⭐    【建议掌握时间】 60 分钟


【核心答案】

金丝雀发布是一种渐进式发布策略:

  • 新版本先部署少量实例(如 10% 流量)
  • 监控新版本的指标(错误率、延迟、资源使用)
  • 如果指标正常,逐步增加流量(25% → 50% → 100%)
  • 如果指标异常,自动回滚

【详细解析】

1. 金丝雀发布工作流程

┌─────────────────────────────────────────────────────────────────────┐
│                      金丝雀发布流程                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  v1.0: ████████████████████ 100% → 90% → 75% → 50% → 25% → 0%      │
│  v2.0:                     0% → 10% → 25% → 50% → 75% → 100%       │
│                               ↓      ↓      ↓      ↓      ↓         │
│                            部署   观察   观察   观察   全量         │
│                            10%    5 分钟  10 分钟 15 分钟 监控       │
│                                                                     │
│  如果任何阶段指标异常 → 立即回滚到 v1.0                              │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

与蓝绿部署的区别

特性 蓝绿部署 金丝雀发布
流量切换 一次性切换(0% → 100%) 渐进式切换(10% → 25% → 50% → 100%)
风险 中等 最低(影响范围小)
资源成本 翻倍(两套完整环境) 较低(只需少量新实例)
回滚速度 秒级 秒级
适用场景 关键业务,要求零停机 大型用户量,对稳定性要求极高

2. Kubernetes + Istio 实现

步骤 1:部署两个版本的 Deployment

# v1.0 Deployment(4 个实例)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-v1
spec:
  replicas: 4
  selector:
    matchLabels:
      app: myapp
      version: v1
  template:
    metadata:
      labels:
        app: myapp
        version: v1
    spec:
      containers:
      - name: myapp
        image: myapp:v1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 1000m
            memory: 1Gi

---
# v2.0 Deployment(初始 1 个实例,金丝雀)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-v2
spec:
  replicas: 1  # 初始少量实例
  selector:
    matchLabels:
      app: myapp
      version: v2
  template:
    metadata:
      labels:
        app: myapp
        version: v2
    spec:
      containers:
      - name: myapp
        image: myapp:v2.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 1000m
            memory: 1Gi

步骤 2:创建 VirtualService 控制流量比例

# virtual-service.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp-vs
spec:
  hosts:
  - myapp
  http:
  - route:
    - destination:
        host: myapp
        subset: v1
      weight: 90  # 90% 流量到 v1
    - destination:
        host: myapp
        subset: v2
      weight: 10  # 10% 流量到 v2

步骤 3:创建 DestinationRule 定义子集

# destination-rule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: myapp-dr
spec:
  host: myapp
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

步骤 4:部署配置

# 应用配置
kubectl apply -f v1-deployment.yaml
kubectl apply -f v2-deployment.yaml
kubectl apply -f virtual-service.yaml
kubectl apply -f destination-rule.yaml

# 查看流量分布
kubectl get virtualservice myapp-vs -o yaml

3. 渐进式发布 Pipeline

pipeline {
    agent any
    
    environment {
        CANARY_WEIGHTS = [10, 25, 50, 100]
        OBSERVATION_TIME = [300, 600, 900]  // 观察时间(秒)
    }
    
    stages {
        stage('部署金丝雀版本') {
            steps {
                script {
                    // 部署新版本(1 个实例)
                    sh 'kubectl apply -f k8s/v2-canary.yaml'
                    
                    // 等待 Pod 就绪
                    sh '''
                        kubectl wait --for=condition=ready pod \\
                            -l version=v2 \\
                            --timeout=120s
                    '''
                }
            }
        }
        
        stage('初始流量 10%') {
            steps {
                script {
                    updateTrafficWeight(10)
                    echo '流量调整到 10%,开始观察...'
                    
                    // 监控 5 分钟
                    def metrics = monitorMetrics(300)
                    
                    if (metrics.errorRate > 0.01) {
                        error '错误率超过 1%,回滚'
                    }
                    if (metrics.p99Latency > 500) {
                        error 'P99 延迟超过 500ms,回滚'
                    }
                }
            }
        }
        
        stage('渐进式增加流量') {
            steps {
                script {
                    for (int i = 1; i < CANARY_WEIGHTS.size(); i++) {
                        def weight = CANARY_WEIGHTS[i]
                        def observeTime = OBSERVATION_TIME[i - 1]
                        
                        echo "调整流量到 ${weight}%"
                        updateTrafficWeight(weight)
                        
                        if (weight < 100) {
                            // 观察一段时间
                            echo "观察 ${observeTime / 60} 分钟..."
                            def metrics = monitorMetrics(observeTime)
                            
                            if (!metrics.isHealthy()) {
                                error "指标异常,停止发布"
                            }
                        }
                    }
                }
            }
        }
        
        stage('完成发布') {
            steps {
                script {
                    // 扩充满额实例
                    sh 'kubectl scale deployment myapp-v2 --replicas=4'
                    
                    // 缩容旧版本
                    sh 'kubectl scale deployment myapp-v1 --replicas=0'
                    
                    echo '发布完成!'
                }
            }
        }
    }
    
    post {
        failure {
            // 自动回滚
            script {
                echo '发布失败,执行回滚'
                updateTrafficWeight(0)
                sh 'kubectl delete deployment myapp-v2'
            }
        }
    }
}

// 更新流量权重
def updateTrafficWeight(int canaryWeight) {
    sh """
        kubectl patch virtualservice myapp-vs \\
            --type=merge \\
            -p '{\\"spec\\":{\\"http\\":[{\\"route\\":[{\\"destination\\":{\\"subset\\":\\"v1\\"},\\"weight\\":${100-canaryWeight}},{\\"destination\\":{\\"subset\\":\\"v2\\"},\\"weight\\":${canaryWeight}}]}}]}'
    """
}

// 监控指标
def monitorMetrics(int duration) {
    echo "监控 ${duration} 秒..."
    
    // 从 Prometheus 获取指标
    def prometheusUrl = 'http://prometheus:9090/api/v1'
    
    // 查询错误率
    def errorRateQuery = '''
        sum(rate(http_requests_total{status=~"5..",version="v2"}[5m])) 
        / sum(rate(http_requests_total{version="v2"}[5m]))
    '''
    
    // 查询 P99 延迟
    def latencyQuery = '''
        histogram_quantile(0.99, 
            sum(rate(http_request_duration_seconds_bucket{version="v2"}[5m])) by (le)
        )
    '''
    
    // 调用 Prometheus API(实际项目中需要实现)
    // def errorRate = callPrometheus(errorRateQuery)
    // def p99Latency = callPrometheus(latencyQuery)
    
    // 模拟返回
    return [
        errorRate: 0.005,
        p99Latency: 200,
        isHealthy: { return true }
    ]
}

4. 监控指标阈值

指标 警告阈值 回滚阈值 说明
错误率 > 0.5% > 1% HTTP 5xx 错误比例
P99 延迟 > 400ms > 500ms 99% 请求的响应时间
CPU 使用率 > 70% > 90% 容器 CPU 使用率
内存使用率 > 80% > 95% 容器内存使用率
Pod 重启次数 > 2 次 > 5 次 5 分钟内重启次数

【优缺点对比】

优点 缺点
✅ 风险最小化(影响范围小) ❌ 实现复杂(需要流量管理)
✅ 基于真实流量验证 ❌ 需要完善的监控体系
✅ 可以自动回滚 ❌ 发布周期较长
✅ 资源利用率高 ❌ 需要服务网格支持(如 Istio)

【适用场景】

  • ✅ 大型用户量的应用
  • ✅ 对稳定性要求极高
  • ✅ 有完善的监控和自动化
  • ✅ 需要基于真实流量验证

【面试官考察点】

  1. 原理理解:是否真正理解金丝雀发布的工作流程
  2. 实践能力:能否给出具体实现方案(Istio、流量控制)
  3. 监控意识:是否了解需要监控哪些指标
  4. 自动化思维:能否设计自动回滚机制

💡 加分回答

  • 提到具体的监控指标和阈值设置
  • 说明与蓝绿部署的选择依据
  • 分享实际项目中遇到的问题和解决方案
  • 提到 A/B 测试与金丝雀发布的结合

【延伸知识】

  • Istio:服务网格,提供流量管理能力
  • Flagger:K8s 渐进式发布工具
  • Argo Rollouts:K8s 高级部署控制器
  • A/B 测试:基于用户特征的流量分发


21. 如何在 Jenkins 中管理凭据?有哪些安全最佳实践?

【难度】 ⭐⭐⭐    【高频指数】 ⭐⭐⭐⭐    【建议掌握时间】 45 分钟


【核心答案】

使用 Credentials 插件管理凭据,支持用户名密码、SSH 密钥、Secret 文件等类型,加密存储,最小权限访问。

安全最佳实践:最小权限、定期轮换、审计日志、外部密钥管理。


【详细解析】

1. 凭据类型

类型 说明 适用场景
Username with password 用户名 + 密码 Docker Registry、数据库登录
SSH Username with private key SSH 密钥对 SSH 部署、Git 访问
Secret file 秘密文件 Kubeconfig、证书文件
Secret text 秘密文本 API Token、Access Key
Certificate 证书 SSL 证书、客户端证书

2. 添加凭据

操作步骤

  1. Jenkins → 凭据 → 系统 → 全局凭据
  2. 选择领域(Global 或特定 Folder)
  3. 添加凭据 → 选择类型 → 填写信息
  4. 设置凭据 ID(用于 Pipeline 引用)

凭据 ID 命名规范

# 推荐格式:[用途]-[环境]-[名称]
docker-registry-prod
ssh-deploy-key
kubeconfig-prod
aws-access-key
sonarqube-token

3. Pipeline 中使用凭据

用户名密码

pipeline {
    agent any
    stages {
        stage('使用用户名密码') {
            steps {
                withCredentials([usernamePassword(
                    credentialsId: 'docker-registry',
                    usernameVariable: 'DOCKER_USER',
                    passwordVariable: 'DOCKER_PASS'
                )]) {
                    sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS registry.example.com'
                }
            }
        }
    }
}

SSH 密钥

pipeline {
    agent any
    stages {
        stage('使用 SSH 密钥') {
            steps {
                withCredentials([sshUserPrivateKey(
                    credentialsId: 'ssh-deploy-key',
                    keyFileVariable: 'SSH_KEY',
                    usernameVariable: 'SSH_USER',
                    passphraseVariable: 'SSH_PASSPHRASE'  // 可选
                )]) {
                    sh '''
                        chmod 600 $SSH_KEY
                        ssh -i $SSH_KEY -o StrictHostKeyChecking=no \\
                            $SSH_USER@server './deploy.sh'
                    '''
                }
            }
        }
    }
}

Secret 文件

pipeline {
    agent any
    stages {
        stage('使用 Secret 文件') {
            steps {
                withCredentials([file(
                    credentialsId: 'kubeconfig',
                    variable: 'KUBECONFIG_FILE'
                )]) {
                    sh 'kubectl --kubeconfig=$KUBECONFIG_FILE get pods'
                }
            }
        }
    }
}

Secret 文本

pipeline {
    agent any
    stages {
        stage('使用 Secret 文本') {
            steps {
                withCredentials([string(
                    credentialsId: 'api-token',
                    variable: 'API_TOKEN'
                )]) {
                    sh 'curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com'
                }
            }
        }
    }
}

多个凭据

withCredentials([
    usernamePassword(credentialsId: 'docker-registry', usernameVariable: 'USER', passwordVariable: 'PASS'),
    sshUserPrivateKey(credentialsId: 'ssh-key', keyFileVariable: 'SSH_KEY', usernameVariable: 'SSH_USER'),
    string(credentialsId: 'api-token', variable: 'TOKEN')
]) {
    sh 'docker login -u $USER -p $PASS'
    sh 'ssh -i $SSH_KEY $SSH_USER@server "./deploy.sh"'
    sh 'curl -H "Authorization: Bearer $TOKEN" https://api.example.com'
}

4. 安全最佳实践

实践 说明 实现方式
最小权限原则 凭据只授予需要的 Job 或 Folder 使用 Folder 级别的凭据,不使用全局凭据
使用 Folder 隔离 不同项目使用不同 Folder 的凭据 Folder A 的 Job 无法访问 Folder B 的凭据
定期轮换 定期更新密码、密钥、Token 设置提醒,每 90 天轮换一次
审计日志 启用凭据使用审计 查看哪些 Job 在什么时候使用了凭据
避免硬编码 永远不要在代码中硬编码凭据 使用 withCredentials 引用
使用外部密钥管理 敏感凭据存储在外部系统 HashiCorp Vault、AWS Secrets Manager

5. 集成 HashiCorp Vault

pipeline {
    agent any
    stages {
        stage('从 Vault 获取凭据') {
            steps {
                withVault([
                    configuration: [
                        vaultUrl: 'https://vault.example.com',
                        vaultCredentialId: 'vault-token',
                        engineVersion: 2
                    ],
                    secrets: [
                        [path: 'secret/data/docker', secretValues: [
                            [envVar: 'DOCKER_USER', vaultKey: 'username'],
                            [envVar: 'DOCKER_PASS', vaultKey: 'password']
                        ]],
                        [path: 'secret/data/aws', secretValues: [
                            [envVar: 'AWS_ACCESS_KEY_ID', vaultKey: 'access_key'],
                            [envVar: 'AWS_SECRET_ACCESS_KEY', vaultKey: 'secret_key']
                        ]]
                    ]
                ]) {
                    sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS'
                    sh 'aws s3 ls'
                }
            }
        }
    }
}

【面试官考察点】

  1. 熟悉程度:是否熟悉 Credentials 插件的使用
  2. 安全意识:是否了解凭据管理的安全最佳实践
  3. 实践经验:是否有实际管理凭据的经验

💡 加分回答

  • 提到使用 Vault 等外部密钥管理系统
  • 说明如何审计凭据使用情况
  • 分享凭据轮换的实践经验

【延伸知识】

  • HashiCorp Vault:企业级密钥管理
  • AWS Secrets Manager:AWS 托管密钥服务
  • Azure Key Vault:Azure 密钥管理
  • Jenkins Credentials Binding Plugin:凭据绑定插件

(第 22-34 题继续...)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注