· John Smith · Drone  · 阅读时长 ≈ 20分钟

Drone 集成实践:完全基于 Docker 搭建的轻量级 CI/CD 系统

Drone 是一个现代化的持续集成和持续交付平台,使忙碌的团队能够自动构建、测试和发布工作流。Drone 是一个使用 Go 语言编写的自助式的持续集成平台,和 Gitea 一样可以完全基于容器部署,轻松扩展流水线规模。

Drone 是一个现代化的持续集成和持续交付平台,使忙碌的团队能够自动构建、测试和发布工作流。Drone 是一个使用 Go 语言编写的自助式的持续集成平台,和 Gitea 一样可以完全基于容器部署,轻松扩展流水线规模。

如今的项目或者个人项目中,大家的代码怎么部署呢?公司一般都有完整的持续集成以及持续交付平台,对于小公司可能也有各自搭建了一些,比如jenkins,以及gitlab集成的gitlab-ci等等,这些都可以完成我们部署的工作甚至是测试集成等等一系列流水化工作。

但是,即使如此,我依旧相信,很多公司或者前端开发者根本不太关注什么持续交付、持续集成等等。

至于部署这一块,不少走的还是本地npm run build+ssh+sftp手动模式。

什么是 Drone

Drone 是一个现代化的持续集成和持续交付平台,使忙碌的团队能够自动构建、测试和发布工作流。使用 Drone 的团队发布软件的频率更高,bug更少。

人话:Drone 是一个轻量级的 jenkins ,可以简单的实现软件的流水线化测试、编译、部署。并且可以和 gitlab github gogs gitea 轻松的结合到一起。

Drone 是一个使用 Go 语言编写的自助式的持续集成平台,和 Gitea 一样可以完全基于容器部署,轻松扩展流水线规模。开发者只需要将持续集成过程通过简单的 YAML 语法写入 Gitea 仓库目录下的描述文件 .drone.yml 就可以完成 CI/CD 配置。

为什么不用jenkins呢?

很多公司就在用它,自己个人的代码就想试试其它的,并且jenkins从界面到配置,再到内存占用,我个人都是不太喜欢的,当然不是说jenkins不好,反而借助插件等等,它的能力是很强大的。

官方是这么说的: Drone是一个容器原生持续集成系统,旨在成为老旧Jenkins安装的自助服务替代品。

准备与前提

本文对读者作出以下假设:

  • 具有较为丰富的 git 使用经验
  • 可以熟练的操作某种 git 服务平台,如 gitea、gogs、github、gitlab… 本文以gitea为例
  • 具有一定的 linux 和 docker 的知识储备和操作经验
  • 或许也会使用 docker-compose
  • 或许懂一点 k8s

参与各工具软件角色

角色 功能

  • 用户 Gitea
  • Drone Server Drone 主服务,提供Web界面
  • Drone Runner 我理解为实现各种操作的适配器,例如ssh、docker、k8s操作
  • Drone Agent 操作宿主机 Docker API 的代理程序
  • Docker Server 宿主机的 Doker 程序

用户将代码推送到 Gitea 时触发 Webhook,调动 Drone 从 Gitea 拉取最新的代码并根据 .drone.yml 描述文件执行 CI/CD 流水线。

下面,我们以 gitea.com 服务器为例,搭建一套使用 Gitea 与 Drone 的 CI/CD 系统。

自动化部署配置

概述: 把重复的手动部署工作, 利用Drone工具自动处理整套部署步骤. 「自动执行.sh脚本」

执行的步骤大概如:

场景1:

  1. 打包: 拉取代码 –> 更新依赖(npm i) –> 构建项目(build)
  2. 部署: 删除旧的资源包 –> 替换新构建的资源包

场景2(Docker):

  1. 打包: 拉取代码 –> 更新依赖(npm i) –> 构建项目(build) –> 打包镜像(DockerFile) –> 推送到镜像仓库
  2. 部署: SSH连接到服务器 –> 拉取新镜像 –> 停止和移除旧容器 —> 启动新容器

对 SEO 更好?

