DevOps 面试题大全(四·一):CI/CD 基础概念 17 题详解(完整版)

前言

CI/CD(持续集成/持续部署)是 DevOps 的核心实践,也是面试必考内容。本文详细解析 50 道 CI/CD 面试题,分三篇发布。

📊 本系列特点

  • ✅ 每题包含核心答案、详细解析、代码示例、实战场景
  • ✅ 标注【难度等级】和【面试高频指数】
  • ✅ 揭示【面试官考察点】,帮你抓住重点
  • ✅ 提供【延伸知识】,帮助深入学习

一、基础概念(1-10 题)

1. 什么是 CI/CD?请详细说明三者的区别和联系

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


【核心答案】

CI/CD 包含三个递进概念:

  • CI(持续集成):开发人员频繁提交代码到 Git 仓库(通常每天多次),每次提交自动触发构建和测试,尽早发现集成错误
  • CD(持续交付):在 CI 基础上,代码随时可以部署到生产环境,但需要手动确认
  • CD(持续部署):在持续交付基础上,所有变更自动部署到生产环境,无需人工干预

【详细解析】

1. 持续集成(CI)的工作流程

┌──────────────┐    ┌──────────┐    ┌─────────┐    ┌──────┐    ┌──────────┐
│ 开发人员提交  │ →  │ Git 仓库  │ →  │ 自动触发 │ →  │ 构建  │ →  │ 自动测试  │
│   代码        │    │  主分支   │    │   CI    │    │      │    │  反馈结果  │
└──────────────┘    └──────────┘    └─────────┘    └──────┘    └──────────┘
  • 频率:每天多次提交(建议每完成一个小功能就提交)
  • 目的:尽早发现集成错误,避免"集成地狱"(多人长期分支后合并时的冲突爆发)
  • 关键实践:自动化构建、自动化测试、快速反馈(构建时间最好<10 分钟)

2. 持续交付 vs 持续部署 对比

特性 持续交付 持续部署
生产部署 手动触发 自动执行
人工审批 需要 不需要
自动化程度 中等(CI+ 自动部署测试环境) 完全自动化
适用场景 大多数企业(推荐起点) 高成熟度团队
风险 较低(有人工把关) 需要完善的测试和监控
代表公司 传统企业、金融机构 Amazon、Netflix、Google

3. 三者关系图

┌─────────────────────────────────────────────────────────────────────┐
│                         CI/CD 演进路径                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   手动集成      持续集成       持续交付        持续部署             │
│      │            │              │               │                  │
│      ▼            ▼              ▼               ▼                  │
│  ┌──────┐    ┌──────────┐   ┌──────────┐   ┌──────────┐           │
│  │ 每周  │    │ 每天多次  │   │ 随时可部署│   │ 自动部署  │           │
│  │ 合并  │    │ 自动构建  │   │ 手动确认  │   │ 无需确认  │           │
│  └──────┘    └──────────┘   └──────────┘   └──────────┘           │
│                 │              │               │                    │
│                 ▼              ▼               ▼                    │
│            发现问题早      降低风险       最快交付价值              │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

【代码示例】 - Jenkins Pipeline 完整配置:

pipeline {
    agent any
    
    stages {
        // ========== CI 阶段:构建和测试 ==========
        stage('CI - 构建和测试') {
            steps {
                echo '拉取代码...'
                checkout scm
                
                echo '安装依赖...'
                sh 'npm install'
                
                echo '运行单元测试...'
                sh 'npm test'
                
                echo '构建项目...'
                sh 'npm run build'
            }
        }
        
        // ========== CD 阶段:部署 ==========
        stage('CD - 部署到测试环境') {
            steps {
                echo '部署到测试环境...'
                sh './deploy-to-staging.sh'
                
                echo '运行集成测试...'
                sh 'npm run test:integration'
            }
        }
        
        stage('CD - 部署到生产环境') {
            steps {
                // 生产部署需要手动确认(持续交付)
                input message: '确认部署到生产环境?', ok: '确认部署'
                sh './deploy-to-prod.sh'
                
                // 部署后验证
                sh 'curl -f https://www.example.com/health || exit 1'
            }
        }
    }
    
    post {
        always {
            echo 'Pipeline 执行完成,发送通知'
            // 发送钉钉/企业微信通知
        }
        failure {
            echo '构建失败,发送告警'
            // 发送告警通知
        }
    }
}

【实战场景】

某电商团队 CI/CD 实践案例

背景:50 人开发团队,之前每周发布 1 次,集成问题频发

实施方案

  • 开发人员每天提交代码 10+ 次到 Git 主分支
  • 每次提交自动触发 Jenkins 构建(约 5 分钟)
  • 自动化测试通过后,自动部署到测试环境
  • 测试人员验证后,手动点击按钮部署到生产

实施效果

  • ✅ 集成问题减少 80%
  • ✅ 发布周期从每周 1 次提升到每天多次
  • ✅ 问题发现时间从平均 3 天缩短到 10 分钟
  • ✅ 团队满意度大幅提升

【面试官考察点】

  1. 概念理解:是否真正理解 CI/CD 的含义,而不是死记硬背
  2. 实践经验:是否有实际落地经验,能否说出具体实施细节
  3. 价值认知:是否理解 CI/CD 带来的业务价值(快速交付、降低风险)
  4. 工具熟悉度:是否熟悉主流 CI/CD 工具(Jenkins、GitLab CI 等)

💡 加分回答

  • 提到 DORA 指标(部署频率、变更前置时间等)
  • 分享自己团队的实施经验和遇到的问题
  • 说明持续交付和持续部署的选择依据

【常见误区】

  • 误区 1:CI/CD 就是买个工具
    • 正解:CI/CD 是流程和文化变革,工具只是支撑
  • 误区 2:只有小团队适合
    • 正解:大企业同样适用(如 Amazon 每天部署数万次)
  • 误区 3:自动化测试可有可无
    • 正解:没有自动化测试的 CI/CD 是空中楼阁
  • 误区 4:持续部署一定比持续交付好
    • 正解:根据团队成熟度选择,持续交付是大多数企业的起点

