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 题继续...)

发表回复

后才能评论