本文教你如何白嫖实现足够自动的开发测试交付,面向读者最好满足以下条件:
- 足够穷
- 足够懒
issueops?
听过chatops,gitops还没听过issueops,顾名思义issueops就是在讨论issue的时候把ops的事给干了,讨论问题就把问题给解决,美哉。
kubernetes项目issue或者PR中会经常看到这样的东西:
类似/kind feature
这样的指令是给机器人看的,这里就要介绍一位非常勤奋的小伙伴了:
他叫k8s-ci-robot,我想没有谁的贡献有他多了吧,这可不是刷出来的,都是实打实的工作量:
收到指令后robot就忙着打标签,验证提问题的有没有cla认证,分配review代码的人等:
然后懒汉们review完代码就又吩咐机器人测试:
小伙伴就一顿操作梦如虎的干了好多事情还不拿年终奖:
以上,企业是不是发现养一个robot胜似招10个员工,是不是比疫情期间面向周报编程程序员靠谱多了~
Prow
Prow 就是robot的实现,原理非常简单,就是通过github webhook去监听github产生的事件,分析里面的指令去执行对应的job,它能干的几个重点的事:
- 执行任务,特别是测试任务
- 合并代码,你可能觉得这不就是点个按钮的事吗?其实不是,比如一个bug的修复可能要合并到很多个版本中,做起来枯燥且恶心,而且很多时候还是希望合并代码时能做些额外的事,如通知,打标签等等
- 解析并执行类似
/foo
的指令,这很重要基本想干啥都行 - 一个前端用于显示merge状态,任务等
Prow部署步骤大概如下:
- 申请一个github账户,给机器人用,推荐用独立的账号
- 有个k8s集群
- 配置权限,以及创建secret用于存储github账户的token,调用github SDK需要用
- 启动prow,就会得到一个http的地址
- 在你的github仓库里配置,webhook指向prow地址
别忘了我们很穷,在家隔离马上都要失业了服务器都买不起哪来的k8s集群?所以讨论这么多prow有啥用!
接下来白嫖模式开启
我们需要解决两个问题:
- 免费的计算资源跑http服务,给github webhook调用
- 一个免费跑job的平台
免费的httpserver
怎么去找一个免费的http服务?我们自然把恶毒的目光投向函数计算:
阿里云腾讯云都有免费额度,而且这个量对于我这种小任务完全够用了,这个羊毛不薅一把我简直良心不安。
FaaS
很遗憾阿里云没有默认的golang的函数模板,但是支持自定义环境,只要监听9000端口就行。
对于鄙视UI的我来说在页面上创建函数丢不起这人,命令行工具fun必须要用:
函数配置信息
template.yml:
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
CRService:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'custom runtime demo'
LogConfig: # 日志配置,需要与日志服务打通,这块对新手体验不是太好
Project: 'sealyun'
Logstore: 'robot'
Policies:
- AliyunLogFullAccess
robot:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
CodeUri: ./code.zip
Description: 'demo with custom runtime'
Runtime: custom
Events:
http_t:
Type: HTTP
Properties:
AuthType: ANONYMOUS
Methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD'
代码框架,body里就是github传过来的events信息了:
func handler(w http.ResponseWriter, req *http.Request) {
b,err := ioutil.ReadAll(req.Body)
event := &issue.IssueCommentEvent{}
...
}
func main() {
fmt.Println("FunctionCompute go runtime inited.")
http.HandleFunc("/", handler)
port := os.Getenv("FC_SERVER_PORT")
if port == "" {
port = "9000"
}
http.ListenAndServe(":" + port, nil)
}
写个简单脚本去部署函数:
函数启动时只认bootstrap二进制文件,用zip格式打包,fun deploy时会提示一些accesskey相关的配置
go build -o bootstrap faas.go
zip code.zip bootstrap
fun deploy
部署完你的触发器里就有个地址了:
把这个地址配置到github webhook中
这基本问题就解决了,要注意函数计算日志配置有点小绕,这里不细提了
执行任务
如果函数计算的自定义环境足够强大,那我们可以直接在函数里去执行一些任务,如编译,测试等,但是这不太友好也不太现实,不友好的地方是自定义环境的方式过于简陋,比如你需要函数中调用git命令,那你只能放zip里一起打包,没有像Dockerfile那样灵活,也没发现哪家能兼容Dockerfile的标准。 还有就是打包的东西太多可能函数的冷启动会很慢。
同样我还希望实现一些功能时不需要对机器人的代码进行变更,只需要修改一些外部配置或者脚本就可以实现不同的任务的处理。如此,drone来也
drone promote事件
先介绍一下drone promote事件,允许我们通过http触发pipeline中的一个动作。这个使用场景非常广泛,典型的一个场景:我们的项目编译发布结束之后需要上线,有两个环境,开发环境,测试环境
pipeline配置中:
- name: dev
image: golang:1.12
commands:
- echo "部署到开发环境"
when:
event:
- promote
target:
- dev
- name: test
image: golang:1.12
commands:
- echo "部署到测试环境"
when:
event:
- promote
target:
- test
这样我们想先只在开发环境中部署,那么就执行下面命令:
# drone build promote 项目 build编号 目标
drone build promote fanux/sealos 42 dev
这样dev这个步骤就会被执行,而test不会.
如果你是开源项目,那可以免费的使用drone提供的公有服务,又薅得一手好羊毛 cloud.drone.io
drone promote对接FaaS
既然是触发promote事件那下载下drone命令行就是了,何必又监听事件,又调用SDK绕一圈? 这就回到最开始的议题:我希望在讨论问题时把问题解决。
我也喜欢把所有东西放在云端,压根不想本地安装drone客户端,而且很多时候我可以在手机上发号施令。而且还希望执行完后能回覆issue执行结果等等
这里使用drone官方sdk即可:
// 创建client
config := new(oauth2.Config)
auther := config.Client(
context.Background(),
&oauth2.Token{
AccessToken: d.DroneToken,
},
)
client := drone.NewClient(d.DroneServer, auther)
// 触发promote事件
_,err := client.Promote(namespace,name,cmd.Build,cmd.Target,cmd.Params)
namespace就是github用户名或者组名,name是仓库名,如 drone/drone-go 项目 drone就是namespace drone-go就是name
Params是一个key value的参数,会注入到pipeline的环境变量中,也是非常有用的。
把以上东西写到FaaS中雏形就有了。
最终效果:
回复一下issue
pipeline被执行
robot框架
当然我希望robot的功能扩展性尽可能好,而且不仅仅能对接drone还可以对接其它的系统,以及扩展别的指令。所以我已经写好了一个框架:robot
只需要写一个特定命令的特定处理器,然后注册到框架中即可:
type Robot interface {
Process(event IssueEvent) error
}
注意要想Processor生效必须要注册处理器 issue.Regist(命令,处理器)
以drone promote处理器为例:
// 结构体包含drone服务器地址和token
type DronePromote struct {
DroneServer string
DroneToken string
}
// 实现一下处理器接口
func (d *DronePromote) Process(event issue.IssueEvent) error {
...
}
这里event会把issue中的命令解析好丢给你的处理器处理:
type IssueEvent struct {
*IssueCommentEvent
Command *Command
Client *github.Client
}
你只需要关心你的处理器要处理啥指令就行,比如使用promote处理器时:
// github 把事件数据以json格式发送过来,已经被解析到event结构体中
func promote(ctx context.Context, event issue.IssueCommentEvent) (string, error) {
// or using env: GITHUB_USER GITHUB_PASSWD
// github 账户名和密码,因为机器人可能还要回复issue什么的操作,这里建议单独给机器人申请个账号
// 不传参数就会从环境变量中读取
config := issue.NewConfig("sealrobot", "xxx")
// regist what robot your need, and the robot config
// 注册一下你希望哪个机器人处理,因为一条issue中可能会有很做指令,我们只关心/promote即可
// Drone的处理器需要知道drone的地址和token是什么
issue.Regist("/promote", &drone_promote.DronePromote{"https://cloud.drone.io", "QS3SmhZVpJAmb7tWPuWIOh3BhuI"})
// 处理issue
err := issue.Process(config, event)
return fmt.Sprintf("goversionecho %s", err), nil
}
使用Regist注册你的处理器,这样这个处理器只会处理issue中"/promote ..."的指令:
issue.Regist("/promote", &drone_promote.DronePromote{"https://cloud.drone.io", "QS3SmhZVpJAmb7tWPuWIOh3BhuI"})
总结
我们还用这种机制来做一些有趣的事,比如为了付给开源开发者一些酬劳,我们会开一个issue, 使用/pay 100
指令,表示这个任务完成并且PR的代码被merge就自动转账100元到开发者的账户作为酬劳。
还有整个sealos离线包发布和测试过程也都是由机器人完成,我们只需要 /test v1.17.3
/release v1.17.3
就全自动发布了,中间有很多过程比如申请虚拟机,下载官方包,下载镜像,安装测试,打包成离线包格式,上传oss,写入卖包网站数据库,等等,如此我们才能每次发布都能做到几乎是全网最快。
乔布斯做产品连用户看不到的机器内部或者工厂机器都要做到极致,确实很多时候做成一件事在用户看不到的地方花了多少功夫极为重要。~~~~
总之,作为一名有节操的程序员一定要有让机器帮我们干活的意识,我们的任务是造机器。