这篇文章上次修改于 302 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

Drone简单实现CI/CD

开始之前先实现一个通过shell和nginx搭建的项目自动化部署环境

一、shell&nginx自动化部署

项目源码实现

代码结构

|--scripts
|    |-deploy.js
|
|--tests
|    |-index.test.js
|
|--.gitignore
|--README.md
|--package.json
|--package-lock.json

说明:

1、scripts目录存放自动化部署脚本deploy.js

2、tests存放项目单元测试脚本

3、.gitignore存放推送远程时需要忽略的文件夹或文件

4、README.md项目展示在页面上的内容,将以Markdown格式展示

5、package.json项目的说明文档和运行规则等

用到的工具

1、vuepress

一个基于Vue、Vue-router、webpack驱动的vue SPA项目,主要作用为了支持Vue项目的文档需求,内置了Markdown语法,支持在Markdown文件中使用Vue组件来开发想要的主题系统。项目运行或打包时会将Markdown格式文档,转换为html格式的文档,供在浏览器上显示。

项目中运用的命令

"scripts": {
  "dev": "vuepress dev .",
  "build": "vuepress build ."
}
  • vuepress dev xxx --> 开发环境,实时查看页面效果
  • vuepress build xxx --> 项目打包,会在根目录生成一个.vuepress文件夹,该文件夹下的dist目录存放着打包后的目录和文件,大致结构是这样

    |--.vuepress
    |      └─dist
    |         |-assets
    |         |   |-js
    |         |   |  └─.......
    |         |   |-img
    |         |   |  └─.......
    |         |   |-css
    |         |      └─.......
    |         |-404.html
    |         |-index.html

2、shelljs

允许在js脚本编写shell脚本

3、async

基于linux命令封装的一个依赖库,指增量同步远程资源文件

scp命令,以加密的方式在本地主机和远程主机之间复制文件,作用类似于cp命令,不过cp只是在本机进行拷贝不能跨服务器。

rsync命令,远程数据同步工具,快速同步多台主机间的文件。该命令使用了“async算法”实现只传送文件不同的部分,不是整份传送

rsyncscp命令的区别

scp是拷贝资源文件,然后去替换远程主机的资源文件,而rsync只是第一次是把所有文件同步过去,当文件修改后,只把修改的文件同步过去。

4、colors

改变打印日志的文本颜色,包括背景色

5、yargs

获取终端输入的命令参数

6、jest

一个单元测试框架,开箱即用

代码实现

先上述安装工具包

npm i vuepress shelljs jest colors yarns rsync --save-dev

1、/tests/index.test.js

简单实现一个单测实例

describe('sum test', () => {
  function sum(a, b) {
    return a + b;
  }
  test('sum add', () => {
    expect(sum(1, 2)).toBe(3);
    expect(sum(4, 2)).toBe(6);
  })
})

2、/scripts/deploy.js

一个js版的shell脚本,整个项目的部署命令都写在里面

部署的步骤:

  • 开始构建发送通知
  • 安装依赖
  • 测试
  • 构建
  • 部署
  • 部署完成发送通知
const shell = require('shelljs');
const path = require('path');
const Rsync = require('rsync');
const colors = require('colors');
const argv = require('yargs').argv;
// 获取命令参数
const [target_server] = argv._;

// 因为我们不可能对一台服务器进行部署,定一个服务器列表,根据用户的命令去部署相应的服务器
const servers_url = {
  stage0001: 'root@zhujingjin:/home/www/doc'
}
// 判断servers_url数组有无定义用户输入的服务器
if(!servers_url[target_server]) {
  shell.echo(colors.red('该目标主机不存在'));
  shell.exit(1);
}

// 企业微信机器人,定一个方法,方便在每个步骤完成后通知相关人员
function sayNews(message) {
  shell.exec(`curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=633a31f6-7f9c-4bc4-97a0-0ec1eefa5898' -H 'Content-Type: application/json' -d '{"msgtype": "text","text": {"content": "${message}"}}'`);
}