【延伸知识】

  • 推荐阅读:《Continuous Delivery》(持续交付)- Jez Humble
  • DORA 指标:部署频率、变更前置时间、变更失败率、MTTR
  • 相关工具:Jenkins、GitLab CI、GitHub Actions、ArgoCD、Spinnaker
  • 进阶概念:GitOps、蓝绿部署、金丝雀发布、特性开关


2. Jenkins 架构由哪些组件组成?各组件的作用是什么?

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


【核心答案】

Jenkins 采用 Master-Agent 分布式架构,核心组件包括:

  • Master(主节点):提供 Web 界面,管理任务调度,监控 Agent
  • Agent(工作节点):执行具体的构建任务,可以分布在多台机器上
  • Plugins(插件):扩展功能(1500+ 插件)
  • Jobs/Pipelines(任务/流水线):构建任务定义

【详细解析】

1. Jenkins Master(主节点)

核心职责

  • 提供 Web UI 和 REST API(用户交互入口)
  • 管理 Job 配置和调度(决定何时执行、在哪执行)
  • 监控 Agent 状态(在线/离线、负载情况)
  • 记录构建历史和日志(审计和排查)

内部组件

  • Jetty/Winstone:内置 Web 服务器,提供 HTTP 服务
  • Scheduler:任务调度器,决定任务执行顺序
  • Launcher:Agent 启动器,连接和管理工作节点
  • Update Center:插件更新中心

⚠️ 注意事项:Master 节点不建议执行构建任务,只负责调度,避免资源竞争影响稳定性。


2. Jenkins Agent(工作节点)

核心职责

  • 执行具体的构建任务(编译、测试、部署等)
  • 可以分布在多台机器上(支持分布式构建)
  • 支持不同操作系统和环境(Linux、Windows、macOS)

连接方式对比

连接方式 原理 优点 缺点 适用场景
SSH Master SSH 连接到 Agent 配置简单,最常用 需要 SSH 权限 大多数场景
JNLP Agent 主动连接 Master 防火墙友好 配置复杂 Agent 在内网
Docker 动态创建容器作为 Agent 环境隔离,用完即销毁 需要 Docker 环境 容器化构建
Kubernetes 动态创建 Pod 作为 Agent 弹性伸缩,资源利用率高 需要 K8s 集群 大规模构建

3. Plugins(插件系统)

作用:扩展 Jenkins 功能,是 Jenkins 生态系统的核心。

插件分类

  • SCM 插件:Git、SVN、Mercurial 等源码管理
  • 构建工具插件:Maven、Gradle、npm、Ant 等
  • 部署插件:Docker、Kubernetes、AWS、Azure 等
  • 通知插件:Email、Slack、钉钉、企业微信等
  • 测试插件:JUnit、TestNG、pytest、Coverage 等
  • 安全插件:Matrix Authorization、LDAP、SAML 等

⚠️ 注意事项

  • 插件不是越多越好,只安装必需的
  • 定期更新插件,修复安全漏洞
  • 生产环境先在测试环境验证插件兼容性

4. Jobs/Pipelines(任务/流水线)

任务类型

  • FreeStyle Project:传统任务类型,通过 UI 配置,适合简单任务
  • Pipeline Project:基于 Jenkinsfile 的代码化流水线,推荐使用
  • Multi-configuration Project:矩阵构建,多环境测试
  • Folder:文件夹,用于组织和管理任务

【架构示意图】

┌─────────────────────────────────────────────────────────────────────┐
│                        Jenkins 架构概览                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────┐                                               │
│  │  Jenkins Master │                                               │
│  │  - Web UI       │                                               │
│  │  - REST API     │                                               │
│  │  - 任务调度      │                                               │
│  │  - 插件系统      │                                               │
│  └────────┬────────┘                                               │
│           │                                                         │
│    ┌──────┼──────┬──────────┬──────────┐                          │
│    │      │      │          │          │                           │
│    ▼      ▼      ▼          ▼          ▼                           │
│ ┌────┐ ┌────┐ ┌────┐    ┌────┐    ┌────┐                         │
│ │Agent│ │Agent│ │Agent│    │Agent│    │Agent│                     │
│ │Linux│ │Windows│ │macOS│    │Docker│    │K8s  │                     │
│ │Java │ │.NET  │ │iOS  │    │容器  │    │Pod  │                     │
│ └────┘ └────┘ └────┘    └────┘    └────┘                         │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                    Plugins (1500+)                          │   │
│  │  Git │ Maven │ Docker │ K8s │ Email │ Slack │ SonarQube... │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

【实战场景】

某公司 Jenkins 集群配置案例

背景:100+ 开发人员,50+ 项目,需要支持高并发构建

架构配置

  • Master 节点:2 核 4G,仅负责任务调度,不执行构建
  • Agent 节点:5 台 4 核 8G 服务器,执行构建任务
  • Agent 分配
    • 2 个 Linux Agent:Java/Go 项目构建
    • 2 个 Windows Agent:.NET 项目构建
    • 1 个 Docker Agent:容器相关任务

实施效果

  • ✅ 并发构建能力提升 5 倍
  • ✅ 构建队列等待时间从 30 分钟降低到 5 分钟
  • ✅ 不同项目环境隔离,互不影响

【面试官考察点】

  1. 架构理解:是否理解 Master-Agent 架构的设计思想
  2. 实践经验:是否有实际配置和管理 Jenkins 的经验
  3. 问题排查:能否说明常见问题的排查思路(如 Agent 离线)
  4. 最佳实践:是否了解 Jenkins 使用的最佳实践

💡 加分回答

  • 提到 Master 节点不应该执行构建任务
  • 说明不同 Agent 连接方式的选择依据
  • 分享插件管理的经验(版本控制、定期更新)

