百度APP 基于Pipeline as Code的持续集成实践
The following article is from 百度App技术 Author 煜松 郭金
一、概述
百度APP 经过多年 DevOps 的建设,已经形成了一套从计划、开发、测试、集成到交付的标准工作流和工具集。其中,持续集成(Continuous integration,简称 CI)作为 DevOps 最核心的流程之一,通过频繁地将代码集成到主干和生产环境以执行预置的自动化任务。
CI一直是我们百度移动研发平台——Tekes,支撑百度APP研发流程的重要的切入点,我们的自动化研发流程(组件自动发布、准入等)都是基于CI的实践,已经支持 百度APP 发版50+次,准入40w+组件和SDK。但当这些自动化研发流程输出到其他产品线时,却遇到了一个问题:不同产品线对研发流程存在定制化需求,例如 百度APP 在中台化组件发布前需要检查API及依赖的变更情况,并在有不兼容变更的情况发生时发起人工审批;而 好看APP 只需要将不兼容变更展示在发布完成后的报表之中,这就导致了我们预置的流水线模板无法直接复用。为了解决这个问题,一种可行的方法是让产品线用结构化的语言去描述他们研发流程需要的一组功能或特性,然后根据描述自动化地生成对应的流水线,这种思想其实就是 Pipeline as Code(流水线即代码,PaC)。
二、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.
# 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 编码并纳入版本控制,除了能解决我们遇到的不同产品线差异化配置的问题,他还有很多其他优点,例如:
“
让产品线团队只需要关注流水线当前版本的DSL,方便团队内部成员共同维护和升级;
流水线本身的环境配置也是DSL的一部分,消除流水线环境由于配置混乱造成的特异性;
DSL非常容易复制和链接代码片段,可以将CI脚本组件化之后作为一种可配置的DSL单元。
我们举一个简单的 Xcode 工程 编译的例子来体会两者 DSL 语法的区别,包含三个步骤:
“
Checkout:从 Git 服务器下拉源码
Build:执行 xcodebuild 编译命令
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 如下:
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
# baiduapp/ios/publish.yml
name: '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@v1
with:
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
我们先介绍 Github Actions 官方文档的工作流程,附上示意图:
工作流程包含一个或者多个作业(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,让实现一个工作流程变得非常简单。
iPipe Agent 是一个基于 iPipe 的代理服务,可以直接调度到我们的虚拟机集群并分配一台全新的包含指定系统和运行器的虚拟机。
整个 Tekes Actions 的工程架构如下图所示:
1. 流程编排。产品线创建和更新工作流程时会生成一个 YML 文件,上传到 DSL文件服务 的该产品线的目录下,DSL文件服务 的文件新增和更新事件会通知云函数,用来新增和更新数据库服务存储的触发规则;
四、Tekes Runner
Tekes Runner 是运行 Tekes Actions 工作流程的工具,架构图如下:
状态机的现态是当前执行的阶段,触发状态迁移的事件是脚本运行的结果。
当整个状态机组的所有状态机都迁移到结束状态(无论是否成功),工作流程就会结束,进入 stopped 状态。此外,如果工作流程超时或者接收到外部的 Kill 命令,也会进入 stopped 状态。
现在我们已经实现了一个可以在本地运行脚本的简单运行器了,但还有几个细节需要进一步阐述:
丨细节1. Action和 Runner 的交互方式
1. 环境变量:Runner将需要传递给Action的参数写入环境变量中,一般包括Action所需的输入以及一些上下文;
2. 工作区文件:Runner将需要传递给Action的文件放入工作区特定的文件夹中,一般是Action所需的中间产物;
3. Action 的打印:Runner 在执行 Action 的过程中会不断监听 Action 的打印内容,Runner 和 Action 约定了一套带有特殊命令标识符的打印语句,当Runner监听到此类语句时会解析并执行预设的命令,包括设置输出,打印日志和上传产物等。
丨细节2. Pause/Unpause 的作用
丨细节3. WebAPI 的作用
Pipeline as Code 既是一种高效的流水线管理形式,也是 CI/CD 转变成 DevOps 的一种新的趋势。借助于 PaC,给整个流水线带来的不可思议的灵活性,也给团队围绕流水线的建设、沟通和协作带来了有益的变化。
[3] What is pipeline as code
[4] Pipeline as Code
[5] 流水线即代码
[6] 解读基础设施即代码
[7] Ansible权威指南
[8] How to Create a Jenkins Shared Library
[9] Migrating from Jenkins to GitHub Actions
[10] Compare and contrast GitHub Actions and Azre Pipelines
推荐阅读:
百度交易中台之钱包系统架构浅析
基于宽表的数据建模应用
百度评论中台的设计与探索
小程序启动性能优化实践
我们是如何穿过低代码 “⽆⼈区”的:amis与爱速搭中的关键设计
一键三连,好运连连,bug不见👇