Gadzan

在 VPS 里搭建 Drone CI 持续集成构建系统

在 VPS 里搭建 Drone CI 持续集成构建系统
持续集成和构建的工具有很多,除了著名的 Jenkins,Travis,CircleCI,还有最近比较热门的 Github Action 和 Gitlab CI/CD。但是作为个人开发者的私人项目如果想要使用并不友好。那么开源免费的 Drone CI 是个不错选择,它不但非常轻量,而且十分强大。

这篇文章是我第一次在 InfoQ 新开的写作平台发表的。

前言

持续集成和构建的工具有很多,除了著名的 Jenkins,Travis,CircleCI,还有最近比较热门的 Github Action 和 Gitlab CI/CD。但是这些工具面对私人项目不是要收费就是占用大量服务器资源,作为个人开发者的私人项目如果想要使用并不友好。那么开源免费的 Drone CI 是个不错选择,它不但非常轻量,而且十分强大。本文讲述 Drone CI 的具体实践,怎么在 VPS 里从零开始搭建一个基于 Drone CI 的持续集成系统。

本文十分依赖 Docker 和 Docker compose,因此用户必须了解使用 Docker 的基础技巧,起码了解怎么启动容器,容器的常用参数有什么用,容器的网络连接,怎么进入容器运行命令等……
如果不太懂 Docker, 推荐阅读这本电子书《Docker — 从入门到实践》

Drone CI 是什么?

Drone CI 是一套持续集成和构建工具,主要利用 Docker 容器帮助完成部署任务,凭借 Docker 因此支持任何平台,更支持任何语言的程序,而且支持大多数主流源码管理平台。

Drone CI 可以部署在自己的VPS里,重点是完全开源和免费,当然你也可以使用他们家的提供的企业服务。Drone CI 用 Go 语言编写,非常高效和轻型,而且可扩展性超强。

Drone CI官网 | Drone CI插件库 | Drone CI社区论坛
版本说明:这里只对 Drone CI 1.0以后的版本安装进行说明,1.0以前的版本官方已经不再兼容和支持,因此尽量安装最新的版本。

Drone CI 采用 Master/Slave 主从架构的分布式设计,一个 Drone CI 包含一个 Server 作为 Master 节点,而 Runner 作为 Slave 节点可以分布在不同的机器或者容器中。

Server 节点负责接收 Webhoot 的请求,与 Runner 节点通讯并分配任务。

Runner 则负责运行任务,Runner 目前分为5种,分别是Docker RunnerExec RunnerKubernetes RUnnerSSH RunnerDigital Ocean Runner

Server和Runner

被分配到 Docker Runner 的任务会在 Docker 容器中运行,而一些不能在 Docker 容器中运行的特殊任务则可以用其他 Runner 运行,例如 Exec Runner, 可以直接调用命令在系统中运行任务。

Drone CI 自动部署的例子

在项目代码的根目录新建一个.drone.yml文件,一旦代码上传到代码仓库( github, gitlab, gitea 等),git 仓库会通过 Drone 预先埋好的 Webhoot 钩子发送事件请求给 Drone,Drone 接收到事件请求后会找到仓库项目根目录中的.drone.yml文件进行解析并根据文件的描述执行任务。

Drone CI 构建的每个 step 都会根据镜像产生一个 Docker 容器,并在容器里运行指定任务。

首先每个 Pipline 都有的头描述部分:

kind: pipeline # Pipeline 的类型,其他的还有 secret and signature。
type: docker # 定义了执行任务的类型,这里会使用 Docker 执行。
name: default # 定义 Pipline 的名字,一个 .drone.yml 可以有多个不同名字的 Pipeline。

然后是描述任务的每个步骤,steps 属性后描述此步骤的 name (名字) 和 image (镜像),每一步都会用到一个镜像,任务进行时会根据提供的镜像名字拉取镜像并生成一个临时 Docker 容器运行任务指令,步骤完成后自动删除。

steps:
- name: some-step # 步骤名
  image: some/image # 步骤需要用到的镜像

下面是一个 Node.js 后端程序打包成 Docker 镜像并部署到服务器的例子。文中介绍的范例主要想覆盖常见的坑,对于新手可能会比较复杂,如果看不懂,没关系,可以直接跳过这一节,自己尝试动手安装 Drone CI 后回头再细品。

.drone.yml 文件:

kind: pipeline
type: docker # 在 Docker Runner 中运行
name: default

# 定义步骤 Steps,每个 step 有属于自己 name,最后显示在 Drone CI 管理页面的侧边栏,代表每一步的名字。
steps:
- name: restore-cache # 把之前缓存好的数据取出
  image: drillster/drone-volume-cache
  settings:
    restore: true
    mount: # 缓存挂载的文件夹
      - ./.npm-cache
      - ./node_modules
  volumes:
    - name: cache
      path: /cache