【常见误区】

  • 误区 1:Master 节点可以执行构建任务
    • 正解:Master 只负责调度,构建应该在 Agent 上执行
  • 误区 2:插件越多越好
    • 正解:只安装必需的插件,减少安全风险和维护成本
  • 误区 3:所有项目用一个 Agent
    • 正解:根据项目类型和环境需求,使用不同的 Agent

【延伸知识】

  • Jenkins 高可用:多 Master + 负载均衡 + 外部数据库
  • Jenkins 备份:定期备份 JENKINS_HOME 目录
  • Pipeline as Code:使用 Jenkinsfile 版本控制构建流程
  • 相关工具:Jenkins X(云原生 Jenkins)、Blue Ocean(现代化 UI)


3. Jenkins Pipeline 有哪几种类型?有什么区别?如何选择?

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


【核心答案】

Jenkins Pipeline 有两种类型:

  • 声明式(Declarative):语法结构清晰,有严格的语法规则,推荐使用
  • 脚本式(Scripted):基于 Groovy 脚本,更灵活但学习曲线陡峭

选择建议:90% 的场景使用声明式,需要复杂逻辑时使用脚本式。


【详细解析】

1. 声明式 Pipeline(Declarative)

语法特点

  • pipeline 块开始
  • 支持 agentstagespost 等声明式块
  • 内置错误检查(语法错误在解析阶段就能发现)
  • 结构清晰,易于阅读和维护

基本结构

pipeline {
    agent any              // 指定执行节点
    
    environment {          // 环境变量
        APP_NAME = "myapp"
    }
    
    stages {               // 阶段定义
        stage('Build') {
            steps {
                sh 'mvn package'
            }
        }
    }
    
    post {                 // 构建后操作
        always {
            echo '构建完成'
        }
        success {
            echo '构建成功'
        }
        failure {
            echo '构建失败'
        }
    }
}

适用场景

  • ✅ 标准 CI/CD 流程(构建→测试→部署)
  • ✅ 团队新手较多,需要统一规范
  • ✅ 需要代码审查和版本控制

2. 脚本式 Pipeline(Scripted)

语法特点

  • node 块开始
  • 可以使用完整的 Groovy 语法(循环、条件、异常处理等)
  • 更灵活,可以动态生成阶段
  • 学习曲线较陡,需要 Groovy 基础

基本结构

node {
    def appName = "myapp"
    def version = "1.0.0"
    
    try {
        stage('Build') {
            sh 'mvn package'
        }
        
        stage('Test') {
            // 动态生成测试阶段
            def testTypes = ['unit', 'integration', 'e2e']
            testTypes.each { testType ->
                stage("Test - ${testType}") {
                    sh "npm run test:${testType}"
                }
            }
        }
    } catch (e) {
        echo "构建失败:${e.message}"
        throw e
    } finally {
        echo '清理工作'
        cleanWs()
    }
}

适用场景

  • ✅ 需要复杂逻辑(动态生成阶段、复杂条件判断)
  • ✅ 需要复用现有 Groovy 代码
  • ✅ 团队有 Groovy 开发经验

3. 两种类型对比

特性 声明式 脚本式
语法 声明式,结构化 脚本式,灵活
学习难度 低(适合新手) 高(需要 Groovy 基础)
灵活性 中等
错误检查 内置(解析阶段发现) 手动(运行时发现)
代码审查 容易 较难
推荐场景 标准 CI/CD 流程 复杂动态流程
社区支持 主流推荐 逐渐减少

【代码对比示例】

场景:构建失败时发送不同通知

声明式实现

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'mvn package'
            }
        }
    }
    post {
        failure {
            // 失败时发送通知
            emailext(
                subject: "构建失败:${env.JOB_NAME}",
                body: "请查看:${env.BUILD_URL}",
                to: 'team@example.com'
            )
        }
    }
}

脚本式实现

node {
    try {
        stage('Build') {
            sh 'mvn package'
        }
    } catch (e) {
        // 根据错误类型发送不同通知
        if (e.message.contains('compilation')) {
            emailext(subject: '编译错误', to: 'dev@example.com')
        } else if (e.message.contains('test')) {
            emailext(subject: '测试失败', to: 'qa@example.com')
        }
        throw e
    }
}

【选择建议】

┌─────────────────────────────────────────────────────────────────────┐
│                    Pipeline 类型选择决策树                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│                          开始创建 Pipeline                           │
│                                │                                    │
│                                ▼                                    │
│                    ┌─────────────────────┐                         │
│                    │ 需要复杂逻辑吗?     │                         │
│                    │ (动态生成/复杂条件)  │                         │
│                    └──────────┬──────────┘                         │
│                               │                                     │
│                    ┌──────────┴──────────┐                         │
│                    │                     │                          │
│                   是                    否                          │
│                    │                     │                          │
│                    ▼                     ▼                          │
│           ┌─────────────┐       ┌─────────────┐                     │
│           │   脚本式     │       │   声明式     │                     │
│           │  Scripted   │       │ Declarative │                     │
│           └─────────────┘       └─────────────┘                     │
│                                                                     │
│   提示:90% 的场景声明式就足够了                                      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

【面试官考察点】

  1. 语法熟悉度:是否熟悉两种 Pipeline 的语法
  2. 选择能力:能否根据场景选择合适的类型
  3. 实践经验:是否有实际编写 Jenkinsfile 的经验
  4. 最佳实践:是否了解推荐的使用方式

💡 加分回答

  • 明确说"推荐使用声明式,除非需要复杂逻辑"
  • 能说出两种类型的核心区别
  • 分享实际项目中遇到的问题和解决方案

【常见误区】

  • 误区 1:脚本式更强大,所以更好
    • 正解:声明式是官方推荐,适合大多数场景
  • 误区 2:两种类型可以混用
    • 正解:一个 Jenkinsfile 只能用一种类型
  • 误区 3:声明式功能有限
    • 正解:声明式支持 script 块,可以嵌入脚本式代码