Astro 的优点在于它介于 Eleventy 和 Hugo 等框架的静态网站生成器方法,以及 Next.js、Vue 等的完全 JavaScript 世界之间。你可以使用 Astro 采用 HTML 和 CSS 优先的方法,但“轻松地”在 Astro 中“点缀”JavaScript(正如 Scanlon 所说)。

Drone安装

Drone由 Server(服务器) 和 Runner(执行器) 组成

Server

  • 连接到代码仓库 (GitHub、GitLab、Gogs、Gitea、Gitee、…)
  • 提供了Web管理界面
  • 管理 Runner

Runner(执行持续部署操作的服务)

  • 执行时会轮询 Server 来确定执行的操作

准备工作

在Gitea上为Drone创建OAuth认证登陆

在Gitea上为Drone创建OAuth认证登陆

输入信息, 点击保存

记录生产的信息

  • 客户端ID: e5c409df-36be-4cb6-aa25-151325754c3f
  • 客户端密钥: x8Uu2gHyjjhJbbLf2VltvwfMFF3mVTLk0xiHdE9bJjy3

生成 Drone Server 和Drone Runner 通信密钥 「共享密钥」

openssl rand -hex 16
# 输出如: efb695f7109af80ff7582768dd07c3ae

通过docker-compose安装Drone

配置 docker-compose 文件

# 创建drone的专属目录
mkdir /home/git/drone
cd /home/git/drone

# 写入配置
vi docker-compose.yml

docker-compose.yml 配置:

version: '3'
services:
  # 容器名称
  fan-drone-server:
    # 构建所使用的镜像
    image: drone/drone:2
    # 映射容器内80端口到宿主机的3222端口
    ports:
      - 3222:80
    # 映射容器内/data目录到宿主机的/home/git/drone目录
    volumes:
      - ./data:/data
    # 容器随docker自动启动
    restart: always
    environment:
      # Gitea 服务器地址
      - DRONE_GITEA_SERVER=http://124.222.237.29:3333
      # Gitea OAuth2客户端ID
      - DRONE_GITEA_CLIENT_ID=e5c409df-36be-4cb6-aa25-151325754c3f
      # Gitea OAuth2客户端密钥
      - DRONE_GITEA_CLIENT_SECRET=x8Uu2gHyjjhJbbLf2VltvwfMFF3mVTLk0xiHdE9bJjy3
      # drone的共享密钥
      - DRONE_RPC_SECRET=efb695f7109af80ff7582768dd07c3ae
      # Drone 的主机名
      - DRONE_SERVER_HOST=124.222.237.29:3222
      # 外部协议方案
      - DRONE_SERVER_PROTO=http
      # username 为创建OAuth2应用的Gitea管理员用户 , 否则不具有管理员权限。 
      # 非管理员用户缺少选择 (Trusted、Auto cancel pushes、Auto cancel running)
      - DRONE_USER_CREATE=username:git,admin:true
      # 拉取代码的时候开启认证
      - DRONE_GIT_ALWAYS_AUTH=true

  fan-docker-runner:
    image: drone/drone-runner-docker:1
    ports:
      - 7080:3000
    restart: always
    depends_on:
      - fan-drone-server
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # 用于连接到Drone服务器的协议。该值必须是http或https。
      - DRONE_RPC_PROTO=http
      # 用于连接到Drone服务器的主机名
      - DRONE_RPC_HOST=124.222.237.29:3222
      # Drone服务器进行身份验证的共享密钥,和上面设置一样
      - DRONE_RPC_SECRET=efb695f7109af80ff7582768dd07c3ae
      # 限制运行程序可以执行的并发管道数。运行程序默认情况下执行2个并发管道。
      - DRONE_RUNNER_CAPACITY=2
      # docker runner 名称
      - DRONE_RUNNER_NAME=fan-docker-runner-1

激活仓库

访问 124.222.237.29:3222 会直接跳转Gitea认证页面

Gitea认证页面

此处省略注册步骤~~~

进入面版看到gitea中的test1项目, 没有的则点击SYNC同步项目

点击SYNC同步项目

点击test1项目, 进去setting选项并点击激活按钮激活项目

