Kubernetes Operator 快速入門教程

在 Kubernetes 的監控方案中我們經常會使用到一個Promethues Operator的項目,該項目可以讓我們更加方便的去使用 Prometheus,而不需要直接去使用最原始的一些資源對象,比如 Pod、Deployment,隨着 Prometheus Operator 項目的成功,CoreOS 公司開源了一個比較厲害的工具:Operator Framework,該工具可以讓開發人員更加容易的開發 Operator 應用。

在本篇文章中我們會爲大家介紹一個簡單示例來演示如何使用 Operator Framework 框架來開發一個 Operator 應用。

Kubernetes Operator

Operator 是由 CoreOS 開發的,用來擴展 Kubernetes API,特定的應用程序控制器,它用來創建、配置和管理複雜的有狀態應用,如數據庫、緩存和監控系統。Operator 基於 Kubernetes 的資源和控制器概念之上構建,但同時又包含了應用程序特定的領域知識。創建Operator 的關鍵是CRD(自定義資源)的設計。

Kubernetes 1.7 版本以來就引入了自定義控制器的概念,該功能可以讓開發人員擴展添加新功能,更新現有的功能,並且可以自動執行一些管理任務,這些自定義的控制器就像 Kubernetes 原生的組件一樣,Operator 直接使用 Kubernetes API進行開發,也就是說他們可以根據這些控制器內部編寫的自定義規則來監控集羣、更改 Pods/Services、對正在運行的應用進行擴縮容。

Operator Framework

Operator Framework 同樣也是 CoreOS 開源的一個用於快速開發 Operator 的工具包,該框架包含兩個主要的部分:

  • Operator SDK: 無需瞭解複雜的 Kubernetes API 特性,即可讓你根據你自己的專業知識構建一個 Operator 應用。

  • Operator Lifecycle Manager OLM: 幫助你安裝、更新和管理跨集羣的運行中的所有 Operator(以及他們的相關服務)

Kubernetes Operator 快速入門教程

Workflow

Operator SDK 提供以下工作流來開發一個新的 Operator:

  • 使用 SDK 創建一個新的 Operator 項目

  • 通過添加自定義資源(CRD)定義新的資源 API

  • 指定使用 SDK API 來 watch 的資源

  • 定義 Operator 的協調(reconcile)邏輯

  • 使用 Operator SDK 構建並生成 Operator 部署清單文件

Demo

我們平時在部署一個簡單的 Webserver 到 Kubernetes 集羣中的時候,都需要先編寫一個 Deployment 的控制器,然後創建一個 Service 對象,通過 Pod 的 label 標籤進行關聯,最後通過 Ingress 或者 type=NodePort 類型的 Service 來暴露服務,每次都需要這樣操作,是不是略顯麻煩,我們就可以創建一個自定義的資源對象,通過我們的 CRD 來描述我們要部署的應用信息,比如鏡像、服務端口、環境變量等等,然後創建我們的自定義類型的資源對象的時候,通過控制器去創建對應的 Deployment 和 Service,是不是就方便很多了,相當於我們用一個資源清單去描述了 Deployment 和 Service 要做的兩件事情。

這裏我們將創建一個名爲 AppService 的 CRD 資源對象,然後定義如下的資源清單進行應用部署:

apiVersion: app.example.com/v1

kind: AppService

metadata:

  name: nginx-app

spec:

  size: 2

  image: nginx:1.7.9

  ports:

    - port: 80

      targetPort: 80

      nodePort: 30002

通過這裏的自定義的 AppService 資源對象去創建副本數爲2的 Pod,然後通過 nodePort=30002 的端口去暴露服務,接下來我們就來一步一步的實現我們這裏的這個簡單的 Operator 應用。

開發環境

環境需求

要開發 Operator 自然 Kubernetes 集羣是少不了的,還需要 Golang 的環境,這裏的安裝就不多說了,然後還需要一個 Go 語言的依賴管理工具包:dep,由於 Operator SDK 是使用的 dep 該工具包,所以需要我們提前安裝好,可以查看資料:https://github.com/golang/dep,另外一個需要說明的是,由於 dep 去安裝的時候需要去谷歌的網站拉取很多代碼,所以正常情況下的話是會失敗的,需要做什麼工作大家應該清楚吧?要科學。