【延伸知识】

  • Shared Library:在声明式中使用共享库实现代码复用
  • Blue Ocean:可视化编辑 Pipeline,适合新手
  • Pipeline 语法生成器:Jenkins 内置工具,帮助生成语法
  • 相关文档:Jenkins 官方 Pipeline 文档

(因篇幅限制,第 4-17 题继续下一篇...)

4. 什么是 Jenkinsfile?为什么推荐使用?

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


【核心答案】

Jenkinsfile 是定义 Pipeline 的文本文件,使用 Groovy 语法编写,放在项目根目录。

推荐使用的原因:版本控制、代码审查、单一事实来源、可重复性、自动化。


【详细解析】

1. Jenkinsfile 的核心价值

价值 说明 带来的好处
版本控制 Jenkinsfile 随代码一起提交到 Git 可以追溯构建流程的变更历史,知道谁在什么时候改了什么
代码审查 通过 Pull Request 审查构建流程 避免错误的构建配置被合并,团队共同维护构建质量
单一事实来源 构建逻辑只在一个地方定义 避免配置分散,减少"在我本地是好的"这类问题
可重复性 任何环境都可以执行相同流程 开发、测试、生产环境构建一致,减少环境差异导致的问题
自动化 新分支自动继承构建流程 无需手动配置 Job,新成员快速上手

2. 完整示例 - Java 项目 Jenkinsfile

// Jenkinsfile - 放在项目根目录
pipeline {
    agent any
    
    // 环境变量定义
    environment {
        DOCKER_REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
        IMAGE_NAME = 'mycompany/myapp'
        SONAR_HOST_URL = 'http://sonarqube.example.com'
    }
    
    // 工具定义
    tools {
        maven 'Maven 3.8'
        jdk 'OpenJDK 17'
    }
    
    stages {
        // ========== 阶段 1:拉取代码 ==========
        stage('Checkout') {
            steps {
                echo '拉取代码...'
                checkout scm
                // 显示当前提交信息
                sh 'git log -1 --pretty=format:"%H %an %ad %s"'
            }
        }
        
        // ========== 阶段 2:代码质量检查 ==========
        stage('Code Quality') {
            steps {
                echo '运行 SonarQube 代码扫描...'
                withSonarQubeEnv('sonarqube-server') {
                    sh '''
                        mvn sonar:sonar \\
                          -Dsonar.projectKey=${JOB_NAME} \\
                          -Dsonar.host.url=${SONAR_HOST_URL}
                    '''
                }
            }
        }
        
        // ========== 阶段 3:编译构建 ==========
        stage('Build') {
            steps {
                echo '编译构建...'
                sh 'mvn clean package -DskipTests'
                // 归档构建产物
                archiveArtifacts artifacts: 'target/*.jar', allowEmptyArchive: true
            }
        }
        
        // ========== 阶段 4:单元测试 ==========
        stage('Unit Test') {
            steps {
                echo '运行单元测试...'
                sh 'mvn test'
            }
            post {
                always {
                    // 收集测试报告
                    junit '**/target/surefire-reports/*.xml'
                    // 收集代码覆盖率
                    publishHTML(target: [
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                        reportDir: 'target/site/jacoco',
                        reportFiles: 'index.html',
                        reportName: '代码覆盖率报告'
                    ])
                }
            }
        }
        
        // ========== 阶段 5:构建 Docker 镜像 ==========
        stage('Build Docker Image') {
            steps {
                echo '构建 Docker 镜像...'
                script {
                    docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}")
                }
            }
        }
        
        // ========== 阶段 6:推送镜像仓库 ==========
        stage('Push Image') {
            steps {
                echo '推送镜像到仓库...'
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
                        docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}").push()
                        docker.image("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}").push('latest')
                    }
                }
            }
        }
        
        // ========== 阶段 7:部署到测试环境 ==========
        stage('Deploy to Dev') {
            when {
                branch 'develop'  // 仅 develop 分支部署到 dev 环境
            }
            steps {
                echo '部署到开发环境...'
                sh '''
                    kubectl set image deployment/myapp \\
                        myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} \\
                        -n dev
                    kubectl rollout status deployment/myapp -n dev
                '''
            }
        }
        
        // ========== 阶段 8:部署到生产环境 ==========
        stage('Deploy to Prod') {
            when {
                branch 'main'  // 仅 main 分支可以部署到生产
            }
            input {
                message '确认部署到生产环境?'
                ok '确认部署'
                submitter 'admin,release-manager'  // 只有特定角色可以审批
            }
            steps {
                echo '部署到生产环境...'
                sh '''
                    kubectl set image deployment/myapp \\
                        myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} \\
                        -n prod
                    kubectl rollout status deployment/myapp -n prod
                    // 部署后健康检查
                    curl -f http://myapp.prod.example.com/health || exit 1
                '''
            }
        }
    }
    
    // ========== 构建后处理 ==========
    post {
        always {
            echo 'Pipeline 执行完成'
            // 清理工作空间
            cleanWs()
        }
        success {
            echo '构建成功!'
            // 发送成功通知(钉钉/企业微信/邮件)
            script {
                sendNotification('SUCCESS')
            }
        }
        failure {
            echo '构建失败!'
            // 发送失败告警
            script {
                sendNotification('FAILURE')
            }
        }
        unstable {
            echo '构建不稳定(测试失败)'
            script {
                sendNotification('UNSTABLE')
            }
        }
    }
}

// 通知函数
def sendNotification(String status) {
    def color = status == 'SUCCESS' ? 'good' : 'danger'
    def message = "构建${status}: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
    // 发送钉钉通知
    // dingTalk(message, color)
}

3. Jenkinsfile 最佳实践

