Operator雲原生應用開發—Operator介紹「雲原生」

唯愛薇o朝聖之路 發佈 2024-04-27T16:14:36.714299+00:00

Operator介紹Tip:本文非人所寫,但實在精采,修改了一下風格,和大家一起分享,本文為「左楊」同學所著一、什麼是 Operator?在 Kubernetes中我們經常使用 Deployment、DaemonSet、Service、ConfigMap 等資源。

Operator介紹

Tip:本文非人所寫,但實在精采,修改了一下風格,和大家一起分享,本文為「左楊」同學所著

一、什麼是 Operator?

在 Kubernetes中我們經常使用 Deployment、DaemonSet、Service、ConfigMap 等資源。

這些資源都是Kubernetes 的內置資源,它們的創建、更新、刪除等均由 Controller Manager 負責管理,觸發相應的動作來滿足期望狀態(Spec),這種聲明方式簡化了用戶的操作,用戶在使用時只需要關心應用程式的最終狀態即可。

隨著大數據、人工智慧等領域出現了一些場景更複雜的分布式應用系統,原生 Kubernetes 內置資源在這些場景下就顯得有些力不從心。

1)不同應用平台需要管理的目標各有差異,如何在 Kubernetes 中兼容定義管理的目標?

2)如何管理和備份系統的應用數據,協調各應用之間不同生命周期的狀態?

3)能否用同樣的 kubectl 命令來管理自己定義的複雜分布式應用?

在這些場景下,Kubernetes 自身基礎模型已經無法支撐不同業務領域下的自動化場景。為了滿足這些場景需求,Google 提出了 Third Party Resources(TPR,TPR後來被 Red Hat 和 Google 建議廢除 TPR,後改名為 CRD)概念,允許開發者根據業務需求的變化自定義資源和控制器,用於編寫面向領域知識的業務邏輯控制,這就是 Operator 的核心概念

Operator 是一種封裝、部署和管理 Kubernetes 應用的方法,用戶可以使用 Kubernetes API 和 kubectl 工具在 Kubernetes 上部署並管理 Kubernetes 應用。Operator 基於基本 Kubernetes 資源和控制器概念構建,但又涵蓋了特定領域或應用的知識,用於實現其所管理軟體的整個生命周期的自動化,它是一種特定於應用的控制器,可擴展 Kubernetes API 的功能,是 Kubernetes 用戶創建、配置和管理複雜應用的實例。

在 Kubernetes 內置資源中,Controller Manager 實施控制循環,反覆比較集群中的期望狀態和實際狀態,如果集群的實際狀態和期望狀態不一致,則採取措施使二者一致。

Operator 使用自定義資源(CR)管理應用,CR 引入新的對象類型後,Operator 監視 CR 類型,並採取特定於應用的操作,確保 CR 對象當前實際狀態和期望狀態一致。

用戶可通過在 Operator 中編寫自定義規則來擴展新功能和更新現有功能,可以像操作 Kubernetes 原生組件一樣,通過聲明式的定義一組業務應用的期望狀態,監控操作自定義資源。該特性使 Operator 幾乎可以在 Kubernetes 中執行任何操作,包括擴展複雜的應用、版本升級、管理有狀態的服務等。


這種設使應用維護人員只需要關注配置自身應用的期望運行狀態,而無須投入大量的精力在部署應用或業務運行過程中頻繁操作可能出錯的運維命令。

二、Operator 組成

簡單來說,Operator = Controller + CRD,Operator 是由 Kubernetes 自定義資源(CRD,Custom Resource Definition)和控制器(Controller)構成的雲原生擴展服務。其中 CRD 定義每個 Operator 需要創建和管理的自定義資源對象,底層實際就是通過 APIServer 接口在 etcd 中註冊一鍾新的資源類型,註冊完成後就可以創建該資源類型的對象了。但僅註冊資源和創建資源對象是沒有任何實際意義的,CRD 最重要的是需要配合對應的 Controller 來實現自定義資源的功能,達到自定義資源期望的狀態,比如內置的 Deployment Controller 用來控制 Deployment 資源的功能,根據配置生成特定數量的 Pod 監控其狀態,並根據事件做出相應的動作。

三、Operator如何使用