// linux 正常输出为0,非零为非正常输出
// 安装依赖
console.log(colors.brightYellow('开始安装依赖....'));
sayNews('开始安装依赖');
if (shell.exec('npm i --registry=https://registry.npm.taobao.org').code !== 0) {
  shell.echo(colors.red('Error: npm i failed'));
  shell.exit(1);
}


// 构建
console.log(colors.brightYellow('开始构建项目....'));
sayNews('开始构建项目');
if (shell.exec('npm run build').code !== 0) {
  shell.echo(colors.red('Error: npm run build failed'));
  shell.exit(1);
}

// 测试
console.log(colors.brightYellow('开始测试项目....'));
sayNews('开始测试项目');
if (shell.exec('npm run test').code !== 0) {
  shell.echo(colors.red('Error: npm run test failed'));
  shell.exit(1);
}

// 部署
// 将打包的文件更新到服务器
console.log(colors.brightYellow('开始部署项目....'));
sayNews('开始部署项目');
var rsync = Rsync.build({
  source:      path.join(__dirname, '../.vuepress/dist/'), // 需要上传的资源目录
  destination: servers_url[target_server],// 上传到远程服务器的目录
  exclude:     ['.git'],
  flags:       'avz',
  shell:       'ssh'
});

rsync.execute(function(error, code, cmd) {
  // error 错误信息,code 0/1,cmd 指向的命令
  console.log(error, code, cmd);
  console.log(colors.random('项目部署成功....'));
  sayNews('项目部署完成');
});

注意:async文档有个错误,rsync.execute()回调句柄的参数对应有误

//正确的
rsync.excute(function(error,code,cmd){
})
// 错误的 
rsync.excute(function(error,stdout,stderr){
})

3、README.md

# 这是个测试项目

啊哈哈哈哈哈

4、.gitignore

/node_modules
/.vuepress

配置服务器nginx

nginx的主配置文件采用通配符*引入/etc/nginx/conf.d /所有的配置文件,所以我们可以新建一个配置文件,来运行部署的项目

$ touch /etc/nginx/conf.d /doc.conf

server{
  listen 8080;   # 端口设置
  server_name zhujingjin.com;  # 访问域名设置
  location / {
      root /home/www/doc;
      index index.html
  }
}

保存,重启nginx

nginx -s reload

开始部署

在本机项目源码目录运行命令

$ node ./scripts/deploy.js

终端运行结果,如图

部署成功,企业微信的提示消息

浏览器访问

完成!

二、Drone简单实现CI/CD

Drone的原理

一个Drone Server(中央Drone服务器)只能对应一个源码库,我用的是github托管。当我们将本地开发好的代码,push到远程github仓库时,该github仓库的webhook会得到一个events,从而告诉Drone Server代码源发生了变化,Drone Server会接受到这个变化任务,然后将任务写到Drone内置的数据库(也可换为mysql等数据库),然后Drone 代理服务器会监听到数据库发生了变化,Drone Agent(接收来自中央Drone服务器的指令以执行部署任务) 通过RPC(Remote Procedure Call:远程过程调用)通知Drone Server调度Drone Agent来执行部署任务,待Drone Agent执行完后再通知Drone Server,Drone Server最后将Drone Agent完成的数据写入数据库。

Drone有自己的数据库,也可使用其他数据库。

Drone server不负责执行任务,它通过调度drone agent来执行任务,drone agent可有多台。

开始实现

安装Docker(在服务器上安装)

我用的是一台服务器,两台服务器更好,一个做Drone Server,另一个做Drone Agent,但都要安装docker

Drone是基于docker的,所以需要安装docker-es(社区版,免费)

安装必要的系统工具

yum install -y yum-utils device-mapper-persistent-data lvm2

添加软件源

yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

更新yum缓存

yum makecache fast

安装docker

yum -y install docker-ce