安裝 operator-sdk

operator sdk 安裝方法非常多,我們可以直接在 github 上面下載需要使用的版本,然後放置到 PATH 環境下面即可,當然也可以將源碼 clone 到本地手動編譯安裝即可,如果你是 Mac,當然還可以使用常用的 brew 工具進行安裝:

$ brew install operator-sdk

......

$ operator-sdk version

operator-sdk version: v0.7.0

$ go version

go version go1.11.4 darwin/amd64

我們這裏使用的 sdk 版本是 v0.7.0,其他安裝方法可以參考文檔:https://github.com/operator-framework/operator-sdk/blob/master/doc/user/install-operator-sdk.md

演示

創建新項目

環境準備好了,接下來就可以使用 operator-sdk 直接創建一個新的項目了,命令格式爲:operator-sdk new

按照上面我們預先定義的 CRD 資源清單,我們這裏可以這樣創建:

# 創建項目目錄

$ mkdir -p operator-learning  

# 設置項目目錄爲 GOPATH 路徑

$ cd operator-learning && export GOPATH=$PWD  

$ mkdir -p $GOPATH/src/github.com/cnych 

$ cd $GOPATH/src/github.com/cnych

# 使用 sdk 創建一個名爲 opdemo 的 operator 項目

$ operator-sdk new opdemo

......

# 該過程需要科學上網,需要花費很長時間,請耐心等待

......

$ cd opdemo && tree -L 2

.

├── Gopkg.lock

├── Gopkg.toml

├── build

│   ├── Dockerfile

│   ├── _output

│   └── bin

├── cmd

│   └── manager

├── deploy

│   ├── crds

│   ├── operator.yaml

│   ├── role.yaml

│   ├── role_binding.yaml

│   └── service_account.yaml

├── pkg

│   ├── apis

│   └── controller

├── vendor

│   ├── cloud.google.com

│   ├── contrib.go.opencensus.io

│   ├── github.com

│   ├── go.opencensus.io

│   ├── go.uber.org

│   ├── golang.org

│   ├── google.golang.org

│   ├── gopkg.in

│   ├── k8s.io

│   └── sigs.k8s.io

└── version

    └── version.go

23 directories, 8 files

到這裏一個全新的 Operator 項目就新建完成了。

項目結構

使用 operator-sdknew命令創建新的 Operator 項目後,項目目錄就包含了很多生成的文件夾和文件。

  • Gopkg.toml Gopkg.lock — Go Dep 清單,用來描述當前 Operator 的依賴包。

  • cmd - 包含 main.go 文件,使用 operator-sdk API 初始化和啓動當前 Operator 的入口。

  • deploy - 包含一組用於在 Kubernetes 集羣上進行部署的通用的 Kubernetes 資源清單文件。

  • pkg/apis - 包含定義的 API 和自定義資源(CRD)的目錄樹,這些文件允許 sdk 爲 CRD 生成代碼並註冊對應的類型,以便正確解碼自定義資源對象。

  • pkg/controller - 用於編寫所有的操作業務邏輯的地方

  • vendor - golang vendor 文件夾,其中包含滿足當前項目的所有外部依賴包,通過 go dep 管理該目錄。

我們主要需要編寫的是pkg目錄下面的 api 定義以及對應的 controller 實現。

添加 API

接下來爲我們的自定義資源添加一個新的 API,按照上面我們預定義的資源清單文件,在 Operator 相關根目錄下面執行如下命令:

$ operator-sdk add api --api-version=app.example.com/v1 --kind=AppService

添加完成後,我們可以看到類似於下面的這樣項目結構:
Kubernetes Operator 快速入門教程

添加控制器

上面我們添加自定義的 API,接下來可以添加對應的自定義 API 的具體實現 Controller,同樣在項目根目錄下面執行如下命令:

$ operator-sdk add controller --api-version=app.example.com/v1 --kind=AppService

這樣整個 Operator 項目的腳手架就已經搭建完成了,接下來就是具體的實現了。

自定義 API

打開源文件 pkg/apis/app/v1/appservice_types.go,需要我們根據我們的需求去自定義結構體 AppServiceSpec,我們最上面預定義的資源清單中就有 size、image、ports 這些屬性,所有我們需要用到的屬性都需要在這個結構體中進行定義:

type AppServiceSpec struct {

    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster

    // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file

    // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html

    Size      *int32                      `json:"size"`

    Image     string                      `json:"image"`

    Resources corev1.ResourceRequirements `json:"resources,omitempty"`

    Envs      []corev1.EnvVar             `json:"envs,omitempty"`

    Ports     []corev1.ServicePort        `json:"ports,omitempty"`

}

代碼中會涉及到一些包名的導入,由於包名較多,所以我們會使用一些別名進行區分,主要的包含下面幾個:

import (

    appsv1 "k8s.io/api/apps/v1"

    corev1 "k8s.io/api/core/v1"

    appv1 "github.com/cnych/opdemo/pkg/apis/app/v1"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

)

這裏的 resources、envs、ports 的定義都是直接引用的 "k8s.io/api/core/v1"中定義的結構體,而且需要注意的是我們這裏使用的是 ServicePort,而不是像傳統的 Pod 中定義的 ContanerPort,這是因爲我們的資源清單中不僅要描述容器的 Port,還要描述 Service 的 Port。

然後一個比較重要的結構體 AppServiceStatus用來描述資源的狀態,當然我們可以根據需要去自定義狀態的描述,我這裏就偷懶直接使用 Deployment 的狀態了:

type AppServiceStatus struct {

    // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster

    // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file

    // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html

    appsv1.DeploymentStatus `json:",inline"`

}

定義完成後,在項目根目錄下面執行如下命令:

$ operator-sdk generate k8s

改命令是用來根據我們自定義的 API 描述來自動生成一些代碼,目錄 pkg/apis/app/v1/下面以 zz_generated開頭的文件就是自動生成的代碼,裏面的內容並不需要我們去手動編寫。

這樣我們就算完成了對自定義資源對象的 API 的聲明。

實現業務邏輯

上面 API 描述聲明完成了,接下來就需要我們來進行具體的業務邏輯實現了,編寫具體的 controller 實現,打開源文件 pkg/controller/appservice/appservice_controller.go,需要我們去更改的地方也不是很多,核心的就是 Reconcile方法,該方法就是去不斷的 watch 資源的狀態,然後根據狀態的不同去實現各種操作邏輯,核心代碼如下:

func (r *ReconcileAppService) Reconcile(request reconcile.Request) (reconcile.Result, error) {

    reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)

    reqLogger.Info("Reconciling AppService")

    // Fetch the AppService instance

    instance := &appv1.AppService{}

    err := r.client.Get(context.TODO(), request.NamespacedName, instance)

    if err != nil {

        if errors.IsNotFound(err) {

            // Request object not found, could have been deleted after reconcile request.

            // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.

            // Return and don't requeue

            return reconcile.Result{}, nil

        }

        // Error reading the object - requeue the request.

        return reconcile.Result{}, err

    }

    if instance.DeletionTimestamp != nil {

        return reconcile.Result{}, err

    }

    // 如果不存在,則創建關聯資源

    // 如果存在,判斷是否需要更新

    //   如果需要更新,則直接更新

    //   如果不需要更新,則正常返回

    deploy := &appsv1.Deployment{}

    if err := r.client.Get(context.TODO(), request.NamespacedName, deploy); err != nil && errors.IsNotFound(err) {

        // 創建關聯資源

        // 1. 創建 Deploy

        deploy := resources.NewDeploy(instance)

        if err := r.client.Create(context.TODO(), deploy); err != nil {

            return reconcile.Result{}, err

        }

        // 2. 創建 Service

        service := resources.NewService(instance)

        if err := r.client.Create(context.TODO(), service); err != nil {

            return reconcile.Result{}, err

        }

        // 3. 關聯 Annotations

        data, _ := json.Marshal(instance.Spec)

        if instance.Annotations != nil {

            instance.Annotations["spec"] = string(data)

        } else {

            instance.Annotations = map[string]string{"spec": string(data)}

        }

        if err := r.client.Update(context.TODO(), instance); err != nil {

            return reconcile.Result{}, nil

        }

        return reconcile.Result{}, nil

    }

    oldspec := appv1.AppServiceSpec{}

    if err := json.Unmarshal([]byte(instance.Annotations["spec"]), oldspec); err != nil {

        return reconcile.Result{}, err

    }

    if !reflect.DeepEqual(instance.Spec, oldspec) {

        // 更新關聯資源

        newDeploy := resources.NewDeploy(instance)

        oldDeploy := &appsv1.Deployment{}

        if err := r.client.Get(context.TODO(), request.NamespacedName, oldDeploy); err != nil {

            return reconcile.Result{}, err

        }

        oldDeploy.Spec = newDeploy.Spec

        if err := r.client.Update(context.TODO(), oldDeploy); err != nil {

            return reconcile.Result{}, err

        }

        newService := resources.NewService(instance)

        oldService := &corev1.Service{}

        if err := r.client.Get(context.TODO(), request.NamespacedName, oldService); err != nil {

            return reconcile.Result{}, err

        }

        oldService.Spec = newService.Spec

        if err := r.client.Update(context.TODO(), oldService); err != nil {

            return reconcile.Result{}, err

        }

        return reconcile.Result{}, nil

    }

    return reconcile.Result{}, nil

}

上面就是業務邏輯實現的核心代碼,邏輯很簡單,就是去判斷資源是否存在,不存在,則直接創建新的資源,創建新的資源除了需要創建 Deployment 資源外,還需要創建 Service 資源對象,因爲這就是我們的需求,當然你還可以自己去擴展,比如在創建一個 Ingress 對象。更新也是一樣的,去對比新舊對象的聲明是否一致,不一致則需要更新,同樣的,兩種資源都需要更新的。

另外兩個核心的方法就是上面的 resources.NewDeploy(instance)和 resources.NewService(instance)方法,這兩個方法實現邏輯也很簡單,就是根據 CRD 中的聲明去填充 Deployment 和 Service 資源對象的 Spec 對象即可。

NewDeploy 方法實現如下:

func NewDeploy(app *appv1.AppService) *appsv1.Deployment {

    labels := map[string]string{"app": app.Name}

    selector := &metav1.LabelSelector{MatchLabels: labels}

    return &appsv1.Deployment{

        TypeMeta: metav1.TypeMeta{

            APIVersion: "apps/v1",

            Kind:       "Deployment",

        },

        ObjectMeta: metav1.ObjectMeta{

            Name:      app.Name,

            Namespace: app.Namespace,

            OwnerReferences: []metav1.OwnerReference{

                *metav1.NewControllerRef(app, schema.GroupVersionKind{

                    Group: v1.SchemeGroupVersion.Group,

                    Version: v1.SchemeGroupVersion.Version,

                    Kind: "AppService",

                }),

            },

        },

        Spec: appsv1.DeploymentSpec{

            Replicas: app.Spec.Size,

            Template: corev1.PodTemplateSpec{

                ObjectMeta: metav1.ObjectMeta{

                    Labels: labels,

                },

                Spec: corev1.PodSpec{

                    Containers: newContainers(app),

                },

            },

            Selector: selector,

        },

    }

}

func newContainers(app *v1.AppService) []corev1.Container {

    containerPorts := []corev1.ContainerPort{}

    for _, svcPort := range app.Spec.Ports {

        cport := corev1.ContainerPort{}

        cport.ContainerPort = svcPort.TargetPort.IntVal

        containerPorts = append(containerPorts, cport)

    }

    return []corev1.Container{

        {

            Name: app.Name,

            Image: app.Spec.Image,

            Resources: app.Spec.Resources,

            Ports: containerPorts,

            ImagePullPolicy: corev1.PullIfNotPresent,

            Env: app.Spec.Envs,

        },

    }

}