- name: install # 安装依赖
  image: 172.17.0.2:5000/nodejs # 使用了本地Registry的nodejs,加快拉取速度
  commands:
    - npm config set cache ./.npm-cache --global 
    - npm i --production --registry=https://registry.npm.taobao.org 
# 不用npm ci,因为 npm ci 会先把 node_modules 文件夹移除,如果非要用 npm ci 可以把 restore-cache 和 rebuild-cache 这两个 step 去掉。

- name: test
  image: 172.17.0.2:5000/nodejs
  commands:
    - npm test

- name: build # 将程序打包成 Docker,放到私有 Docker 仓库中
  image: plugins/docker
  settings:
    insecure: true # 因为使用的是私有仓库,没有 https 支持,所以要设置成 insecure 才能连接
    dockerfile: Dockerfile # 使用 Dockerfile 的名字
    username:
      from_secret: account # 声明 username 从 Secret 为 account 的值中取出,在Drone的控制面板里可以设置,后面Q&A中介绍Secret
    password:
      from_secret: registry_password
    repo: 172.17.0.2:5000/backendsample # 打包后 Docker 镜像的名字
    registry: 172.17.0.2:5000 # 私有镜像仓库的地址

- name: rebuild-cache # 把依赖和 npm 缓存放到缓存里
  image: drillster/drone-volume-cache
  settings:
    rebuild: true
    mount:
      - ./.npm-cache
      - ./node_modules
  volumes:
    - name: cache
      path: /cache
# when 定义的是运行此 step 的条件
  when: # 只在事件为 push,分支为 master 而且上一步成功的时候运行
    branch:
      - master
    event:
      - push
    status:
      - success

- name: deploy # 用SSH连接到宿主机器运行部署命令
  image: appleboy/drone-ssh
  settings:
    host: xxxx.com
    username:
      from_secret: account
    password:
      from_secret: account_password
    port: 22
    command_timeout: 30m # ssh命令行执行超时时间,30分钟
    script_stop: false # 设置为false,遇到第一次错误会继续运行后面的命令
    # script定义的是所要运行的command
    script:
      - sudo docker pull 172.17.0.2:5000/backendsample # 拉取backendsample的新镜像
      - sudo docker stop backendsample && sudo docker rm backendsample # 停止并删除旧容器
      - sudo docker run --name backendsample --network redis_network --network-alias server_network -d -p 7001:7001 172.17.0.2:5000/backendsample # 从新镜像中建立容器并运行
      - sudo docker image prune -f --filter "dangling=true" # 清除无用的镜像
  when:
    branch:
      - master
    event:
      - push
    status:
      - success

- name: notify # 用了Server酱的webhook微信通知部署消息
  image: plugins/webhook
  settings:
    urls:
      from_secret: notify_url
    content_type: application/x-www-form-urlencoded
    template: |
      text=自动部署{{ build.status }}了&desp=在{{ repo.name }}的{{build.branch}}上{{ build.event }}了{{truncate build.commit 8}}的commit, 结果 `{{ build.status }}` 了, 耗时{{ since build.started }}, 点这里[查询]({{ build.link }})
    headers: origin=https://xxxx.com
    debug: true
  when: # 无论成功失败都运行
    status:
      - failure
      - success

# 私有镜像仓库的密钥在后面填坑里详细解释
# image_pull_secrets:
# - dockerconfig

# 声明并挂载缓存文件夹,宿主机的路径为 /tmp/cache
volumes:
- name: cache
  host:
    path: /tmp/cache

# 运行一个redis服务,在npm test测试时可以作为测试环境,语法与docker-compose类似。
services:
  - name: redis
    image: 172.17.0.2:5000/redis
    ports:
      - 6379

dockerfile 文件:

# 设置基础镜像,如果本地没有该镜像,会从Docker.io服务器pull镜像
# 这里会直接调用宿主机的密钥登录私有仓库。
FROM 172.17.0.2:5000/node11a

# 创建app目录,保存我们的代码(下面的WORKDIR会自动创建, 这段RUN mkdir不必要)
# RUN mkdir -p /home/gadzan/node
# 设置工作目录
WORKDIR /home/xxxx/node

# 编译运行node项目,使用npm安装程序的所有依赖,利用taobao的npm安装
# 这两步在drone里已经利用缓存完成
# COPY package.json package-lock.json /home/xxxx/node/

# 复制所有文件到 工作目录。
COPY . /home/xxxx/node