用戶想為自己的自定義資源構建一個 Kubernetes Operator,有很多工具可供選擇,比如 Operator SDK、Kubebuilder,甚至可以使用 Operator SDK(Helm、Ansible、Go)這些工具創建 Kubernetes Operator 用來監控自定義資源,並且根據資源的變化調整資源狀態:

Operator 作為自定義擴展資源以 Deployment 的方式部署到 k8s 中,通過 ListWatch 方式監聽對應資源的變化,當用戶修改自定義資源中的任何內容時,Operator 會監控資源的更改,並根據更改內容執行特定的操作,這些操作通常會對 Kubernetes API 中某些資源進行調用。

四、Operator應用案例

前面介紹了基於 CR 和相應的自定義資源控制器,我們可以自定義擴展 Kubernetes 原生的模型元素,這樣的自定義模型可以加入到原生 Kubernetes API 管理;同時 Operator 開發者可以像使原生 API 進行管理一樣,通過聲明式的方式定義一組業務應用的期望狀態,並且根據業務應用的自身特點編寫相應控制器邏輯,以此完成對應用運行時刻聲明周期的管理,並持續維護與期望狀態的一致性。

我們介紹如何使用 Kubebuilder 工具,快速構建一個 Kubernetes Operator,通過創建 CRD 完成一個簡單的 Web 應用部署,通過編寫控制器相關業務邏輯,完成對 CRD 的自動化管理。

Kubebuilder 是一個使用 Go 語言構建 Kubernetes API 控制器和 CRD 的腳手架工具,通過使用 Kubebuilder,用戶可以遵循一套簡單的編程框架,編寫 Operator 應用案例,本質就是使用 CRD 構建 K8s API 的 SDK,主要是:

  • 提供腳手架工具初始化 CRDs 工程,自動生成 boilerplate 代碼和配置;
  • 提供代碼庫封裝底層的 K8s go-client;


方便用戶從零開始開發 CRDs,Controllers 和 Admission Webhooks 來擴展 K8s。

4.1)kubebuilder 依賴條件:

Kubebuilder安裝

點擊 Installation 會跳轉到此網站,顯示要求如下:

  • go version v1.17.9+
  • docker version 17.03+.
  • kubectl version v1.11.3+.
  • Access to a Kubernetes v1.11.3+ cluster.

4.2)安裝 kubebuilder

4.2.1)安裝GO

我這裡原來用的Go舊的版本,現升級到了 1.18.3,就不展示升級步驟了:

[root@edu-vlovev-cn ~]# go version
go version go1.18.3 linux/amd64
[root@edu-vlovev-cn ~]# go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/root/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/root/go"
GOPRIVATE=""
GOPROXY="https://goproxy.cn"
GOROOT="/usr/local/go/current"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/current/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.18.3"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build781782133=/tmp/go-build -gno-record-gcc-switches"
[root@edu-vlovev-cn ~]#

4.2.2)安裝 kubebuilder

[root@edu-vlovev-cn ~]# curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    68  100    68    0     0    254      0 --:--:-- --:--:-- --:--:--   255
100   110  100   110    0     0    360      0 --:--:-- --:--:-- --:--:--   360
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
100 30.4M  100 30.4M    0     0  3369k      0  0:00:09  0:00:09 --:--:-- 3579k
[root@edu-vlovev-cn ~]# chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
[root@edu-vlovev-cn ~]# kubebuilder version
Version: main.version{KubeBuilderVersion:"3.5.0", KubernetesVendor:"1.24.1", GitCommit:"26d12ab1134964dbbc3f68877ebe9cf6314e926a", BuildDate:"2022-06-24T12:17:52Z", GoOs:"linux", GoArch:"amd64"}

4.4.3)Welcome 案例介紹

Welcome 案例主要實現使用 Operator 和 CRD 部署一套完整的應用環境,可以實現根據自定義類型創建資源,通過創建一個 Welcome 類型的資源,後台自動創建 Deployment 和 Service,通過 Web 頁面訪問 Service 呈現應用部署,通過自定義控制器方法進行控制管理,整體流程如下:

之前我們已經創建過 CRDs,就接著改改,創建這次我們需要的 kind;welcome,我們需要創建 Welcome 自定義資源及對應的 Controllers,最終我們可以通過類似代碼清單的 yaml 文件部署簡單的 Web 應用。

