百度APP 基于Pipeline as Code的持续集成实践

全文8150字,预计阅读时间21分钟

一、概述

百度APP 经过多年 DevOps 的建造,现已形成了一套从计划、开发、测验、集成到交付的规范作业流和东西集。其中,继续集成(Continuous integration,简称 CI)作为 DevOps 最中心的流程之一,经过频繁地将代码集成到主干和生产环境以履行预置的自动化使命。

CI一直是咱们百度移动研制渠道——Tekes,支撑百度APP研制流程的重要的切入点,咱们的自动化研制流程(组件自动发布、准入等)都是依据CI的实践,现已支持 百度APP 发版50+次,准入40w+组件和SDK。但当这些自动化研制流程输出到其他产品线时,却遇到了一个问题:不同产品线对研制流程存在定制化需求,例如 百度APP 在中台化组件发布前需求检查API及依赖的改变情况,并在有不兼容改变的情况产生时发起人工批阅;而 好看APP 只需求将不兼容改变展示在发布完成后的报表之中,这就导致了咱们预置的流水线模板无法直接复用。为了处理这个问题,一种可行的办法是让产品线用结构化的语言去描绘他们研制流程需求的一组功能或特性,然后依据描绘自动化地生成对应的流水线,这种思想其实便是 Pipeline as Code(流水线即代码,PaC)。

百度APP 基于Pipeline as Code的持续集成实践

二、Pipeline as Code

Pipeline as Code 是 “as Code” 运动的一种 ,引证 Gitlab 官网对 Pipeline as Code 的解释:

Pipeline as code is a practice of defining deployment pipelines through source code, such as Git. Pipeline as code is part of a larger “as code” movement that includes infrastructure as code. Teams can configure builds, tests, and deployment in code that is trackable and stored in a centralized source repository. Teams can use a declarative YAML approach or a vendor-specific programming language, such as Jenkins and Groovy, but the premise remains the same.

百度APP 基于Pipeline as Code的持续集成实践

提到 “as Code” ,咱们最简略想到 Infrastructure as Code (根底设施即代码,IaC) , IaC 是将根底设施、资源及环境运用 DSL(Domain Specified Language,领域专有语言)编码,例如 Ansible 的 playbook 便是一种依据YML 的 DSL, 一个在 macOS 体系上规范化装置 Xcode 的 playbook 简略示例如下:

# playbook
- name: Install Xcode
  block:
    - name: check that the xcode archive is valid
      command: >
        pkgutil --check-signature {{ xcode_xip_location }} |
        grep \"Status: signed Apple Software\"
    - name: Clean up existing Xcode installation
      file:
        path: /Applications/Xcode.app
        state: absent
    - name: Install Xcode from XIP file Location
      command: xip --expand {{ xcode_xip_location }}
      args:
        chdir: /Applications
      poll: 5
      async: "{{ xcode_xip_extraction_timeout }}" # Prevent SSH connections timing out waiting for extraction
    - name: Accept License Agreement
      command: "{{ xcode_build }} -license accept"
      become: true
    - name: Run Xcode first launch
      command: "{{ xcode_build }} -runFirstLaunch"
      become: true
      when: xcode_major_version | int >= 13
  when: not xcode_installed or xcode_installed_version is version(xcode_target_version, '!=')

好像 Ansible,咱们也运用了相似的方案对流水线进行 DSL 编码并纳入版别操控,除了能处理咱们遇到的不同产品线差异化装备的问题,他还有很多其他长处,例如:

  1. 让产品线团队只需求关注流水线当时版别的DSL,方便团队内部成员一起保护和晋级;

  2. 流水线本身的环境装备也是DSL的一部分,消除流水线环境因为装备混乱形成的特异性;

  3. DSL十分简略仿制和链接代码片段,能够将CI脚本组件化之后作为一种可装备的DSL单元。

不过在介绍咱们的方案之前,咱们先介绍下业界比较有代表性的两种方案:Jenkins Pipeline Github Actions

Jenkins Pipeline 是 Jenkins2.0 推出的一套Groovy DSL语法,将原本独立运转于多个Job或许多个节点的使命一致运用代码的方式进行办理和保护。