实践 说明 示例
放在根目录 Jenkinsfile 放在项目根目录 ./Jenkinsfile
使用声明式 优先使用声明式语法 pipeline { }
环境变量集中管理 在 environment 块定义 environment { APP = 'myapp' }
敏感信息加密 使用 credentials,不硬编码 credentials('docker-registry')
使用 post 块 处理构建后操作 post { always { } }
添加健康检查 部署后验证服务正常 curl -f http://service/health
清理工作空间 构建完成清理临时文件 cleanWs()

【面试官考察点】

  1. 理解深度:是否真正理解 Jenkinsfile 的价值,而不是死记硬背
  2. 实践经验:是否有实际编写 Jenkinsfile 的经验
  3. 安全意识:是否知道敏感信息不能硬编码
  4. 最佳实践:是否了解推荐的编写规范

💡 加分回答

  • 提到"Pipeline as Code"的理念
  • 说明如何在团队中推广 Jenkinsfile
  • 分享使用 Shared Library 实现代码复用的经验
  • 提到多分支 Pipeline 的自动发现机制

【常见误区】

  • 误区 1:Jenkinsfile 可以放在任意目录
    • 正解:必须放在项目根目录,Jenkins 才能自动发现
  • 误区 2:敏感信息可以写在 Jenkinsfile 里
    • 正解:必须使用 credentials 插件,通过 withCredentials 引用
  • 误区 3:Jenkinsfile 不需要版本控制
    • 正解:Jenkinsfile 必须随代码一起提交到 Git

【延伸知识】

  • 多分支 Pipeline:自动为每个分支创建 Pipeline
  • Shared Library:跨项目共享 Pipeline 代码
  • Blueprint:Jenkins 模板,快速创建标准 Pipeline


5. Jenkins 有哪些触发构建的方式?

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


【核心答案】

5 种触发方式:

  1. 手动触发 - UI 点击或 API 调用
  2. 定时触发(Cron) - 按计划时间执行
  3. 代码提交触发(Webhook) - Git 推送自动触发
  4. 上游任务触发 - 其他 Job 完成后触发
  5. API 触发 - 外部系统调用

【详细解析】

1. 手动触发

操作方式

  • Jenkins UI 点击"Build Now"按钮
  • API 调用触发

适用场景

  • 临时构建、测试环境部署
  • 紧急修复、回滚操作
  • 调试和验证

API 触发示例

# 使用 curl 触发构建
curl -X POST http://jenkins/job/myjob/build \\
  --user username:api-token

# 带参数触发
curl -X POST http://jenkins/job/myjob/buildWithParameters \\
  --user username:api-token \\
  --data "BRANCH=main&ENV=prod"

2. 定时触发(Cron)

配置方式

// Jenkinsfile 配置
triggers {
    cron('H 2 * * *')      // 每天凌晨 2 点
    cron('H/15 * * * *')   // 每 15 分钟
    cron('@daily')         // 每天
    cron('@weekly')        // 每周
    cron('0 0 1 * *')      // 每月 1 号
}

Cron 语法说明

┌───────────── 分钟 (0-59)
│ ┌───────────── 小时 (0-23)
│ │ ┌───────────── 日期 (1-31)
│ │ │ ┌───────────── 月份 (1-12)
│ │ │ │ ┌───────────── 星期 (0-7,0 和 7 都是周日)
│ │ │ │ │
* * * * *

特殊符号

  • H - 散列值,避免所有任务同时执行(推荐)
  • */15 - 每 15 分钟
  • @daily - 每天凌晨
  • @weekly - 每周日凌晨

适用场景

  • 夜间构建(避免占用白天资源)
  • 定期回归测试
  • 数据备份、清理任务
  • 定期生成报告

3. 代码提交触发(Webhook)

配置方式

  1. 在 Git 仓库(GitLab/GitHub)配置 Webhook
  2. Webhook URL 指向 Jenkins
  3. Jenkins 安装对应插件(GitLab/GitHub plugin)

GitLab Webhook 配置

GitLab 项目 → Settings → Webhooks

URL: http://jenkins/project/myjob
Trigger: Push events, Merge request events
Secret Token: [可选,增加安全性]

GitHub Webhook 配置

GitHub 项目 → Settings → Webhooks

Payload URL: http://jenkins/github-webhook/
Content type: application/json
Trigger: Push events, Pull request events

适用场景

  • CI/CD 标准流程(最常用)
  • 代码提交后自动构建和测试
  • PR/MR 自动触发检查

优点

  • 实时触发,无需轮询
  • 减少 Jenkins 负载
  • 构建及时,反馈快速

4. 上游任务触发

配置方式

// 上游 Job 配置
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'mvn package'
            }
        }
    }
    post {
        success {
            // 构建成功后触发下游
            build job: 'deploy-job', 
                  parameters: [string(name: 'VERSION', value: env.BUILD_NUMBER)],
                  wait: true  // 等待下游完成
        }
    }
}

适用场景

  • 多阶段流水线(构建→测试→部署)
  • 职责分离,不同 Job 负责不同阶段
  • 需要传递参数给下游任务

5. API 触发

使用场景

  • 外部系统集成
  • 自定义触发逻辑
  • 第三方工具调用

Python 示例

import requests
from requests.auth import HTTPBasicAuth

# 触发构建
response = requests.post(
    'http://jenkins/job/myjob/buildWithParameters',
    params={'BRANCH': 'main', 'ENV': 'prod'},
    auth=HTTPBasicAuth('username', 'api-token')
)

# 获取构建状态
build_number = response.headers.get('X-Jenkins-Build-Number')
status = requests.get(
    f'http://jenkins/job/myjob/{build_number}/api/json',
    auth=HTTPBasicAuth('username', 'api-token')
).json().get('result')

【5 种触发方式对比】

触发方式 自动化程度 适用场景 推荐指数
手动触发 临时构建、紧急修复 ⭐⭐
定时触发 夜间构建、定期测试 ⭐⭐⭐
Webhook CI/CD 标准流程 ⭐⭐⭐⭐⭐
上游触发 多阶段流水线 ⭐⭐⭐⭐
API 触发 外部系统集成 ⭐⭐⭐

