天天看點

Zadig 和 ChatOps 能擦出火花嗎

大家好,我是喬克,一個愛折騰的運維工程,一個睡覺都被自己醜醒的雲原生愛好者。

作者:喬克

公衆号:運維開發故事

部落格:www.jokerbai.com

背景介紹

Zadig 是目前很火的雲原生持續傳遞平台,具備靈活易用的高并發工作流、面向開發者的雲原生環境、高效協同的測試管理、強大免運維的模闆庫、客觀精确的效能洞察以 及雲原生 IDE 插件等重要特性,為工程師提供統一的協作平面,可以滿足大部分的企業傳遞場景。

但是,大家有沒有遇到過以下情況:

  • 當你在”帶薪拉屎“的時候,叫你發流水線
  • 當你在”聆聽會議精神“的時候,叫你發流水線
  • 當你身邊隻有手機的時候,叫你發流水線
  • ......

總之,随時随地都可能叫你發流水線,對于這種無聊而又頻繁的操作,有沒有更好的解決辦法呢?

Zadig 在

1.15.0

版本的時候,已經很友好的支援手機端了,按理說應該能滿足平時的工作需求。但是,作為一個愛折騰的運維,并不滿足于此,我希望能夠通過機器人的方式來完成某些運維工作,比如合并分支、發流水線、執行腳本等,這樣做主要有以下兩個好處:

  1. 移動化:随時随地能夠通過移動 APP 和機器人溝通,讓機器人完成本來在指令行,或者是 web 端才能完成的任務。
  2. 共享化:機器人所在群裡的成員都能看到群聊資訊,能夠收到任務的處理結果,極大的提高了資訊溝通的效率。

這其實就是 ChatOps 的實作,但是這隻是初級階段——也就是

字元串比對

的方式進行操作,但是随着人工智能、機器學習等技術不斷成熟,ChatOps 的傳遞性體驗會越來越好。

當然,我還停留在初級階段,本文也是帶大家通過釘釘機器人的方式釋出 Zadig 流水線。

架構解析

ChatOps 的核心在于把 WEB 端或者指令行下的人工操作,轉換能通過聊天工具機器人來完成,是以整體的架構并不會很複雜,如下:

Zadig 和 ChatOps 能擦出火花嗎

整體流程如下:

  • 技術人員在聊天群裡@機器人,發送需要執行的指令
  • 機器人接收到指令,對指令進行判斷
  • 根據指令執行相關操作,并将結果回報到聊天群

要想接入到 ChatOps,需要服務有對應的開放 API。所幸,Zadig 提供了一些 API【1】,可以到文檔中進行檢視學習。

開發階段

為了不重複造輪子,我使用的是 Github 上一個 ChatOps bot 架構【2】,該架構已經實作了

指令行

微信網頁版

企業微信

釘釘

等聊天機器人,我們隻需要在此基礎上實作具體的業務即可。

封裝 Zadig 請求

要實作對 Zadig 進行 API 操作,就需要我們封裝 HTTP 請求,為了便于操作,我将 Zadig 的一些 API 封裝了一個 SDK【3】,該 SDK 簡單實作了 Zadig 開發 API 的功能(沒仔細調試,也許有 Bug),如下:

Zadig 和 ChatOps 能擦出火花嗎

現在我們隻需要在項目中實作自己需要的功能即可。

首先,需要建立 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】,這裡不再贅述。

建立到内部機器人過後,就會在釘釘上生成一個測試群并建立了一個機器人,如下:

Zadig 和 ChatOps 能擦出火花嗎

該機器人和普通機器人的不同之處在于多了一個 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 類項目,如下:

Zadig 和 ChatOps 能擦出火花嗎

然後在項目中建立添加服務,我們選擇從代碼倉庫中同步,如下:

Zadig 和 ChatOps 能擦出火花嗎

接下來我們需要給該應用增加建構操作,配置如下:

Zadig 和 ChatOps 能擦出火花嗎

接着我們把服務添加到環境即可。

現在就可以執行工作流釋出任務了,如下:

Zadig 和 ChatOps 能擦出火花嗎

測試機器人

現在我們可以在群裡進行測試了,先測試簡單的

help

,看能不能輸出我們想要的幫助資訊,如下:

Zadig 和 ChatOps 能擦出火花嗎

我們發現可以得到我們想要的資訊。

接下來測試釋出 Zadig 流水線,如下:

Zadig 和 ChatOps 能擦出火花嗎

可以看到給我們回報的是流水線建立成功,那到底有沒有成功呢?

我們到 Zadig WEB 端檢視如下:

Zadig 和 ChatOps 能擦出火花嗎

我們可以看到有一個由 openAPI 觸發的流水線正在運作,這表示流水線已經觸發成功。

為了得到工作流執行的最終結果,我們可以在 Zadig 上為工作流添加 IM 通知,同樣可以使用該機器人,這樣就形成閉環了。

最後

到此,我們把 Zadig 和 ChatOps(聊天機器人)結合就算完成了,當然,這種機器人需要我們根據規則來玩,如果你輸的指令和規則不比對,就沒法進行下一步了。

  • 使用 openAPI 觸發 Helm 項目目前存在問題,無法正常擷取到服務,導緻流水線無法進行
  • 使用 openAPI 觸發的工作流不會進行 IM 通知

文檔