前言
持续集成和构建的工具有很多,除了著名的 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.
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 就能够自动化的进行编译、测试、发布。
为什么使用 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)
代码语言:yaml复制---
- 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:
代码语言:shell复制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):
代码语言:yaml复制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)
代码语言:yaml复制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使用说明
代码语言:shell复制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 文件
代码语言:yaml复制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配置必须按照下面示例的格式和路径,并且必须是真实存在的
- 应用名称-Drone CI
- 重定向URI-指向Drone的登陆URI
- 客户端ID
- 客户端密钥
Drone安装及配置
创建新的共享密钥
创建一个新的共享密钥,用于授权Runners和Drone Server之间进行通信。
可以使用openssl命令生成一个共享密钥:
代码语言:shell复制openssl rand -hex 16
61379d57490fe37822267e7984acc934
下载镜像
Drone Server 以轻量级的Docker镜像的形式发布,镜像是自包含的,没有任何外部依赖。
代码语言:shell复制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
代码语言:shell复制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
代码语言:shell复制$ 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针对不同用户场景和运行时环境进行了优化,可以根据情况安装一个或多个,一种或多种。
代码语言:shell复制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):
代码语言:yaml复制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)
代码语言:shell复制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构建钩子。
如果 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 都有的头描述部分:
代码语言:shell复制kind: pipeline # Pipeline 的类型,其他的还有 secret and signature。
type: docker # 定义了执行任务的类型,这里会使用 Docker 执行。
name: web # 定义 Pipline 的名字,一个 .drone.yml 可以有多个不同名字的 Pipeline。
然后是描述任务的每个步骤,steps 属性后描述此步骤的 name (名字) 和 image (镜像),每一步都会用到一个镜像,任务进行时会根据提供的镜像名字拉取镜像并生成一个临时 Docker 容器运行任务指令,步骤完成后自动删除。
代码语言:shell复制steps:
- name: build-imaeg # 步骤名
image: docker # 步骤需要用到的镜像
下面是一个 vue 前端程序打包成 Docker 镜像并部署到服务器的例子。文中介绍的范例主要想覆盖常见的坑,对于新手可能会比较复杂,如果看不懂,没关系,可以直接跳过这一节,自己尝试动手安装 Drone CI 后回头再细品。
.drone.yml 文件
代码语言:yaml复制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 "