【面试官考察点】

  1. 熟悉程度:是否了解所有触发方式
  2. 场景理解:能否根据场景选择合适的触发方式
  3. 实践经验:是否有实际配置经验

💡 加分回答

  • 说明 Webhook 相比轮询的优势
  • 提到 Cron 中 H 符号的作用(散列分布)
  • 分享多阶段流水线的触发设计经验

【延伸知识】

  • 轮询 SCMpollSCM('*/5 * * * *') - 不推荐,浪费资源
  • Gitee Webhook:国内 Git 服务,配置类似 GitHub
  • Generic Webhook:通用 Webhook 触发器插件


6. 什么是 Blue Ocean?它解决了什么问题?

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


【核心答案】

Blue Ocean 是 Jenkins 的现代化 UI 插件,专为 Pipeline 设计。

解决的问题:传统 UI 复杂、可视化不足、错误定位困难、创建 Pipeline 复杂。


【详细解析】

1. 传统 UI 的痛点

痛点 说明 影响
信息密集 大量文本和链接堆砌 新手难以理解 Pipeline 执行流程
可视化不足 无法直观看到并行阶段 难以分析构建瓶颈
错误定位困难 需要在大量日志中查找 排查问题耗时
创建复杂 需要手写 Jenkinsfile 学习曲线陡峭

2. Blue Ocean 核心功能

功能 说明 带来的价值
可视化 Pipeline 图形化展示各阶段执行状态 一目了然了解构建进度
并行展示 清晰显示并行阶段 方便分析并行执行效果
快速定位失败 直接跳转到失败阶段和日志 快速排查问题
简化创建 可视化编辑器创建 Pipeline 降低学习门槛
实时日志 流式显示构建日志 实时查看构建输出
多分支支持 清晰展示多分支状态 方便管理多个分支

3. 安装和使用

# 安装步骤
1. Jenkins 管理 → 插件管理
2. 搜索 "Blue Ocean"
3. 安装并重启 Jenkins

# 访问 Blue Ocean
http://jenkins/blue

# 查看特定 Job
http://jenkins/blue/organizations/jenkins/myjob/activity

# 创建新 Pipeline
Blue Ocean → 创建新 Pipeline → 选择 Git 仓库

【Blue Ocean vs 经典 UI】

特性 Blue Ocean 经典 UI
可视化 优秀(图形化) 一般(文本为主)
易用性 优秀(新手友好) 中等(需要学习)
功能完整性 中等(核心功能) 优秀(全部功能)
性能 中等 优秀
推荐场景 新手、可视化需求 高级用户、完整功能

【面试官考察点】

  1. 工具了解:是否知道 Jenkins 的现代化 UI
  2. 对比分析:能否说明 Blue Ocean 的优势和局限

💡 加分回答

  • 提到 Blue Ocean 适合新手,但经典 UI 功能更完整
  • 说明团队中如何结合使用两种 UI

【延伸知识】

  • Jenkins X:云原生 Jenkins,内置现代化 UI
  • Pipeline 可视化编辑器:Blue Ocean 内置


7. 什么是 Jenkins Shared Library?如何使用?

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


【核心答案】

Shared Library(共享库)允许多个 Pipeline 共享代码,实现代码复用和统一管理。

适用场景:多项目使用相同构建逻辑、统一公司规范、复杂功能封装。


【详细解析】

1. 为什么需要 Shared Library?

问题场景

  • 公司有 50+ 项目,每个项目都有类似的 Jenkinsfile
  • 构建逻辑变更需要修改 50+ 个文件
  • 难以保证所有项目使用相同的构建标准
  • 重复代码多,维护成本高

Shared Library 解决方案

  • 将通用构建逻辑封装到共享库
  • 各项目 Jenkinsfile 只需几行代码
  • 修改共享库,所有项目自动生效
  • 统一构建标准,便于管理

2. 目录结构

shared-library/
├── vars/                          # 全局变量/函数
│   ├── commonBuild.groovy         # 通用构建函数
│   ├── deploy.groovy              # 部署函数
│   └── sendNotification.groovy    # 通知函数
├── src/                           # Groovy 源代码
│   └── com/
│       └── company/
│           ├── Helpers.groovy     # 工具类
│           └── Constants.groovy   # 常量定义
├── resources/                     # 资源文件
│   └── config.json
└── Jenkinsfile                    # 库的测试流水线

3. 定义共享函数

vars/commonBuild.groovy

// 定义全局函数
def call(Map config = [:]) {
    def appName = config.appName ?: 'unknown'
    def buildTool = config.buildTool ?: 'maven'
    def testEnabled = config.testEnabled ?: true
    
    pipeline {
        agent any
        
        environment {
            APP_NAME = appName
        }
        
        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }
            
            stage('Build') {
                steps {
                    script {
                        if (buildTool == 'maven') {
                            sh 'mvn clean package -DskipTests'
                        } else if (buildTool == 'gradle') {
                            sh './gradlew build'
                        } else if (buildTool == 'npm') {
                            sh 'npm install && npm run build'
                        }
                    }
                }
            }
            
            stage('Test') {
                when {
                    expression { return testEnabled }
                }
                steps {
                    script {
                        if (buildTool == 'maven') {
                            sh 'mvn test'
                        } else if (buildTool == 'npm') {
                            sh 'npm test'
                        }
                    }
                }
            }
        }
        
        post {
            always {
                cleanWs()
            }
        }
    }
}

4. 在 Pipeline 中使用

// 加载共享库
@Library('my-shared-library') _

// 使用共享库函数
commonBuild(appName: 'myapp', buildTool: 'maven', testEnabled: true)

或者在声明式 Pipeline 中使用

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                script {
                    // 调用共享库函数
                    com.company.Helpers.build()
                }
            }
        }
    }
}

