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


原文:Continuous Integration at Segment
译者:杰微刊兼职翻译巫明瀚

 

在我们在内部推广开源化的过程中, 我们决定要构建我们的CI(持续集成)构建系统。 我们绝大多数的做法都跟标准的最佳实践一致, 但是我们可以分享几个小技巧来加速我们的构建任务。


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


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



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的通知系统。

org=$(basename $(dirname $(pwd)))
repo=$(basename $(pwd))

echo enabling project
curl "https://circleci.com/api/v1/project/${org}/${repo}/follow?circle-token=${circletoken}" \
  -X POST \
  -H "Accept: application/json" \
  --silent > /dev/null

echo enabling notifications
curl "https://circleci.com/api/v1/project/${org}/${repo}/settings?circle-token=${circletoken}" \
  -X PUT \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"slack_webhook_url": "xxxxxxxxx"}' \
  --silent > /dev/null

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

repo=$(git remote -v)
re="github.com/([^/]+/[^[:space:]]+)(.git)"
if [[ $repo =~ $re ]]; then open "https://circleci.com/gh/${BASH_REMATCH[1]}"; fi

 

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

org=$(basename $(dirname $(pwd)))
repo=`basename $(pwd)`

echo creating status token
response=`curl "https://circleci.com/api/v1/project/$org/$repo/token?circle-token=$circletoken" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"label":"badge","scope":"status"}' \
  --silent`
statustoken=`node -pe 'JSON.parse(process.argv[1]).token' "$response"`

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

echo adding badge to Readme.md
echo $badge > temp-readme.md
cat Readme.md >> temp-readme.md
cp temp-readme.md Readme.md
rm temp-readme.md

 

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


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


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

machine:
  node:
    version: 4
  services:
    - docker

deployment:
  deploy:
    tag: /[0-9]+(\.[0-9]+)*/
    commands:
      - git clone github.com/segmentio/circle-scripts.git
      - sh ./circle-scripts/node-deploy.js

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


Docker容器
最终所有的构建还是会基于Docker容器的. 使用容器让我们更新服务变得更加的简单, 也更容易和内部的服务进行集成测试, 本地部署也能从中受益。
当我们测试服务的时候, 我们用docker-compose.yml文件来运行我们的测试。 这样一来, 一个服务可以用生产环境中完全一样的镜像来进行测试, 这样减少了很多伪造数据和配置文件的工作。



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


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


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


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


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

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

# Find our individual versions from the tags
if [ -n "$(echo $tag | grep -E '.*\..*\..*')" ]
then
    major=$(echo $tag | cut -d. -f1)
    minor=$(echo $tag | cut -d. -f2)
    patch=$(echo $tag | cut -d. -f3)

    major_version_tag=$major.x
    minor_version_tag=$major.$minor.x
    patch_version_tag=$major.$minor.$patch

    tag_list="$major_version_tag $minor_version_tag $patch_version_tag"
else
    tag_list=$tag
    fi

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

docker build -t segment/$CIRCLE_PROJECT_REPONAME .

# Tag the new image with major, minor and patch version tags.
for version in $tag_list
do
    echo "==> tagging $version"
    docker tag segment/$CIRCLE_PROJECT_REPONAME:latest segment/$CIRCLE_PROJECT_REPONAME:$version
done

# Push each of the tags to docker hub, including latest
for version in $tag_list latest
do
    echo "==> pushing $version"
    docker push segment/$CIRCLE_PROJECT_REPONAME:$version
done


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


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


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


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


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

 

更多美文推荐:
程序员的七种武器
短篇鬼故事---程序员之死
[专题]20年后,程序员将成为最富有的群体
[译]揭秘,为什么我可以黑Facebook所有帐号?
2016世界最热门的编程语言与薪资揭秘
“借钱”上看“程序员”恶劣的品质?
大学毕业十年,你现在的生活过得怎样?

------------好久不见的分隔线------------


杰微刊旨在分享优质的内容。
我们水平有限,但理想高远。
也同样期待有理想的您对这个世界的贡献。
欢迎任何目的的联系。

欢迎关注我们

 

分享到:
赚钱
喜欢
精品汇 精品汇总

手机应用大起底:APP如何让用户习惯成瘾?

随着手机APP应用的全面开花,如何让越来越多的用户上钩、让用户形成一种习惯,已经成为科技公司们最痴迷的一件事情。而手机的小小屏幕,用户的注意力只放在几个常用的APP上,在排队、喝咖啡、吃饭时,情不自禁地打开这些APP,究竟这些APP应该具有怎样的魔力?让我们从开发者的角度来一一探究

评论
*

还可以输入140个字符

提交
全部评论(条)

点击这里,查看赚钱机会