热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Zeroconfiguration(almost)CI/CDwithDrone

本文以npmtypescript的monorepo项目为例,介绍了如何使用Drone的YAMLEndpoint功能实现一个集中管理、生成构建配置的DroneConfigProvid



本文以npm typescript的monorepo项目为例,介绍了如何使用Drone的YAML Endpoint功能实现一个集中管理、生成构建配置的Drone Config Provider,方便项目以几乎为0的成本接入CI/CD。并粗略比较了Drone与JenkinsX和运行在k8s之上的Jenkins。


即刻后端服务早已运行在k8s之上,k8s带来的高可用和水平扩展能力,使开发者无需关心机器配置就可以快速发布和伸缩服务。然而我们的Jenkins却一直难以享受到这种便利,一直跑在一台8核心单机上,闲的时候(如午饭、下午茶、晚饭)没有负载,忙的时候多个服务同时构建就要排队。


JenkinsX


首先尝试使用JenkinsX,部署非常简单,本地安装好jx命令行工具,配置好kubectl,使用:


jx install

就可以根据交互式创建出一个运行在k8s中的JenkinsX了。装完后发现JenkinsX和Jenkins除了名字相近之外并没有什么关系,甚至连UI都没有,考虑到我们的CI/CD不只后端使用,只能放弃JenkinsX。


Jenkins with k8s


然后尝试Jenkins使用k8s节点进行构建,在这种模式下,Jenkins master是否部署在k8s中无所谓,构建任务运行在k8s中。本以为原来项目中的Jenkinsfile稍微改动就可以适配,但后来发现诸如npm registry鉴权、docker in docker构建、volume挂载等问题接踵而至,Jenkinsfile本质是groovy dsl,虽然支持声明式写法,但稍微有点复杂的逻辑都不得不使用脚本式写法,团队中只有少数人能够看懂并修改。并且Jenkinsfile调试起来也不太方便,我们认为这不是一个好的解决方案。


Drone


Drone生而为docker设计,使用更加简洁的yaml作为配置文件,并支持 请求外部服务,动态生成配置文件 。对于一个普通的npm typescript项目,我们使用的配置文件.drone.yml如下:


kind: pipeline
name: example-service
trigger:
branch:
- master
steps:
- name: npm-auth
image: robertstettner/drone-npm-auth
settings:
username: username
password: password
email: username@iftech.io
registry: https://npm.yourcompany.com
scope: '@jike'
- name: install
image: node:10
commands:
- npm ci
- name: lint
image: node:10
commands:
- npm run lint
- name: compile
image: node:10
commands:
- npm run compile
- name: test
image: node:10
commands:
- npm t
- name: ecr-build-push
image: plugins/ecr
settings:
repo: example.ecr.region.amazonaws.com/example-service
registry: example.ecr.region.amazonaws.com
region: region
create_repository: true
tags:
- ${DRONE_SOURCE_BRANCH//\//-}-${DRONE_COMMIT:0:7}
- name: notify
image: plugins/webhook
settings:
urls: http://jkdpy-dashboard-svc.infra:8000/api/images/
content_type: application/x-www-form-urlencoded
template: |
image_name=example.ecr.region.amazonaws.com/example-service:${DRONE_COMMIT:0:7}&\
branch_name=${DRONE_SOURCE_BRANCH}&\
service_name=example-service&\
commit_message=${DRONE_COMMIT_MESSAGE}
services:
- name: kafka
image: spotify/kafka
ports:
- 9092
- name: redis
image: redis
ports:
- 6379
- name: mongo
image: mongo
ports:
- 27017

即使没有看过drone的文档,也不难理解这个构建任务:这是一个名为example-service的任务,首先获得私有npm访问权限,然后clean install、lint、compile、test,再构建生产环境镜像发布到ecr上,最后触发一个自定义的webhook。测试时需要启动三个临时服务kafka、redis和mongo。文件中诸如${}是模板变量,还支持shell字符串操作,${DRONE_COMMIT:0:7}表示取commit hash的前7个字符。steps中的每一步都会启动一个docker,而且plugin、service也都是以docker的形式提供。


如果是monorepo项目,可以使用 drone-config-changeset-conditional 插件,.drone.yml写成如下形式:


kind: pipeline
name: a-service
steps:
...
trigger:
changeset:
includes:
- a-service/*
---
kind: pipeline
name: b-service
steps:
...
trigger:
changeset:
includes:
- b-service/*

当a-service目录下的文件发生变化时,构建a-service;当b-service目录下的文件发生变化时,构建b-service


看起来已经够用了,但仍有些不足:



  • npm registry的password会跟随.drone.yml存在每个项目中,泄露风险较高

  • 假如registy、ecr或webhook的endpoint变了,每个项目都要改一遍

  • monorepo类型项目的配置文件会写的很长


Drone YAML Endpoint


为了解决这些问题,就需要使用DRONE_YAML_ENDPOINT环境变量给drone指定一个动态生产yaml的服务地址,官方的Jsonnet Extension就是这样实现的。


每当一个构建开始前,Drone会向DRONE_YAML_ENDPOINT发送一个post请求,并带上以下参数:


interface Payload {
build: {
id: number
repo_id: number
trigger: string
number: number
status: string
event: string
action: string
link: string
timestamp: number
message: string
before: string
after: string
ref: string
source_repo: string
source: string
target: string
author_login: string
author_name: string
author_email: string
author_avatar: string
sender: string
started: number
finished: number
created: number
updated: number
version: number
}
repo: {
id: number
uid: string
user_id: number
namespace: string
name: string
slug: string
scm: string
git_http_url: string
git_ssh_url: string
link: string
default_branch: string
private: boolean
visibility: string
active: boolean
config_path: string
trusted: boolean
protected: boolean
ignore_forks: boolean
ignore_pull_requests: boolean
timeout: number
counter: number
synced: number
created: number
updated: number
version: number
}
}

这是一段typescript代码,由于没有官方文档,这些类型是根据调试整理推断出的。


请求返回的Data字段中的字符串,即是生成的YAML:


{
"Data": "kind: pipeline\nname: example-service..."
}

基于Drone的自定义YAML Endpoint功能,实现了Drone Config Provider服务。


Drone Config Provider


我们后端的npm项目还算比较规范,基本都是同一个脚手架fork出来的,一般package.json都会使用一些固定的script和dependence:


{
"name": "example-service",
"scripts": {
"start": "node dist/src/app.js",
"compile": "tsc",
"lint": "tslint --project .",
"clean": "rm -rf dist",
"dev": "tsc -w & NODE_ENV=dev PORT=3000 nodemon dist/src/app.js",
"test": "ava"
},
"dependencies": {
"config": "3.2.2",
"ioredis": "4.14.0",
"lodash": "4.17.15",
"mongoose": "5.6.9",
"request": "2.88.0",
"request-promise-native": "1.0.7",
"source-map-support": "0.5.13"
},
...
}

因此可以通过package.json中的scripts来生成steps,根据dependencies生成services:如果scripts中含有lint,就npm run lint,如果有compile就npm run compile。如果dependencies中包含mongoose、mongodb等,就生成一个mongo service,等等。


为了区分不同的项目不同的生成策略,我们通过自定义.drone.yml中的kind来区分不同的策略,最终实现了只需在项目根目录中新建文件.drone.yml:


kind: npm-service

只要项目中有这样一个文件,每次构建触发时,就可以根据该项目的package.json生成一份YAML,密码等敏感信息也可以不用再分散在各个项目中,如果需要修改构建参数,也可以统一修改。


我们的npm monorepo都是采用了一级目录来区分不同的子repo,目录结构大致如下:


.
├── Jenkinsfile
├── README.md
├── a-service
├── b-service
└── c-service

对于monorepo,使用不同的kind来区分:


kind: npm-monorepo

每次构建触发时,根据payload里的commit信息,使用 Github的compareCommits API 来检测此次提交更改的目录,只构建修改过的子repo,就可以简单一行代码给整个repo中的所有子repo接入CI/CD。





同时构建a-service和b-service,c-service没有改动,不构建


Drone Config Provider项目被设计为可以方便增加不同kind,只需增加一个文件,就可以多处理一种新的构建类型。如果某个项目十分特殊,则可以像原来一样使用kind: pipeline完全自定义构建流程。


至此一个几乎不用配置就可以接入的CI/CD就搭建完成了,说的玄乎,其实只是利用了Drone的自定义YAML Endpoint特性,并得益于我们团队较为规范的项目代码,package.json的scripts和dependencies都比较统一,以及我们一直使用的k8s。在此感谢同事们较高的代码品味和良好的编程习惯,感谢基础设施team提供的稳定的k8s服务。


参考资料:





推荐阅读
  • CentOS 7环境下Jenkins的安装与前后端应用部署详解
    CentOS 7环境下Jenkins的安装与前后端应用部署详解 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • 基于iSCSI的SQL Server 2012群集测试(一)SQL群集安装
    一、测试需求介绍与准备公司计划服务器迁移过程计划同时上线SQLServer2012,引入SQLServer2012群集提高高可用性,需要对SQLServ ... [详细]
  • 本文介绍如何使用OpenCV和线性支持向量机(SVM)模型来开发一个简单的人脸识别系统,特别关注在只有一个用户数据集时的处理方法。 ... [详细]
  • 本文详细介绍了在 CentOS 7 系统中配置 fstab 文件以实现开机自动挂载 NFS 共享目录的方法,并解决了常见的配置失败问题。 ... [详细]
  • 单元测试:使用mocha和should.js搭建nodejs的单元测试
    2019独角兽企业重金招聘Python工程师标准BDD测试利器:mochashould.js众所周知对于任何一个项目来说,做好单元测试都是必不可少 ... [详细]
  • ARM汇编基础基于Keil创建STM32汇编程序的编写
    文章目录一、新建项目(1)工具介绍(2)创建项目:二、配置环境(1)配置芯片&#x ... [详细]
  • Parallels Desktop for Mac 是一款功能强大的虚拟化软件,能够在不重启的情况下实现在同一台电脑上无缝切换和使用 Windows 和 macOS 系统中的各种应用程序。该软件不仅提供了高效稳定的性能,还支持多种高级功能,如拖放文件、共享剪贴板等,极大地提升了用户的生产力和使用体验。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
  • 小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限
    小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限 ... [详细]
  • 本文详细介绍了在 Vue.js 前端框架中集成 vue-i18n 插件以实现多语言支持的方法。通过具体的配置步骤和示例代码,帮助开发者快速掌握如何在项目中实现国际化功能,提升用户体验。同时,文章还探讨了常见的多语言切换问题及解决方案,为开发人员提供了实用的参考。 ... [详细]
  • TypeScript 实战分享:Google 工程师深度解析 TypeScript 开发经验与心得
    TypeScript 实战分享:Google 工程师深度解析 TypeScript 开发经验与心得 ... [详细]
  • 深入解析 Vue 中的 Axios 请求库
    本文深入探讨了 Vue 中的 Axios 请求库,详细解析了其核心功能与使用方法。Axios 是一个基于 Promise 的 HTTP 客户端,支持浏览器和 Node.js 环境。文章首先介绍了 Axios 的基本概念,随后通过具体示例展示了如何在 Vue 项目中集成和使用 Axios 进行数据请求。无论你是初学者还是有经验的开发者,本文都能为你解决 Vue.js 相关问题提供有价值的参考。 ... [详细]
author-avatar
花儿在绽放2502857073
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有