使用 Go 從零開發併發佈一個 Kubectl 插件

作者:KaliArch(薛磊),某 Cloud MSP 服務商產品負責人,熟悉企業級高可用 / 高併發架構,包括混合雲架構、異地災,熟練企業 DevOPS 改造優化,熟悉 Shell/Python/Go 等開發語言,熟悉 Kubernetes、 Docker、 雲原生、微服務架構等

前言

十年雲計算浪潮下,DevOps、容器、微服務等技術飛速發展,雲原生成爲潮流。企業雲化從 “ON Cloud” 走向 “IN Cloud”,成爲 “新雲原生企業”,新生能力與既有能力立而不破、有機協同,實現資源高效、應用敏捷、業務智能、安全可信。整個雲原生概念很大,細化到可能是我們在真實場景中遇到的一些小問題,本文就針對日常工作中遇到的自己的小需求,及解決思路方法,分享給大家。

背景

在我日常使用 kubectl 查看 K8s 資源的時候,想直接查看對應資源的容器名稱和鏡像名稱,目前 kubectl 還不支持該選型,需要我們 describe 然後來查看,對於集羣自己比較多,不是很方便,因此萌生了自己開發 kubectl 插件來實現該功能。

相關技術

首先需要調用 Kubernetes 需要使用 client-go 項目來實現對 Kubernetes 資源的獲取,對於插件使用 Golang 語言開發,因爲是客戶端執行,爲了方便集成到及命令行工具,採用和 K8s 相同的命令行腳手架工具 Cobra,最後將其開源發佈到 GitHub。

Golang

在雲原生開發中,Google 非常多的開源項目都是使用 Golang 開發,其跨平臺編譯後可以發佈到多個平臺,我們開發的插件基於 Golang,後續也就支持多平臺使用。

Cobra

Cobra 是一個命令行程序庫,其是一個用來編寫命令行的神器,提供了一個腳手架,用於快速生成基於 Cobra 應用程序框架。我們可以利用 Cobra 快速的去開發出我們想要的命令行工具,非常的方便快捷。

Client-go

在 K8s 運維中,我們可以使用 kubectl、客戶端庫或者 REST 請求來訪問 K8s API。而實際上,無論是 kubectl 還是客戶端庫,都是封裝了 REST 請求的工具。client-go 作爲一個客戶端庫,能夠調用 K8s API,實現對 K8s 集羣中資源對象(包括 deployment、service、Ingress、ReplicaSet、Pod、Namespace、Node 等)的增刪改查等操作。

krew

Krew 是 類似於系統的 apt、dnf 或者 brew 的 kubectl 插件包管理工具,利用其可以輕鬆的完成 kubectl 插件的全上面週期管理,包括搜索、下載、卸載等。

kubectl 其工具已經比較完善,但是對於一些個性化的命令,其宗旨是希望開發者能以獨立而緊張形式發佈自定義的 kubectl 子命令,插件的開發語言不限,需要將最終的腳步或二進制可執行程序以 kubectl- 的前綴命名,然後放到 PATH 中即可,可以使用 kubectl plugin list 查看目前已經安裝的插件。

Github 發佈相關工具

如果你需要某個 Action,不必自己寫複雜的腳本,直接引用他人寫好的 Action 即可,整個持續集成過程,就變成了一個 Actions 的組合。Github[1] 是做了一個商店的功能。這樣大家就可以自己定義自己的 Action,然後方便別人複用。同時也可以統一自己的或者組織在構建過程中的一些公共流程。

GoReleaser 採用 Golang 開發,是一款用於 Golang 項目的自動發佈工具。無需太多配置,只需要幾行命令就可以輕鬆實現跨平臺的包編譯、打包和發佈到 Github、Gitlab 等版本倉庫種。

插件規劃

開發

項目初始化

在開發環境中安裝 Cobra,後去基於改命令行工具來生成項目腳手架,K8s 中很多組建也是用的改框架來生成的。