# .dockerignore文件如果登记了node_modules, 上下文文件会将其忽略
# 上面COPY了所有文件包括node_modules文件, 因此不需要再安装依赖.
# RUN npm i --production --registry=https://registry.npm.taobao.org

# 暴露container的端口
EXPOSE 7001

# 运行命令
CMD ["npm", "run", "start"]

上面的范例有6个Steps

  • restore-cache
  • install
  • test
  • build
  • rebuild-cache
  • deploy
  • notify

简单整理一下每一步(详细的上面注释都有解释)

  1. restore-cache 步骤会把之前缓存的文件从宿主机中取出;
  2. install 步骤时 npm 跳过已经安装过的依赖;
  3. test 步骤会依赖services下挂载的redis服务进行测试;
  4. build 步骤会由 plugins/docker 插件找到 dockerfile 并按照配置生成docker镜像后推送到相应的镜像仓库中;
  5. deploy 步骤使用 appleboy/drone-ssh 插件通过 ssh 在容器中访问宿主机并运行命令拉取并部署镜像;
  6. rebuild-cache 步骤把缓存通过挂载文件放到宿主机中;
  7. 最后无论部署的任务成功还是失败,在notify 步骤中会使用 plugins/webhook 发送请求把消息推送到微信。

优化:

  1. 因为一次构建每一个 steps 都会新生成一个容器并在容器里运行构建,沙盒环境里没有缓存数据。通过restore-cacherebuild-cache这两个 steps 建立宿主机与容器的缓存,把 Node.js 的依赖 node_modules 目录和 npm 缓存通过 volumes 到映射到宿主机上,在下一次构建并安装依赖时 npm 会自动跳过没有变化的依赖包,从而加快构建速度。
  2. 常用的容器镜像打个 tag 放到本地镜像仓库中可以加快拉取镜像的速度,例如上面步骤里所有172.17.0.2:5000/开头的镜像。
  3. 实际上,Drone 还会在retore-cache之前先git clone一份代码,因此如果在国内环境下,使用自建的 Gitea 服务内网拉取可以极致地加快构建速度。

填坑:

  1. 在 deploy 步骤时,appleboy/drone-ssh最好加上参数command_timeout以免服务器无响应耗时过久,参数script_stop设置为false可以直接跳过一次错误继续运行后面命令。

  2. notify 步骤官方提供的变量示例不多,其实是用的是 Drone CI 的环境变量,环境变量可以在 yaml 文件中使用${xx_xx}格式读取,而在 template 里使用了{{xx.xx}}格式读取,例如环境变量DRONE_BUILD_STATUS可以在 template 中使用{{build.status}}读取。

  3. 私有镜像仓库的密钥官方推荐使用image_pull_secrets参数可以用 用下面的 json 格式作为值存入 secret 里,变量名为dockerconfig, auth的值可以在使用命令cat ~/.docker/config.json查看,它的值在使用docker login以后自动更新。

    {
      "auths": {
        "172.17.0.2:5000": {
          "auth": "aAsaswWDWe123asfY="
        }
      }
    }
    

    172.17.0.2:5000是私有镜像仓库地址,auth是使用 base64 加密后的密钥信息。但是到写稿目前这个方法有 bug,IP 地址形式的私有 docker 仓库会导致 Drone 运行时错误,因此用了 registry-plugin 的方法获取私有仓库密钥,下面的《方法2:docker-compose 启动》和《进阶玩法:扩展》部分会介绍。

上面例子用到的 image 都可以在 Drone CI插件库 找到对应的使用方法。

安装 Drone CI

Drone CI 十分依赖 Docker,所以必须首先安装 Docker,Docker 安装方法这里不多介绍。

安装 Drone Server

Server 的作用有三个,跟代码仓库通讯,跟 Runner 通讯和作为 Drone CI 的前端服务器

Server 目前支持5种常见的代码仓库Github,GitLab,Gogs,GiteaBitbucket

我们下面用 Gitea 为例子,Github 和 Gitlab 的安装方法大同小异,Gogs 作为 Gitea 的前身方法基本一样,Bitbucket 可以自己尝试阅读官网文档,这里不作介绍。

如果没有安装过Gitea的,文章末尾的附录部分有安装方法。

步骤1:准备

创建一个 OAuth2 应用

登录 Gitea,在右上角头像下拉的设置中点击应用标签,在管理OAuth2应用程序中新建一个 OAuth 应用。

客户端 ID 和客户端密钥用于授权 Drone CI 访问 Gitea 的资源

重定向 URI 一定要按照下面格式去填写,返回到自己 Drone CI 的登录页。

Gitea OAuth2 Application