激活按钮激活项目

简单测试, 分享常见错误

在test1测试项目, 建立 .drone.yml 文件, 并写入以下内容进行测试

PS: test1项目 是由vite脚手架, 简单构建出来的ts-react项目

# .drone.yml 文件
kind: pipeline # 定义一个管道
type: docker # 类型
name: test # 名称
steps: # 管道的执行步骤
  - name: test # 步骤名称
    image: node:latest # 步骤使用的镜像
    commands: # 当前步骤执行的命令
      - echo 测试drone # 测试输出
      - pwd # 查看执行目录

正确例子

  • 输出1: 测试drone drone正常执行
  • 输出2: drone执行的绝对路径为 /drone/src

正确例子

错误例子

错误1: 用户非Gitea管理员用户

错误效果

错误1 错误效果

正确效果

错误1 正确效果

解决

确保Gitea用户是管理员用户

确保docker-compose配置中git用户是Gitea的管理员用户

DRONE_USER_CREATE=username:git,admin:true

PS: 修改配置之后记得重启drone服务

错误2: 不受信任的仓库 untrusted repositories cannot mount host volumes

错误效果

错误2 错误效果

解决

确保Trusted选项已勾选

Settings –> Project Settings –> Trusted

正确效果

错误2 正确效果

错误3: 拉取代码失败 Fatal: could not read Username for

错误效果

错误3 错误效果

解决

官方解答

方向1:

开启私有仓库选项 「Settings –> Project Visibility –> Public改成Private」

开启认证, docker-compose.yml 中 DRONE_GIT_ALWAYS_AUTH=true 「为true开启认证」

方向2: 验证用户密码是有有错, 如下配置是否错误:

# 如果有配置用户密码, 验证 docker-compose.yml 配置的用户密码是否有错
DRONE_GIT_USERNAME=<username>
DRONE_GIT_PASSWORD=<password>

实现 场景一

场景1:

  • 打包: 拉取代码 –> 更新依赖(npm i) –> 构建项目(build)
  • 部署: 删除旧的资源包 –> 替换新构建的资源包

知识点:

  • 将宿主机的卷轴映射(挂载)进Drone执行器里面 「公共卷轴」; 简要说明 Drone 提供的Volume
    • Host Volume:数据挂载到宿主机上与宿主机公用卷轴
    • Temporary Volume: 临时卷轴, 不同Step「步骤」共享, Pipeline「管道」 执行完毕后清除.
  • 使用Nginx简单部署打包产物
  • 拓展: 使用 appleboy/drone-scp 上传文件到另外的服务器
  • 拓展: 使用 Setting —> Secrets配置 储存敏感数据

前提条件:

  • 有可以正常打包访问的前端项目(本例子是由vite脚手架, 简单构建出来的ts-react项目)
  • 部署服务已安装nginx, 并可以正常使用

drone和部署的服务在同一台主机

PS: 在同一台部署服务器, 直接共享卷轴, 就可以完成打包并部署

重要说明:

  • 通过与宿主机共享Volume, 缓存node_modules 「每次构建都是全新的drone-runner-docker容器, 共享卷轴可以省去install时间」
  • drone服务和部署服务器同一台主机, 而主机上又有nginx. 通过共享目录, 达到直接部署的目的

项目的 .drone.yml 文件

# 项目的.drone.yml 文件
kind: pipeline # 定义一个管道
type: docker # 定义管道类型
name: build-test1 # 定义管道名称

# 声明宿主机 映射到 drone执行器的数据卷
volumes: 
  - name: node_modules # 数据卷名称
    # Host Volume, 挂载到宿主机上的卷轴
    host: 
      # 宿主机的绝对路径
      path: /home/gite/drone/cache/node_modules
  - name: web_build_dir
    host:
      path: /usr/share/nginx/test1

