大家好,我是喬克,一個愛折騰的運維工程,一個睡覺都被自己醜醒的雲原生愛好者。
作者:喬克
公衆号:運維開發故事
部落格:www.jokerbai.com
背景介紹
Zadig 是目前很火的雲原生持續傳遞平台,具備靈活易用的高并發工作流、面向開發者的雲原生環境、高效協同的測試管理、強大免運維的模闆庫、客觀精确的效能洞察以 及雲原生 IDE 插件等重要特性,為工程師提供統一的協作平面,可以滿足大部分的企業傳遞場景。
但是,大家有沒有遇到過以下情況:
- 當你在”帶薪拉屎“的時候,叫你發流水線
- 當你在”聆聽會議精神“的時候,叫你發流水線
- 當你身邊隻有手機的時候,叫你發流水線
- ......
總之,随時随地都可能叫你發流水線,對于這種無聊而又頻繁的操作,有沒有更好的解決辦法呢?
Zadig 在
1.15.0
版本的時候,已經很友好的支援手機端了,按理說應該能滿足平時的工作需求。但是,作為一個愛折騰的運維,并不滿足于此,我希望能夠通過機器人的方式來完成某些運維工作,比如合并分支、發流水線、執行腳本等,這樣做主要有以下兩個好處:
- 移動化:随時随地能夠通過移動 APP 和機器人溝通,讓機器人完成本來在指令行,或者是 web 端才能完成的任務。
- 共享化:機器人所在群裡的成員都能看到群聊資訊,能夠收到任務的處理結果,極大的提高了資訊溝通的效率。
這其實就是 ChatOps 的實作,但是這隻是初級階段——也就是
字元串比對
的方式進行操作,但是随着人工智能、機器學習等技術不斷成熟,ChatOps 的傳遞性體驗會越來越好。
當然,我還停留在初級階段,本文也是帶大家通過釘釘機器人的方式釋出 Zadig 流水線。
架構解析
ChatOps 的核心在于把 WEB 端或者指令行下的人工操作,轉換能通過聊天工具機器人來完成,是以整體的架構并不會很複雜,如下:

