prefacio
Por lo general, escribo algunos servicios para mi propio uso. Antes de eso, se implementaba manualmente, es decir, se empaquetaba localmente, se cargaba en el servidor, se detenía el servicio original y se ejecutaba el nuevo servicio. Es muy complicado escribir la línea de comando manualmente cada vez Basado en el principio de que se puede automatizar, no es necesario hacerlo manualmente, por lo que decidí desarrollar un proceso de CI/CD para completar automáticamente el trabajo de empaquetado e implementación. Después de leer algunas plataformas de CI introducidas en Internet, combinadas con el rendimiento limitado de mi servidor y requisitos relativamente simples, finalmente elegí Drone .
Frameworks y herramientas implicadas en todo el proceso:
- Nginx como servidor proxy;
- SpringBoot;
- Gradle como herramienta de construcción;
Instalar Dron
Drone se puede usar con múltiples plataformas Git, incluidas Github, GitLab, Gitea, Gogs, etc. Aquí se usa Github como ejemplo.
Crear una aplicación OAuth
Para usar Drone con Github, primero debe crear una aplicación Github OAuth y crear una nueva aplicación en Configuración de Github - Configuración del desarrollador - Aplicaciones OAuth. Preste atención al formato que Authorization callback URL
debe tener http(s)://域名/login
, los ejemplos son los siguientes:
Una vez completada la creación, debe recordar ClientId
y ClientSecret
, y debe usarlo más tarde;
Instalar Drone y Drone-Runner
Una vez completada la configuración, Drone se instala oficialmente. Drone debe instalarse usando Docker, use el siguiente comando:
# 拉取镜像
docker pull drone/drone:2
# 运行
docker run \
--volume=/var/lib/drone:/data \
--env=DRONE_USER_CREATE=username:${GITHUB_USERNAME},admin:true \
--env=DRONE_GITHUB_CLIENT_ID=${CLIENT_ID} \
--env=DRONE_GITHUB_CLIENT_SECRET=${CLIENT_SECRET} \
--env=DRONE_SERVER_HOST=${URL} \
--env=DRONE_SERVER_PROTO=http \
--env=DRONE_RPC_SECRET=d1dcf4f192da23plpm25k126zh87otv0 \
--publish=7000:80 \
--restart=always \
--detach=true \
--name=drone \
drone/drone:2
复制代码
DRONE_USER_CREATE
Es el nombre de usuario del administrador,username
seguido del nombre de usuario de Github.Si no está configurado, es imposible realizar configuraciones avanzadas en el almacén;DRONE_GITHUB_CLIENT_ID
es el ClientId después de crear la aplicación OAuth;DRONE_GITHUB_CLIENT_SECRET
es el ClientSecret después de crear la aplicación OAuth;DRONE_SERVER_HOST
Debe ser el mismo que el nombre de dominio de la URL de devolución de llamada de autorización al crear la aplicación OAuth (sin/login
ruta);DRONE_SERVER_PROTO
需要与创建OAuth Application时的Authorization callback URL的proto相同;DRONE_RPC_SECRET
将在后面配置Drone-Runner时使用,作为Drone与Drone-Runner通信的“凭证”,可以用openssl rand -hex 16
生成一个;--publish
设置Docker的端口映射规则,这里将Drone的80端口映射到主机的7000端口;
接着安装Drone-Runner,命令如下:
# 拉取镜像
docker pull drone/drone-runner-docker:1
# 运行
docker run --detach \
--volume=/var/run/docker.sock:/var/run/docker.sock \
--env=DRONE_RPC_PROTO=http \
--env=DRONE_RPC_HOST=drone \
--env=DRONE_RPC_SECRET=d1dcf4f192da23plpm25k126zh87otv0 \
--env=DRONE_RUNNER_CAPACITY=2 \
--env=DRONE_RUNNER_NAME=drone-runner \
--publish=7001:3000 \
--restart=always \
--name=runner \
--link drone:drone \
drone/drone-runner-docker:1
复制代码
需要注意DRONE_RPC_SECRET
需要与运行Drone时的配置保持一致;此外还要注意这里的DRONE_RPC_PROTO
, DRONE_RPC_HOST
以及--link
,由于我们这里的Drone与Drone-Runner在同一台主机上运行,需要它们之间可以相互通信,因此采用了--link
选项,使得Runner的DRONE_RPC_HOST
能够访问到Drone;
上面的Runner叫做Docker Runner,是Drone-Runner最常用的一种,主要为在无状态容器中编译及测试的代码提供了优化。除了Docker Runner以外,还有Kubernetes Runner, Exec Runner, SSH Runner等,由于与本文内容无关,不在此详细介绍
运行后,使用docker logs runner
命令查看日志,如果有successfully pinged the remote server信息,代表Runner运行成功。
配置Nginx
Nginx的配置比较简单,只需要在Nginx的配置文件中添加以下内容,然后重启Nginx即可:
# /etc/nginx/nginx.conf
server {
listen 80;
# URL与Authorization callback URL保持一致,如drone.test.com
server_name ${URL};
location / {
# 与Docker配置的端口号保持一致
proxy_pass http://localhost:7000;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
}
}
复制代码
重启Nginx:
systemctl restart nginx
复制代码
对于比较新的Nginx版本,对于站点的配置可能在sites-enabled目录下,这时候在/etc/nginx/sites-enabled
目录下创建drone.conf文件,然后将上面的配置复制到drone.conf文件中即可;
完成配置后,访问Drone URL,登录Github账号,针对仓库进行配置。
配置Repository
在Drone后台首页,可以看到Github账号下的所有仓库,点击我们想要启用CI的仓库,进入Settings Tab下,点击Activate Repository
后,可以看到如下配置页面:
我们需要将Trusted
开关打开,以保证后续pipeline中的命令可以正常执行。此外还有Timeout和Configuration等配置项,Timeout指定Pipeline脚本超时时间,而Configuration则对应了脚本名称。
如果Settings中没有这么多配置项,可能是运行Drone时没有设置Github用户名或者用户名不对
Settings Tab下还有Secrets
等配置,可以在Sercets
中新建secret,比如密码等,其值可以在后续的pipeline中获取到。
然后切换到Builds Tab下,点击右上角的NEW BUILD按钮,在弹出的对话框中输入目标分支名,然后点击Create。当目标分支有新的commit时,Drone会自动根据脚本运行Pipeline。
配置Pipeline
完成配置仓库后,我们需要手工编写Drone脚本,告诉Drone当有新提交时需要做什么。对于大部分服务,都需要 构建 - 部署 步骤。我们分开进行介绍。
构建任务
在需要开启CI的仓库的根目录下,创建.drone.yml
文件,输入以下内容:
kind: pipeline
type: docker
name: MyService
steps:
- name: build
image: gradle:jdk11
volumes:
- name: service-root
path: /app/build
- name: gradle-cache
path: /root/.gradle
commands:
- ./gradlew bootJar
- cp /drone/src/app-api/build/libs/*.jar /app/build/my-service-latest.jar
volumes:
- name: service-root
host:
path: /root/service
- name: gradle-cache
host:
path: /root/.gradle
复制代码
kind
代表脚本的类型,这里指定为pipeline脚本,除了pipeline脚本还有secret和signature脚本;type
定义pipeline的类型,与runner类型,除了Docker外,还有Kubernetes, Exec, SSH等;name
为pipeline的名称;steps
定义了pipeline的步骤,其由多个如上面形式的步骤组成:name
为此步骤的命名;image
指定了这次步骤所依赖的Docker镜像;volumes
将Docker的路径与主机路径进行映射,以能在文件系统上相互访问;commands
为此步骤需要运行的命令;
volumes
挂载了主机的卷,我们可以在steps
的volumes
中直接使用这里定义的卷名称;
在上面的脚本中,我们进行了如下的操作:
- 定义pipeline名称为
MyService
; - 接着在steps中,定义了名为
build
的步骤,指定使用gradle:jdk11
作为此步骤使用的镜像; - 在volumes中,将主机的
/root/service
路径与Docker容器内的/app/build
路径以及主机的/root/.gradle
路径与Docker容器内的/root/.gradle
路径做了映射,这样便可以直接访问主机指定目录下的文件系统; - 在commands中,首先使用
gradlew
命令打包SpringBoot程序,然后将打包后的Jar文件复制到/app/build
目录下,由于我们将/app/build
路径与主机的/root/service
做了映射,因此打包后的Jar文件会被直接复制到主机的/root/service
目录下;
在复制命令中,cp的源目录是/drone/src/...,/drone/src是Drone的默认工作目录,代码会直接被clone到此目录下,可以在.drone.yml中通过workspace:path的方式进行配置
完成后,我们将.drone.yml
进行提交并推送到Github上,打开Drone后台,可以看到Drone已经自动开始了pipeline任务:
Drone默认为pipeline增加clone步骤,并在自定义steps之前运行,特殊配置我们会在下文进行介绍
经过如上的操作,我们便完成了构建任务;
部署任务
由于Drone运行在Docker中,无法直接访问主机,因此部署任务需要SSH来“绕行”。
仍然是在.drone.yml
文件中,在steps下增加以下内容:
steps:
- name: build
# ...
- name: deploy
image: appleboy/drone-ssh
settings:
host: 123.12.34.56
username: root
password:
from_secret: ssh_password
port: 22
command_timeout: 5m
script:
- cd /root/service
- mv my-service.jar my-service.backup.jar
- mv my-service-latest.jar my-service.jar
- chmod +x run.sh
- ./run.sh
when:
event:
- promote
复制代码
image
指定此步骤需要使用SSH镜像;settings
针对SSH进行配置:host
指定主机IP地址;username
SSH用户名;password
SSH密码,这里我们使用from_secret
的方式从Secrets中取值(需要在仓库的Settings配置);port
SSH端口号;command_timeout
远程操作命令超时时间;script
是需要SSH运行的脚本,类比于steps的commands;
when
指定了此步骤运行的时机,event=promote
指当构建任务完成后,手动在这次pipeline的后台点击右上角三个点 - Promote时运行;
在script
中,首先将当前目录切换至构建任务目标Jar包所在目录,然后执行一些重命名操作,并将运行脚本修改为execuable,最后运行脚本;
以运行Jar包为例,脚本内容如下:
#!/bin/sh
originalPid=$(ps x | grep "my-service" | grep -v grep | awk '{print $1}')
if [[ -n "$originalPid" ]]
then
echo "Running my-service PID=$originalPid"
echo "Stop Running my-service..."
kill $originalPid
else
echo "No Running my-service Found..."
fi
newPid=$(nohup java -jar my-service.jar >/dev/null 2>&1 &)
echo "Start my-service PID=$newPid"
复制代码
以上便是构建和部署任务的相关配置,下面我们介绍一些针对Pipeline的其他配置。
Clone配置
上文中讲到Drone会自动执行Clone步骤,如果我们需要某些自定义配置,也可以在脚本中进行声明:
kind: pipeline
type: docker
name: default
clone:
depth: 50
retries: 3
复制代码
depth
同Git的--depth
tag;retries
失败的重试次数,默认不会重试;
Drone默认clone不会fetch tag,如果需要可以通过以下配置:
steps:
- name: fetch
image: alpine/git
commands:
- git fetch --tags
复制代码
同样,Drone也不会拉取submodules,如需要,则使用以下配置:
steps:
- name: submodules
image: alpine/git
commands:
- git submodule update --init --recursive
复制代码
如果仍需要比较复杂的Git逻辑,可以直接禁用Drone的clone,自己定义Git逻辑:
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone https://github.com/octocat/hello-world.git .
- git checkout $DRONE_COMMIT
复制代码
DRONE_COMMIT
是触发此次pipeline的commit id;
Trigger配置
如果想要pipeline在某些特殊的情景下运行,可以通过Drone提供的trigger进行配置:
steps:
- name: build
# ...
trigger:
branch:
- master
include:
- master
- feature/*
exclude:
- alpha/*
ref:
- refs/heads/master
- refs/heads/**
- refs/pull/*/head
event:
- cron
- custom
- push
- pull_request
- tag
- promote
- rollback
复制代码
branch
当某个pull request以此分支作为目标分支时触发;ref
基于Git引用有更新时触发;event
当遇到某个事件时触发;
ref
与event
都可以使用include与exclude