steps:
  - name: build-project # 步骤名称
    image: node:16.13.1 # 使用镜像和版本
    depends_on: [clone] # 依赖 clone 步骤,
    volumes: # 当前步骤使用(挂载)的卷轴
      - name: node_modules # 数据卷名称
        path: /drone/src/node_modules # 容器内的绝对路径
      - name: web_build_dir
        path: /drone/src/dist
    # 执行的命令
    commands: 
      - pwd # 查看当前目录 `/drone/src`
      - npm config set registry https://registry.npm.taobao.org # 切换淘宝镜像
      - npm install # 安装依赖
      - npm run build # 执行构建指令
  • 检查构建目录 ll /usr/share/nginx/test1
  • 浏览器访问查看效果

drone和部署的服务不在同一台主机

重要说明:

  • 配置secrets, drone配置界面 Setting —> Secrets配置 储存敏感数据
  • 同上, 共享node_modules
  • drone官网插件
  • 使用 appleboy/drone-scp 镜像进行上传文件 地址
  • 官网还有一个cschlosser/drone-ftps插件用ftp上传, 试了半天愣是没成功
    • 官网插件的文档和github 的文档不是一样的「估计是镜像和仓库应该就是不一样的」
    • 一直在运行~~
    • 感觉需要ftp还是自己写个sh吧

项目的 .drone.yml 文件

# 项目的.drone.yml 文件
kind: pipeline # 定义一个管道
type: docker # 定义管道类型
name: build-test1 # 定义管道名称

# 声明宿主机 映射到 drone执行器的数据卷
volumes: 
  - name: node_modules # 数据卷名称
    # Host Volume, 挂载到宿主机上的卷轴
    host: 
      # 宿主机的绝对路径
      path: /home/gite/drone/cache/node_modules
  # 已经不需要共享此卷轴了
  # - name: web_build
  #   host:
  #     path: /home/git/drone/web_build/test1
      # path: /usr/share/nginx/test1

steps:
  - name: build-project # 构建项目步骤
    image: node:16.13.1 # 使用镜像和版本
    depends_on: [clone] # 依赖 clone 步骤,
    volumes: # 当前步骤使用(挂载)的卷轴
      - name: node_modules # 数据卷名称
        path: /drone/src/node_modules # 容器内的绝对路径
      # - name: web_build
        # path: /drone/src/dist
    # 执行的命令
    commands: 
      - pwd # 查看当前目录
      - npm config set registry https://registry.npm.taobao.org # 切换淘宝镜像
      - npm install # 安装依赖
      - npm run build # 执行构建指令
      # - tar -zcvPf tmp.tar.gz /drone/src/dist/* # 有打包需要再打包

  - name: deploy-project # 上传文件步骤
    image: appleboy/drone-scp
    depends_on: [build-project] # 依赖 build-project 步骤,
    settings:
      # 服务器地址, 账号, 密码
      host: o2packs.com # 不使用secrets, 明文
      username: 
        from_secret: deploy_username # 使用secrets
      password: 
        from_secret: deploy_password
      # 需要上传的文件 「需要相对路径, 如果用绝对路径会整条路径打包上传」
      # source: ./dist #  单个
      source:  #  多个
        # - !* # 全都忽略
        - ./dist
      # 上传的目录文件夹
      target: /usr/share/nginx/test1
      # ps: 移除的目录是test1, 将source所有中定义的所有内容放入test1目录中
      rm: true 
      port: 22
      command_timeout: 2m

实现 场景二(Docker)

场景2(Docker):

  • 打包: 拉取代码 –> 更新依赖(npm i) –> 构建项目(build) –> 打包镜像(DockerFile) –> 推送到镜像仓库
  • 部署: SSH连接到服务器 –> 拉取新镜像 –> 停止和移除旧容器 —> 启动新容器

知识点:

  • drone多个pipeline使用
  • 利用Dockerfile打包docker镜像
  • 利用 appleboy/drone-ssh 插件远程到部署服务器部署项目

前提条件:

  • 有可以正常打包访问的前端项目(本例子是由vite脚手架, 简单构建出来的ts-react项目)
  • 部署服务器已安装docker和docker-compose
  • 部署服务器允许密码登陆 「PS: 若使用SSH, 请参考官网修改
kind: pipeline # 定义一个管道
type: docker # 定义管道类型
name: build-test1 # 定义管道名称

