谈到到 DevOps,继续交付流水线是绕不开的一个话题,绝对于其余实际,通过流水线来实现疾速高质量的交付价值是绝对能疾速奏效的,特地对于开发测试人员,可能取得实实在在的收益。很多文章介绍流水线,不论是 jenkins,gitlab-ci, 流水线,还是 drone,github action 流水线,文章都很多,然而不论什么工具,流水线设计的思路是统一的。于此同时,在实际过程中,发现大家对流水像有些误区,不是一大堆流水线,就是一个流水线调一个超级简单的脚本,各种硬编码和环境依赖,所以心愿通过这篇文章可能给大家分享本人对于 Pipeline 流水线的设计心得体会。
概念
- 继续集成(Continuous Integration,CI)
继续集成(CI)是在源代码变更后自动检测、拉取、构建和(在大多数状况下)进行单元测试的过程
对我的项目而言,继续集成(CI)的指标是确保开发人员新提交的变更是好的,不会产生 break build; 并且最终的骨干分支始终处于可公布的状态,
对于开发人员而言,要求他们必须频繁地向骨干提交代码,相应也能够即时失去问题的反馈。实时获取到相干谬误的信息,以便疾速地定位与解决问题
显然这个过程能够大大地进步开发人员以及整个 IT 团队的工作效率,防止陷入好几天得不到好的“部署产出”,影响后续的测试和交付。
- 继续交付(Continuous Delivery,CD)
继续交付在继续集成的根底上,将集成后的代码部署到更贴近实在运行环境的「预公布环境」(production-like environments)中。交付给品质团队或者用户,以供评审。如果评审通过,代码就进入生产阶段 继续交付并不是指软件每一个改变都要尽快部署到产品环境中,它指的是任何的代码批改都能够在任何时候实时部署。
强调:1、手动部署 2、有部署的能力,但不肯定部署
- 继续部署(Continuous Deployment, CD)
代码通过评审之后,主动部署到生产环境中。继续部署是继续交付的最高阶段。
强调 1、继续部署是主动的 2、继续部署是继续交付的最高阶段 3、继续交付示意的是一种能力,继续部署则是一种形式
流水线的编排设计
参考:https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html
这里十分举荐以版本控制系统为源的构建流水线设计,从每一位开发人员提交代码即可对以后提交代码进行查看编译构建,尽快将谬误反馈给每位提交人员。
对于 DevOps 流水线,次要是由各类工作串联起来,而对于工作自身又分为两张类型,一种是自动化工作,一种是人工执行工作。具体如下:
- 自动化工作:包含了代码动态查看,构建,打包,部署,单元测试,环境迁徙,自定义脚本运行等。
- 人工工作:人工工作次要包含了查看审核,打标签基线,组件包制作等相似工作。
而通常咱们看到的流水线根本都由上述两类工作组合编排而成,一个流水线能够是齐全自动化执行,也能够两头退出了人工干预节点,在人工干预解决后再持续朝下执行。比方流水线中到了测试部署实现后,能够到测试环境人工验证环节,只有人工验证通过再流转到迁徙公布到生产环境动作工作。
DevOps 流水线实际上和咱们原来常常谈到的继续集成最佳实际是相当相似的,较大的一个差别点就在于引入了容器化技术来实现自动化部署和利用托管。至于在 DevOps 实际中,是否必须马上将我的项目切换到微服务架构框架模式,反而不是必须得。
在整个 DevOps 流水线中,咱们实际上强调个一个关键点在于“一套 Docker 镜像文件 + 多套环境配置 + 多套构建版本标签”做法。以确保咱们最终构建和测试通过的版本就是咱们部署到生产环境的版本。
构建操作只有一次,而前面到测试环境,到 UAT 环境,到生产环境,都属于是镜像的环境迁徙和部署。而不波及到须要再次从新打包的问题。这个是继续集成,也是 DevOps 的根本要求。
流水线工作的标准化 / 原子化
明天谈 DevOps 流水线编排,次要是对流水线编排自身的灵活性进一步思考。
- 构建操作:构建咱们通常采纳 Maven 进行自动化构建,构建实现输入一个或多个 Jar 包或 War 包。
留神惯例形式下构建完执行进行部署操作,部署操作个别就是将构建的后果拷贝到咱们的测试环境服务器,同时对初始化脚本进行启动等。而在 DevOps 下,该操作会变成两个操作,即一个打包,一个部署。打包是将构建实现的内容制作为镜像,部署是将镜像部署到具体的资源池和指定集群。
- 打包镜像操作:实际上即基于构建实现的部署包来生成镜像。该操作个别首先基于一个根底镜像文件根底上进行,在根底镜像文件上拷贝和写入具体的部署包文件,同时在启动相应的初始化脚本。
那么首先要思考构建操作和打包操作如何松耦合开,打包操作简略来就是就是一个镜像制作,须要的是构建操作产生的输入。咱们能够对其输入和须要拷贝的内容在构建的时候进行约定。而打包工作则是一个标准化的镜像制作工作,咱们须要思考的仅仅是基于
1)基于哪个根底镜像
2)中间件容器默认目录设置
3)初始化启动命令。
即在理论的打包工作设计的时候,咱们不会指定具体的部署包和部署文件,这个齐全由编排的时候由上游输出。
- 部署操作:部署操作相当更加简略,重点就是将镜像部署到哪个资源池,哪个集群节点,初始化的节点配置等。具体部署哪个镜像不要指定,而是由上游工作节点输出。
工作节点间松耦合设计的意义
这种松耦合设计才可能使流水线编排更加灵便。比方咱们在进行了构建打包后,咱们心愿同时讲打包内容部署到开发环境和测试环境。那么则是打包动作实现后须要对接两个利用部署工作。这两个部署工作都依靠下面的打包后果进行自动化部署,能够并行进行。
对于测试环境部署实现后,咱们须要进行测试人员手工验证测试,如果测试通过,咱们打标签后心愿可能间接公布到 UAT 环境。而这种操作咱们也心愿在一个流水线来设计和实现。这样咱们更加容易在继续集成看板上看到整个版本构建和迁徙的残缺过程。如果这是在一个大流水线外面,那么对于 UAT 环境部署工作就须要始终去追溯流水线上的最近的一个打包工作节点,同时取该工作节点产生的输入来进行相应的环境部署操作。
在谈 DevOps 的时候,一个重点就是和 QA/QC 的协同,因而在流水线编排的时候肯定要思考各类测试节点,包含动态代码查看,自动化的单元测试,人工的测试验证。同时最好基于继续集成实际,可能将测试过程和整个自动化构建过程紧密结合起来。
简略来说,测试人员发现 build1.0.0001 版本 4 个 bug 并提交,那么在下次自动化构建实现并单元测试通过后,测试人员可能很分明的看到哪些 Bug 曾经批改并能够在新构建的版本进行验证。只有这样才可能造成闭环,整个流水线作业才可能更好的施展协同作用。
流水线中蕴含的工程实际
流水线除了工作步骤的编排,更重要的外围是最佳工程实际的体现。过来传统的思维,自动化就是写个 shell/python 脚本批量执行,在 DevOps/ 微服务时代,这一招太 out 了,每种工程实际的背地都有须要解决的问题,通过在流水线设计中注入最佳的工程实际,能够让流水线的价值最大化,也让流水线更高级不是嘛。
- 版本控制 – 解决的问题:需要和代码的关系,版本变动的跟踪
- 最优的分支策略 – 解决的问题:版本公布和团队合作,某些状况会和环境有关系
- 代码动态扫描 – 解决的问题:开发标准和平安的问题
- 80% 以上的单元测试覆盖率 – 解决的问题:代码性能品质的问题,让测试左移
- 破绽(Vulnerability)扫描 – 解决的问题:部署环境 / 产品安全的问题
- 开源工具扫描 – 解决的问题:解决供应链平安问题,别忘了 log4j
- 制品(Artifact)版本控制 – 解决的问题:制品的版本控制,制品的升级,某些状况下环境的回滚
- 环境主动创立 – 解决的问题:解决的是构建 / 部署环境一致性的问题,开发测的好好的,测试一验证怎么不行啊,容器化 / 云原生让这个问题更好的解决
- 不可变服务器(Immutable Server)– 解决的问题:可能不好了解,打个比方如果如果你的服务器挂了,或者某次配置更改了服务就起不来了,应用不可变基础设施的次要益处是部署的简略性、可靠性和一致性,服务器能够随时替换上线
- 集成测试
- 性能测试
- 每次提交都触发:构建、部署和自动化测试 – 解决的问题:疾速失败,防止上游工夫的节约
- 自动化变更申请 – 解决问题:某些场景下通过状态变更触发某些动作
- 零停机公布 – 解决的问题:滚动 / 蓝绿 / 灰度公布等,用户无感知
-
性能开关 – 解决的问题:骨干开发中,如果某个性能没凋谢完,就通过 on/off 某个个性来让稳固的性能上线;还有一个场景,比方某些面对消费者的广告网站,想看看本人某个性能客户是否细化,通过性能开关看看市场反馈,个别和 A / B 测试配合
基于场景设计流水线
是否须要一条残缺的流水线?流水线是越多越好,还是越少越好?
倡议依照场景来设计,一条流水线通吃所有流程是不事实的,搞了好多流水线(比方一个构建就一个流水线,一个复制操作就一个流水线)这些都是不可取的,保护老本微小,得失相当。流水线依照场景分类如下:
-
端到端自动化流水线
- 需要、代码构建、测试、部署环境内嵌自动化能力,每次提交都触发残缺流水线
- 提交阶段流水线(集体级)、
- 验收阶段流水线(团队级)、
- 部署阶段流水线(部署 / 公布)
- 流水线自动化触发,递次自动化 (制品) 升级;
- 流水线工作按需串行、并行、非凡场景下跳过执行
- 必要环节人工干预, e.g. 在手工测试、正式公布等环节导入手工确认环节,流水线牵引流动
1)提交流水线
过程如下:
- 提交即构建
- 编译单测打包代码质量检查
- 构建谬误第一工夫告诉提交人
以 Jenkins 实现为例,
通过 webhook 触发 CI 构建,首先配置 Jenkins 我的项目
- 应用 generic webhook 形式触发我的项目构建
- 配置构建触发器参数(获取 gitlab 返回的数据,比方分支、用户等信息)
- 配置构建触发器中的 token(确保惟一,倡议能够用项目名称)
- 配置触发器中的申请过滤(merge_request,opend)
其次是 Gitlab 的配置
- 我的项目 -》集成 -》新建 webhook
- 填写 webhook 地址?token=projectName
- MergeRequest 操作触发
剩下的就是编写 Jenkinsfile 了,上面列出几个关键点
1. 获取 gitlab 数据中的分支名称,作为本次构建的分支名称。
2. 获取 gitlab 数据中的用户邮箱,作为构建失败后告诉对象。
2)MR 流水线
过程如下:
- codereview
- 配置分支爱护
- 创立合并申请对将代码审查后果在评论区展示
- 由 assignUser 合并代码
合并流水线设计:合并流水线的步骤其实跟提交流水线很相似,然而在代码质量检查的步骤中严格要求查看品质阈的状态,当品质阈状态为谬误的时候,须要立刻失败并告诉发起人。
第一次设计
- 开发人员创立 MR 并指定 AssignUser。
- CI 工具开始对 MR 中的源分支进行编译构建打包代码查看。
- 构建胜利(代码品质没问题)在 MR 页面评论提示信息。
- 构建失败在 MR 页面评论失败信息
第二次设计(借助 GitlabCI)- 优化点:退出 MR 构建失败拦挡,胜利主动合并
- 我的项目配置当流水线胜利时能力 merge。
- 开发人员创立 MR 并指定 AssignUser。
- Jenkins 开始对 MR 中的源分支的最初一次 commit 状态改为 running。
- 而后进行编译构建打包代码查看。
- 构建胜利,更新最初一次 commit 的状态为 success。
-
构建失败,更新最初一次 commit 的状态为 faild。
3)SQL 公布流水线
除了代码有版本,其实 SQL 也有“版本的”,SQL 脚本的版本对于产品的降级回滚至关重要。
个别对 SQL 的集成,会蕴含如下因素
- 构建环节,对 SQL 语法进行查看,防止打进包里语法是错的;某些状况下,多个开发会写不同的增量脚本,最初公布时候须要做脚本的合并
- SQL 脚本的版本,某些状况下产品本身要用表来记录本身业务脚本的版本,通过产品版本来判断某些脚本是否应该被执行。
当然,也有其余数据库版本管理工具,比方 flyway 和 liquibase;
- Flyway 是独立于数据库的利用、治理并跟踪数据库变更的数据库版本管理工具。用艰深的话讲,Flyway 能够像 Git 治理不同人的代码那样,治理不同人的 sql 脚本,从而做到数据库同步。
- liquibase 只是在性能上和 Flyway 有差别
不论怎么样,它们底层的原理都是用另外的表记录 SQL 脚本的版本,降级更新是比拟版本差别,来决定是否执行。
python 自带的 model 模块 python manage.py makemigrations 同样在做相似 的事件
数据库版本治理
流水线的要害元素
不论你用什么 CI/CD 平台,开源的 Jenkins, GitLab CI, Teckton, Drone,还是商用的 Azure, 阿里云效等,不论是代码化,还是可视化,流水线蕴含的元素根本都差不多,上面通过不同的示例来阐明这些元素的作用和含意。
参考:
- https://docs.gitlab.com/ee/ci/pipelines/
- https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/key-pipelines-concepts?view=azure-devops
- https://www.jenkins.io/doc/book/pipeline/
Agent&Runner(执行代理)
image: "registry.example.com/my/image:latest" #gitlab-ci
pool:
vmImage: ubuntu-latest #auzure
agent {label 'linux'} //jenkins
agent {
docker {
image 'maven:3-alpine'
label 'Ubuntu'
args '-v /root/.m2:/root/.m2'
}
}
Parameter(参数变量)
-
流水线级别参数 (全局参数),范畴限于整个流水线运行时,可被整个流水线其余工作应用
- 内置全局参数 – 个别称为 built-in(预约义)variable, 有的平台成为环境变量
export CI_JOB_ID="50" export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a" export CI_COMMIT_SHORT_SHA="1ecfd275" export CI_COMMIT_REF_NAME="main" export CI_REPOSITORY_URL="https://gitlab-ci-token:[masked]@example.com/gitlab-org/gitlab-foss.git"
- BUILD_ID : 以后 build 的 id
- BUILD_NUM : 以后 build 的在 pipeline 中的 build num
- PIPELINE_NAME : pipeline 名称
- PIPELINE_ID: pipeline Id
- GROUP: pipeline 所属的 group 名称
- TRIGGER_USER: 触发 build 的 user(event 触发的为触发 gitlab event 的 user)
- STAGE_NAME: 以后运行的 stage 的名称
- STAGE_DISPLAY_NAME : 以后运行的 stage 的显示名称
- PIPELINE_URL : pipeline 在 ui 中的网页的链接
- BUILD_URL: build 在 ui 的网页链接
-
WORKSPACE: 以后 stage 运行的工作目录,通常用作拼接绝对路径
- 非内置全局参数
environment {
HARBOR_ACCESS_KEY = credentials('harbor-userpwd-pair') SERVER_ACCESS_KEY = credentials('deploy-userpwd-pair') GITLAB_API_TOKEN = credentials('gitlab_api_token_secret')
}
- 内部参数 - 个别作为运行时参数
variables:
TEST_SUITE:
description: “The test suite that will run. Valid options are: ‘default’, ‘short’, ‘full’.”
value: “default”
DEPLOY_ENVIRONMENT:
description: “Select the deployment target. Valid options are: ‘canary’, ‘staging’, ‘production’, or a stable branch of your choice.”
parameters([separator(name: "PROJECT_PARAMETERS", sectionHeader: "Project Parameters"),
string(name: 'PROJECT_NAME', defaultValue: 'vue-app', description: '项目名称') ,
string(name: 'GIT_URL', defaultValue: '[email protected]:devopsing/vuejs-docker.git', description: 'Git 仓库 URL') ,
])
- 步骤工作参数(局部参数)– 个别作为某个插件工作的输出参数,也能够应用上个工作的输入作为参数,范畴仅限于该工作内
-
加密变量 – 对非凡变量进行加密解决
secrets: DATABASE_PASSWORD: vault: production/db/[email protected] # translates to secret `ops/data/production/db`, field `password`
Step(步骤)
参考:https://docs.drone.io/pipeline/overview/
--- kind: pipeline type: docker name: default
-
name: backend
image: golang
commands:- go build
- go test
-
name: frontend
image: node
commands:- npm install
- npm run test
…
<a name="QjYXz"></a> ### Stage(阶段) 个别用于对多个工作(step)进行分组归类,便于管理
stage(‘Pull code’) {
steps { echo 'Pull code...' script {git branch: '${Branch_Or_Tags}', credentialsId: 'gitlab-private-key', url: '[email protected]:xxxx/platform-frontend.git' } }
}
<a name="FTs9v"></a> ### Trigger(触发器)
trigger:
- master
-
releases/*
trigger_pipeline:
stage: deploy
script: - ‘curl –fail –request POST –form token=$MY_TRIGGER_TOKEN –form ref=main “https://gitlab.example.com/api/v4/projects/123456/trigger/pipeline”‘
rules: -
if: $CI_COMMIT_TAG
environment: productiontrigger:
event:- promote
target: - production
trigger:
type: cron
cron: ‘/5 *’ #每 5 分钟执行一次<a name="le2yJ"></a> ### 制品归档 & 缓存(artifacts&cache)个别用于 CI 制品的归档,以及 CI 构建的缓存
archiveArtifacts artifacts: ‘target/*.jar’, fingerprint: true
- promote
job:
artifacts:
name: "$CI_JOB_NAME"
paths:
- binaries/
cache: &global_cache
key: $CI_COMMIT_REF_SLUG
paths:
- node_modules/
- public/
- vendor/
policy: pull-push
集成凭证(Credentials)
参考:
- https://www.cnblogs.com/FLY_DREAM/p/13888423.html
- https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#handling-credentials
次要用于 CI/CD 流水线对接内部工具,通过 token/pwd/private key 等形式连贯内部服务。个别须要在界面做些提前配置,生成 token 或者凭证 ID,将 ID 在 CI/CD yaml 或 jenkinsfile 中应用
withCredentials([usernamePassword(credentialsId: 'amazon', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
// available as an env variable, but will be masked if you try to print it out any which way
// note: single quotes prevent Groovy interpolation; expansion is by Bourne Shell, which is what you want
sh 'echo $PASSWORD'
// also available as a Groovy variable
echo USERNAME
// or inside double quotes for string interpolation
echo "username is $USERNAME"
}
Service(服务)
该元素利用于一些简单的场景,比方须要一种内部(公共)服务为流水线提供某种输出或者后果。
您能够将相互依赖的服务用于简单的作业,例如端到端测试,其中内部 API 须要与本人的数据库通信。
例如,对于应用 API 的前端应用程序的端到端测试,并且 API 须要数据库:
end-to-end-tests:
image: node:latest
services:
- name: selenium/standalone-firefox:${FIREFOX_VERSION}
alias: firefox
- name: registry.gitlab.com/organization/private-api:latest
alias: backend-api
- postgres:14.3
variables:
FF_NETWORK_PER_BUILD: 1
POSTGRES_PASSWORD: supersecretpassword
BACKEND_POSTGRES_HOST: postgres
script:
- npm install
- npm test
模板(Template)
参考:https://docs.drone.io/template/yaml/
某些平台会应用“模板“的概念,其实就是复用的思维,通过加载固定模板实现一些快捷动作
kind: template
load: plugin.yaml
data:
name: name
image: image
commands: commands
kind: pipeline
type: docker
name: default
steps:
- name: {{.input.name}}
image: {{.input.image}}
commands:
- {{.input.commands}}
执行逻辑管制
参考:https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html
stage('run-parallel') {
steps {
parallel(
a: {echo "task 1"},
b: {echo "task 2"}
)
}
}
stage('Build') {
when {environment name: 'ACTION_TYPE', value: 'CI&CD'}
steps {buildDocker("vue")
}
}
stages:
- build
- test
- deploy
image: alpine
build_a:
stage: build
script:
- echo "This job builds something quickly."
build_b:
stage: build
script:
- echo "This job builds something else slowly."
test_a:
stage: test
needs: [build_a]
script:
- echo "This test job will start as soon as build_a finishes."
- echo "It will not wait for build_b, or other jobs in the build stage, to finish."
test_b:
stage: test
needs: [build_b]
script:
- echo "This test job will start as soon as build_b finishes."
- echo "It will not wait for other jobs in the build stage to finish."
deploy_a:
stage: deploy
needs: [test_a]
script:
- echo "Since build_a and test_a run quickly, this deploy job can run much earlier."
- echo "It does not need to wait for build_b or test_b."
environment: production
deploy_b:
stage: deploy
needs: [test_b]
script:
- echo "Since build_b and test_b run slowly, this deploy job will run much later."
environment: production
门禁审批
参考:https://learn.microsoft.com/en-us/azure/devops/pipelines/release/deploy-using-approvals?view=azure-devops
pipeline {
agent any
stages {stage('Example') {
input {
message "Should we continue?"
ok "Yes, we should."
submitter "alice,bob"
parameters {string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
}
}
steps {echo "Hello, ${PERSON}, nice to meet you."
}
}
}
}
pool:
vmImage: ubuntu-latest
jobs:
- job: waitForValidation
displayName: Wait for external validation
pool: server
timeoutInMinutes: 4320 # job times out in 3 days
steps:
- task: [email protected]
timeoutInMinutes: 1440 # task times out in 1 day
inputs:
notifyUsers: |
[email protected]
instructions: 'Please validate the build configuration and resume'
onTimeout: 'resume'
部署流水线分步骤施行
说了这么多,如果从 0 开始写流水线呢,能够依照上面的步骤,从“点”到“线”联合业务须要串起来,适宜本人团队合作开发节奏的流水线才是最好的。
- 对 价值流 进行建模并创立简略的可工作流程
- 将构建和部署流程自动化
- 将单元测试和代码剖析自动化
- 将验收测试自动化
- 将公布自动化
留神的事项
开始写流水线须要留神一下几个方面,请思考进去
- 确定变量 – 哪些是你每次构建或者部署须要变动的,比方构建参数,代码地址,分支名称,装置版本,部署机器 IP 等,管制变动的,这样保障工作的可复制性,不要写很多 hardcode 进去
- 变量 / 命名的规范化,不要为了一时之快,最初换个机器 / 换个我的项目,流水线就不能玩了,还要再改
- 如果能够,最好是封装规范动作成为插件,甚至做成自研平台服务化,让更多团队受害
- 如果你还在用手动的形式配置流水线,请尽快切换到代码形式,不论是 jenkinsfile, 还是 yaml , 所有皆代码 也是 DevOps 提倡的。