5. 配置共享库

  1. Jenkins 管理 → 系统配置 → Global Pipeline Libraries
  2. 添加库名称:my-shared-library
  3. Git 仓库 URL:https://github.com/company/shared-library.git
  4. 分支:main
  5. 加载方式:Implicit(自动加载)或 Explicit(需要@Library)

【面试官考察点】

  1. 代码复用意识:是否理解 DRY 原则
  2. 架构思维:能否设计可复用的构建逻辑
  3. 实践经验:是否有实际使用 Shared Library 的经验

💡 加分回答

  • 分享如何在团队中推广 Shared Library
  • 说明版本管理策略(语义化版本)
  • 提到如何测试共享库代码

【延伸知识】

  • 版本化共享库:使用 Git 标签管理版本
  • 共享库测试:使用 Jenkins Pipeline Unit Testing
  • 相关文档:Jenkins 官方 Shared Library 文档

(第 8-17 题继续...)

8. GitLab CI 的核心概念有哪些?

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


【核心答案】

GitLab CI 核心概念包括:

  1. .gitlab-ci.yml - 配置文件,定义整个 CI/CD 流程
  2. Runner - 执行代理,负责运行 Job
  3. Pipeline/Stage/Job - 流程/阶段/任务三层结构
  4. Artifact - 构建产物,传递给下游 Job
  5. Cache - 依赖缓存,加速构建

【详细解析】

1. .gitlab-ci.yml 配置文件

完整示例

# .gitlab-ci.yml - 放在项目根目录

# 定义阶段(按顺序执行)
stages:
  - build      # 构建阶段
  - test       # 测试阶段
  - deploy     # 部署阶段

# 全局变量
variables:
  MAVEN_OPTS: "-Xmx2048m"
  DOCKER_DRIVER: overlay2

# 默认配置
default:
  image: maven:3.8-openjdk-17
  tags:
    - docker

# ========== Build 阶段 ==========
build_job:
  stage: build
  script:
    - echo "编译构建..."
    - mvn clean package -DskipTests
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 week  # 产物保留时间
  only:
    - main
    - develop

# ========== Test 阶段 ==========
unit_test:
  stage: test
  script:
    - echo "运行单元测试..."
    - mvn test
  dependencies:
    - build_job  # 依赖 build_job 的产物
  artifacts:
    reports:
      junit: '**/target/surefire-reports/*.xml'
    paths:
      - target/site/jacoco/
  coverage: '/Coverage: (\\d+\\.\\d+)%/'  # 提取覆盖率

integration_test:
  stage: test
  script:
    - echo "运行集成测试..."
    - mvn integration-test
  dependencies:
    - build_job
  allow_failure: true  # 失败不影响后续

# ========== Deploy 阶段 ==========
deploy_dev:
  stage: deploy
  script:
    - echo "部署到开发环境..."
    - ./deploy.sh dev
  environment:
    name: development
    url: https://dev.example.com
  only:
    - develop

deploy_staging:
  stage: deploy
  script:
    - echo "部署到预发环境..."
    - ./deploy.sh staging
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - main
  when: manual  # 手动触发

deploy_prod:
  stage: deploy
  script:
    - echo "部署到生产环境..."
    - ./deploy.sh prod
  environment:
    name: production
    url: https://www.example.com
  only:
    - main
  when: manual
  dependencies:
    - deploy_staging
  needs:
    - unit_test  # 等待单元测试完成

2. Runner 执行代理

Runner 类型对比

类型 说明 适用场景
Shared Runners GitLab 提供,所有项目可用 公共项目、小团队
Specific Runners 项目专用 需要特定环境的项目
Group Runners 组内项目共享 部门内多项目

Runner 执行方式

  • Docker:在容器中运行(最常用,环境隔离)
  • Shell:直接在主机上运行(需要手动维护环境)
  • Kubernetes:在 K8s Pod 中运行(弹性伸缩)
  • SSH:通过 SSH 连接到远程主机运行

安装和注册 Runner

# 安装 GitLab Runner
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
apt-get install gitlab-runner

# 注册 Runner
gitlab-runner register

# 输入信息:
# GitLab URL: https://gitlab.com/
# Registration Token: [从 GitLab 项目设置获取]
# Description: my-docker-runner
# Tags: docker,linux
# Executor: docker
# Docker Image: maven:3.8-openjdk-17

3. Pipeline/Stage/Job 关系

┌─────────────────────────────────────────────────────────┐
│                      Pipeline                            │
│  ┌───────────────────────────────────────────────────┐  │
│  │ Stage 1: build                                    │  │
│  │  ┌─────────────┐  ┌─────────────┐                │  │
│  │  │ Job: build  │  │ Job: lint   │ (并行执行)      │  │
│  │  └─────────────┘  └─────────────┘                │  │
│  └───────────────────────────────────────────────────┘  │
│                          ↓                               │
│  ┌───────────────────────────────────────────────────┐  │
│  │ Stage 2: test                                     │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌───────────┐ │  │
│  │  │ unit test   │  │ integ test  │  │ e2e test  │ │  │
│  │  └─────────────┘  └─────────────┘  └───────────┘ │  │
│  └───────────────────────────────────────────────────┘  │
│                          ↓                               │
│  ┌───────────────────────────────────────────────────┐  │
│  │ Stage 3: deploy                                   │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌───────────┐ │  │
│  │  │ deploy dev  │  │deploy stage │  │deploy prod│ │  │
│  │  └─────────────┘  └─────────────┘  └───────────┘ │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

4. Artifact vs Cache

特性 Artifact(产物) Cache(缓存)
用途 构建输出(jar 包、二进制文件) 依赖缓存(node_modules、.m2)
传递性 可以传递给下游 Job 不传递,仅当前 Job 使用
存储位置 GitLab Server Runner 本地或共享存储
保留时间 可配置(expire_in) 直到被覆盖或删除
下载 可以从 GitLab UI 下载 不可下载
示例 target/*.jar .m2/repository/, node_modules/

Artifact 配置示例

artifacts:
  paths:
    - target/*.jar
    - dist/
  exclude:
    - target/test-classes/
  expire_in: 30 days
  when: always  # always/success/failure
  reports:
    junit: '**/test-results/*.xml'
    coverage_report:
      coverage_format: cobertura
      path: '**/coverage/cobertura-coverage.xml'