配置国内镜像源

curl -sSL http://oyh1cogl9.bkt.clouddn.com/setmirror.sh | sh -s <镜像 加速地址>

curl -sSL http://oyh1cogl9.bkt.clouddn.com/setmirror.sh | sh -s http://dockerhub.azk8s.cn # Azure 
# https://github.com/Azure/container-service-for-azure-china/blob/master/aks/README.md#22-container-registry-proxy

启动Docker

systemctl start docker

准备工作

首先需要在github创建一个OAuth App点击进Drong官网的创建教程。

然后服务器需要安装Drone的镜像

docker pull drone/drone:1

配置Drone Server服务

docker run \
  --volume=/var/lib/drone:/data \
  --env=DRONE_AGENTS_ENABLED=true \
  --env=DRONE_GITHUB_SERVER=https://github.com \
  --env=DRONE_GITHUB_CLIENT_ID=${DRONE_GITHUB_CLIENT_ID} \ # Github创建app后的 Client ID
  --env=DRONE_GITHUB_CLIENT_SECRET=${DRONE_GITHUB_CLIENT_SECRET} \ # Github创建app后的 Client Secret
  --env=DRONE_RPC_SECRET=123456 \ # 自定义设置,方便调度对应的Drone Agent
  --env=DRONE_SERVER_HOST=xiaoliua.com \ # Github创建app时的域名
  --env=DRONE_SERVER_PROTO=http \   # 请求协议
  --publish=80:80 \
  --publish=443:443 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone:1

将上述代码配置好后,在服务器终端上执行,创建一个Drone Server服务

然后浏览器访问域名,我配置的域名是xiaoliua.com

当访问到上面的页面证明Drone Server服务配置成功,然后点击绿色按钮,进入下一步,输入Github的登录密码,对Drone进行授权,然后就会看到下面的页面,显示了我们Github上所有的代码仓库。

