云原生语义化 CI/CD最佳实践

2019年8月7日06:05:14 评论 82

主流 CI/CD 工具对比

项目名称 开发语言 配置语言 公有云服务 私有部署 备注
Travis CI Ruby YAML 不支持 公共项目免费,私有项目 $69/单进程, $129/2 进程
CircleCI Clojure YAML 不支持 单进程免费,$50/加 1 进程
Gitlab CI Ruby YAML 支持 阶梯付费, 免费版功能过少, 绑定 Gitlab
Jenkins Java Groovy 支持
Drone Go YAML 支持 Cloud 版本不支持私有项目,自建版本无此限制

Travis CI 和 CircleCI 是目前占有率最高的两个公有云 CI,易用性上相差无几,只是收费方式有差异。由于不支持私有部署,如果并行的任务量一大,按进程收费其实并不划算;而且由于服务器位置的原因,如果推送镜像到国内,速度很不理想。

Gitlab CI 虽然好用,但和 Gitlab 是深度绑定的,我们的代码托管在 Github,整体迁移代码库的成本太大,放弃。

Jenkins 作为老牌劲旅,也是目前市场占有率最高的 CI,几乎可以覆盖所有 CI 的使用场景,由于使用 Java 编写,配置文件使用 Groovy 语法,非常适合 Java 为主语言的团队。Jenkins 显然是可以满足我们需要的,只是团队并非 Java 为主,又已经习惯了使用 YAML 书写 CI 配置,抱着尝鲜的心态,将 Jenkins 作为了保底的选择。

综上,最终选择 Drone 的结论也就不难得出了,Drone 即开源,又可以私有化部署,同时作为云原生应用,官方提供了针对 Docker、Docker Swarm、K8s 等多种容器场景下的部署方案,针对不同容器场景还有特别优化,比如在 Docker Swarm 下 Drone 是以 agent 方式运行 CI 任务的,而在 K8s 下则通过创建 K8s Job 来实现,显然充分利用了容器的优势所在,这也是 Drone 优于其他 CI 应用之处。个人还觉得 Drone 的语法是所有 CI 中最容易理解和掌握的,由于 Drone 每一个步骤都是运行一个 Docker 容器,本地模拟或调试也非常容易。

一句话概况 Drone,可以将其看做是可以支持私有化部署的开源版 CircleCI,并且目前仍然没有看到有其他主打这个定位的 CI 工具,因此个人认为 Drone 是 CI/CD 方面云原生应用头把交椅的有力竞争者。

一次规范的发布应该包含哪些内容

  • 代码的下载构建及编译
  • 静态扫描
  • 运行单元测试,生成单元测试报告及覆盖率报告等
  • 在测试环境对当前版本进行测试
  • 为待发布的代码打上版本号
  • 编写 ChangeLog 说明当前版本所涉及的修改
  • 构建 Docker 镜像
  • 将 Docker 镜像推送到镜像仓库
  • 在预发布环境测试当前版本
  • 正式发布到生产环境

看上去很繁琐对吗,如果每次发布都需要人工去处理上述的所有内容,不仅容易出错,而且也无法应对 DevOps 时代一天至少数次的发布频率,那么下面就来使用 CI/CD来解决所有问题吧。

下面就用一个简单的例子结合GitFlow分支模型讲解:

Step.1: 在gitlab创建工程并配置drone模板

#.drone.yml

matrix:

  IMAGE_REPO:

    - hub.baidu.com.cn/baidu/cicd-demo 

  KUBECTL_IMAGE:

    - hub.baidu.com.cn/baidu/kubectl:1.0.0

  BVT_IMAGE:

    - hub.baidu.com.cn/baidu.com/bvt-demo:1.0.0

  REPORT_EMAIL:

    - test@baidu.com 

 

