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 中构建的项目,保持旧的环境就好,不需要为了技术而技术,那就失去了意义。


相关文章