整體流程如下:
- 技術人員在聊天群裡@機器人,發送需要執行的指令
- 機器人接收到指令,對指令進行判斷
- 根據指令執行相關操作,并将結果回報到聊天群
要想接入到 ChatOps,需要服務有對應的開放 API。所幸,Zadig 提供了一些 API【1】,可以到文檔中進行檢視學習。
開發階段
為了不重複造輪子,我使用的是 Github 上一個 ChatOps bot 架構【2】,該架構已經實作了
指令行
、
微信網頁版
、
企業微信
、
釘釘
等聊天機器人,我們隻需要在此基礎上實作具體的業務即可。
封裝 Zadig 請求
要實作對 Zadig 進行 API 操作,就需要我們封裝 HTTP 請求,為了便于操作,我将 Zadig 的一些 API 封裝了一個 SDK【3】,該 SDK 簡單實作了 Zadig 開發 API 的功能(沒仔細調試,也許有 Bug),如下:
現在我們隻需要在項目中實作自己需要的功能即可。
首先,需要建立 Zadig 請求,建立一個
zadig/zadig.go
檔案,實作 Zadig 初始化,代碼如下:
package zadig
import (
"errors"
"log"
"github.com/joker-bai/go-zadig"
)
var MyZadig myZadig
type myZadig struct {
client *zadig.Client
}
var (
token = "x.x.x"
baseURL = "http://xxx/"
)
func Setup() *myZadig {
client, err := zadig.NewClient(token, zadig.WithBaseURL(baseURL))
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
return &myZadig{client: client}
}
PS:token、url 這些配置其實是可以放到配置檔案中,這裡為了便于示範,就放在代碼檔案中了。
其中:
- token 是使用者認證使用,在 WEB 端右上角使用者->賬号設定中擷取
- baseURL 是 zadig 的位址
然後再在該檔案中實作
CreateWorkflowTask
方法,該方法用于執行工作流,如下:
package zadig
var (
callbackURL="xxx"
)
......
func (m *myZadig) CreateWorkflowTask(workfolwName, envName, serviceName, serviceType, repoName, branch string) error {
_, _, err := m.client.Workflow.CreateWorkflowTask(&zadig.CreateWorkflowTaskOptions{
WorkflowName: workfolwName,
EnvName: envName,
Targets: []zadig.TargetArgs{
{
Name: serviceName,
ServiceType: serviceType,
Build: zadig.BuildArgs{
Repos: []zadig.Repository{
{
RepoName: repoName,
Branch: branch,
},
},
},
},
},
Callback: zadig.Callback{
CallbackUrl: callbackURL,
},
})
if err != nil {
return errors.New("執行工作流失敗")
}
return nil
}
該方法接收執行工作流所需的參數,然後調用 SDK 完成執行。由于我這裡都是
建構部署
的方式,是以隻寫了
Targets
實作。
注冊 Zadig 插件
上面簡單的把 Zadig 執行工作流的請求封裝了,接下來就注冊 Zadig 插件了。
rboot 項目【2】采用插件的方式注冊新的指令,系統會自動把這些指令加載到應用中,并且可以通過使用
help
指令檢視運作規則。
在
robot/plugins
中建立
zadig/zadig.go
檔案,用來注冊 zadig 執行流水線指令,内容如下:
package zadig
import (
"fmt"
"regexp"
"strings"
"github.com/sirupsen/logrus"
"devops-chatops/rboot"
"devops-chatops/zadig"
)
var zadigInfo = map[string]map[string]string{
"dev": {
"branch": "dev",
"workflow": "devops-dev",
"serviceType": "helm",
"env": "dev",
},
"test": {
"branch": "test",
"workflow": "devops-qa",
"serviceType": "helm",
"env": "qa",
},
"uat": {
"branch": "uat",
"workflow": "devops-uat",
"serviceType": "helm",
"env": "uat",
},
"prod": {
"branch": "master",
"workflow": "devops-prod",
"serviceType": "helm",
"env": "prod",
},
"yamldev": {
"branch": "master",
"workflow": "chatops-dev",
"serviceType": "k8s",
"env": "dev",
},
}
func init() {
// 注冊腳本
rboot.RegisterPlugin(`pipeline`, rboot.Plugin{
// 腳本處理函數
Action: func(bot *rboot.Robot, incoming *rboot.Message) []*rboot.Message {
// 去除空格
content := strings.Replace(incoming.Content, " ", "", -1)
res := createWorkflowTask(content)
return rboot.NewMessages(res)
},
Ruleset: map[string]string{`pipeline`: `執行[\w ]+環境[\w- ]+流水線`}, // 腳本規則集
Usage: map[string]string{
"pipeline": "執行dev環境devops-chatops流水線",
},
Description: `example '執行dev環境devops-chatops流水線'`,
})
}
func createWorkflowTask(content string) string {
res := regexp.MustCompile(`[\w-/]+流水線`)
s := res.FindStringSubmatch(content)
sp := strings.Split(s[0], "流水線")
pipelineName := sp[0]
br := regexp.MustCompile(`[\w]+環境`)
tb := br.FindStringSubmatch(content)
env := strings.Split(tb[0], "環境")[0]
workflow := zadigInfo[env]["workflow"]
repoName := pipelineName
branch := zadigInfo[env]["branch"]
envName := zadigInfo[env]["env"]
serviceName := pipelineName
serviceType := zadigInfo[env]["serviceType"]
logrus.Debugf("工作流:%v 流水線: %v 環境: %v 服務:%v 服務類型:%v 分支:%v\n",
workflow,
pipelineName,
envName,
serviceName,
serviceType,
branch,
)
zd := zadig.Setup()
err := zd.CreateWorkflowTask(workflow, envName, serviceName, serviceType, repoName, branch)
if err != nil {
return fmt.Sprintf("執行%s環境的流水線%s失敗", env, pipelineName)
}
return fmt.Sprintf("執行%s環境的流水線%s成功", env, pipelineName)
}
其中:
- init 方法就是插件注冊的實作
- Action 腳本的處理函數
- Ruleset 是指令規則
- Usage 使用方式
- Description 描述資訊
- createWorkflowTask 執行工作流方法,主要用來擷取指令的關鍵詞,然後調用 zadig.CreateWorkflowTask 執行工作流
- zadigInfo 用來定義 zadig 的環境資訊
- workflow 是工作流名稱
- branch 是分支名
- serviceType 是服務類型,有 k8s 和 helm 服務
- env 部署環境資訊
上面的比對規則、環境資訊等比較簡單粗暴,最好是把這些資料存到資料庫裡,我這裡為了不引入額外的元件就直接放代碼中了。
業務代碼開發完,我們需要把 zadig 插件引入,在 robot/plugins/plugins.go 中 import 即可,如下:
package plugins
import (
_ "github.com/ghaoo/rboot/robot/plugins/hello"
_ "github.com/ghaoo/rboot/robot/plugins/ping"
_ "github.com/ghaoo/rboot/robot/plugins/vote"
_ "devops-chatops/robot/plugins/zadig"
)
至此,執行流水線業務開發完成。
部署 ChatOps
開發完成就要部署,部署要分幾個階段:
- 建立聊天機器人
- 部署應用
建立聊天機器人
該聊天機器人不是釘釘的普通自定義機器人,而是需要在釘釘開發者背景【4】建立機器人,具體操作見文檔【5】,這裡不再贅述。
建立到内部機器人過後,就會在釘釘上生成一個測試群并建立了一個機器人,如下:
該機器人和普通機器人的不同之處在于多了一個 POST 位址,該位址是我們建立機器人的時候配置的,也是應用的通路位址。
随着機器人的不斷開發,關鍵詞會越來越多,是以我這裡選擇的是加簽校驗。
部署應用
(1)修改配置檔案,為了簡單,我直接将配置檔案放到代碼倉庫,推到鏡像中。在代碼根目錄下建立.env 檔案,内容如下:
# 機器人名稱
ROBOT_NAME=DEVOPS-CHATOPS
# 聊天轉接器名稱
ROBOT_ADAPTER="dingtalk"
# 緩存器名稱
ROBOT_BRAIN=bolt
# 消息秘鑰
ROBOT_SECRET=
# 是否開啟DEBUG
DEBUG=false
# 緩存位置
DATA_PATH=.data
# bolt資料儲存位址
BOLT_DB_FILE=db/rboot.db
# web 服務監聽端口
WEB_SERVER_PORT=9000
# 是否啟用TSL
WEB_SERVER_TLS=false
# CA憑證位置
WEB_SERVER_CERT=
# CA秘鑰位置
WEB_SERVER_CERT_KEY=
# 釘釘機器人秘鑰
DING_ROBOT_SECRET="xxxx"
# 釘釘webhook機器人access_token
DING_ROBOT_HOOK_ACCESS_TOKEN="xxxx"
# 釘釘webhook機器人秘鑰
DING_ROBOT_HOOK_SECRET="xxxx"
配置轉接器名稱以及釘釘機器人相關資訊。
(2)添加 Dockerfile,用于制作應用鏡像,如下:
FROM golang:1.19.1 AS build-env
ENV GOPROXY https://goproxy.cn
ADD . /go/src/app
WORKDIR /go/src/app
RUN go mod tidy
RUN GOOS=linux GOARCH=386 go build -v -o /go/src/app/app-server
FROM alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \
apk add -U tzdata
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=build-env /go/src/app/app-server /app/app-server
COPY --from=build-env /go/src/app/.env /app/.env
WORKDIR /app
EXPOSE 9000
CMD [ "./app-server" ]
(3)添加應用 K8S YAML 配置清單,主要有 deployment、service、ingress 資源,如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: devops-chatops
name: devops-chatops
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: devops-chatops
app.kubernetes.io/name: devops-chatops
template:
metadata:
labels:
app.kubernetes.io/instance: devops-chatops
app.kubernetes.io/name: devops-chatops
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/coolops/devops-chatops:1c5e4c9274959c8efcecfb286103b052abb44d27
imagePullPolicy: IfNotPresent
name: devops-chatops
ports:
- containerPort: 9000
name: http
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/instance: devops-chatops
app.kubernetes.io/name: devops-chatops
name: devops-chatops
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
selector:
app.kubernetes.io/instance: devops-chatops
app.kubernetes.io/name: devops-chatops
sessionAffinity: None
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: devops-chatops
spec:
rules:
- host: chatops.jokerbai.com
http:
paths:
- backend:
serviceName: devops-chatops
servicePort: 80
path: /
(4)在 Zadig 上部署應用
由于我們這裡使用的 YAML 類應用,是以先在 Zadig 上建立一個 YAML 類項目,如下:
然後在項目中建立添加服務,我們選擇從代碼倉庫中同步,如下:
接下來我們需要給該應用增加建構操作,配置如下:
接着我們把服務添加到環境即可。
現在就可以執行工作流釋出任務了,如下:
測試機器人
現在我們可以在群裡進行測試了,先測試簡單的
help
,看能不能輸出我們想要的幫助資訊,如下:
我們發現可以得到我們想要的資訊。
接下來測試釋出 Zadig 流水線,如下:
可以看到給我們回報的是流水線建立成功,那到底有沒有成功呢?
我們到 Zadig WEB 端檢視如下:
我們可以看到有一個由 openAPI 觸發的流水線正在運作,這表示流水線已經觸發成功。
為了得到工作流執行的最終結果,我們可以在 Zadig 上為工作流添加 IM 通知,同樣可以使用該機器人,這樣就形成閉環了。
最後
到此,我們把 Zadig 和 ChatOps(聊天機器人)結合就算完成了,當然,這種機器人需要我們根據規則來玩,如果你輸的指令和規則不比對,就沒法進行下一步了。
- 使用 openAPI 觸發 Helm 項目目前存在問題,無法正常擷取到服務,導緻流水線無法進行
- 使用 openAPI 觸發的工作流不會進行 IM 通知