# 声明宿主机 映射到 drone执行器的数据卷
volumes: 
  - name: node_modules # 缓存
    host: 
      path: /home/gite/drone/cache/node_modules

# 这里禁用是没有效果的, 因为第一步clone默认执行
# clone:
#   disable: false # 禁用代码拉取

steps:
  - name: build-project # 步骤名称
    image: node:16.13.1 # 使用镜像和版本
    depends_on: [clone] # 依赖 clone 步骤,
    volumes: # 当前步骤使用(挂载)的卷轴
      - name: node_modules # 数据卷名称
        path: /drone/src/node_modules # 容器内的绝对路径
    # 执行的命令
    commands: 
      - pwd # 查看当前目录
      - npm config set registry https://registry.npm.taobao.org # 切换淘宝镜像
      - npm install # 安装依赖
      - npm run build # 执行构建指令

  - name: build-image # 构建镜像
    image: plugins/docker 
    depends_on: [build-project] # 依赖build-project
    settings: # 当前设置
      username: # 账号名称
        from_secret: docker_username
      password: # 账号密码
        from_secret: docker_password
      dockerfile: deploy/Dockerfile # Dockerfile地址, 注意是相对地址
      repo: soulweapon/test1 # docker仓库名称

---

# 新的pipeline
kind: pipeline
type: docker
name: deploy

depends_on: # 依赖build管道
  - build-test1

clone:
  disable: true # 禁用拉取、

steps:
  - name: deploy-project
    image: appleboy/drone-ssh
    settings:
      host: o2packs.com
      username: 
        from_secret: deploy_username # 使用secrets
      password: 
        from_secret: deploy_password
      port: 22
      command_timeout: 2m
      script:
        - echo ==-----==开始部署==-----==
        - docker pull soulweapon/test1:latest
        - docker-compose -p test1 down
        # - docker volume rm xxx # 有挂载卷轴的记得卸载
        - docker-compose -f /home/ubuntu/compose-file/test1.yml -p test1 up -d
        # 过滤出dockerImages的id, 删除none镜像
        - docker rmi $(docker images | grep test1 | grep none | awk '{print $3}')
        - echo ==-----==部署成功==-----==

必备文件1「deploy/Dockerfile」:

# 基于nginx镜像
FROM nginx:latest

# 复制打包文件
COPY ./dist /usr/share/nginx/html

# 复制配置文件
COPY ./deploy/nginx.conf /etc/nginx

# 容器应用端口
EXPOSE 80

必备文件2「deploy/nginx.conf」:

events {
  worker_connections 1024;
}

http {
  include mime.types;
  default_type text/html;
  sendfile on;
  keepalive_timeout 65;
  charset utf-8;
  error_log /var/log/nginx/error.log;
  access_log /var/log/nginx/access.log;

  server {
    listen 80;

    location / {
      root /usr/share/nginx/html;
      index index.html index.htm;
      try_files $uri $uri/ /index.html;
    }

    # 反向代理
    # location ~* /api/(.*) {
    #     resolver 8.8.8.8;
    #     proxy_set_header Host $proxy_host;
    #     proxy_set_header X-Real-IP $remote_addr;
    #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #     proxy_set_header X-NginX-Proxy true;
    #     proxy_pass $SERVER_URL/$1$is_args$args;
    # }
  }
}

必备文件3「部署服务器 - /home/ubuntu/compose-file/test1.yml」:

version: '3.0'

services:
  nginx: 
    image: soulweapon/test1:latest
    container_name: test1_web # 服务名称
    restart: always # 重启方式
    ports: 
      - "8088:80"

以上就是本次的分享,如有错误欢迎指出,谢谢!

声明:本文素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。

参考

其他参考

声明:以上内容为博主记录的博客文章,仅供参考。本文素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。

返回博客列表

Related Posts

View All Posts »
嵌入式开发接口Interfaces

嵌入式开发接口Interfaces

嵌入式开发中,经常遇到很多接口,比如串口、COM口、UART口, TTL、RS-232、RS-485,这里重点讲一下他们之间的关系。