选择一个自己的Github仓库,作为代码源,接着点解蓝色按钮来激活该仓库(激活也是通过该仓库的webhook触发了drone的一个任务,从而完成激活任务

待看到下面的页面证明激活成功

可以看到有两个Tab导航

  • ACTIVITY FEED(活动源),显示每次push时触发的部署任务的列表。
  • SETTINGS(设置)

简单配置下本地根目录的.drone.yml文件,该处先不解释,下面会解释是什么意思

kind: pipeline
type: docker
name: drone-test

steps:
  - name: install
    image: node:alpine
    commands:
      - npm i --registry=https://registry.npm.taobao.org
    

  - name: test
    image: node:alpine
    commands:
      - npm run test

配置Drone Agent代理服务器

Drone Server不执行任务,所以当我们push发起一个任务时,在活动源Tab栏可以看到,部署任务一直处于等待状态,因为没有处理任务的Drone Agent。

点击进入官网的教程,首先需要安装镜像

$ docker pull drone/drone-runner-docker:1

然后填写配置信息

$ docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e DRONE_RPC_PROTO=http \   # 请求协议
  -e DRONE_RPC_HOST=xiaoliua.com \ # 域名设置
  -e DRONE_RPC_SECRET=123456 \ # 与Drone Server保持一致
  -e DRONE_RUNNER_CAPACITY=2 \
  -e DRONE_RUNNER_NAME=agent \ # 自定义代理服务器名字
  -p 3000:3000 \
  --restart always \
  --name runner \
  drone/drone-runner-docker:1

服务器终端执行上述指令,再去看Drone主界面,部署任务已经开始了

代码具体实现

该仓库的代码目录,就是在根目录下加了个.drone.yml文件

|--scripts
|    |-deploy.js
|
|--tests
|    |-index.test.js
|
|--.gitignore
|--README.md
|--package.json
|--package-lock.json
|--.drone.yml

其他代码均不变,我们只需编写.done.yml1的代码

我的是这样实现的

kind: pipeline
type: docker
name: drone-test

steps:
    # 安装依赖
  - name: install # 自定义step名称
    image: node:alpine  # 使用的docker镜像
    commands:      # 定义终端需要执行的命令
      - npm i --registry=https://registry.npm.taobao.org
    
    # 代码测试
  - name: test
    image: node:alpine
    commands:
      - npm run test
    
    # 构建
  - name: build
    image: node:alpine
    commands:
      - npm run build
    when:   # 定义条件,当条件满足时,执行该step
      target:  # 定义部署到的环境,名称自定义
        - production
    
    # 部署(生产)
  - name: async production  
    image: drillster/drone-rsync
    environment:
      RSYNC_KEY:
        from_secret: rsync_key # 密钥配置
    settings:
      user: root # 部署的服务器用户名
      hosts:
        - 39.107.37.175  # 部署的服务器ip
      source: ./.vuepress/dist/* # 指定需要部署同步的代码目录
      target: /home/www/doc # 指定部署的目标服务器的目录
      secrets: [ rsync_key ]
    when:
      target:
        - production # 辨别部署环境,通过drone cli 实现
      events:
        - promote
  
  # 部署(测试)
  - name: async test
    image: drillster/drone-rsync
    environment:
      RSYNC_KEY:
        from_secret: rsync_key
    settings:
      user: root
      hosts:
        - 39.107.37.175
      source: ./.vuepress/dist/*
      target: /home/www/doc
      secrets: [ rsync_key ]
    when:
      target:
        - test
      events:
        - promote

    # 配置企业机器人
    # 部署成功的通知
  - name: notify success
    image: curlimages/curl
    commands:
      - | # 注意:该命令需要加|,然后换行
        curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=633a31f6-7f9c-4bc4-97a0-0ec1eefa5898' \-H 'Content-Type: application/json' -d '{"msgtype": "text","text": {"content": "ok"}}'
    when:
      target:
        - production
      status:
        - success

    # 部署失败的通知
  - name: notify fail
    image: curlimages/curl
    commands:
      - |
        curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=e2e7031e-3691-4f98-a46d-99c4a58e5d69' -H 'Content-Type: application/json' -d '{"msgtype": "text","text": {"content": "file"}}'
    when:
      target:
        - production
      status:
        - failure

说明:

需要的docker镜像,点击去这里面搜索

还要去Drone Server界面配值密钥,把自己的私钥,存到里面,名字要和.drone.yml里的一样。

下面push一次,只会进行CI的部分(clone,install,test)

所以需要本地安装Drone Cli来本地进行控制部署CD部分

安装Drone Cli(我的是Mac)

$ brew tap drone/drone
$ brew install drone

然后配置环境变量,简单的方法,在Drone Server主界面的右上角点击头像,再点击User Settings选项,就能看到,最后复制执行

$ export DRONE_SERVER=http://xiaoliua.com
$ export DRONE_TOKEN=xxxxxx
$ drone info

等待执行完后,我们就可以用drone命令进行持续部署了

运行命令

$ drone build promote xiaoliuing/drone-doc 6 production

promote对应.drone.ymlwhen eventsproduction对应.drone.ymlwhen target

  ........
  when:
      target:
        - production
      events:
        - promote

然后回过来看,Drone Server主界面

企业微信机器人也通知了

在输入部署的服务器ip访问

再次执行整个流程

修改一下README.md

# 这是个测试项目

啊哈哈哈哈

ok!!!!!

然后,本地push到远程仓库

$ git add .
$ git commit -m "7"
$ git push origin master

持续集成完成

然后通过drone cli工具进行持续部署工作

$ drone build promote xiaoliuing/drone-doc 7 production

企业微信收到了通知

然后浏览器访问

整个流程完成!!!

总结

本篇文章只是一个简单的Drone实践,旨在会使Drone CI工具,待优化的地方很多,比如node_modules的缓存啊、怎么图形化监控整个CI&CD的每个环节等。