newService 對應的方法實現如下:

func NewService(app *v1.AppService) *corev1.Service {

    return &corev1.Service {

        TypeMeta: metav1.TypeMeta {

            Kind: "Service",

            APIVersion: "v1",

        },

        ObjectMeta: metav1.ObjectMeta{

            Name: app.Name,

            Namespace: app.Namespace,

            OwnerReferences: []metav1.OwnerReference{

                *metav1.NewControllerRef(app, schema.GroupVersionKind{

                    Group: v1.SchemeGroupVersion.Group,

                    Version: v1.SchemeGroupVersion.Version,

                    Kind: "AppService",

                }),

            },

        },

        Spec: corev1.ServiceSpec{

            Type: corev1.ServiceTypeNodePort,

            Ports: app.Spec.Ports,

            Selector: map[string]string{

                "app": app.Name,

            },

        },

    }

}

這樣我們就實現了 AppService 這種資源對象的業務邏輯。

調試

如果我們本地有一個可以訪問的 Kubernetes 集羣,我們也可以直接進行調試,在本地用戶 ~/.kube/config文件中配置集羣訪問信息,下面的信息表明可以訪問 Kubernetes 集羣:

$ kubectl cluster-info

Kubernetes master is running at https://ydzs-master:6443

KubeDNS is running at https://ydzs-master:6443/api/v1/namespaces/kube-system/services/kube-dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

首先,在集羣中安裝 CRD 對象:

$ kubectl create -f deploy/crds/app_v1_appservice_crd.yaml

customresourcedefinition "appservices.app.example.com" created

$ kubectl get crd

NAME                                   AGE

appservices.app.example.com            <invalid>

......

當我們通過 kubectlgetcrd命令獲取到我們定義的 CRD 資源對象,就證明我們定義的 CRD 安裝成功了。其實現在只是 CRD 的這個聲明安裝成功了,但是我們這個 CRD 的具體業務邏輯實現方式還在我們本地,並沒有部署到集羣之中,我們可以通過下面的命令來在本地項目中啓動 Operator 的調試:

$ operator-sdk up local                                                     

INFO[0000] Running the operator locally.                

INFO[0000] Using namespace default.                     

{"level":"info","ts":1559207203.964137,"logger":"cmd","msg":"Go Version: go1.11.4"}

{"level":"info","ts":1559207203.964192,"logger":"cmd","msg":"Go OS/Arch: darwin/amd64"}

{"level":"info","ts":1559207203.9641972,"logger":"cmd","msg":"Version of operator-sdk: v0.7.0"}

{"level":"info","ts":1559207203.965905,"logger":"leader","msg":"Trying to become the leader."}

{"level":"info","ts":1559207203.965945,"logger":"leader","msg":"Skipping leader election; not running in a cluster."}

{"level":"info","ts":1559207206.928867,"logger":"cmd","msg":"Registering Components."}

{"level":"info","ts":1559207206.929077,"logger":"kubebuilder.controller","msg":"Starting EventSource","controller":"appservice-controller","source":"kind source: /, Kind="}

{"level":"info","ts":1559207206.9292521,"logger":"kubebuilder.controller","msg":"Starting EventSource","controller":"appservice-controller","source":"kind source: /, Kind="}

{"level":"info","ts":1559207209.622659,"logger":"cmd","msg":"failed to initialize service object for metrics: OPERATOR_NAME must be set"}

{"level":"info","ts":1559207209.622693,"logger":"cmd","msg":"Starting the Cmd."}

{"level":"info","ts":1559207209.7236018,"logger":"kubebuilder.controller","msg":"Starting Controller","controller":"appservice-controller"}

{"level":"info","ts":1559207209.8284118,"logger":"kubebuilder.controller","msg":"Starting workers","controller":"appservice-controller","worker count":1}

上面的命令會在本地運行 Operator 應用,通過 ~/.kube/config去關聯集羣信息,現在我們去添加一個 AppService 類型的資源然後觀察本地 Operator 的變化情況,資源清單文件就是我們上面預定義的(deploy/crds/appv1appservice_cr.yaml)

