持续集成和构建的工具工具有很多,除了著名的团队 Jenkins,Travis,用轻CircleCI,量级还有最近比较热门的开源 Github Action 和 Gitlab CI/CD。但是工具这些工具面对私人项目不是要收费就是占用大量服务器资源,作为个人开发者的团队私人项目如果想要使用并不友好。那么开源免费的用轻 Drone CI 是个不错选择,它不但非常轻量,量级而且十分强大。开源并可以结合私有代码仓库自动编译、工具构建服务,团队几行脚本即可实现自动化部署。本文讲述 Drone CI 的具体实践,结合 Gitea,怎么在 VPS 里从零开始搭建一个基于 Gitea + Drone CI 的持续集成系统。
Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。采用 Go 作为后端语言,只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86,amd64,还包括 ARM 和 PowerPC.
Gitea 功能特性
Drone 是一款基于 Docker 的 CI/CD 工具,所有编译、测试、发布的流程都在 Docker 容器中进行.
开发者只需在项目中包含 .drone.yml 文件,将代码推送到 git 仓库,Drone 就能够自动化的进行编译、测试、发布。
1、首先到各大公有云厂商提供的云平台上购买对应的机器,配置可以选择 1 核 2g,或者 2 核 2g,不需要购买太大的配置。
2、机器开通完成后,部署 docker 环境,可以选择手动部署,或者使用 Ansible 脚本部署,本次使用 Ansible 部署,部署脚本如下:(docker-install.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:
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 在其 Docker Hub 组织内提供自动更新的 Docker 镜像。可以始终使用最新的稳定标签或使用其他服务来更新 Docker 镜像,安装的配置文件如下(docker-compose-gitea.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)
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、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 文件
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
创建一个 Gitea 的 OAuth2 应用程序,“客户端 ID”和“客户端密钥”用于授权访问 Gitea 的资源。重定向 URI 配置必须按照下面示例的格式和路径,并且必须是真实存在的
创建一个新的共享密钥,用于授权 Runners 和 Drone Server 之间进行通信。
可以使用 openssl 命令生成一个共享密钥:
openssl rand -hex 16
61379d57490fe37822267e7984acc934
Drone Server 以轻量级的 Docker 镜像的形式发布,镜像是自包含的,没有任何外部依赖。
docker pull drone/drone
Drone 服务器使用环境变量进行配置。本文引用了配置选项的子集,定义如下。有关配置选项的完整列表,请参阅配置
Drone Server 部分
DRONE_DATABASE_DATASOURCE=root:password@tcp(1.2.3.4:3306)/drone?parseTime=true
可选的字符串值。配置数据库连接字符串。默认值为嵌入的 sqlite 数据库文件的路径
$ openssl rand -hex 16
55f24eb3d61ef6ac5e83d550178638dc
DRONE_USER_CREATE=username:octocat,machine:false,admin:true,token:55f24eb3d61ef6ac5e83d550178638dc
在启动时创建的可选用户帐户。这应该用于使用管理帐户为系统播种。它可以是真实账户(即真实的 GitHub 用户),也可以是机器账户
Drone Runner 部分
Drone Runner 说明
一旦 Drone 服务已启动并运行,可以安装 runners 来执行构建流水线(pipeline).
Drone runners 轮询服务器以查找要执行的工作任务,这里提供了几种不同的 runners 针对不同用户场景和运行时环境进行了优化,可以根据情况安装一个或多个,一种或多种。
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):
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)
docker stack deploy -c docker-compose-drone.yaml drone
以上设置中 Server 的端口号为 7000,因此本地环境浏览器进入 localhost:7000 即可访问管理页面,建议配置域名和 Nginx 或 addy 反向代理访问。本次使用的代理组件是 traefik 代理,更多关于 traefik 的使用说明请参考traefik 使用说明Drone 的登录账号默认是绑定 Gitea 账号的,因此只要登录了 Gitea,Drone 也会自动登录。
在打开并登录 Drone 后,你的 Repositories 应该是空的,因为没有同步 Gitea 的代码仓库到 Drone CI 里,只要在首页里的右上角点击 SYNC 按钮,Drone 便会自动开始同步 Gitea
的代码仓库。同步完成后需要激活仓库,配置完成后,会自动到对应的私有仓库中创建 Webhook 构建钩子。
如果 Steps 需要挂载宿主机的文件夹,需要在 Drone 对应项目中的 SETTINGS 里的 Project settings 里需要勾选 Trusted,这意味着开启容器的特权模式去挂载宿主机的文件夹。开启这个设置用户的权限必须是 admin ,其他用户没有权限开启。
在项目代码的根目录新建一个.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: web # 定义 Pipline 的名字,一个 .drone.yml 可以有多个不同名字的 Pipeline。
然后是描述任务的每个步骤,steps 属性后描述此步骤的 name (名字) 和 image (镜像),每一步都会用到一个镜像,任务进行时会根据提供的镜像名字拉取镜像并生成一个临时 Docker 容器运行任务指令,步骤完成后自动删除。
steps:
- name: build-imaeg # 步骤名
image: docker # 步骤需要用到的镜像
下面是一个 vue 前端程序打包成 Docker 镜像并部署到服务器的例子。文中介绍的范例主要想覆盖常见的坑,对于新手可能会比较复杂,如果看不懂,没关系,可以直接跳过这一节,自己尝试动手安装 Drone CI 后回头再细品。
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
# 设置基础镜像,如果本地没有该镜像,会从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
简单整理一下每一步(详细的上面注释都有解释)
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 文件中我们可以定义
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 机、笔记本,甚至是树莓派。
责任编辑:庞桂玉 来源: 运维派 开源GiteaDrone(责任编辑:知识)
合丰集团(02320.HK)发布公告:年度公司拥有人应占亏损1.72亿港元
四川省达州市达川区“春雷行动2023”打响!严查严打严抓6项违法行为
四川省达州市达川区“春雷行动2023”打响!严查严打严抓6项违法行为