目录

基于gitea+drone完成小团队的CI/CD

前言

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

Gitea 简介

Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证

Gitea的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。采用Go作为后端语言,只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了x86,amd64,还包括 ARM 和 PowerPC.

https://tc.ctq6.cn/tc/20220415150527.png

Gitea 功能特性

  • 支持活动时间线
  • 支持 SSH 以及 HTTP/HTTPS 协议
  • 支持 SMTP、LDAP 和反向代理的用户认证
  • 支持反向代理子路径
  • 支持用户、组织和仓库管理系统
  • 支持添加和删除仓库协作者
  • 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
  • 支持仓库 Git 钩子和部署密钥
  • 支持仓库工单(Issue)、合并请求(Pull Request)以及 Wiki
  • 支持迁移和镜像仓库以及它的 Wiki
  • 支持在线编辑仓库文件和 Wiki
  • 支持自定义源的 Gravatar 和 Federated Avatar
  • 支持邮件服务
  • 支持后台管理面板
  • 支持 MySQL、PostgreSQL、SQLite3、MSSQL 和 TiDB(MySQL) 数据库
  • 支持多语言本地化(21 种语言)

Drone 简介

Drone 是一款基于 Docker 的 CI/CD 工具,所有编译、测试、发布的流程都在 Docker 容器中进行.

开发者只需在项目中包含 .drone.yml 文件,将代码推送到 git 仓库,Drone 就能够自动化的进行编译、测试、发布。

https://tc.ctq6.cn/tc/20220415151730.png

为什么使用 Drone 作为CI/CD 工具

  • 功能灵活强大:构建、测试、发布、部署,你想干什么都可以,一套系统全搞定
  • 兼容性好:支持所有SCM、所有平台、所有语言
  • 环境部署简单:原生支持Docker容器,启动两个容器就完成了部署,其它构建、测试、部署工具在使用时会自动从docker仓库拉取
  • 扩展性强:强大的插件系统,丰富的插件可以免费使用,也可以自定义
  • 配置简单:正如官方宣传的那样,“configuration as a code”,所有功能、步骤、工具、命令,一个yaml配置文件全搞定
  • 维护简单:直接复用SCM的账号体系和权限管理,无需注册用户、分配权限

安装前准备

docker环境安装

1、首先到各大公有云厂商提供的云平台上购买对应的机器,配置可以选择1核2g,或者2核2g,不需要购买太大的配置。

2、机器开通完成后,部署docker环境,可以选择手动部署,或者使用Ansible脚本部署,本次使用Ansible部署,部署脚本如下:(docker-install.yaml)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
---

- name: Remove Docker system
  yum:
    name:
    - docker-client
    - docker-client-latest
    - docker-common
    - docker-latest
    - docker-latest-logrotate
    - docker-logrotate
    - docker-selinux
    - docker-engine-selinux
    - docker-engine
    state: absent
  tags:
  - cicd
  - docker_remove

- name: Remove Docker files
  shell: |
    rm -rf /etc/systemd/system/docker.service.d
    rm -rf /var/lib/docker
    rm -rf /var/run/docke
    rm -rf /etc/docker    
  tags:
  - cicd
  - docker_remove

- name: Install Docker yum
  yum:
    name:
    - yum-utils 
    - device-mapper-persistent-data 
    - lvm2
    state: present
  tags:
  - cicd
  - docker_install

- name: Install yum manager
  shell: |
        yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  tags:
  - cicd
  - docker_install

- name: Install Docker
  yum:
    name: docker-ce
    state: present
  tags:
  - cicd
  - docker_install

- name: Configure Docker for files
  file:
    path: "{{ item }}"
    state: directory
  with_items:
  - /etc/docker
  - /etc/systemd/system/docker.service.d
  tags:
  - cicd
  - docker_install

- name: Configure Docker for config
  template:
    src: "{{ item.name }}"
    dest: "{{ item.dest }}"
  loop:
  - { name: "daemon.json.j2", dest: "/etc/docker/daemon.json" }
  - { name: "docker.service.j2", dest: "/usr/lib/systemd/system/docker.service" }
  tags:
  - cicd
  - docker_install

