UDN-企业互联网技术人气社区

板块导航

浏览  : 792
回复  : 1

[持续集成] Segment是如何做持续集成的

[复制链接]
开花包的头像 楼主
发表于 2016-7-21 21:08:36 | 显示全部楼层 |阅读模式
  在我们在内部推广开源化的过程中, 我们决定要构建我们的CI(持续集成)构建系统。 我们绝大多数的做法都跟标准的最佳实践一致, 但是我们可以分享几个小技巧来加速我们的构建任务。

  我们的构建基于 CircleCI , Github , 基于 Docker Hub。 每当Github上有了一个新的提交, 我们的代码库就会触发一个CircleCI的构建任务。 如果这次构建是基于一次含Tag的发布, 并且所有的测试都通过了, 我们就会给对应的容器(container, 一种虚拟化技术)发布一个镜像。

  接下来, 镜像会被推送到Docker Hub, 并随时准备部署到我们的生产环境:

c.png


  CircleCI 和 Travis CI

  首先我来聊聊一个老前辈: Travis CI 。 几乎每个关于CI工具的讨论都会涉及到Travis CI和CircleCI的比较。 他们都好看, 提供托管, 可交互性强 。 而且他们都非常好用。

  实话说, 两款工具我们都很喜欢。 我们在很多开源库中用了Travis CI, 而CircleCI则更多的用在我们的私有库中。 两者都很棒, 并且能帮助我们完成任务。

  然而, CircleCI有一个我们非常喜欢的特性: SSH访问。

  大部分的时候, 配置我们的测试环境都不会出现任何问题。 但是总有那么些时候, 我们不得不用SSH登录到容器中来启动我们的代码。

  先介绍一下前提, 我们Segment的整个构建是建立在数百个小型服务上的。 每一个小型服务都从一个单独的代码库更新, 每个依赖都是单独通过 Docker-compose来处理的(我们接下来会介绍)。

  我们绝大多数的CI都是标准流程, 但是我们偶尔会建立一些新的服务需要自定义的运行环境。 新的服务会建立在一个新的代码仓库上, 并且有着自己的依赖管理和构建方式。 在这种情况下, 如果能够直接在实际容器中直接运行相应的命令就显得很重要了——你可以直接在容器中修改配置文件。

  再也看不见大量的“修复CI”的提交啦!

  Dotfiles(隐藏配置文件)

  我们有大量的代码库需要维护, 因此我们希望能够简单的新建一个有CI支持的代码库。 我们经常使用的Circle命令(译者注: circle是CircleCI的特有概念, 表示一套构建预备的命令组)有三套, 我们都是通过Dotfiles来进行共享的。 第一个circle()负责建立起所有的环境变量, 并且开启我们Slack的通知系统。

  
  1. org=$(basename $(dirname $(pwd)))

  2.   repo=$(basename $(pwd))

  3.   echo enabling project

  4.   curl "https://circleci.com/API/v1/project/${org}/${repo}/follow?circle-token=${circletoken}" \

  5.   -X POST \

  6.   -H "Accept: application/json" \

  7.   --silent > /dev/null

  8.   echo enabling notifications

  9.   curl "https://circleci.com/api/v1/project/${org}/${repo}/settings?circle-token=${circletoken}" \

  10.   -X PUT \

  11.   -H "Content-Type: application/json" \

  12.   -H "Accept: application/json" \

  13.   -d '{"slack_webhook_url": "xxxxxxxxx"}' \

  14.   --silent > /dev/null
复制代码


  除此之外, 我们会实现一个circle.open()命令, 把测试的结果自动输出到浏览器的CLI里。1

  
  1. repo=$(git remote -v)

  2.   re="github.com/([^/]+/[^[:space:]]+)(.git)"

  3.   if [[ $repo =~ $re ]]; then open "https://circleci.com/gh/${BASH_REMATCH[1]}"; fi
复制代码


  最后, 有一个circle.badge()命令来自动添加一个badge到代码库。 (译者注: badge在github中一般是一串图片地址, 这个地址可以静态嵌入到README.md文件中去, 图片的内容会从服务器动态更新。 内容可以包含各种信息, 比如代码库的测试覆盖率, 测试是否通过等)

  
  1. org=$(basename $(dirname $(pwd)))

  2.   repo=`basename $(pwd)`

  3.   echo creating status token

  4.   response=`curl "https://circleci.com/api/v1/project/$org/$repo/token?circle-token=$circletoken" \

  5.   -X POST \

  6.   -H "Content-Type: application/json" \

  7.   -H "Accept: application/json" \

  8.   -d '{"label":"badge","scope":"status"}' \

  9.   --silent`

  10.   statustoken=`node -pe 'JSON.parse(process.argv[1]).token' "$response"`

  11.   badge="[![Circle CI](https://circleci.com/gh/segmentio/$repo.svg?style=svg&circle-token=$statustoken)](https://circleci.com/gh/segmentio/$repo)"

  12.   echo adding badge to Readme.md

  13.   echo $badge > temp-readme.md

  14.   cat Readme.md >> temp-readme.md

  15.   cp temp-readme.md Readme.md

  16.   rm temp-readme.md