完成后会得到以下信息,记好客户端 ID 和客户端密钥,后面配置需要用到。

Edit Gitea OAuth2 Application

创建一个 RPC 共享密钥

这个密钥用于 Runner 和 Server 之间通过 RPC 通讯

你可以在 linux 主机命令行用下面命令生成密钥:

$ openssl rand -hex 16
bea26a2221fd8090ea38720fc445eca6

步骤2:下载

Drone CI Server 的镜像非常轻量,而且没有任何其他依赖,你可以输入以下命令获取镜像

$ docker pull drone/drone

步骤3:配置

Drone CI Server 可以用以下环境变量配置. 这些环境变量在步骤4中使用,可以结合步骤4查看。

这里只列出 Gitea 版的对应配置说明.

Server 更详细的环境变量参数可以 Drone Server Reference 里查看

Runner 更详细的环境变量参数可以在 Drone Runner Reference 里查看

Drone Server 部分:
  • DRONE_GITEA_CLIENT_ID

    填写刚刚在 GItea 申请的客户端 ID

  • DRONE_GITEA_CLIENT_SECRET

    填写刚刚在 GItea 申请的客户端密钥

  • DRONE_GITEA_SERVER

    填写 Gitea 的访问地址. 例如 https://gitea.xxxx.com

  • DRONE_GIT_ALWAYS_AUTH

    可选配置,填写一个 boolean 值,启用会使 Drone CI 每次 Clone 公共仓库都使用授权

  • DRONE_RPC_SECRET

    填刚刚生成的 RPC 共享密钥,Server 和 Runner 的 RPC 共享密钥的值必须一样才能通讯。

  • DRONE_SERVER_HOST

    填写 Drone CI 的访问地址,例如drone.xxxx.com,可以是 IP 地址或者域名,如果是 IP 地址则一定要附加加上端口号.

  • DRONE_SERVER_PROTO

    http或者https.

Drone Runner 部分:
  • DRONE_RPC_HOST

    提供 Drone Server 的网络地址(可以带上端口号),Drone Runner 会根据地址连接到 Drone Server 以接收来自 Server 的 piplines 任务。

  • DRONE_RPC_PROTO

    http或者https。 取决于访问 Drone Server 的地址是否使用 https。

  • DRONE_RPC_SECRET

    填刚刚生成的 RPC 共享密钥,Server 和 Runner 的 RPC 共享密钥的值必须一样才能通讯。

步骤4:启动 Server 和 Runner

官网只给出 docker run 命令的启动方法,我个人推荐 docker-compose 的启动,如何安装 docker-compose 可以在网上搜索,非常简单,这里不做展开。

方法1:docker run 命令启动

可以直接使用docker run命令启动 Drone Server. Drone CI 使用 SQLite3 作为数据库,数据储存在容器的/data目录里。

安装 drone server:

docker run \
  --volume=/var/lib/drone:/data \
  --env=DRONE_GITEA_SERVER={{DRONE_GITEA_SERVER}} \
  --env=DRONE_GITEA_CLIENT_ID={{DRONE_GITEA_CLIENT_ID}} \
  --env=DRONE_GITEA_CLIENT_SECRET={{DRONE_GITEA_CLIENT_SECRET}} \
  --env=DRONE_RPC_SECRET={{DRONE_RPC_SECRET}} \
  --env=DRONE_SERVER_HOST={{DRONE_SERVER_HOST}} \
  --env=DRONE_SERVER_PROTO={{DRONE_SERVER_PROTO}} \
  --publish=80:80 \
  --publish=443:443 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone

安装 drone runner (Docker runner for Linux):

$ docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e DRONE_RPC_PROTO=https \
  -e DRONE_RPC_HOST=drone.company.com \
  -e DRONE_RPC_SECRET=super-duper-secret \
  -e DRONE_RUNNER_CAPACITY=2 \
  -e DRONE_RUNNER_NAME=${HOSTNAME} \
  -p 3000:3000 \
  --restart always \
  --name runner \
  drone/drone-runner-docker
方法2:docker-compose 启动

下面提供了 Server 和 Runner 以及私有 Docker 仓库密钥服务的配置,我把它们都放到一个固定的 docker 网络(名为 alpha )中,所以事先需要创建这个 alpha 网络:

docker network create alpha --subnet 172.17.0.0/16

alpha 网络子网 IP 会分配在172.17.0.1~172.17.0.254之间。

私有 Docker 仓库密钥服务我会在<进阶玩法>部分再详细说明。

Server 的固定 IP 地址为:172.17.0.5,Runner 的固定 IP 地址为:172.17.0.6,私有镜像仓库密钥服务的固定 IP 地址为:172.17.0.12