- name: Started Docker
  systemd:
    name: docker
    enabled: yes
    state: started
  tags:
  - cicd
  - docker_install

- name: Install Docker-Compose
  environment: 
    DOCKER_COMPOSE_VERSION: 1.25.0-rc2
  shell: |
    curl -L https://get.daocloud.io/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose    
  tags:
  - cicd
  - docker_install

- name: Install Docker Swarm
  shell:
    docker swarm init --advertise-addr {{ groups['pankuibo'][0] }}
  tags:
  - cicd
  - docker_install

3、安装docker环境,由于本次Ansible为剧本形式,所以执行如下命令来安装docker:

1
ansible-playbook --inventory-file='./../inventory/inventory.ini' ./deploy.yml -e target='test' --tags='docker_remove,docker_install,docker_compose' --forks=5 --user='root'

这里说明一下:

1、由于定义了不同的tags,来执行不同的操作

2、部署文件、主机文件、角色文件单独分开,更加灵活方便

Gitea安装及配置

安装

Gitea 在其 Docker Hub 组织内提供自动更新的 Docker 镜像。可以始终使用最新的稳定标签或使用其他服务来更新 Docker 镜像,安装的配置文件如下(docker-compose-gitea.yaml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
version: "3.8"

services:
  gitea:
    image: gitea/gitea:1.16.5
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - DB_TYPE=mysql
      - DB_HOST=localhost:3306
      - DB_NAME=gitea
      - DB_USER=gitea
      - SSH_PORT=2224
    volumes:
      - /data/gitea:/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2224:2224"
    networks:
      - "default"
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - "traefik.enable=true"
        - "traefik.docker.network=default"
        - "traefik.http.services.gitea_gitea.loadbalancer.server.port=3000"
        # http 80
        - "traefik.http.routers.gitea.rule=Host(`gitea.localhost.com`)" 
        - "traefik.http.routers.gitea.entrypoints=web"
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
networks:
  default:
    external:
      name: traefik_default

由于依赖于数据库,所以需要先安装Mysql服务到环境中,使用Mysql的安装配置文件如下(docker-compose-mysql.yaml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
version: "3.8"

services:
  mysql:
    image: mysql:5.7.37
    environment:
      - MYSQL_ROOT_PASSWORD=PWD
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - /data/mysql:/var/lib/mysql
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3306:3306"
    networks:
      - "default"
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
networks:
  default:
    external:
      name: traefik_default

要基于 docker-compose 启动gitea,请执行 docker-compose up -d,以在后台启动 Gitea。使用 docker-compose ps 将显示 Gitea 是否正确启动。可以使用 docker-compose logs 查看日志。

要停止gitea,请执行 docker-compose down。这将停止并杀死容器。这些卷将仍然存在。

本次使用如下命令来安装Gitea,目前环境中使用Docker Swarm集群,所以使用如下命令安装即可,关于Docker Swarm的使用说明可以参照Docker Swarm使用说明

1
2
1、docker stack deploy -c docker-compose-mysql.yaml mysql
2、docker stack deploy -c docker-compose-gitea.yaml gitea

以上设置中 Gitea 的端口号为 3000,因此本地环境浏览器进入localhost:3000即可访问页面,建议配置域名和 Nginx 或 Caddy 反向代理访问。本次使用的代理组件是traefik代理,更多关于traefik的使用说明请参考traefik使用说明

关于上面的配置说明

数据库

要将 Gitea 与 MySQL 数据库结合使用,请将这些更改应用于上面创建的 docker-compose-gitea.yaml 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: "3.8"

services:
  gitea:
    image: gitea/gitea:1.16.5
    environment:
+      - DB_TYPE=mysql
+      - DB_HOST=localhost:3306
+      - DB_NAME=gitea
+      - DB_USER=gitea

环境变量

  • APP_NAME:“Gitea: Git with a cup of tea”:应用程序名称,在页面标题中使用。
  • RUN_MODE:prod:应用程序运行模式,会影响性能和调试。“dev”,“prod"或"test”。
  • DOMAIN:localhost:此服务器的域名,用于 Gitea UI 中显示的 http 克隆 URL。
  • SSH_DOMAIN:localhost:该服务器的域名,用于 Gitea UI 中显示的 ssh 克隆 URL。如果启用了安装页面,则 SSH 域服务器将采用以下形式的 DOMAIN 值(保存时将覆盖此设置)。
  • SSH_PORT:22:克隆 URL 中显示的 SSH 端口。
  • SSH_LISTEN_PORT:%(SSH_PORT)s:内置 SSH 服务器的端口。
  • DISABLE_SSH:false:如果不可用,请禁用 SSH 功能。如果要禁用 SSH 功能,则在安装 Gitea 时应将 - SSH 端口设置为 0。
  • HTTP_PORT:3000:HTTP 监听端口。
  • ROOT_URL:"":覆盖自动生成的公共 URL。如果内部 URL 和外部 URL 不匹配(例如在 Docker 中),这很有用。
  • LFS_START_SERVER:false:启用 git-lfs 支持。
  • DB_TYPE:sqlite3:正在使用的数据库类型[mysql,postgres,mssql,sqlite3]。
  • DB_HOST:localhost:3306:数据库主机地址和端口。
  • DB_NAME:gitea:数据库名称。
  • DB_USER:root:数据库用户名。
  • DB_PASSWD:"” :数据库用户密码。如果您在密码中使用特殊字符,请使用“您的密码”进行引用。
  • INSTALL_LOCK:false:禁止访问安装页面。
  • SECRET_KEY:"" :全局密钥。这应该更改。如果它具有一个值并且 INSTALL_LOCK 为空,则 INSTALL_LOCK 将自动设置为 true。
  • DISABLE_REGISTRATION:false:禁用注册,之后只有管理员才能为用户创建帐户。
  • REQUIRE_SIGNIN_VIEW:false:启用此选项可强制用户登录以查看任何页面。
  • USER_UID:1000:在容器内运行 Gitea 的用户的 UID(Unix 用户 ID)。如果使用主机卷,则将其与 /data - 卷的所有者的 UID 匹配(对于命名卷,则不需要这样做)。
  • USER_GID:1000:在容器内运行 Gitea 的用户的 GID(Unix 组 ID)。如果使用主机卷,则将其与 /data 卷的所有者的 GID 匹配(对于命名卷,则不需要这样做)

创建新的 OAuth2 应用程序

创建一个Gitea的 OAuth2 应用程序,“客户端ID”和“客户端密钥”用于授权访问Gitea的资源。 重定向 URI配置必须按照下面示例的格式和路径,并且必须是真实存在的

https://tc.ctq6.cn/tc/20220415201104.png

https://tc.ctq6.cn/tc/20220415201325.png

  • 应用名称-Drone CI
  • 重定向URI-指向Drone的登陆URI
  • 客户端ID
  • 客户端密钥

Drone安装及配置

创建新的共享密钥

创建一个新的共享密钥,用于授权Runners和Drone Server之间进行通信。

可以使用openssl命令生成一个共享密钥:

1
2
openssl rand -hex 16
61379d57490fe37822267e7984acc934

下载镜像

Drone Server 以轻量级的Docker镜像的形式发布,镜像是自包含的,没有任何外部依赖。

1
docker pull drone/drone

配置

Drone 服务器使用环境变量进行配置。本文引用了配置选项的子集,定义如下。有关配置选项的完整列表,请参阅配置

Drone Server 部分

  • DRONE_GITEA_CLIENT_ID 必需的字符串值提供您的 Gitea oauth 客户端 ID
  • DRONE_GITEA_CLIENT_SECRET 必需的字符串值提供您的 Gitea oauth 客户端密码
  • DRONE_GITEA_SERVER 必需的字符串值提供您的 Gitea 服务器地址。例如https://gitea.company.com,请注意,http(s)否则您将看到来自 Gitea 的“不支持的协议方案”错误
  • DRONE_RPC_SECRET 必需的字符串值提供在上一步中生成的共享密钥。这用于验证服务器和运行器之间的 rpc 连接。必须为服务器和运行器提供相同的秘密值
  • DRONE_SERVER_HOST 必需的字符串值提供您的外部主机名或 IP 地址。如果使用 IP 地址,您可以包括端口。例如drone.company.com
  • DRONE_SERVER_PROTO 必需的字符串值提供您的外部协议方案。此值应设置为 http 或 https。如果您配置 ssl 或 acme,此字段默认为 https
  • DRONE_DATABASE_DATASOURCE
    1
    
    DRONE_DATABASE_DATASOURCE=root:password@tcp(1.2.3.4:3306)/drone?parseTime=true
    
    可选的字符串值。配置数据库连接字符串。默认值为嵌入的 sqlite 数据库文件的路径
  • DRONE_DATABASE_DRIVER 可选字符串值。配置数据库驱动程序名称。默认值为 sqlite3 驱动程序。替代驱动程序是 postgres 和 mysql
  • DRONE_GITEA_SKIP_VERIFY 布尔值在建立与远程 Gitea 服务器的连接时禁用 tls 验证。默认值为假
  • DRONE_RUNNER_CAPACITY 可选数字值。限制运行器可以执行的并发管道的数量。这并不限制可以在单个远程实例上执行的并发管道的数量
  • DRONE_USER_CREATE
    1
    2
    3
    
    $ openssl rand -hex 16
    55f24eb3d61ef6ac5e83d550178638dc
    DRONE_USER_CREATE=username:octocat,machine:false,admin:true,token:55f24eb3d61ef6ac5e83d550178638dc
    
    在启动时创建的可选用户帐户。这应该用于使用管理帐户为系统播种。它可以是真实账户(即真实的 GitHub 用户),也可以是机器账户
  • DRONE_USER_FILTER 可选的以逗号分隔的帐户列表。注册仅限于此列表中的用户,或属于此列表中组织成员的用户

Drone Runner 部分

  • DRONE_RPC_HOST 提供 Drone Server 的网络地址(可以带上端口号),Drone Runner 会根据地址连接到 Drone Server 以接收来自 Server 的 piplines 任务
  • DRONE_RPC_SECRET 提供在上一步中生成的共享密钥。这用于验证服务器和运行器之间的 rpc 连接。必须为服务器和运行器提供相同的秘密值
  • DRONE_RPC_PROTO 填http或者https。 取决于访问 Drone Server 的地址是否使用 https
  • DRONE_RUNNER_CAPACITY 一次可以执行几个job,不可为0
  • DRONE_RUNNER_NAME 可选的字符串值。设置Runnner的名字。Runner名称存储在服务器中,可用于将构建追溯到特定Runner
  • DRONE_RUNNER_LABELS 可选的字符串映射。提供一组标签,用于将管道路由到特定机器或一组机器
  • DRONE_LOGS_DEBUG 启用调试日志记录。此配置参数是布尔类型,是可选的
  • DRONE_LOGS_PRETTY 启用日志作为默认 json 格式的替代。此配置参数是布尔类型,是可选的
  • DRONE_LOGS_NOCOLOR 启用日志的颜色格式;与漂亮的打印日志一起使用。此配置参数是布尔类型,是可选的

安装Drone server 和 Drone Runner

Drone Runner 说明

一旦Drone服务已启动并运行,可以安装runners来执行构建流水线(pipeline).

Drone runners 轮询服务器以查找要执行的工作任务,这里提供了几种不同的runners针对不同用户场景和运行时环境进行了优化,可以根据情况安装一个或多个,一种或多种。

1
2
3
4
5
6
1、Docker Runner
2、kubernetes Runner
3、Exec Runner
4、SSH Runner
5、Digital Ocean Runner
6、Macstadium Runner

Docker runner 是一个守护进程,它在一个短生命周期容器中执行流水线(pipeline)任务。可以安装一个单独的 Docker runner,或者在多台机器上安装来创建一个构建集群。

Docker runner 是一个通用的 runner,针对可以在无状态容器中运行测试和编译代码的项目进行了优化。

Docker runner 不太适合不能在容器内运行测试或编译代码的项目,包括以 Docker 不支持的操作系统或体系结构为目标的项目,如macOS

启动 Drone Server 和 Drone Runnner

安装的配置文件如下(docker-compose-drone.yaml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
version: "3.8"

services:
  drone:
    image: drone/drone:2.0.0 #不要用latest,latest并非稳定版本
    ports:
      - "7000:80"
    networks:
      - "drone"
    volumes:
      - /data/drone/:/var/lib/drone/:rw
      - /var/run/docker.sock:/var/run/docker.sock:rw
    environment:
      #- "DB_PASSWD_FILE=/run/secrets/db_passwd"
      - DRONE_DEBUG=true
      - DRONE_DATABASE_DATASOURCE=drone:123456@tcp(localhost:3306)/drone?parseTime=true #mysql配置,要与上边mysql容器中的配置一致
      - DRONE_DATABASE_DRIVER=mysql
      - DRONE_GITEA_SKIP_VERIFY=false
      - DRONE_GITEA_CLIENT_ID=xxxxxx
      - DRONE_GITEA_CLIENT_SECRET=xxxxxx
      - DRONE_GITEA_SERVER=http://localhost:3000/
      - DRONE_TLS_AUTOCERT=false
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RPC_SECRET=48f11fe546a25099cde4a05ce35a4815 #RPC秘钥
      - DRONE_SERVER_PROTO=http #这个配置决定了你激活时仓库中的webhook地址的proto
      - DRONE_SERVER_HOST=localhost:7000
      - DRONE_USER_CREATE=username:root,admin:true #管理员账号,是你想要作为管理员的Gitea用户名
      - DRONE_USER_FILTER=root
      - DRONE_DATADOG_ENABLE=false
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
  drone-runner:
    image: drone/drone-runner-docker:1.6.3
    networks:
      - "drone"
    depends_on:
      - drone
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:rw
    environment:
      - DRONE_RPC_HOST=localhost:7000
      - DRONE_RPC_SECRET=48f11fe546a25099cde4a05ce35a4815
      - DRONE_RPC_PROTO=http
      - DRONE_RUNNER_CAPACITY=4
      - DRONE_RUNNER_NAME=runner
      - DRONE_RUNNER_LABELS=machine1:runner1
      - DRONE_DEBUG=true
      - DRONE_LOGS_DEBUG=true
      - DRONE_LOGS_PRETTY=true
      - DRONE_LOGS_NOCOLOR=false
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
networks:
  drone:
    external: true
    name: traefik_default

如果是使用docker-compose方式启动,只需要在docker-compose-drone.yaml的目录下输入docker-compose up -d 即可

本次通过以下命令可以启动Drone服务,容器通过环境变量配置,如果想要查看完整的配置参数,请查看配置参考(https://docs.drone.io/server/reference)

1
docker stack deploy -c docker-compose-drone.yaml drone

以上设置中 Server 的端口号为 7000,因此本地环境浏览器进入localhost:7000即可访问管理页面,建议配置域名和 Nginx 或 Caddy 反向代理访问。本次使用的代理组件是traefik代理,更多关于traefik的使用说明请参考traefik使用说明

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

在打开并登录 Drone 后,你的 Repositories 应该是空的,因为没有同步 Gitea 的代码仓库到 Drone CI 里,只要在首页里的右上角点击SYNC按钮,Drone 便会自动开始同步 Gitea 的代码仓库。同步完成后需要激活仓库,配置完成后,会自动到对应的私有仓库中创建Webhook构建钩子。

https://tc.ctq6.cn/tc/20220416084628.png

https://tc.ctq6.cn/tc/20220416084751.png

如果 Steps 需要挂载宿主机的文件夹,需要在 Drone 对应项目中的 SETTINGS 里的 Project settings 里需要勾选Trusted,这意味着开启容器的特权模式去挂载宿主机的文件夹。开启这个设置用户的权限必须是 admin ,其他用户没有权限开启。

Drone CI 自动部署的实例

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

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

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

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

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

1
2
3
steps:
- name: build-imaeg # 步骤名
  image: docker # 步骤需要用到的镜像

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

.drone.yml 文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
kind: pipeline
type: docker #在docker runner中运行
name: web

#定义setups,每个setup有属于自己的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: compile #编译
  image: node:12
  commands:
  - yarn config set registry https://registry.npm.taobao.org -g
  - yarn config set cache ./.npm-cache --global
  - yarn install
  - yarn run build

- name: build image #打成docker镜像
  image: docker
  failure: ignore
  volumes:
  - name: sock
    path: /var/run/docker.sock
  commands:
  - docker build -t localhost:v1.0 -f Dockerfile .
  - docker image prune -f --filter "dangling=true"   # 清理无用镜像

- name: rebuild-cache # 把依赖和 npm 缓存放到缓存里
  image: drillster/drone-volume-cache
  settings:
    rebuild: true
    mount:
      - ./.npm-cache
      - ./node_modules
  volumes:
    - name: cache
      path: /cache

- name: deploy #部署到服务器上
  image: docker
  failure: ignore
  volumes:
  - name: sock
    path: /var/run/docker.sock
  commands:
  - docker service ls|grep test || export SERVICE=down #先检查服务是否存在,存在更新,不存在创建
  - |
    if [ "$SERVICE" != "down" ]
    then
      docker service update --image test:v1.0 test_test
    else
      docker stack deploy -c deploy.yaml autocd-web
    fi            
  # 循环检测服务是否启动成功
  - |
    while true
    do
      docker service ps test_test|awk '{print $6}'|awk 'NR==2'|grep 'Running' || export SERVICE=down
      if [ "$SERVICE" == "down" ]
      then
          echo -e "\033[5;35;40m 正在启动中请稍后 ... \033[0m"
          export SERVICE=up
          continue
      else
          docker service logs -n 200 test_test
          sleep 3
          break
      fi
    done          

# 挂载宿主机文件到docker容器中      
volumes:
- name: sock
  host:
    path: /var/run/docker.sock
- name: cache
  host:
    path: /tmp/cache
  
# 创建触发器,绑定分支及事件及上一次成功时才运行
trigger:
  branch:
    - master
  event:
    - pull_request
    - push
  status:
    - success
    - failure

node:
  machine1:runner1

Dockerfile 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 设置基础镜像,如果本地没有该镜像,会从Docker.io服务器pull镜像
# 这里会直接调用宿主机的密钥登录私有仓库。
FROM nginx:1.19.2-alpine

# 编译项目,使用npm安装程序的所有依赖,利用taobao的npm安装,并打包编译成静态文件
# 这两步在drone里已经完成
# 复制所有静态文件到 /usr/share/nginx/html下。
# 拷贝配置文件到nginx配置目录中
COPY dist/ /usr/share/nginx/html/
ADD nginx.conf /etc/nginx/nginx.conf
ADD default.conf /etc/nginx/conf.d/default.conf


# 暴露container的端口
EXPOSE 80

# 运行命令
CMD ["nginx", "-g", "daemon off;"]

流水线说明

上面的范例有5个Steps

  • restore-cache
  • compile
  • build image
  • rebuild-cache
  • deploy

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

1、clone克隆私有仓库代码(默认自动添加);

2、restore-cache 步骤会把之前缓存的文件从宿主机中取出;

3、compile 步骤时 yarn或npm 跳过已经安装过的依赖;

4、build 步骤会时根据仓库中的 dockerfile 打成本地镜像包,由于不需要推送到docker私有镜像仓库即并没有使用plugins/docker插件;

5、rebuild-cache 步骤把缓存通过挂载文件放到宿主机中;

6、deploy 步骤使用 将应用部署到容器中;

优化

因为一次构建每一个 steps 都会新生成一个容器并在容器里运行构建,沙盒环境里没有缓存数据。通过restore-cache和rebuild-cache这两个 steps 建立宿主机与容器的缓存,把 vue 的依赖 node_modules 目录和 yran 缓存通过 volumes 映射到宿主机上,在下一次构建并安装依赖时 yarn 会自动跳过没有变化的依赖包,从而加快构建速度。

实际在构建过程中,Drone CI会默认在所有setup最前面添加一个克隆代码的setup(clone), 使用自建的 Gitea 服务内网拉取可以极致地加快构建速度,等代码克隆完成后才会开始执行预定义的一些setup,如果中途报错,即会直接报错退出整个pipeline流水线流程。

多节点运行

在 docker-compose-drone.yaml 文件中定义 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文件中我们可以定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
kind: pipeline
type: docker
name: default

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

node:
  nodeA: runnerA

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

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

1
2
3
node:
  nodeA: runnerA
  nodeB: runnerB

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