CI 进阶篇:
上一篇里介绍了如何使用 drone ci从零部署一套自己的 CI,并简单地介绍了如何配置一个自动进行单元测试和编译的示例,这一篇则更加详细地介绍Drone 是如何满足我们在 CI 上的各种需求的。本篇所有内容都基于上篇介绍的.drone.yaml示例,在这里再熟悉一下,不熟悉的同学可以找到上一篇介绍如何部署和配置的文章看一下。
kind: pipeline
name: api
steps:
# api unit test
- name: api-test
image: node:11
depends_on: [clone]
commands:
- cd ./api/
- npm install --registry https://registry.npm.taobao.org
- cd ./dgc_sdk && npm install --production
- cd .. && npm run test
# web lint
- name: web-lint
image: node:10.15.1
depends_on: [clone]
commands:
- cd ./web
- npm install --registry https://registry.npm.taobao.org
- npm run lint
trigger:
branch:
- master
event:
- pull_request
- push
一、自定义clone 步骤
有一些时候,我们可能需要对 CI 上的 clone 步骤进行一些自定义的操作,比如跳过 clone,比如 clone 的时候也要拉取 tag
# 这是一个空的 pipeline
# 当 pull_request事件被触发,并且不是提忘 master 分支时
# 或者出去 tag 的其他的事件时会触发
# 触发后不进行任何操作
# 这个 pipeline 的主要目的是,我们在 gitlba 中定义了如果 CI 不成功,pr 不允许合入分支
# 所以当我们往非 master提交 pr 时,不想让 CI 执行任何步骤,但也不能不触发 CI,因为不触发 CI 那上面的规则就会限制 pr 无法合入
kind: pipeline
type: docker
name: pipeline
clone:
disable: true
trigger:
event:
- pull_request
branch:
exclude:
- master
ref:
exclude:
- refs/tags/*
kind: pipeline
type: docker
name: build
# 自定义 clone 的 git 操作
# image 指定使用 drone 提供的 git 插件,当然我们也可以使用我们自定义的或者其他的 git 镜像
# tags: true 指定在 clone 代码时也将仓库的 tag同步到本地
clone:
git:
image: plugins/git
network_mode: host
tags: true
# 下面可以定义你的编译步骤和升级操作
二、并行执行steps
当我们的 CI 步骤比较长的时候,我们希望这些步骤不是串行的,并行执行能给我们节省很多时间,同时呢可能多个步骤之间还存在着依赖关系,比如某一个模块的镜像编译与推送可能依赖于这个模块本身的 build(比如 npm build,mvn clean package等等)。使用 drone 提供的 depends_on可以轻松完成以上任务:depends_on 需要设置为一个数组形式,如果数组中存在多个值,本步骤会等到这多个步骤都执行完成后再执行。
首先明确的是,drone 中所有的 steps默认都是并行执行的,所以如果我们定义如下,便可以实现我们想要的功能
kind: pipeline
type: docker
name: build
steps:
# get tag
- name: tag
image: bashell/alpine-bash:latest
network_mode: host
depends_on: [clone]
commands:
# 当tag 事件触发后,drone 会默认将 tag 名字写入到环境变量DRONE_TAG中,更多默认的环境变量请查看文档:https://docs.drone.io/pipeline/environment/reference/
# 将当前的 tag 名称输出到.tags文件中,如果tag 名不存在那么输出latest 到 .tags文件中
- /bin/bash -c '[ -z $DRONE_TAG ] && echo "latest" > .tags || echo $DRONE_TAG > .tags'
# build api
- name: api-build
image: node:12.18.1
network_mode: host
depends_on: [tag]
volumes:
- name: cache_api_node
path: /drone/src/api/node_modules
commands:
- cd ./api/
- npm install --registry http://mirrors.cloud.tencent.com/npm
- npm run build
# build web
- name: web-build
image: node:12.18.1
network_mode: host
depends_on: [tag]
volumes:
- name: cache_web_app_node
path: /drone/src/web/app/node_modules
commands:
- cd ./web/app
- npm install --registry http://mirrors.cloud.tencent.com/npm
- npm run build
上面定义的 steps 将会按照下面这种并行方式执行,上下排布并使用箭头连接的步骤是串行执行的,并排并且没有箭头连接的步骤是串行执行的:
三、提供文件挂载和环境变量
有些时候我们需要将宿主机的文件或文件夹挂载到 CI 步骤当中去,比如缓存文件,比如外部不适合放在代码仓库中但CI 步骤需要的一些文件。
有些时候我们需要添加或者变更一下 CI 步骤中运行的 docker 容器的环境变量,比如当我将外部缓存挂载到容器里之后,更改 node 或者 golang 的默认包路径。
下面这个示例将演示如何使用挂载和环境变量:
需要注意的是,想要使用挂载功能,需要将仓库在 Drone CI 中的 trusted 选项打开。这个操作可以参考下面的第六步。
kind: pipeline
type: docker
name: unit test
# 将宿主机的路径挂载到 pipeline 中
# 将宿主机的路径/drone/cache/star/golang-app/go挂载到 pipeline 中,并命名为 cache_golang_app_go,用作 golang 程序编译时的缓存文件夹
# 将宿主机的路径/drone/cache/star/api/node_modules挂载到 pipeline 中,并命名为 cache_api_node,用作 node 程序编译时的缓存文件夹
# 将宿主机的路径 /var/run/docker.sock挂载到 pipeline 中,并命名为 docker,让 pipeline 中 docker 镜像使用宿主机的 docker socket
# 将宿主机的路径 /etc/docker/daemon.json挂载到 pipeline 中,并命名为 docker-daemon,让 pipeline 中 docker 镜像使用宿主机的 docker配置
volumes:
- name: cache_golang_app_go
host:
path: /drone/cache/star/golang-app/go
- name: cache_api_node
host:
path: /drone/cache/star/api/node_modules
- name: docker
host:
path: /var/run/docker.sock
- name: docker-daemon
host:
path: /etc/docker/daemon.json
# master pull requset
steps:
- name: clean
image: docker:19.03.1
network_mode: host
# 将宿主机的 docker和配置挂载到运行的 docker 容器中,那么在容器中运行 docker 命令时,等同于在宿主机中运行该docker 命令
volumes:
- name: docker
path: /var/run/docker.sock
- name: images
path: /images
- name: docker-daemon
path: /etc/docker/daemon.json
commands:
# 清理 docker 服务中不再使用的镜像、挂载、网络等资源
- docker system prune --force --volumes || true
# api unit test
- name: api-test
image: node:12.18.1
depends_on: [clean]
network_mode: bridge
# 将宿主机中文件夹挂载到容器中,宿主机文件夹中的文件会被容器访问并修改,起到使用缓存的作用,避免每次运行都要重现下载依赖,提高运行速度
volumes:
- name: cache_api_node
path: /drone/src/api/node_modules
commands:
- cd ./api/
- npm install --registry http://mirrors.cloud.tencent.com/npm
- npm run test
when:
branch:
- master
# golang-app test
- name: golang-app-test
image: library/golang:1.13.3-alpine3.10
network_mode: host
depends_on: [clean]
# 将宿主机中文件夹挂载到容器中,宿主机文件夹中的文件会被容器访问并修改,起到使用缓存的作用,避免每次运行都要重现下载依赖,提高运行速度
volumes:
- name: cache_golang_app_go
path: /drone/src/golang-app/go
# 由于 golang 是通过GOPATH寻找本地包路径的,因此除了将依赖文件挂载到容器中之外,还要指定GOPATH到这个目录下
# 设置GOPROXY为腾讯元镜像,提高 golang 依赖下载速度
environment:
GOPATH: /drone/src/golang-app/go
GOPROXY: http://mirrors.cloud.tencent.com/go/
commands:
- cd ./golang
- CGO_ENABLED=0 go test -v
when:
branch:
- master
trigger:
branch:
- master
event:
- pull_request
四、拉取私有镜像
在生产环境中,很可能我们需要运行或者推送的镜像并不是开放在 dockerhub 中的,我们会使用私有镜像,或者私有镜像仓库,这个时候我们需要在drone 中指定image_pull_secrets来配置私有镜像或者私有仓库。这个时候我们还需要上一篇文章提到的 Secret 来保证这个私有配置是被加密的。
上图中的Secrets 项中可以添加自定义的内容,比如我这里就设置了三个 secret,docker_password,docker_username 和 dockerconfigjson。拉取私有镜像的配置便是用到了这个dockerconfigjson,另外两个 Secret 是我用在了其他地方(请查看第五步docker 镜像的编译与推送)。在 Secret Name中填写dockerconfigjson(当然也可以是其他的名字),然后在 Secret Value 填写 docker 的配置内容。该内容可以通过以下方式获取到(ubuntu):
* 运行如下命令,使用用户名登录到指定的镜像仓库中,输入密码后确认登录成功
docker login --username=[username] ccr.ccs.tencentyun.com
* 查看并复制/etc/docker/key.json文件内容,其大致格式如下
{
"auths": {
"ccr.ccs.tencentyun.com": {
"auth": ""
},
"hub.tencentyun.com": {
"auth": ""
}
}
}
kind: pipeline
type: docker
name: build
# 这里使用image_pull_secrets指定拉取docker镜像时的 secret
image_pull_secrets:
- dockerconfigjson
clone:
git:
image: plugins/git
network_mode: host
tags: true
steps:
# build api
- name: api-build
image: my-private-node:12.18.1
network_mode: host
depends_on: [tag]
volumes:
- name: cache_api_node
path: /drone/src/api/node_modules
commands:
- cd ./api/
- npm install --registry http://mirrors.cloud.tencent.com/npm
- npm run build
五、docker 镜像编译与推送
当我们使用 docker 作为服务部署方式的时候,很自然地想要在 CI 中每次将新的docker 镜像编译完成并推送到镜像仓库中去。
这个时候我们可以使用以下两种方式
steps:
# push docker-image
- name: docker-push
# docker 插件会根据配置将指定的镜像编译完成,并推送到给定的仓库中去
image: plugins/docker:18.09.0
depends_on: [your-app-build]
settings:
# 配置推送镜像时连接镜像仓库的用户名
username:
# 从配置的 Secret 中获取
from_secret: docker_username
# 配置推送镜像时连接镜像仓库的密码
password:
# 从配置的 Secret 中获取
from_secret: docker_password
# 配置私有镜像仓库地址
repo: hub.tencentyun.com/demo/your-image-name
# 指定 context,默认git 仓库根路径
context: ./
# 指定 dockerfile
dockerfile: ./Dockerfile
# 配置编译的镜像的 tag,可以指定多个
# 也可以在其他步骤(如your-app-build)中,将想要使用的 tag 名写入 .tags 文件中
# 如: echo ‘1.0.01’ > .tags
# 那个下面的 tags 项可以不写,默认会使用.tags 文件中的内容
tags:
- latest
- ‘1.0.0'
具体配置项可以看这个文档:http://plugins.drone.io/drone-plugins/drone-docker/
手动的方式配置起来比上面的插件方式要复杂,但上面的 plugin 很愚蠢的一件事情是,会在启动的 docker 容器中再启动docker 命令去编译和推送,相当于 docker 容器中再运行一个 docker 容器,这样的逻辑在并行执行多个时总是会出现异常情况,比如会报错说docker 引擎没有运行。所以使用手动的方式虽然复杂但运行效率高,因为通过挂载的方式使编译过程使用宿主机的 docker 引擎。
steps:
# 这里用到了上面说的挂载,忘记了可以看一下本篇文章上面的挂载与环境变量的内容
# 将宿主机的路径挂载到 pipeline 中
# 将宿主机的路径 /var/run/docker.sock挂载到 pipeline 中,并命名为 docker,让 pipeline 中 docker 镜像使用宿主机的 docker socket
# 将宿主机的路径 /etc/docker/daemon.json挂载到 pipeline 中,并命名为 docker-daemon,让 pipeline 中 docker 镜像使用宿主机的 docker配置
volumes:
- name: docker
host:
path: /var/run/docker.sock
- name: docker-daemon
host:
path: /etc/docker/daemon.json
# push docker-image
- name: docker-push
image: docker:19.03.1
depends_on: [your-app-build]
volumes:
- name: docker
path: /var/run/docker.sock
- name: docker-daemon
path: /etc/docker/daemon.json
environment:
IMAGE: hub.tencentyun.com/demo/your-image-name
commands:
- export DRONE_TAG=$(cat .tags)
- docker build -f ./Dockerfile -t $IMAGE:$DRONE_TAG ./
- docker push $IMAGE:$DRONE_TAG
六、drone 打开 trusted 选项
想在pipeline 中使用挂载功能首先要保证该仓库的 Trusted 选项已经打开,打开方式如下:
- DRONE_USER_CREATE=username:DRONE_ADMIN_USER,admin:true
好啦,对于 Drone CI 的介绍和我的一些使用经验就分享这么多了,大家感兴趣的话可以一试,详细的内容可以查看它的官方网站进行更多的了解哦~!
参考资料:Drone CI - Automate Software Testing and Delivery