[root@edu-vlovev-cn-k8s-masternode-2-121 crds]# cat web.app.demo.welcome.domain.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: welcomes.auth.ai12306.cn
spec:
  group: auth.ai12306.cn
  names:
    kind: welcome
    plural: welcomes
    singular: welcome
    shortNames:
    - w
  scope: Namespaced
  versions:
  - served: true
    storage: true
    name: v1alpha1
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              name:
                type: string
              userID:
                type: integer
                minimum: 1
                maximum: 65535
              groups :
                type: array
                items:
                  type: string
              email:
                type: string
              password:
                type: string
                format: password
            #required: ["userID","groups"]
[root@edu-vlovev-cn-k8s-masternode-2-121 crds]#
[root@edu-vlovev-cn-k8s-masternode-2-121 crds]# cat web.app.demo.welcome.domain_v1.yaml apiVersion: auth.ai12306.cn/v1alpha1
kind: welcome
metadata:
  name: welcome-sample
  namespace: default
spec:
  name: myfriends
[root@edu-vlovev-cn-k8s-masternode-2-121 crds]#

"kubectl get welcome" 命令查看如下:

[root@edu-vlovev-cn-k8s-masternode-2-121 crds]# kubectl get welcome -o yaml
apiVersion: v1
items:
- apiVersion: auth.ai12306.cn/v1alpha1
  kind: welcome
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"auth.ai12306.cn/v1alpha1","kind":"welcome","metadata":{"annotations":{},"name":"welcome-sample","namespace":"default"},"spec":{"name":"myfriends"}}
    creationTimestamp: "2022-07-14T09:58:43Z"
    generation: 1
    name: welcome-sample
    namespace: default
    resourceVersion: "21193"
    uid: f462efbb-c520-45a2-93cc-011cde1ca200
  spec:
    name: myfriends
kind: List
metadata:
  resourceVersion: ""
[root@edu-vlovev-cn-k8s-masternode-2-121 crds]#

1)一個簡單的 Go 語言 http 模塊創建了一個 web 伺服器,用戶訪問頁面後會自動加載 NAME 及 PORT 環境變量並渲染 index.html 靜態文件,代碼邏輯如下:

package main
 
import (
  "fmt"
  "net/http"
  "os"
)
 
func main() {
  name := os.Getenv("NAME")
  hello := fmt.Sprintf("Hello %s", name)
   
  fs := http.FileServer(http.Dir("static"))
  http.Handle("/hello/", http.StripPrefix("/hello/", fs))
 
  f, err := os.OpenFile("./static/index.html", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
  if err != nil {
    panic(err)
  }
  defer f.Close()
  if _, err = f.WriteString(hello); err != nil {
    panic(err)
  }
  
  port := os.Getenv("PORT")
  if port == ""{
     port = "8080"
  }
 
  http.ListenAndServe("192.168.2.121:8088",nil)
 
}

頁面展示如下:

2)項目初始化

接下來,我們對項目進行初始化,初始化命令如下:

[root@edu-vlovev-cn demo]# kubebuilder init --domain demo.welcome.domain
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.12.1
go: downloading sigs.k8s.io/controller-runtime v0.12.1
go: downloading k8s.io/apimachinery v0.24.0
go: downloading github.com/gogo/protobuf v1.3.2
go: downloading github.com/go-logr/logr v1.2.0
go: downloading k8s.io/client-go v0.24.0
go: downloading k8s.io/klog/v2 v2.60.1
go: downloading k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
go: downloading github.com/google/gofuzz v1.1.0
go: downloading github.com/prometheus/client_golang v1.12.1
go: downloading sigs.k8s.io/structured-merge-diff/v4 v4.2.1
go: downloading github.com/evanphx/json-patch v4.12.0+incompatible
go: downloading golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
go: downloading gomodules.xyz/jsonpatch/v2 v2.2.0
go: downloading k8s.io/api v0.24.0
go: downloading k8s.io/apiextensions-apiserver v0.24.0
go: downloading golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
go: downloading github.com/imdario/mergo v0.3.12
go: downloading github.com/spf13/pflag v1.0.5
go: downloading golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
go: downloading k8s.io/component-base v0.24.0
go: downloading gopkg.in/inf.v0 v0.9.1
go: downloading github.com/prometheus/client_model v0.2.0
go: downloading github.com/prometheus/common v0.32.1
go: downloading github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
go: downloading sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2
go: downloading github.com/json-iterator/go v1.1.12
go: downloading gopkg.in/yaml.v2 v2.4.0
go: downloading k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42
go: downloading github.com/pkg/errors v0.9.1
go: downloading github.com/golang/protobuf v1.5.2
go: downloading github.com/google/gnostic v0.5.7-v3refs
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
go: downloading golang.org/x/sys v0.0.0-20220209214540-3681064d5158
go: downloading sigs.k8s.io/yaml v1.3.0
go: downloading github.com/beorn7/perks v1.0.1
go: downloading github.com/cespare/xxhash/v2 v2.1.2
go: downloading github.com/prometheus/procfs v0.7.3
go: downloading google.golang.org/protobuf v1.27.1
go: downloading github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369
go: downloading github.com/google/uuid v1.1.2
go: downloading github.com/fsnotify/fsnotify v1.5.1
go: downloading github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
go: downloading github.com/modern-go/reflect2 v1.0.2
go: downloading gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
go: downloading golang.org/x/text v0.3.7
go: downloading github.com/google/go-cmp v0.5.5
go: downloading github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
go: downloading google.golang.org/appengine v1.6.7
go: downloading github.com/emicklei/go-restful v2.9.5+incompatible
go: downloading github.com/go-openapi/swag v0.19.14
go: downloading github.com/go-openapi/jsonreference v0.19.5
go: downloading github.com/mailru/easyjson v0.7.6
go: downloading github.com/josharian/intern v1.0.0
go: downloading github.com/PuerkitoBio/purell v1.1.1
go: downloading github.com/go-openapi/jsonpointer v0.19.5
go: downloading github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
Update dependencies:
$ go mod tidy
go: downloading github.com/stretchr/testify v1.7.0
go: downloading github.com/onsi/ginkgo v1.16.5
go: downloading github.com/onsi/gomega v1.18.1
go: downloading github.com/go-logr/zapr v1.2.0
go: downloading go.uber.org/zap v1.19.1
go: downloading github.com/Azure/go-autorest/autorest v0.11.18
go: downloading github.com/Azure/go-autorest/autorest/adal v0.9.13
go: downloading github.com/Azure/go-autorest v14.2.0+incompatible
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading go.uber.org/goleak v1.1.12
go: downloading go.uber.org/atomic v1.7.0
go: downloading go.uber.org/multierr v1.6.0
go: downloading github.com/benbjohnson/clock v1.1.0
go: downloading github.com/Azure/go-autorest/logger v0.2.1
go: downloading github.com/Azure/go-autorest/autorest/date v0.3.0
go: downloading github.com/Azure/go-autorest/tracing v0.6.0
go: downloading github.com/form3tech-oss/jwt-go v3.2.3+incompatible
go: downloading golang.org/x/crypto v0.0.0-20220214200702-86341886e292
go: downloading github.com/Azure/go-autorest/autorest/mocks v0.4.1
go: downloading cloud.google.com/go v0.81.0
go: downloading gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
go: downloading github.com/nxadm/tail v1.4.8
go: downloading golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
go: downloading github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e
go: downloading gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
go: downloading github.com/kr/text v0.2.0
Next: define a resource with:
$ kubebuilder create api
[root@edu-vlovev-cn demo]#
[root@edu-vlovev-cn demo]# tree .
.
├── config
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── controller_manager_config.yaml
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   └── rbac
│       ├── auth_proxy_client_clusterrole.yaml
│       ├── auth_proxy_role_binding.yaml
│       ├── auth_proxy_role.yaml
│       ├── auth_proxy_service.yaml
│       ├── kustomization.yaml
│       ├── leader_election_role_binding.yaml
│       ├── leader_election_role.yaml
│       ├── role_binding.yaml
│       └── service_account.yaml
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── main.go
├── Makefile
├── PROJECT
└── README.md
 
6 directories, 25 files
[root@edu-vlovev-cn demo]#
關鍵字: