热门标签 | 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服务。


参考资料:





推荐阅读
  • Vue cli2.0 项目中使用Monaco Editor编辑器
    monaco-editor是微软出的一条开源web在线编辑器支持多种语言,代码高亮,代码提示等功能,与VisualStudioCode功能几乎相同。在项目中可能会用带代码编 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了三种方法来实现在Win7系统中显示桌面的快捷方式,包括使用任务栏快速启动栏、运行命令和自己创建快捷方式的方法。具体操作步骤详细说明,并提供了保存图标的路径,方便以后使用。 ... [详细]
  • 基于dlib的人脸68特征点提取(眨眼张嘴检测)python版本
    文章目录引言开发环境和库流程设计张嘴和闭眼的检测引言(1)利用Dlib官方训练好的模型“shape_predictor_68_face_landmarks.dat”进行68个点标定 ... [详细]
  • k8s进阶之搭建私有镜像仓库
    企业级私有镜像仓 ... [详细]
  • 程度|也就是_论文精读:Neural Architecture Search without Training
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了论文精读:NeuralArchitectureSearchwithoutTraining相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了JavaScript进化到TypeScript的历史和背景,解释了TypeScript相对于JavaScript的优势和特点。作者分享了自己对TypeScript的观察和认识,并提到了在项目开发中使用TypeScript的好处。最后,作者表示对TypeScript进行尝试和探索的态度。 ... [详细]
  • 关键词: ... [详细]
  • RN即ReactNative基于React框架针对移动端的跨平台框架,在学习RN前建议最好熟悉下html,css,js,当然如果比较急,那就直接上手吧,毕竟用学习前面基础的时间,R ... [详细]
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社区 版权所有