go get -v github.com/spf13/cobra/cobra
$ cobra init --pkg-name kubectl-img
$ ls
LICENSE cmd     main.go
$ tree
├── LICENSE
├── cmd
│   └── root.go
└── main.go
go mod init github.com/redhatxl/kubectl-img

增加子命令

增加一個子命令 image,在此爲我們的插件添加子命令。

$ cobra add image

添加參數

通過子命令 + flag 形式,顯示不同的資源鏡像名稱。

func Execute() {
 cobra.CheckErr(rootCmd.Execute())
}

func init() {
 KubernetesConfigFlags = genericclioptions.NewConfigFlags(true)
 imageCmd.Flags().BoolP("deployments""d", false, "show deployments image")
 imageCmd.Flags().BoolP("daemonsets""e", false, "show daemonsets image")
 imageCmd.Flags().BoolP("statefulsets""f", false, "show statefulsets image")
 imageCmd.Flags().BoolP("jobs""o", false, "show jobs image")
 imageCmd.Flags().BoolP("cronjobs""b", false, "show cronjobs image")
 imageCmd.Flags().BoolP("json""j", false, "show json format")
 KubernetesConfigFlags.AddFlags(rootCmd.PersistentFlags())
}

實現 image 命令

註冊子命令,並修改命令使用說明。

var imageCmd = &cobra.Command{
 Use:   "image",
 Short: "show resource image",
 Long:  `show k8s resource image`,
 RunE:  image,
}

func init() {
 rootCmd.AddCommand(imageCmd)
}

初始化 clientset

由於需要調用 K8s 資源,在此我們使用 Client-go 中的 ClientSet 來根據用戶輸入的不同 flag 來獲取不同的資源鏡像。

// ClientSet k8s clientset
func ClientSet(configFlags *genericclioptions.ConfigFlags) *kubernetes.Clientset {
 config, err := configFlags.ToRESTConfig()
 if err != nil {
  panic("kube config load error")
 }
 clientSet, err := kubernetes.NewForConfig(config)
 if err != nil {

  panic("gen kube config error")
 }
 return clientSet
}

實現查看資源對象

利用反射實現根據不同資源類型查看具體對應資源鏡像及鏡像名稱功能。