百度APP 基于Pipeline as Code的持续集成实践

GitHub Actions 是 GitHub 推出的自动化服务,经过在库房装备一个依据 YML 的 DSL 文件来创立一个作业流,当库房触发某个事情的时候,作业流就会运转。

百度APP 基于Pipeline as Code的持续集成实践

咱们举一个简略的 Xcode 工程 编译的例子来体会两者 DSL 语法的差异,包含三个步骤:

  1. Checkout:从 Git 服务器下拉源码

  2. Build:履行 xcodebuild 编译指令

  3. UseMyLibrary:引证自定义的脚本办法

Jenkins Pipeline 的 DSL 如下:

// Jenkinsfile(Declarative Pipeline)
@Library('my-library') _
pipeline {
  agent {
    node {
      label 'MACOS'
    }
  }
  stages {
    stage('Checkout') {
      steps {
        checkout scm
      }
    }
    stage('Build') {
      steps {
        sh 'xcodebuild -workspace projectname.xcworkspace -scheme schemename -destination generic/platform=iOS'
      }
    }
    stage('UseMyLibrary') {
      steps {
        myCustomFunc 'Hello world'
      }
    }
  }
}

Github Actions 的 DSL 如下:

# .github/workflows/ios.yml
name: iOS workflow
on: [push]
jobs:
  build:
    runs-on: macos-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Build
        run: xcodebuild -workspace projectname.xcworkspace -scheme schemename -destination generic/platform=iOS
      - name: UseMyLibrary
        uses: my-library/my-custom-action@master
        with:
            args: Hello world

能够看到两者 DSL 的表述都十分明晰简练,很多语法甚至能够相互转化,实际上 DSL 不是 PaC 首要的选型依据,而是看事务运用何种继续集成体系。

Jenkins 是一个彻底自保管的继续集成体系,运用 Groovy 脚原本定义 Pipeline 也供给了极大的灵活性。而 Github Actions 与 Github 高度整合,能够直接运用,并且 Workflow 的 YML 是一种组件化规划 ,结构明晰,语法简略。能够说两者各有千秋。咱们事务运用的继续集成体系是百度自研的 iPipe,咱们在做 PaC 的实践时第一原则是运用公司的根底设施,避免重复造轮子,因而咱们采取了一种创新的方案和体系——Tekes Actions。

三、Tekes Actions

从命名能够看出 Tekes Actions 参照了 Github Actions ,DSL语法也根本照搬 Github Actions,例如 百度APP 组件发布流程的 DSL如下:

# baiduapp/ios/publish.ymlname: 'iOS Module Publish Workflow'author: 'zhuyusong'description: 'iOS 组件发布流程'on:  events:    topic_merge:      branches: ['master', 'release/**']      repositories: ['baidu/baidu-app/*', 'baidu/third-party/*']jobs:  publish:    name: 'publish modules using Easybox'    runs-on: macos-latest    steps:    - name: 'Checkout'      uses: actions/checkout@v2    - name: 'Setup Easybox'      uses: actions/setup-easybox@v2      with:        is_public_storage: true    - name: 'Build Task use Easybox'        uses: actions/easybox-ios-build@v1with:          component_check: true          quality_check: true    - name: 'Publish Task use Easybox'        uses: actions/easybox-ios-publish@v1    - name: 'Access Task use Easybox'        uses:actions/easybox-ios-access@v1

其实上一节提到了 DSL 本身不是 PaC 最关键的点,咱们选用 Github Actions 的 DSL 主要原因是因为其组件化的作业流程能够很好地与咱们Tekes渠道以及公司的继续集成体系进行交融。

咱们先介绍 Github Actions 官方文档的作业流程,附上示意图:

百度APP 基于Pipeline as Code的持续集成实践

作业流程包含一个或许多个作业(Job),由事情(Event)触发,在Github保管或自保管的运转器(Runner)上运转**。**下面介绍这几个中心组件:

1. 作业:每个作业由一组步骤(Step)组成,每个Step是能够运转操作(Action)或许 Shell指令的单个使命,Action 能够看作封装的独立脚本,能够自定义或引证第三方。