apiVersion: app.example.com/v1

kind: AppService

metadata:

  name: nginx-app

spec:

  size: 2

  image: nginx:1.7.9

  ports:

    - port: 80

      targetPort: 80

      nodePort: 30002

直接創建這個資源對象:

$ kubectl create -f deploy/crds/app_v1_appservice_cr.yaml

appservice "nginx-app" created

我們可以看到我們的應用創建成功了,這個時候查看 Operator 的調試窗口會有如下的信息出現:

......

{"level":"info","ts":1559207416.670523,"logger":"controller_appservice","msg":"Reconciling AppService","Request.Namespace":"default","Request.Name":"nginx-app"}

{"level":"info","ts":1559207417.004226,"logger":"controller_appservice","msg":"Reconciling AppService","Request.Namespace":"default","Request.Name":"nginx-app"}

{"level":"info","ts":1559207417.004331,"logger":"controller_appservice","msg":"Reconciling AppService","Request.Namespace":"default","Request.Name":"nginx-app"}

{"level":"info","ts":1559207418.33779,"logger":"controller_appservice","msg":"Reconciling AppService","Request.Namespace":"default","Request.Name":"nginx-app"}

{"level":"info","ts":1559207418.951193,"logger":"controller_appservice","msg":"Reconciling AppService","Request.Namespace":"default","Request.Name":"nginx-app"}

......

然後我們可以去查看集羣中是否有符合我們預期的資源出現:

$ kubectl get AppService

NAME        AGE

nginx-app   <invalid>

$ kubectl get deploy

NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

nginx-app                2         2         2            2           <invalid>

$ kubectl get svc

NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE

kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        76d

nginx-app    NodePort    10.108.227.5   <none>        80:30002/TCP   <invalid>

$ kubectl get pods

NAME                                      READY     STATUS    RESTARTS   AGE

nginx-app-76b6449498-2j82j                1/1       Running   0          <invalid>

nginx-app-76b6449498-m4h58                1/1       Running   0          <invalid>

看到了吧,我們定義了兩個副本(size=2),這裏就出現了兩個 Pod,還有一個 NodePort=30002 的 Service 對象,我們可以通過該端口去訪問下應用:
Kubernetes Operator 快速入門教程
如果應用在安裝過程中出現了任何問題,我們都可以通過本地的 Operator 調試窗口找到有用的信息,然後調試修改即可。

清理:

$ kubectl delete -f deploy/crds/app_v1_appservice_crd.yaml

$ kubectl delete -f deploy/crds/app_v1_appservice_cr.yaml

部署

自定義的資源對象現在測試通過了,但是如果我們將本地的 operator-sdk uplocal命令終止掉,我們可以猜想到就沒辦法處理 AppService 資源對象的一些操作了,所以我們需要將我們的業務邏輯實現部署到集羣中去。

執行下面的命令構建 Operator 應用打包成 Docker 鏡像:

$ operator-sdk build cnych/opdemo                         

INFO[0002] Building Docker image cnych/opdemo           

Sending build context to Docker daemon  400.7MB

Step 1/7 : FROM registry.access.redhat.com/ubi7-dev-preview/ubi-minimal:7.6

......

Successfully built a8cde91be6ab

Successfully tagged cnych/opdemo:latest

INFO[0053] Operator build complete.              

鏡像構建成功後,推送到 docker hub:

$ docker push cnych/opdemo

鏡像推送成功後,使用上面的鏡像地址更新 Operator 的資源清單:

$ sed -i 's|REPLACE_IMAGE|cnych/opdemo|g' deploy/operator.yaml

# 如果你使用的是 Mac 系統,使用下面的命令

$ sed -i "" 's|REPLACE_IMAGE|cnych/opdemo|g' deploy/operator.yaml

現在 Operator 的資源清單文件準備好了,然後創建對應的 RBAC 的對象:

# Setup Service Account

$ kubectl create -f deploy/service_account.yaml