pipeline:

  build:

    image: golang:1.11.0 

    commands: 

      - go test -v -coverprofile=coverage.out

      - go build -o cicd-demo

    when:

      event: push

 

  code_analysis:

    image: 172.16.59.153/baidu.com/sonar

    secrets: [ sonar_host, sonar_token ]

    source: .

    when:

      event: [ push, tag ]

 

  publish_commit: 

    image: plugins/docker 

    username: baidu.com 

    password: test_cloud

    registry: hub.baidu.com

    repo: ${IMAGE_REPO} 

    tags: ${DRONE_COMMIT} 

    dockerfile: Dockerfile

    insecure: true

    when:

      event: push          

 

  publish_tag:            

    image: plugins/docker

    username: baidu.com

    password: test_cloud

    registry: hub.baidu.com

    repo: ${IMAGE_REPO}

    tags:

      - ${DRONE_TAG}       

    dockerfile: Dockerfile

    insecure: true

    when:

      event: tag           

 

  semantic-release:

    image: hub.test.com/baidu.com/semantic-release:1.0.0

    commands:

      - sh /semantic-release/release.sh

    when:

      event: tag 

 

  deploy_dev:              

    image: ${KUBECTL_IMAGE} 

    commands:

      - mkdir -p /root/.kube && cp .kube/config-dev /root/.kube/config 

      - sed -i "s/$IMAGE_TAG/${DRONE_COMMIT}/g" deployment.yml

      - sed -i "s/$NAMESPACE/185-caas/g" deployment.yml

      - kubectl delete -f deployment.yml || true 

      - sleep 30

      - kubectl apply -f deployment.yml

    when:

      event: push

      branch: dev

 

  deploy_test: 

    image: ${KUBECTL_IMAGE}

    commands:

      - mkdir -p /root/.kube && cp .kube/config-test /root/.kube/config 

      - sed -i "s/$IMAGE_TAG/${DRONE_COMMIT}/g" deployment.yml

      - sed -i "s/$NAMESPACE/185-caas/g" deployment.yml

      - kubectl delete -f deployment.yml || true

      - sleep 30

      - kubectl apply -f deployment.yml

    when:

      event: deployment

      environment: deploy_test

 

  deploy_prod: 

    image: ${KUBECTL_IMAGE}

    commands:

      - mkdir -p /root/.kube && cp .kube/config-test /root/.kube/config 

      - sed -i "s/$IMAGE_TAG/${DRONE_TAG}/g" deployment.yml

      - sed -i "s/$NAMESPACE/185-caas/g" deployment.yml

      - kubectl delete -f deployment.yml || true

      - sleep 30

      - kubectl apply -f deployment.yml

    when:

      event: deployment

      environment: deploy_prod

 

  autokit:

    image: hub.baidu.com.cn/baidu.com/kitlab

    commands:

      - report_email ${REPORT_EMAIL} ${DRONE_REPO} ${DRONE_BUILD_NUMBER} ${DRONE_COMMIT} ${DRONE_BUILD_LINK}

      - autocom ${DRONE_REPO_NAME} ${DRONE_REPO_NAME} ${DRONE_COMMIT} lwhong ${DRONE_BUILD_LINK}

      - autokit ${DRONE_REPO_NAME} ${DRONE_REPO_NAME} lwhong ${DRONE_BUILD_CREATED} ${DRONE_BUILD_LINK} ${DRONE_PREV_BUILD_STATUS}

    when:

      event: push

      #branch: release/*

      status:  [ success, failure ]

 

  wechat_notice:

    image: hub.baidu.com.cn/baidu/wechat

    corpid: wx0df510cbsdf9110

    corp_secret: 6sdfsdfaaafefsdf

    agent_id: 10001

    to_user: test

    title: ${DRONE_REPO}

    description: '流水线号: ${DRONE_BUILD_NUMBER}<br>本次结果: ${DRONE_PREV_BUILD_STATUS} <br>负责人: ${DRONE_COMMIT_AUTHOR}<br>详情: ${DRONE_BUILD_LINK}'

    msg_url: ${DRONE_BUILD_LINK}

    btn_txt: btn

    when:

      status:  [ success, failure ]

 