2. 事情:事情是触发作业流程的特定活动,例如推送代码、拉取分支、创立问题等。原本Github就供给了十分丰富类型,能够很方便地作为Github Actions的触发源。

3. 运转器:运转器是运转触发后的作业流程的服务,GitHub 供给 Linux、Windows 和 macOS 虚拟机环境的运转器,也能够创立自保管运转器运转在自定义的环境中。

Action 是组成作业流程最中心最根底的元素,能够说正是因为有了 Action 这种可复用扩展的规划,给 Github Actions 生态带来了极大的活力,Github 不仅供给了许多官方的 Action, 并且还搭建了一个 Action 市场,能够搜索到各种三方 Action,让完成一个作业流程变得十分简略。

Tekes Actions 在作业、事情和运转器这三个组件上都有自己特征的规划:

作业上,Tekes 经过 Action 可复用扩展的这种思想,把自己多年建造的CI脚本分解成Tekes官方的 Action,让产品线能够自在插拔到自己的研制流程中,完成定制化流水线;一起开放出 CI 的能力,搭建了一个揭露的 Action制品库,支持 保障组件化、质量、功能等人物 能够上传自己的Action,一起树立Tekes生态。

事情上,因为Tekes 在建造移动DevOps服务时抽象了自己的事情类型,咱们将此作为 Tekes Actions 作业流程的触发源。并且因为咱们的事情并不是和库房一对一的联系,咱们还规划了一个产品线流程编排的服务和事情处理的服务,前者用来协助产品线办理作业流程的DSL文件,后者用来裁决何种事情应该触发何种产品线的何种作业流程。

运转器上,咱们彻底完成了运转器用来解释履行作业流程的DSL,包含监听触发事情,调度Job,下载Action,履行脚本,上传日志等功能,并支持 Cli 指令本地调用,方便流水线开发者在自己本地的作业区中进行调试;一起远端的运转器跑在咱们虚拟机集群,当作业流程触发时咱们经过 iPipe Agent 去调度。

iPipe Agent 是一个依据 iPipe 的代理服务,能够直接调度到咱们的虚拟机集群并分配一台全新的包含指定体系和运转器的虚拟机。

整个 Tekes Actions 的工程架构如下图所示:

百度APP 基于Pipeline as Code的持续集成实践

工程上,Tekes Actions 采用了一个彻底依据百度云的 Serverless 服务,中心的事情处理服务只是一个云函数服务,这个云函数服务负责处理两种事情源:

1. 流程编排。产品线创立和更新作业流程时会生成一个 YML 文件,上传到 DSL文件服务 的该产品线的目录下,DSL文件服务 的文件新增和更新事情会通知云函数,用来新增和更新数据库服务存储的触发规矩;

2. 事情。例如DevOps服务产生的组合入事情,这些事情会发布到音讯服务特定的 topic 中,云函数订阅这个topic以接纳事情,用来匹配数据库服务存储的各个产品线作业流程触发规矩,并在匹配成功时调度一个运转器。

四、Tekes Runner

Tekes Runner 是运转 Tekes Actions 作业流程的东西,架构图如下:

百度APP 基于Pipeline as Code的持续集成实践

Tekes Runner 服务层由 Template、Worker 和 WebAPI 三个模块组成,别离负责读取和校验DSL文件、办理作业流程以及与后端服务通讯,其中 WebAPI 模块是能够在装备文件中封闭的。

Tekes Runner 在交互层的指令模块供给了四种 Cli 指令,别离是 Run、Pause、Unpause 和 Kill, 都是办理作业流程生命周期的指令。作业流程生命周期如下图所示:

百度APP 基于Pipeline as Code的持续集成实践

当履行 Run 指令时, Tekes Runner 首先会依据装备来初始化 Tekes Actions 作业流程,包含读取对应作业流程的YML,下载并校验依赖的 Action,创立作业区等,成功之后进入 initialized 状况。

接着会依据解析后的 作业流程目标 创立一组状况机并运转,每一个作业对应一个简略的有限状况机(Finite State Machine, FSM),此刻进入到 running 状况。

百度APP 基于Pipeline as Code的持续集成实践

状况机的现态是当时履行的阶段,触发状况迁移的事情是脚本运转的成果。