复制代码


  共享脚本

  考虑到我们有数百个代码库, 我们必须保证我们每次修改circle.yml的时候, 我们的代码库和测试依然能保持同步。

  想在几百个代码库中维持同样的表现是非常痛苦的, 但我们觉得比起抽象并解决这个问题(很困难), 还是通过更多的工具维持一致性更好(简单的多)。

  因此, 我们维护了一套通用的脚本, 并且在代码库里面共享它们。 这些脚本每次跑测试的时候都会被更新并运行, 共享的包对应的部署也会在这时被完成。 每个服务的circle.yml大概看起来长这样:1

  
  1. machine:

  2.   node:

  3.   version: 4

  4.   services:

  5.   - Docker

  6.   deployment:

  7.   deploy:

  8.   tag: /[0-9]+(\.[0-9]+)*/

  9.   commands:

  10.   - git clone github.com/segmentio/circle-scripts.git

  11.   - sh ./circle-scripts/node-deploy.js
复制代码


  这样一来, 当我们想修改我们的部署策略的时候, 我们只需要在一个地方更新circle.yml就好了。 我们可以在不同的代码库里面引用不同的脚本来控制具体的构建流程。

  Docker容器

  最终所有的构建还是会基于Docker容器的. 使用容器让我们更新服务变得更加的简单, 也更容易和内部的服务进行集成测试, 本地部署也能从中受益。

  当我们测试服务的时候, 我们用Docker-compose.yml文件来运行我们的测试。 这样一来, 一个服务可以用生产环境中完全一样的镜像来进行测试, 这样减少了很多伪造数据和配置文件的工作。

b.png


  除此之外, 每当CI完成了镜像的构建之后, 我们还可以把镜像拉到本地进行运行。

  如果要构建一串代码, 并且发布到生产环境, CircleCI会先运行所有测试, 接下来检测是不是一个打了标签的发布版本。 如果是一个打了标签的发布版本, 我们会让CircleCI通过Dockerfile来构建容器, 最后标记它并发布到Docker Hub。

  我们并不会总用Latest标记我们的发布, 我们会显式的在提交到Docker Hub的镜像上标记主版本(1.x)和子版本(1.2.x)。

  这样我们就可以指定回滚到具体的某个版本, 当然如果我们不关心具体是那个版本的话, 也可以发布某个分支最近的一次构建。 (本地开发和写Docker-compose文件的时候很有用)。

  上述代码很简单, 首先我们检测版本:

  
  1. tag="$(git describe --tags --always)"

  2.   # Find our individual versions from the tags

  3.   if [ -n "$(echo $tag | grep -E '.*\..*\..*')" ]

  4.   then

  5.   major=$(echo $tag | cut -d. -f1)

  6.   minor=$(echo $tag | cut -d. -f2)

  7.   patch=$(echo $tag | cut -d. -f3)

  8.   major_version_tag=$major.x

  9.   minor_version_tag=$major.$minor.x

  10.   patch_version_tag=$major.$minor.$patch

  11.   tag_list="$major_version_tag $minor_version_tag $patch_version_tag"

  12.   else

  13.   tag_list=$tag

  14.   fi
复制代码


  接下来我们构建, 标记, 并且推送我们的Docker镜像:

  
  1. Docker build -t segment/$CIRCLE_PROJECT_REPONAME .

  2.   # Tag the new image with major, minor and patch version tags.

  3.   for version in $tag_list

  4.   do

  5.   echo "==> tagging $version"

  6.   Docker tag segment/$CIRCLE_PROJECT_REPONAME:latest segment/$CIRCLE_PROJECT_REPONAME:$version

  7.   done

  8.   # Push each of the tags to Docker hub, including latest

  9.   for version in $tag_list latest

  10.   do

  11.   echo "==> pushing $version"

  12.   Docker push segment/$CIRCLE_PROJECT_REPONAME:$version

  13.   done
复制代码


  当我们所有的镜像都被推送到Docker Hub之后, 我们就可以把对应的版本发送到服务环境中并在 并在ECS中启动它 。

  由于容器的存在, 我们的CI构建流程让我们微服务的发布更稳健了。

  依赖Circle

  大概介绍完了: 我们的CI构建管道, 重度依赖Github,CircleCI和Docker。

  尽管我们希望更加无缝的集成我们的整个构建管道, 但是我们对现在这种第三方工具驱动的低维护成本, 高并行化, 高度分离的构架非常满意。

  最后说一句, 如果你需要维护大量的代码库, 我们也很希望听到你们是用的怎样的技术来构建自己的管道的。 通过邮件或者Twitter联系我们吧~

原文作者:佚名  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部