DevOps

DevOps Pipeline设计的最佳实践

2022-10-25  本文已影响0人  DevOps在路上
谈到到DevOps,持续交付流水线是绕不开的一个话题,相对于其他实践,通过流水线来实现快速高质量的交付价值是相对能快速见效的,特别对于开发测试人员,能够获得实实在在的收益。很多文章介绍流水线,不管是jenkins,gitlab-ci, 流水线,还是drone, github action 流水线, 文章都很多,但是不管什么工具,流水线设计的思路是一致的。于此同时,在实践过程中,发现大家对流水像有些误区,不是一大堆流水线,就是一个流水线调一个超级复杂的脚本,各种硬编码和环境依赖,所以希望通过这篇文章能够给大家分享自己对于Pipeline流水线的设计心得体会。

概念

  1. 持续集成 (Continuous Integration,CI)

持续集成(CI)是在源代码变更后自动检测、拉取、构建和(在大多数情况下)进行单元测试的过程
对项目而言,持续集成(CI)的目标是确保开发人员新提交的变更是好的,不会发生break build; 并且最终的主干分支一直处于可发布的状态,
对于开发人员而言,要求他们必须频繁地向主干提交代码,相应也可以即时得到问题的反馈。实时获取到相关错误的信息,以便快速地定位与解决问题
显然这个过程可以大大地提高开发人员以及整个IT团队的工作效率,避免陷入好几天得不到好的“部署产出”,影响后续的测试和交付。

  1. 持续交付 (Continuous Delivery,CD)

持续交付在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「预发布环境」(production-like environments)中。交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段 持续交付并不是指软件每一个改动都要尽快部署到产品环境中,它指的是任何的代码修改都可以在任何时候实时部署。
强调: 1、手动部署 2、有部署的能力,但不一定部署

  1. 持续部署 (Continuous Deployment, CD)

代码通过评审之后,自动部署到生产环境中。持续部署是持续交付的最高阶段。
强调 1、持续部署是自动的 2、持续部署是持续交付的最高阶段 3、持续交付表示的是一种能力,持续部署则是一种方式

流水线的编排设计

参考: https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html
这里非常推荐以版本控制系统为源的构建流水线设计,从每一位开发人员提交代码即可对当前提交代码进行检查编译构建,尽快将错误反馈给每位提交人员。
对于DevOps流水线,主要是由各类任务串联起来,而对于任务本身又分为两张类型,一种是自动化任务,一种是人工执行任务。具体如下:

  1. 自动化任务:包括了代码静态检查,构建,打包,部署,单元测试,环境迁移,自定义脚本运行等。
  2. 人工任务:人工任务主要包括了检查审核,打标签基线,组件包制作等类似工作。

而通常我们看到的流水线基本都由上述两类任务组合编排而成,一个流水线可以是完全自动化执行,也可以中间加入了人工干预节点,在人工干预处理后再继续朝下执行。比如流水线中到了测试部署完成后,可以到测试环境人工验证环节,只有人工验证通过再流转到迁移发布到生产环境动作任务。DevOps流水线实际上和我们原来经常谈到的持续集成最佳实践是相当类似的,较大的一个差异点就在于引入了容器化技术来实现自动化部署和应用托管。至于在DevOps实践中,是否必须马上将项目切换到微服务架构框架模式,反而不是必须得。

在整个DevOps流水线中,我们实际上强调个一个关键点在于“一套Docker镜像文件+多套环境配置+多套构建版本标签”做法。以确保我们最终构建和测试通过的版本就是我们部署到生产环境的版本。
构建操作只有一次,而后面到测试环境,到UAT环境,到生产环境,都属于是镜像的环境迁移和部署。而不涉及到需要再次重新打包的问题。这个是持续集成,也是DevOps的基本要求。

流水线任务的标准化/原子化

今天谈DevOps流水线编排,主要是对流水线编排本身的灵活性进一步思考。

注意常规方式下构建完执行进行部署操作,部署操作一般就是将构建的结果拷贝到我们的测试环境服务器,同时对初始化脚本进行启动等。而在DevOps下,该操作会变成两个操作,即一个打包,一个部署。打包是将构建完成的内容制作为镜像,部署是将镜像部署到具体的资源池和指定集群。