func image(cmd *cobra.Command, args []string) error {

 clientSet := kube.ClientSet(KubernetesConfigFlags)
 ns, _ := rootCmd.Flags().GetString("namespace")
 // 生命一個全局資源列表
 var rList []interface{}

 if flag, _ := cmd.Flags().GetBool("deployments"); flag {
  deployList, err := clientSet.AppsV1().Deployments(ns).List(context.Background(), v1.ListOptions{})
  if err != nil {
   fmt.Printf("list deployments error: %s", err.Error())
  }
  rList = append(rList, deployList)
 }
  ...
   deployMapList := make([]map[string]string, 0)
 for i := 0; i < len(rList); i++ {
  switch t := rList[i].(type) {
  case *kv1.DeploymentList:
   for k := 0; k < len(t.Items); k++ {
    for j := 0; j < len(t.Items[k].Spec.Template.Spec.Containers); j++ {
     deployMap := make(map[string]string)
     deployMap["NAMESPACE"] = ns
     deployMap["TYPE"] = "deployment"
     deployMap["RESOURCE_NAME"] = t.Items[k].GetName()
     deployMap["CONTAINER_NAME"] = t.Items[k].Spec.Template.Spec.Containers[j].Name
     deployMap["IMAGE"] = t.Items[k].Spec.Template.Spec.Containers[j].Image
     deployMapList = append(deployMapList, deployMap)
    }
   }

實現輸出

利用 Table 來對結果進行輸出,同樣擴展 JSON 輸出

func GenTable(mapList []map[string]string) *table.Table {
 t, err := gotable.Create(title...)
 if err != nil {
  fmt.Printf("create table error: %s", err.Error())
  return nil
 }
 t.AddRows(mapList)
 return t
}

最終項目結構:

集成 krew

需要將最終的腳步或二進制可執行程序以 kubectl- 的前綴命名,然後放到 PATH 中即可,可以使用 kubectl plugin list 查看目前已經安裝的插件。

$ kubectl plugin list
The following compatible plugins are available:=
/usr/local/bin/kubectl-debug
  - warning: kubectl-debug overwrites existing command: "kubectl debug"
/usr/local/bin/kubectl-v1.10.11
/usr/local/bin/kubectl-v1.20.0
/Users/xuel/.krew/bin/kubectl-df_pv
/Users/xuel/.krew/bin/kubectl-krew

# 將自己開發的插件重新命名爲kubectl-img放到可執行路基下
$ cp kubectl-img /Users/xuel/.krew/bin/kubectl-img

$ kubectl plugin list
The following compatible plugins are available:=
/usr/local/bin/kubectl-debug
  - warning: kubectl-debug overwrites existing command: "kubectl debug"
/usr/local/bin/kubectl-v1.10.11
/usr/local/bin/kubectl-v1.20.0
/Users/xuel/.krew/bin/kubectl-df_pv
/Users/xuel/.krew/bin/kubectl-krew
/Users/xuel/.krew/bin/kubectl-img

使用

$ kubectl img image -h
show k8s resource image

Usage:
  kubectl-img image [flags]

Flags:
  -b, --cronjobs       show cronjobs image
  -e, --daemonsets     show daemonsets image
  -d, --deployments    show deployments image
  -h, --help           help for image
  -o, --jobs           show jobs image
  -j, --json           show json format
  -f, --statefulsets   show statefulsets image

Global Flags:
      --as string                      Username to impersonate for the operation
      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
      --cache-dir string               Default cache directory (default "/Users/xuel/.kube/cache")
      --certificate-authority string   Path to a cert file for the certificate authority
      --client-certificate string      Path to a client certificate file for TLS
      --client-key string              Path to a client key file for TLS
      --cluster string                 The name of the kubeconfig cluster to use
      --context string                 The name of the kubeconfig context to use
      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.
  -n, --namespace string               If present, the namespace scope for this CLI request
      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
  -s, --server string                  The address and port of the Kubernetes API server
      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
      --token string                   Bearer token for authentication to the API server
      --user string                    The name of the kubeconfig user to use
# View the images of all deployments of the entire kubernetes cluster
kubectl img image --deployments
# View the images of all deployments of the entire kubernetes cluster
kubectl img image --deployments -n default

# view all resource
kubectl img image -bedof

# Table display is used by default
kubectl img image --deployments -n default -j

開源發佈

完成代碼編寫後,爲了更多朋友學習交流,將其發佈到 GitHub 上。

Github Action

在項目根目錄下創建 .github/workflows/ci.yml,文件內容如下

name: ci
on:
  push:
  pull_request:
jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@master
      - name: Setup Go
        uses: actions/setup-go@v1
        with:
          go-version: 1.16
      - name: GoReleaser
        uses: goreleaser/goreleaser-action@v1
        with:
          version: latest
          args: release --snapshot --rm-dist

GO Report Card

添加 Go 項目報告:https://goreportcard.com/

GoReleaser

對於 Golang 項目,可以使用 GoReleaser 來發佈一個漂亮的 Release。

由於使用的的 macOS ,這裏使用 brew 來安裝:

brew install goreleaser

在項目根目錄生成 .goreleaser.yml 配置:

goreleaser init

配置好了以後要記得往 .gitignore 加上 dist,因爲 goreleaser 會默認把編譯編譯好的文件輸出到 dist 目錄中。

# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
  hooks:
    # You may remove this if you don't use go modules.
    - go mod tidy
    # you may remove this if you don't need go generate
    - go generate ./...
builds:
  - env:
      - CGO_ENABLED=0
    goos:
      - linux
      - windows
      - darwin
archives:
  - replacements:
      darwin: Darwin
      linux: Linux
      windows: Windows
      386: i386
      amd64: x86_64
checksum:
  name_template: 'checksums.txt'
snapshot:
  name_template: "{{ incpatch .Version }}-next"
changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'

project_name: kubectl-img

GoReleaser 配置好後,可以先編譯測試一下:

注意: 首次使用 GoReleaser 要配置 GITHUB_TOKEN ,可以在這裏申請,申請好之後運行下面的命令配置 GITHUB_TOKEN

export GITHUB_TOKEN=<YOUR_TOKEN>

確保沒有問題,那麼就可以操作 Git 和 GoReleaser 來發布 Release 了。

git add .
git commit -m "add goreleaser"
git tag -a v0.0.2 -m "First release"
git push origin main
git push origin v0.0.2

全部搞定後,一行命令起飛:

$ goreleaser release --rm-dist
   • releasing...
   • loading config file       file=.goreleaser.yaml
   • loading environment variables
   • getting and validating git state
      • building...               commit=98703b3b9d9ac7f4661c5669c1e164d2cf3675d2 latest tag=v1.0.0
   • parsing tag
   • running before hooks
      • running                   hook=go mod tidy
      • running                   hook=go generate ./...
   • setting defaults
      • DEPRECATED: skipped windows/arm64 build on Go < 1.17 for compatibility, check https://goreleaser.com/deprecations/#builds-for-windowsarm64 for more info.
   • checking distribution directory
      • --rm-dist is set, cleaning it up
   • loading go mod information
   • build prerequisites
   • writing effective config file
      • writing                   config=dist/config.yaml
   • generating changelog
      • writing                   changelog=dist/CHANGELOG.md
   • building binaries
      • building                  binary=/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/kubectl-img/dist/kubectl-img_linux_386/kubectl-img
      • building                  binary=/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/kubectl-img/dist/kubectl-img_linux_amd64/kubectl-img
      • building                  binary=/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/kubectl-img/dist/kubectl-img_darwin_arm64/kubectl-img
      • building                  binary=/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/kubectl-img/dist/kubectl-img_linux_arm64/kubectl-img
      • building                  binary=/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/kubectl-img/dist/kubectl-img_windows_amd64/kubectl-img.exe
      • building                  binary=/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/kubectl-img/dist/kubectl-img_windows_386/kubectl-img.exe
      • building                  binary=/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/kubectl-img/dist/kubectl-img_darwin_amd64/kubectl-img
   • archives
      • creating                  archive=dist/kubectl-img_1.0.0_Linux_i386.tar.gz
      • creating                  archive=dist/kubectl-img_1.0.0_Darwin_x86_64.tar.gz
      • creating                  archive=dist/kubectl-img_1.0.0_Linux_x86_64.tar.gz
      • creating                  archive=dist/kubectl-img_1.0.0_Windows_x86_64.tar.gz
      • creating                  archive=dist/kubectl-img_1.0.0_Linux_arm64.tar.gz
      • creating                  archive=dist/kubectl-img_1.0.0_Windows_i386.tar.gz
      • creating                  archive=dist/kubectl-img_1.0.0_Darwin_arm64.tar.gz
   • calculating checksums
   • storing release metadata
      • writing                   file=dist/artifacts.json
      • writing                   file=dist/metadata.json
   • publishing
      • scm releases
         • creating or updating release repo=redhatxl/kubectl-img tag=v1.0.0
         • release updated           url=https://github.com/redhatxl/kubectl-img/releases/tag/v1.0.0
         • uploading to release      file=dist/checksums.txt name=checksums.txt
         • uploading to release      file=dist/kubectl-img_1.0.0_Linux_i386.tar.gz name=kubectl-img_1.0.0_Linux_i386.tar.gz
         • uploading to release      file=dist/kubectl-img_1.0.0_Linux_x86_64.tar.gz name=kubectl-img_1.0.0_Linux_x86_64.tar.gz
         • uploading to release      file=dist/kubectl-img_1.0.0_Windows_i386.tar.gz name=kubectl-img_1.0.0_Windows_i386.tar.gz
         • uploading to release      file=dist/kubectl-img_1.0.0_Linux_arm64.tar.gz name=kubectl-img_1.0.0_Linux_arm64.tar.gz
         • uploading to release      file=dist/kubectl-img_1.0.0_Darwin_x86_64.tar.gz name=kubectl-img_1.0.0_Darwin_x86_64.tar.gz
         • uploading to release      file=dist/kubectl-img_1.0.0_Windows_x86_64.tar.gz name=kubectl-img_1.0.0_Windows_x86_64.tar.gz
         • uploading to release      file=dist/kubectl-img_1.0.0_Darwin_arm64.tar.gz name=kubectl-img_1.0.0_Darwin_arm64.tar.gz
   • announcing
   • release succeeded after 183.24s

查看發佈好的 Release

在項目 README 中添加不同平臺的安裝方式。

Linux

export release=v1.0.0
curl -L -o kubectl-img.tar.gz https://github.com/redhatxl/kubectl-img/releases/download/${release}/kubectl-img_${release}_Linux_arm64.tar.gz
tar -xvf kubectl-img.tar.gz
cp kubectl-img /usr/local/bin/kubectl-img
# use kubectl krew
cp kubectl-img $HOME/.krew/bin

OSX

export release=v1.0.0
curl -L -o kubectl-img.tar.gz https://github.com/redhatxl/kubectl-img/releases/download/${release}/kubectl-img_${release}_Darwin_x86_64.tar.gz
tar -xvf kubectl-img.tar.gz
mv kubectl-img /usr/local/bin/kubectl-img
# use kubectl krew
cp kubectl-img $HOME/.krew/bin

Windows

In PowerShell v5+

$url = "https://github.com/redhatxl/kubectl-img/releases/download/v1.0.0/kubectl-img_1.0.0_Windows_x86_64.tar.gz"
$output = "$PSScriptRoot\kubectl-img.zip"

Invoke-WebRequest -Uri $url -OutFile $output
Expand-Archive "$PSScriptRoot\kubectl-img.zip" -DestinationPath "$PSScriptRoot\kubectl-img"

Badges 展示神器

這裏介紹一個展示 Badges 的神器:https://shields.io/ 。這個網站提供各種各樣的 Badges ,如果你願意,完全可以把你的 GitHub README.md 填滿,有興趣的同學可以自取。

總結

目前實現的比較簡單,以此來拋磚引玉的功能,後期可以進行更多功能或其他插件的開發,自己動手豐衣足食。從技術角度看,以容器、微服務以及動態編排爲代表的雲原生技術蓬勃發展,成爲賦能業務創新的重要推動力,並已經應用到企業核心業務。從市場角度看,雲原生技術已在金融、製造、互聯網等多個行業得到廣泛驗證,支持的業務場景也愈加豐富,行業生態日漸繁榮。

本文從日常工作中最小的切入點,從 0 到 1 實戰 K8s 插件開發並開源的思路及過程,希望相關同學可以一塊交流學習。最近由於業務開發 Operator,也在研讀 K8s 控制器相關代碼,並做了一些自己的筆記,有興趣的可以一塊交流學習,博客地址 :kaliarch blog[2]。

其他

引用鏈接

[1]

Github: https://link.zhihu.com/?target=https%3A//github.com/

[2]

kaliarch blog: https://redhatxl.github.io/cloud-native/develop/01-k8s%20%E5%BC%80%E5%8F%91%E7%9B%B8%E5%85%B3%E6%A6%82%E5%BF%B5%E4%BB%8B%E7%BB%8D/

[3]

kubectl-img: https://github.com/redhatxl/kubectl-img

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/K_oWmEwONNTwBAxIBmS7SQ