version: "3.6"

services:
  drone-server:
    image: drone/drone
    container_name: drone_server
    volumes:
      - ./data:/var/lib/drone/:rw
    restart: always
    ports:
      - "1280:80"
      - "1243:443"
    environment:
      - DRONE_DEBUG=true
      - DRONE_GITEA_SERVER=https://gitea.xxxx.com
      - DRONE_GITEA_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
      - DRONE_GITEA_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
      - DRONE_DATABASE_DRIVER=sqlite3
      - DRONE_DATABASE_DATASOURCE=/var/lib/drone/drone.sqlite
      - DRONE_RPC_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
      - DRONE_TLS_AUTOCERT=false
# 这个配置决定了你激活时仓库中的 webhook 地址的 proto
      - DRONE_SERVER_PROTO=https
      - DRONE_SERVER_HOST=drone.xxxx.com
# 创建一个管理员号,跟你的 gitea 账号一致
      - DRONE_USER_CREATE=username:admin,admin:true
# 管理员账号,一般是你 gitea 用户名
      - DRONE_USER_FILTER=admin
      - DRONE_DATADOG_ENABLE=false
# 限制注册,只有这里的用户能注册
      - DRONE_REGISTRATION_CLOSED=true
# 如果 Runner 节点在不同国家可以设置时区
      - TZ=Asia/Shanghai
# 下面是 debug 输出的 log 的配置,可以忽略
    # - DRONE_LOGS_DEBUG=true
    # - DRONE_LOGS_TEXT=true
    # - DRONE_LOGS_PRETTY=true
    # - DRONE_LOGS_COLOR=true
    networks:
      outside:
        ipv4_address: 172.17.0.5

  drone-runner:
    image: drone/drone-runner-docker
    container_name: drone_runner
    restart: always
    ports:
      - "1230:3000"
    depends_on:
      - drone-server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:rw
#     - /home/{$user}/.docker/config.json:/root/.docker/config.json
    environment:
#     - DRONE_DOCKER_CONFIG=/root/.docker/config.json
# 设置 drone-registry-plugin 的网络地址
      - DRONE_REGISTRY_ENDPOINT=http://172.17.0.12:3000
# DRONE_REGISTRY_SECRET 必须要和 drone-registry-plugin 的 DRONE_SECRET 的值一样
      - DRONE_REGISTRY_SECRET=bea26a2221fd8090ea38720fc445eca61
      - DRONE_RPC_HOST=drone_server
      - DRONE_RPC_PROTO=http
      - DRONE_RUNNER_CAPACITY=1
      - DRONE_RUNNER_NAME=runner
      - DRONE_RUNNER_NETWORKS=alpha
      - DRONE_RPC_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxx
# Runner 的标签,可以让 Server 实现分流
      - DRONE_RUNNER_LABELS=machine1:runner1
#     - DRONE_DEBUG=true
#     - DRONE_TRACE=true
      - TZ=Asia/Shanghai
    networks:
      outside:
        ipv4_address: 172.17.0.6

  drone-registry-plugin:
    image: drone/registry-plugin
    container_name: drone_registry_plugin
    ports:
      - 3030:3000
    environment:
      - DRONE_DEBUG=true
      - DRONE_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxx
      - DRONE_CONFIG_FILE=/etc/registry_config.yml
    volumes:
      - /var/drone/registry.yml:/etc/registry_config.yml
    networks:
      outside:
        ipv4_address: 172.17.0.12

networks:
  outside:
    external: true
    name: alpha

Server 的固定 IP 地址为:172.17.0.5;

Runner 的固定 IP 地址为:172.17.0.6;

私有镜像仓库密钥服务的固定 IP 地址为:172.17.0.12;

docker-compose.yml的目录下输入docker-compose up -d运行。

以上设置中 Server 的端口号为 1280,因此本地环境浏览器进入localhost:1280即可访问管理页面,建议配置域名和 Nginx 或 Caddy 反向代理访问。

Drone 的登录账号默认是绑定 Gitea 账号的,因此只要登录了 Gitea,Drone 也会自动登录。

在打开并登录 Drone 后,你的 Repositories 应该是空的,因为没有同步 Gitea 的代码仓库到 Drone CI 里,只要在首页里的右上角点击SYNC按钮,Drone 便会自动开始同步 Gitea 的代码仓库。

在上一节< Drone CI 自动部署的例子>中,我们用到了restore-cacherebuild-cache,这两个 Steps 需要挂载宿主机的文件夹,因此需要在 Drone 对应项目中的 SETTINGS 里的 Project settings 里需要勾选Trusted,这意味着开启容器的特权模式去挂载宿主机的文件夹。开启这个设置用户的权限必须是 admin ,其他用户没有权限开启。