那么首先要考虑构建操作和打包操作如何松耦合开,打包操作简单来就是就是一个镜像制作,需要的是构建操作产生的输出。我们可以对其输出和需要拷贝的内容在构建的时候进行约定。而打包任务则是一个标准化的镜像制作任务,我们需要考虑的仅仅是基于
1)基于哪个基础镜像
2)中间件容器默认目录设置
3)初始化启动命令。即在实际的打包任务设计的时候,我们不会指定具体的部署包和部署文件,这个完全由编排的时候由上游输入。

任务节点间松耦合设计的意义
这种松耦合设计才能够使流水线编排更加灵活。比如我们在进行了构建打包后,我们希望同时讲打包内容部署到开发环境和测试环境。那么则是打包动作完成后需要对接两个应用部署任务。这两个部署任务都依托上面的打包结果进行自动化部署,可以并行进行。
对于测试环境部署完成后,我们需要进行测试人员手工验证测试,如果测试通过,我们打标签后希望能够直接发布到UAT环境。而这种操作我们也希望在一个流水线来设计和完成。这样我们更加容易在持续集成看板上看到整个版本构建和迁移的完整过程。如果这是在一个大流水线里面,那么对于UAT环境部署任务就需要一直去追溯流水线上的最近的一个打包任务节点,同时取该任务节点产生的输出来进行相应的环境部署操作。
在谈DevOps的时候,一个重点就是和QA/QC的协同,因此在流水线编排的时候一定要考虑各类测试节点,包括静态代码检查,自动化的单元测试,人工的测试验证。同时最好基于持续集成实践,能够将测试过程和整个自动化构建过程紧密结合起来。
简单来说,测试人员发现build1.0.0001版本4个bug并提交,那么在下次自动化构建完成并单元测试通过后,测试人员能够很清楚的看到哪些Bug已经修改并可以在新构建的版本进行验证。只有这样才能够形成闭环,整个流水线作业才能够更好的发挥协同作用。

流水线中蕴含的工程实践

流水线除了任务步骤的编排,更重要的核心是最佳工程实践的体现。过去传统的思维,自动化就是写个shell/python脚本批量执行,在DevOps/微服务时代,这一招太out了,每种工程实践的背后都有需要解决的问题,通过在流水线设计中注入最佳的工程实践,可以让流水线的价值最大化,也让流水线更高级不是嘛。

  1. 版本控制 - 解决的问题:需求和代码的关系,版本变化的跟踪
  2. 最优的分支策略 - 解决的问题:版本发布和团队协作,某些情况会和环境有关系
  3. 代码静态扫描 - 解决的问题: 开发规范和安全的问题
  4. 80%以上的单元测试覆盖率 - 解决的问题:代码功能质量的问题,让测试左移
  5. 漏洞(Vulnerability)扫描 - 解决的问题:部署环境/产品安全的问题
  6. 开源工具扫描 - 解决的问题:解决供应链安全问题,别忘了log4j
  7. 制品(Artifact)版本控制 - 解决的问题:制品的版本控制,制品的晋级,某些情况下环境的回滚
  8. 环境自动创建 - 解决的问题:解决的是构建/部署环境一致性的问题,开发测的好好的,测试一验证怎么不行啊,容器化/云原生让这个问题更好的解决
  9. 不可变服务器(Immutable Server ) - 解决的问题: 可能不好理解,打个比方如果如果你的服务器挂了,或者某次配置更改了服务就起不来了,使用不可变基础设施的主要好处是部署的简单性、可靠性和一致性,服务器可以随时替换上线
  10. 集成测试
  11. 性能测试
  12. 每次提交都触发:构建、部署和自动化测试 - 解决的问题:快速失败,避免下游时间的浪费
  13. 自动化变更请求 - 解决问题:某些场景下通过状态变更触发某些动作
  14. 零停机发布 - 解决的问题:滚动/蓝绿/灰度发布等,用户无感知
  15. 功能开关 - 解决的问题: 主干开发中,如果某个功能没开放完,就通过on/off某个特性来让稳定的功能上线;还有一个场景,比如某些面对消费者的广告网站,想看看自己某个功能客户是否细化,通过功能开关看看市场反馈,一般和A/B测试配合

    基于场景设计流水线

    是否需要一条完整的流水线?流水线是越多越好,还是越少越好?
    建议按照场景来设计,一条流水线通吃所有流程是不现实的,搞了好多流水线(比如一个构建就一个流水线,一个复制操作就一个流水线)这些都是不可取的,维护成本巨大,得不偿失。

    流水线按照场景分类如下:


1)提交流水线

过程如下:

以Jenkins实现为例,通过webhook触发CI构建,首先配置Jenkins项目

其次是Gitlab的配置


剩下的就是编写Jenkinsfile了,下面列出几个关键点
1.获取gitlab数据中的分支名称,作为本次构建的分支名称。
2.获取gitlab数据中的用户邮箱,作为构建失败后通知对象。

2)MR流水线

过程如下:

合并流水线设计:合并流水线的步骤其实跟提交流水线很类似,但是在代码质量检查的步骤中严格要求检查质量阈的状态,当质量阈状态为错误的时候,需要立即失败并通知发起人。第一次设计

第二次设计(借助GitlabCI)- 优化点:加入MR构建失败拦截,成功自动合并

  1. 构建环节,对SQL语法进行检查,避免打进包里语法是错的;某些情况下,多个开发会写不同的增量脚本,最后发布时候需要做脚本的合并
  2. SQL脚本的版本,某些情况下产品自身要用表来记录自身业务脚本的版本,通过产品版本来判断某些脚本是否应该被执行。

当然,也有其他数据库版本管理工具,比如 flyway 和 liquibase;

不管怎么样,它们底层的原理都是用另外的表记录SQL脚本的版本,升级更新是比较版本差异,来决定是否执行。
python自带的model模块 python manage.py makemigrations 同样在做类似 的事情
数据库版本管理

流水线的关键元素

不管你用什么CI/CD平台,开源的Jenkins, GitLab CI, Teckton, Drone,还是商用的Azure,阿里云效等,不管是代码化,还是可视化,流水线包含的元素基本都差不多,下面通过不同的示例来说明这些元素的作用和含义。
参考:


Agent&Runner(执行代理)

image: "registry.example.com/my/image:latest" #gitlab-ci
pool:  vmImage: ubuntu-latest  #auzure
agent { label 'linux' }  //jenkinsagent {    docker {       image 'maven:3-alpine'       label 'Ubuntu'       args '-v /root/.m2:/root/.m2'    }}

Parameter(参数变量)

parameters([         separator(name: "PROJECT_PARAMETERS", sectionHeader: "Project Parameters"),        string(name: 'PROJECT_NAME', defaultValue: 'vue-app', description: '项目名称') ,        string(name: 'GIT_URL', defaultValue: 'git@git.xxxx.com.cn:devopsing/vuejs-docker.git', description: 'Git仓库URL') ,])
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)

参考:

主要用于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 wantsh 'echo $PASSWORD'// also available as a Groovy variableecho USERNAME// or inside double quotes for string interpolationecho "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: templateload: plugin.yamldata:  name: name  image: image  commands: commandskind: pipelinetype: dockername: defaultsteps:   - 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  - deployimage: alpinebuild_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: productiondeploy_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-latestjobs:- job: waitForValidation  displayName: Wait for external validation    pool: server      timeoutInMinutes: 4320 # job times out in 3 days  steps:      - task: ManualValidation@0     timeoutInMinutes: 1440 # task times out in 1 day     inputs:         notifyUsers: |            someone@example.com         instructions: 'Please validate the build configuration and resume'         onTimeout: 'resume'

部署流水线分步骤实施

说了这么多,如果从0开始写流水线呢,可以按照下面的步骤,从“点”到“线”结合业务需要串起来,适合自己团队协作开发节奏的流水线才是最好的。

  1. 价值流进行建模并创建简单的可工作流程
  2. 将构建和部署流程自动化
  3. 将单元测试和代码分析自动化
  4. 将验收测试自动化
  5. 将发布自动化


注意的事项开始写流水线需要注意一下几个方面,请考虑进去



流水线案例

案例-1


案例-2

本文使用 文章同步助手 同步

上一篇下一篇

猜你喜欢

热点阅读