DTeam 团队日志

Doer、Delivery、Dream

耗时三天,我将Gitlab CI由shell executor平滑迁移Docker环境

冯宇 Posted at — Nov 10, 2019 阅读

前言

在前一篇文章《浅谈如何打造对容器友好的应用》中,我谈到了对于容器化交付的软件,应该如何让软件本身适配容器环境。今天写一篇文章,谈谈我为何要将Gitlab CI迁移到Docker环境,以及我做了哪些努力。

我为什么将CI迁移到Docker环境?

之前我们的Gitlab已经在线上运行了多年,CI流程已经相对成熟。由于CI使用的较早,当时的Gitlab CI也只支持shell executor,所以就一直使用shell executor用到现在,并且工作的很好。

随着项目越来越多,开发节奏逐渐顺畅,shell executor的一些问题也逐步暴露出来了:

随着Docker的出现,这一类的环境隔离的问题被解决了,因此并行化流水线的问题其实也就迎刃而解了。并且由于环境的隔离性,因此我可以放心的在一些资源利用并不充分的服务器上部署docker executor来进行CI流程,不会干扰正在运行中的系统和应用。这样整个CI的部署工作量少了很多,并且大大提高了CI的效率。

在简单的体验Docker executor之后,我下定决心将CI环境逐步迁移到Docker环境。

我做了哪些工作

在Docker环境中搭建CI环境不是一件轻松的事情,和shell环境直接运行CI相比:

添加一个docker executor并不难,不过为了能在docker executor中构建docker镜像,需要用到docker in docker环境,因此在添加runner的时候就需要注意runner参数。

为了有别于shell runner(需要考虑到旧的兼容性,下面会叙述),需要添加一个tagdocker,这个可以通过runner register参数或在gitlab页面直接添加。

自制镜像。实际上最花时间的也是这部分。有些构建的环境比较繁琐,需要多个技术栈,而Docker的镜像大多是单一运行环境的镜像,因此需要自己定制镜像(如ionic,为了构建Android的apk安装包,需要环境有nodejs, java, gradle, android_sdk这么多种运行环境才能构建)

比如我自己就构建了以下几个镜像:

表面上看比shell环境工作量大了很多,但是这些工作量的付出是值得的。因为对于一个团队来说,大部分情况下技术栈都会保持相对的一个稳定,所以构建一次的docker镜像可以在多个项目进行复用,并且很方便的带着构建环境一并迁移,只要有docker环境就可以打包整个CI环境。

此外,为了保证镜像的一些必要更新,还添加了相应的github action定时任务,检测到FROM镜像更新的时候自动build(因为docker hub的的auto rebuild不会因为官方镜像的更新而触发)

事后的一些注意事项

shell executor 兼容性

由于之前大量的项目都跑在shell executor环境中,因此首先需要保证这些旧项目依旧能正常运行,gitlab会同时运行两套CI环境。为了让shell executor成为默认的runner,需要勾选runner配置中的Run untagged jobs,同时,docker runner则不能勾选这个选项。

对于期望使用docker的项目,在.gitlab-ci.yml配置中强制指定tag选择:

job:
  stage: build
  image: myimage
  scripts:
  - echo hello
  tags:
  - docker

这样只需要对新迁移到docker环境构建的项目修改ci脚本即可,而对旧的项目保持兼容,做到平滑迁移。

容量考虑

docker环境构建往往需要更大的磁盘空间,镜像、容器、存储卷、缓存等等,会比shell环境下占用多几倍的存储空间。对于磁盘空间不那么宽裕的服务器来说,需要充分考虑如何利用有限的存储空间。比如:

缓存考虑

为了加快构建速度,通常都需要使用构建缓存(通常是构建工具缓存的依赖包等等)。在shell环境下通常不需要为缓存特殊考虑,因为它直接用的系统文件系统,缓存直接存储在磁盘上。但是对于docker环境则必须考虑。gitlab ci对于缓存有个cache指令控制,这边我放几个供参考:

gradle的缓存

variables:
  GRADLE_USER_HOME: build/gradle_user_home

cache:
  key: my-project
  paths:
  - ${GRADLE_USER_HOME}/caches/
  - ${GRADLE_USER_HOME}/wrapper/

nodejs缓存

variables:
  NPM_CONFIG_CACHE: npm_cache
  NPM_CONFIG_REGISTRY: https://registry.npm.taobao.org
  NPM_CONFIG_ELECTRON_MIRROR: https://npm.taobao.org/mirrors/electron/
  SASS_BINARY_SITE: https://npm.taobao.org/mirrors/node-sass
  NPM_CONFIG_PHANTOMJS_CDNURL: https://npm.taobao.org/mirrors/phantomjs

cache:
  key: my-another-project
  paths:
  - ${NPM_CONFIG_CACHE}

由于gitlab ci的cache只能支持相对路径,不能用绝对路径,因此需要将构建工具产生的缓存改到当前目录下。加上key,并且标定为项目名,是为了尽可能复用缓存。因为一个项目通常依赖相对稳定,指定项目名为key可以尽可能复用同一个缓存(比如fork项目也可以享受到上游缓存的好处)。

后记

通过这次对Gitlab CI的迁移改造,服务器的资源利用率大大提升,也为了未来可以方便的迁移到k8s集群做了铺垫。未来期望能使用阿里云的serverless k8s集群环境做CI,一些细节还在研究中。

而且也充分考虑到了旧的CI环境兼容,做到平滑切换。对于一些旧项目或者不怎么维护的项目保持旧的环境就好,对于一些实在依赖太重,难以迁移到docker中构建的项目,保持旧的环境就好,不需要为了技术而技术,那就失去了意义。