Project settings

Protected - If Enabled, blocks pipeline if the yaml signature cannot be verified.

Trusted - Enables privileged capabilities: an ability to start privileged containers and mount host machine volumes.

参考:

进阶玩法

上面的用法基本能满足很大一部分的场景,但是总有人会有一些特殊的需求,下面介绍几种进阶的玩法。

定时启动

定时启动构建的方法很简单,在 Drone CI 管理页面对应项目的 Settings 里就有 Cron Jobs 栏目,根据页面要求输入即可,详细可以参考官方文档页

image-20200418134012781

多节点运行

在 docker-compose.yml 文件中定义 Runner 的DRONE_RUNNER_LABELS环境变量可以为 Runner 加上标签,在定义 .drone.yml 时通过这个标签让 pipeline 路由到不同的 Runner 执行任务。

例如我有两个不同的机器放在不同的地方,在这两台机器上运行 Runner 并使用DRONE_RUNNER_LABELS环境变量分别定义这两个 Runner 的标签,例如在第一个 Runner 里DRONE_RUNNER_LABELS=nodeA:runnerA,另一个 Runner 里DRONE_RUNNER_LABELS=nodeB:runnerB,那么在.drone.yml文件中我们可以定义:

kind: pipeline
type: docker
name: default

steps:
- name: build
  image: golang
  commands:
  - go build
  - go test

node:
  nodeA: runnerA

那么这个任务就只会在标签是nodeA:runnerA的 Runner 里运行。

如果想要在两个节点中运行,可以把这两个标签都加上,例如:

node:
  nodeA: runnerA
  nodeB: runnerB

因为 Runner 会主动心跳连接 Server 并在 Server 上注册自己,不需要固定的网络地址而且足够轻量, 因此这个 Runner 节点可以是你的 PC 机、笔记本,甚至是树莓派。

参考:

自定义步骤触发

某个任务或者某个步骤完成以后需要触发另一个仓库的任务开始,怎么办呢?

Drone Server 提供了两个自带的服务,Webhook 和 API 服务

Drone Server 会在每次构建时使用钩子发送请求到指定地址。

https://docs.drone.io/webhooks/overview/

发送带token的请求到API服务可以自定义查询drone或者触发构建。

https://docs.drone.io/api/overview/

读者可以自行查询上面文档构建。

扩展

monorepo 项目有条件触发

使用 drone-config-changeset-conditional 插件,.drone.yml 写成下面这个形式:

kind: pipeline
name: a_service
steps:
  ...

trigger:
  changeset:
    includes:
      - a_folder/*
---
kind: pipeline
name: b_service

steps:
  ...

trigger:
  changeset:
    includes:
      - b_folder/*
---
kind: pipeline
name: c_service

steps:
- name: frontend
   image: node
   commands:
     - cd app
     - npm run test
   when:
      changeset:
        includes: [ **/**.js, **/**.css, **/**.html ]

上面的配置有三种条件触发效果:

  1. 如果 a_folder 文件夹下文件有变化则会触发 a_service 的构建;
  2. 如果 b_folder 文件夹下文件有变化则触发 b_service;
  3. 如果一旦 .js、.css、.html 文件发生变化,则会触发 c_service 构建测试。
安装

⚠该插件只支持 Github,Gitea 目前不支持。插件的源码不多,有能力的读者可以尝试自己移植使用。

生成 share key

$ openssl rand -hex 16
558f3eacbfd5928157cbfe34823ab921

在 Drone Server 的配置加上两条环境变量

-e DRONE_YAML_ENDPOINT=http://${PLUGIN_HOST}:${PLUGIN_PORT}
-e DRONE_YAML_SECRET=558f3eacbfd5928157cbfe34823ab921

上面两条环境变量利用了官方提供的解析.drone.yml接口,让第三方去解析.drone.yml并替代 Drone Server 的解析服务,这个插件可以通过不同文件的变化规则从而触发生成不同的流程并返回给 Drone Server。

docker run 命令运行插件:

docker run \
  -p ${PLUGIN_PORT}:3000 \
  -e PLUGIN_SECRET=558f3eacbfd5928157cbfe34823ab921 \
  -e GITHUB_TOKEN=xxxxxxxxxxxxxxxxx \
  --name drone-changeset-conditional \
  --detach=true \
  --restart=always \
  microadam/drone-config-plugin-changeset-conditional

私有仓库密钥服务drone-registry-plugin

如果使用了私有的 docker 仓库,而私有的 docker 仓库使用时需要授权登录,那么就必须给 Runner 一个密钥才能使用,drone-registry-plugin 就是提供这个密钥的服务。