# Setup RBAC

$ kubectl create -f deploy/role.yaml

$ kubectl create -f deploy/role_binding.yaml

權限相關聲明已經玩CN,接下來安裝 CRD 和 Operator:

# Setup the CRD

$ kubectl apply -f deploy/crds/app_v1_appservice_crd.yaml

$ kubectl get crd

NAME                                   CREATED AT

appservices.app.example.com            2019-05-30T17:03:32Z

......

# Deploy the Operator

$ kubectl create -f deploy/operator.yaml

deployment.apps/opdemo created

$ kubectl get pods

NAME                                      READY   STATUS    RESTARTS   AGE

opdemo-64db96d575-9vtq6                   1/1     Running   0          2m2s

到這裏我們的 CRD 和 Operator 實現都已經安裝成功了。

現在我們再來部署我們的 AppService 資源清單文件,現在的業務邏輯就會在上面的 opdemo-64db96d575-9vtq6的 Pod 中去處理了。

$ kubectl create -f deploy/crds/app_v1_appservice_cr.yaml

appservice.app.example.com/nginx-app created

$ kubectl get appservice

NAME        AGE

nginx-app   18s

$  kubectl get deploy

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE

nginx-app                2/2     2            2           24s

opdemo                   1/1     1            1           5m51s

$  kubectl get svc

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE

kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        76d

nginx-app    NodePort    10.106.129.82   <none>        80:30002/TCP   29s

opdemo       ClusterIP   10.100.233.51   <none>        8383/TCP       4m25s

$  kubectl get pods

NAME                                      READY   STATUS    RESTARTS   AGE

nginx-app-76b6449498-ffhgx                1/1     Running   0          32s

nginx-app-76b6449498-wzjq2                1/1     Running   0          32s

opdemo-64db96d575-9vtq6                   1/1     Running   0          5m59s

$ kubectl describe appservice nginx-app

Name:         nginx-app

Namespace:    default

Labels:       <none>

Annotations:  spec: {"size":2,"image":"nginx:1.7.9","resources":{},"ports":[{"protocol":"TCP","port":80,"targetPort":80,"nodePort":30002}]}

API Version:  app.example.com/v1

Kind:         AppService

Metadata:

  Creation Timestamp:  2019-05-30T17:41:28Z

  Generation:          2

  Resource Version:    19666617

  Self Link:           /apis/app.example.com/v1/namespaces/default/appservices/nginx-app

  UID:                 2756f232-8302-11e9-80ca-525400cc3c00

Spec:

  Image:  nginx:1.7.9

  Ports:

    Node Port:    30002

    Port:         80

    Protocol:     TCP

    Target Port:  80

  Resources:

  Size:  2

Events:  <none>

然後同樣的可以通過 30002 這個 NodePort 端口去訪問應用,到這裏應用就部署成功了。

清理

有資源清單文件,直接刪除即可:

$ kubectl delete -f deploy/crds/app_v1_appservice_cr.yaml

$ kubectl delete -f deploy/operator.yaml

$ kubectl delete -f deploy/role.yaml

$ kubectl delete -f deploy/role_binding.yaml

$ kubectl delete -f deploy/service_account.yaml

$ kubectl delete -f deploy/crds/app_v1_appservice_crd.yaml

開發

Operator SDK 爲我們創建了一個快速啓動的代碼和相關配置,如果我們要開始處理相關的邏輯,我們可以在項目中搜索 TODO(user)這個註釋來實現我們自己的邏輯,比如在我的 VSCode 環境中,看上去是這樣的:
Kubernetes Operator 快速入門教程

本篇文章示例代碼地址:https://github.com/cnych/opdemo

參考資料

給大家推薦一個本人精心打造的一個精品課程,現在限時優惠中:從 Docker 到 Kubernetes 進階

掃描下面的二維碼(或微信搜索 k8s技術圈)關注我們的微信公衆帳號,在微信公衆帳號中回覆 加羣 即可加入到我們的 kubernetes 討論羣裏面共同學習。

Kubernetes Operator 快速入門教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章