Step.2: 在drone里配置sonarqube秘钥对

云原生语义化 CI/CD最佳实践

 

Step.3: 获取工程code

#git clone https://git.baidu.com/DevOps/ago.git

Step.4: 基于dev分支创建新feature开发

 

   

 #git checkout -b feature/a dev

   

 ## 做一些改动 ## 

 # git status 

 # git add some-file 

 # git commit 

 # git push -u origin feature/a

 

 ## 没有dev分支需要先创建 ##

 # git branch dev 

 # git push -u origin dev

 


Step.5: webhook触发drone,自动运行单元测试->静扫->构建->发布镜像->部署研发环境->邮件通知+微信

云原生语义化 CI/CD最佳实践

云原生语义化 CI/CD最佳实践

云原生语义化 CI/CD最佳实践

云原生语义化 CI/CD最佳实践

Step.6: 自测没问题后,合并feature/a分支到dev(合并前git pull防止有任提交)

# git checkout dev

# git pull origin dev

# git merge --no-ff feature/a

# git push origin dev

# git branch –d feature/a

# git push origin –delete feature/a

 

Step.7: 新建release分支, webhook触发drone,类似步骤3,部署测试环境,自动运行BVT/通知测试进行测试。

 

# git checkout -b release/0.0.1 dev

# git push –u origin release/0.1.0

 


Step.8:验证无问题,合并release/0.1.0 to master/dev,语义化版本自动生成CHANGLOG和版本提交到运维部署,即进行生产环境的部署。

 

# git checkout master

# git merge --no-ff release-0.1.0

# git push 

 

# git checkout dev

# git merge --no-ff release-0.1.0

# git push

 

# git branch -d release-0.1.0

# git push origin --delete release-0.1.0

 

 


云原生语义化 CI/CD最佳实践

云原生语义化 CI/CD最佳实践

Step.9:语义化版本说明

语义化版本号一共有 3 位,形如 1.0.0,分别代表:

  1. 主版本号:当你做了不兼容的 API 修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。

虽然有了这个指导意见,但并没有很方便的解决实际问题,每次发布要搞清楚代码的修改到底是不是向下兼容的,有哪些新的功能等,仍然要花费很多时间。

语义化发布 (Semantic Release) 就能很好的解决这些问题。

语义化发布的原理很简单,就是让每一次 Commit 所附带的 Message 格式遵守一定规范,保证每次提交格式一致且都是可以被解析的,那么进行 Release 时,只要统计一下距离上次 Release 所有的提交,就分析出本次提交做了何种程度的改动,并可以自动生成版本号、自动生成 ChangeLog 等。

语义化发布中,Commit 所遵守的规范称为约定式提交 (Conventional Commits)比如 node.js、 Angular、Electron 等知名项目都在使用这套规范。

语义化发布首先将 Commit 进行分类,常用的分类 (Type) 有:

  • feat: 新功能
  • fix: BUG 修复
  • docs: 文档变更
  • style: 文字格式修改
  • refactor: 代码重构
  • perf: 性能改进
  • test: 测试代码
  • chore: 工具自动生成

每个 Commit 可以对应一个作用域(Scope),在一个项目中作用域一般可以指不同的模块。

当 Commit 内容较多时,可以追加正文和脚注,如果正文起始为BREAKING CHANGE,代表这是一个破坏性变更。

以下都是符合规范的 Commit:

feat: 增加重置密码功能
fix(通知模块): 修复一个小BUG
feat(API): API重构

BREAKING CHANGE: API v2上线,API v1停止支持

有了这些规范的 Commit,版本号如何变化就很容易确定了,目前语义化发布默认的规则如下

Commit 版本号变更
BREAKING CHANGE 主版本号
feat 次版本号
fix / perf 修订号

因此在 CI 部署 semantic-release 之后,作为开发人员只需要按照规范书写 Commit 即可。

本篇文章来源于微信公众号: DevOps

发表评论

您必须才能发表评论!