安装方法在上文的《方法2:docker-compose 启动》里。

Runner 在运行任务时遇到需要授权登录的私有仓库可以通过 RPC 方式获取存储在 drone-registry-plugin 的密钥,drone-registry-plugin 通过其配置里定义的环境变量DRONE_CONFIG_FILE获取文件/etc/registry_config.yml里定义的密钥。

在定义 docker-compose.yml 文件时注意, DRONE_CONFIG_FILE定义的是在容器中registry_config.yml的位置,通过 volumes 参数把容器中的registry_config.yml文件映射到宿主机中的/var/drone/registry.yml文件。

environment:
  - DRONE_CONFIG_FILE=/etc/registry_config.yml
volumes:
  - /var/drone/registry.yml:/etc/registry_config.yml
# /etc/registry_config.yml 格式
- address: 172.17.0.2:5000
  username: xxxxx
  password: xxxxxxxxxxxxxx
- address: registry.xxxxxx.com
  username: xxxxx
  password: xxxxxxxxxxxxxx

其实 Drone CI 获取 Docker 私有仓库密钥有三种方法,用 drone-registry-plugin 为一种,

第二种为映射/home/{$user}/.docker/config.json,因为/home/{$user}.docker/config.json文件里保存了docker login登录过的密钥值,把这个文件映射到容器中的/root/.docker/config.json文件,Runner 即可获取宿主机的 Docker 私有仓库密钥信息。

例如:

  drone-runner:    
    volumes:
      - /home/{$user}/.docker/config.json:/root/.docker/config.json
    environment:
      - DRONE_DOCKER_CONFIG=/root/.docker/config.json

第三种方法为 Drone Secrect,在 Drone CI 的管理页面对应项目的 Setting 里定义一个名为dockerconfig的 Secrect ,其值的格式为:

{
  "auths": {
    "172.17.0.2:5000": {
      "auth": "aAsaswWDWe123asfY="
    }
  }
}

其中auth的值可以在第二种方法的/home/{$user}/.docker/config.json文件中获得。

最后定义在 pipline 任务文件.drone.yml里最后加上:

image_pull_secrets:
  - dockerconfig

这样Drone运行此任务时便可以获得密钥值。

问题是到写稿为止,第二第三种方法在遇到私有仓库为 IP 值时会报错导致任务失败,目前官方已经在最新代码里修复,但建议还是使用drone-registry-plugin这种方法比较灵活。

Q&A

什么是Secret,怎么用?

Secret 主要存放一些敏感的信息,例如登录的账号密码密钥等等信息,因为代码存放在代码仓库中,是 Private 的还好,但如果是 Public 的,所有人都能看到,包括.drone.yml里的信息,那么这时候就需要 Secrect,

使用方法非常简单,Drone CI 的管理页面中对应仓库的 SETTINGS 标签里,有 Secrets 一栏,这里贴上官方使用方法

在上图的 Secrect Name 和 Secrect Value 中填上对应的值,然后在.drone.yml中使用from_secret参数引用 Secrect 名即可(就像文中例子那样)。

如何手动跳过构建?

如果在推送代码后不想让 Drone 构建,可以在 git commit 时加上[CI SKIP],例如:

$ git commit -m "Updated README [CI SKIP]"

怎么生成 Badges 徽章?

Drone CI 有内建支持渲染徽章的功能,可以显示当前构建的状态。你可以按照下面格式手动填入仓库信息。
格式:

http(s)://[hostname]/api/badges/[namespace]/[name]/status.svg(?ref=[ref])

例子:

https://drone.acme.com/api/badges/octocat/hello-world/status.svg

https://drone.acme.com/api/badges/octocat/hello-world/status.svg?ref=refs/heads/master

怎么搭配Caddy使用?

因为 Drone CI 管理页面需要用到 websocket,因此需要在 Caddyfile 里设置 gzip 排除一些 websocket 用到的地址路径,并且要把 proxy 的 transparent 打开。

drone.xxxxx.com {
  gzip {
    not /api/stream /stream
  }
  tls xxxx@xxxx.com
  proxy / localhost:1280 {
    websocket
    transparent
  }
}

附录

安装 Gitea

这里使用 docker-compose 方法安装,

# docker-compose.yml