Cache 配置示例

cache:
  key: "$CI_COMMIT_REF_SLUG"  # 按分支缓存
  paths:
    - .m2/repository/
    - node_modules/
  policy: pull-push  # pull/push/pull-push

【面试官考察点】

  1. 配置熟悉度:是否熟悉.gitlab-ci.yml 语法
  2. 概念理解:能否区分 Artifact 和 Cache
  3. 实践经验:是否有实际配置 GitLab CI 的经验

💡 加分回答

  • 说明 Auto DevOps 功能(GitLab 自动检测项目类型并生成 CI 配置)
  • 提到 Review Apps(为每个 MR 创建临时环境)
  • 分享多环境部署的最佳实践

【延伸知识】

  • Auto DevOps:GitLab 自动 CI/CD 配置
  • Review Apps:为 MR 创建临时预览环境
  • Environments:环境管理功能
  • Merge Trains:合并列车,避免频繁重跑 CI


9. GitHub Actions 的核心概念有哪些?

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


【核心答案】

GitHub Actions 核心概念:

  1. Workflow - 工作流程,定义完整的 CI/CD 流程
  2. Event - 触发事件(push、pull_request、schedule 等)
  3. Job - 任务,Workflow 中的执行单元
  4. Step - 步骤,Job 中的具体操作
  5. Action - 可复用的步骤(预定义或自定义)
  6. Runner - 执行器,运行 Job 的服务器

【详细解析】

1. Workflow 工作流

完整示例

# .github/workflows/ci.yml
name: CI Pipeline

# 触发事件
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'  # 每天凌晨 2 点
  workflow_dispatch:  # 手动触发

# 环境变量
env:
  NODE_VERSION: '18'
  REGISTRY: ghcr.io

# 任务定义
jobs:
  # ========== Job 1: Build ==========
  build:
    name: Build and Test
    runs-on: ubuntu-latest
    timeout-minutes: 30
    
    # 矩阵策略(多版本测试)
    strategy:
      matrix:
        node-version: [16, 18, 20]
        os: [ubuntu-latest, macos-latest]
      fail-fast: false
    
    steps:
      # Step 1: 拉取代码
      - name: Checkout code
        uses: actions/checkout@v3
      
      # Step 2: 设置 Node.js 环境
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      
      # Step 3: 安装依赖
      - name: Install dependencies
        run: npm ci
      
      # Step 4: 运行代码检查
      - name: Run linter
        run: npm run lint
      
      # Step 5: 运行测试
      - name: Run tests
        run: npm test
      
      # Step 6: 构建项目
      - name: Build
        run: npm run build
      
      # Step 7: 上传构建产物
      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-output-${{ matrix.node-version }}
          path: dist/
          retention-days: 7

  # ========== Job 2: Deploy ==========
  deploy:
    name: Deploy to Production
    needs: build  # 依赖 build job
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'  # 仅 main 分支
    
    environment:
      name: production
      url: https://www.example.com
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Download artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-output-18
          path: dist/
      
      - name: Deploy to production
        run: ./deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

2. Event 触发事件

事件类型 说明 示例
push 代码推送 on: push: branches: [main]
pull_request PR 事件 on: pull_request: branches: [main]
release 发布事件 on: release: types: [published]
schedule 定时触发 on: schedule: - cron: '0 2 * * *'
workflow_dispatch 手动触发 on: workflow_dispatch
repository_dispatch 外部触发 on: repository_dispatch

3. Action 市场

常用官方 Action

  • actions/checkout@v3 - 拉取代码
  • actions/setup-node@v3 - 设置 Node.js 环境
  • actions/setup-python@v4 - 设置 Python 环境
  • actions/upload-artifact@v3 - 上传产物
  • actions/download-artifact@v3 - 下载产物
  • actions/cache@v3 - 缓存依赖

使用社区 Action

steps:
  # 使用 Docker 登录
  - uses: docker/login-action@v2
    with:
      registry: ghcr.io
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}
  
  # 构建和推送 Docker 镜像
  - uses: docker/build-push-action@v4
    with:
      context: .
      push: true
      tags: ghcr.io/${{ github.repository }}:latest

4. Runner 执行器

类型 说明 优点 缺点
GitHub-hosted GitHub 提供的运行器 免维护,预装常用工具 有使用限制,成本较高
Self-hosted 自托管运行器 完全控制,成本可控 需要自行维护

自托管 Runner 安装

# 在 GitHub 项目 Settings → Actions → Runners
# 下载并运行
./config.sh --url https://github.com/owner/repo --token TOKEN

# 启动 Runner
./run.sh

【GitHub Actions vs GitLab CI】

特性 GitHub Actions GitLab CI
配置文件 .github/workflows/*.yml .gitlab-ci.yml
市场生态 丰富(官方 + 社区) 较少
免费额度 公共仓库无限,私有仓库 2000 分钟/月 公共仓库无限,私有仓库 400 分钟/月
自托管 支持 支持
矩阵构建 strategy.matrix parallel

【面试官考察点】

  1. 配置熟悉度:是否熟悉 Workflow 语法
  2. 生态了解:是否知道常用 Action
  3. 对比分析:能否对比不同 CI 工具

💡 加分回答

  • 提到 GitHub Actions 的市场生态优势
  • 说明如何创建自定义 Action
  • 分享使用 Secrets 管理敏感信息的经验

【延伸知识】

  • 自定义 Action:使用 JavaScript 或 Docker 创建
  • Composite Actions:组合多个步骤
  • Reusable Workflows:可重用的工作流

(第 10-17 题继续...)

发表回复

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