当整个状况机组的一切状况机都迁移到完毕状况(无论是否成功),作业流程就会完毕,进入 stopped 状况。此外,如果作业流程超时或许接纳到外部的 Kill 指令,也会进入 stopped 状况。

现在咱们现已完成了一个能够在本地运转脚本的简略运转器了,但还有几个细节需求进一步论述:

丨细节1. Action和 Runner 的交互办法

Action 和 Runner 交互是一个十分常见的行为,例如从Runner获取输入写入输出,又或许将履行成果通知Runner。

Action和 Runner 有三种交互办法:

  1. 环境变量:Runner将需求传递给Action的参数写入环境变量中,一般包含Action所需的输入以及一些上下文;

  2. 作业区文件:Runner将需求传递给Action的文件放入作业区特定的文件夹中,一般是Action所需的中心产品;

  3. Action 的打印:Runner 在履行 Action 的过程中会不断监听 Action 的打印内容,Runner 和 Action 约定了一套带有特殊指令标识符的打印句子,当Runner监听到此类句子时会解析并履行预设的指令,包含设置输出,打印日志和上传产品等。

丨细节2. Pause/Unpause 的效果

作业流程有时候不可避免会插入人工批阅、代码评定等需求长期等待的使命,例如组件准入产品线需求产品线负责人批阅。如果作业流程没有暂停状况,那就意味着有一个 Action 一直阻塞着线程或许不断地轮询,这对资源无疑是一个巨大的糟蹋。

当履行 Pause 指令时, Runner 会耐久化当时的上下文并完毕进程。相对的,当履行 Unpause 指令时,Runner会从耐久化中康复上下文,然后继续运转当时的阶段。

丨细节3. WebAPI 的效果

在大多数情况下,Runner 是履行在远端的虚拟机中,由 ipipe agent调度,并在履行完作业流程后被回收。因而需求一个机制将本地的日志和耐久化上下文保存到一个服务中,这其实便是 WebAPI 的效果。

回顾一下 Tekes Actions 的工程架构图,日志服务其实就担任着保存作业流程日志和上下文的人物,其他的下流服务还能够经过咱们的日志服务去查询作业流程履行的情况和详细的日志。

四、结语

Pipeline as Code 既是一种高效的流水线办理方式,也是 CI/CD 转变成 DevOps 的一种新的趋势。借助于 PaC,给整个流水线带来的不可思议的灵活性,也给团队环绕流水线的建造、交流和协作带来了有利的改变。

建造好 PaC 需求一些前置的依赖,包含云原生渠道和继续集成东西,咱们 Tekes 也是依据公司强壮的根底设施加上本身丰富的继续集成实践,然后参阅了业界成熟的方案,站在伟人的肩膀上伸手摘星。希望咱们的这篇文章能够给大家在处理继续集成问题时带来一定借鉴意义。

参阅

[1] 继续集成维基百科

zh.wikipedia.org/zh-sg/%E6%8…

[2]What is CI/CD

www.redhat.com/zh/topics/d…
[3]What is pipeline as code

about.gitlab.com/topics/ci-c…
[4]Pipeline as Code

www.jenkins.io/doc/book/pi…
[5]流水线即代码

insights.thoughtworks.cn/pipeline-as…
[6]解读根底设施即代码

insights.thoughtworks.cn/nfrastructu…
[7]Ansible权威攻略

ansible-tran.readthedocs.io/en/latest/d…
[8]How to Create a Jenkins Shared Library

https//www.tutorialworks.com/jenkins-sha…
[9]Migrating from Jenkins to GitHub Actions

docs.github.com/cn/actions/…
[10]Compare and contrast GitHub Actions and Azre Pipelines

docs.microsoft.com/en-us/dotne…

推荐阅读:

Go 语言运用 MySQL 的常见故障分析和应对办法

百度交易中台之钱包体系架构浅析

依据宽表的数据建模应用

百度谈论中台的规划与探索

依据模板装备的数据可视化渠道

怎么正确的评测视频画质

小程序发动功能优化实践

咱们是怎么穿过低代码 “⽆⼈区”的:amis与爱速搭中的关键规划