version: '3.7'
services:
  git:
    image: gitea/gitea
    container_name: gitea
    restart: always
    depends_on:
      - database
    environment:
      - APP_NAME="Gadzan's Git Service"
      - DISABLE_REGISTRATION=true
      - REQUIRE_SIGNIN_VIEW=true
      - DB_TYPE=postgres
      - DB_HOST=database:2345
      - DB_NAME=gitea
      - DB_USER=root
      - DB_PASSWD=xxxxxxxxxxx
      - GITEA_CUSTOM=/data/custom
    networks:
      default:
        ipv4_address: 172.17.0.8
    volumes:
      - ./data/git:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3330:3000"
      - "9922:22"

  database:
    image: postgres:12.2-alpine
    container_name: gitea-postgres
    restart: always
    ports:
      - "2345:5432"
    networks:
      default:
        ipv4_address: 172.17.0.7
    environment:
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=xxxxxxxxxxx
      - PGDATA=/var/lib/postgresql/data/pgdata
    volumes:
      - ./data/postgre:/var/lib/postgresql/data

networks:
  default:
    external:
      name: alpha

修改配置文件

安装完成后进入 Gitea 首页会自动进入安装页面,按照页面提示填好信息安装完成后可以通过app.ini文件修改配置。

如果设置了按照上面配置了GITEA_CUSTOM变量,app.ini就在以docker-compose.yml所在文件夹为根目录的/data/git/custom/conf/app.ini

APP_NAME = Gadzan's Git Service
RUN_MODE = prod
RUN_USER = git

[repository]
ROOT = /data/git/repositories

[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo

[repository.upload]
TEMP_PATH = /data/gitea/uploads

[server]
APP_DATA_PATH    = /data/gitea
SSH_DOMAIN       = gitea.domain.com
HTTP_PORT        = 3000
ROOT_URL         = https://gitea.domain.com/
DISABLE_SSH      = false
SSH_PORT         = 9922
SSH_LISTEN_PORT  = 22
LFS_START_SERVER = true
LFS_CONTENT_PATH = /data/git/lfs
DOMAIN           = localhost
LFS_JWT_SECRET   = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OFFLINE_MODE     = false

[database]
PATH     = /data/gitea/gitea.db
DB_TYPE  = postgres
HOST     = database:5432
NAME     = giteadb
USER     = root
PASSWD   = xxxxxxxxxxxxxxxxx
SCHEMA   =
SSL_MODE = disable
CHARSET  = utf8

[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve

[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER        = file

[picture]
AVATAR_UPLOAD_PATH            = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
DISABLE_GRAVATAR              = true
ENABLE_FEDERATED_AVATAR       = false

[attachment]
PATH = /data/gitea/attachments

[log]
ROOT_PATH = /data/gitea/log
MODE      = file
LEVEL     = info

[security]
INSTALL_LOCK   = true
SECRET_KEY     = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
INTERNAL_TOKEN = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

[service]
DISABLE_REGISTRATION              = true
REQUIRE_SIGNIN_VIEW               = true
REGISTER_EMAIL_CONFIRM            = false
ENABLE_NOTIFY_MAIL                = true
ALLOW_ONLY_EXTERNAL_REGISTRATION  = false
ENABLE_CAPTCHA                    = false
DEFAULT_KEEP_EMAIL_PRIVATE        = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING       = true
NO_REPLY_ADDRESS                  = noreply.localhost

[oauth2]
JWT_SECRET = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

[mailer]
ENABLED = true
HOST    = smtp.xxxx.com:465
FROM    = xxx@domain.com
USER    = xxx@domain.com
PASSWD  = xxxxxxxxxxxxxxx

[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = false

[ui]
DEFAULT_THEME = arc-green
THEMES = gitea,arc-green

最后一行THEMES加上arc-green可以设置深色主题,DEFAULT_THEME = arc-green设置默认为深色主题。

修改页面模板

环境变量GITEA_CUSTOM可以不指定。如果想要修改首页,到 Gitea 的源码地址https://github.com/go-gitea/gitea,找到 templates 文件夹,修改后的文件按照源码的文件目录直接放到 Gitea 的/data/git/gitea/templates文件夹下(与conf同一个文件夹),如果设置了GITEA_CUSTOM,在那个文件夹下新建 templates 文件夹,与上一步一样放入修改后的文件,如果不清楚,说简单点就是 templates 放在conf同一文件夹下。

使用自定义的公共文件

将自定义的公共文件(比如页面和图片)作为 webroot 放在 custom/public/ 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。

举例说明:image.png 存放在 custom/public/中,那么它可以通过链接 http://gitea.domain.tld/image.png 访问。


版权声明: 本文为 InfoQ 作者【Gadzan】的原创文章。

原文链接:【https://xie.infoq.cn/article/b1e1aa33c5d2e7841592b2786?from=message】。

本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。


打赏码

知识共享许可协议 本作品采用知识共享署名 4.0 国际许可协议进行许可。

评论