原文鏈接:https://www.qikqiak.com/post/k8s-operator-101/
在 Kubernetes 的監(jiān)控方案中我們經(jīng)常會(huì)使用到一個(gè)Promethues Operator的項(xiàng)目今魔,該項(xiàng)目可以讓我們更加方便的去使用 Prometheus诡壁,而不需要直接去使用最原始的一些資源對(duì)象,比如 Pod洛巢、Deployment,隨著 Prometheus Operator 項(xiàng)目的成功厦滤,CoreOS 公司開源了一個(gè)比較厲害的工具:Operator Framework援岩,該工具可以讓開發(fā)人員更加容易的開發(fā) Operator 應(yīng)用。
在本篇文章中我們會(huì)為大家介紹一個(gè)簡單示例來演示如何使用 Operator Framework 框架來開發(fā)一個(gè) Operator 應(yīng)用掏导。
Kubernetes Operator
Operator 是由 CoreOS 開發(fā)的享怀,用來擴(kuò)展 Kubernetes API,特定的應(yīng)用程序控制器趟咆,它用來創(chuàng)建添瓷、配置和管理復(fù)雜的有狀態(tài)應(yīng)用,如數(shù)據(jù)庫值纱、緩存和監(jiān)控系統(tǒng)鳞贷。Operator 基于 Kubernetes 的資源和控制器概念之上構(gòu)建,但同時(shí)又包含了應(yīng)用程序特定的領(lǐng)域知識(shí)虐唠。創(chuàng)建Operator 的關(guān)鍵是CRD(自定義資源)的設(shè)計(jì)搀愧。
Kubernetes 1.7 版本以來就引入了自定義控制器的概念,該功能可以讓開發(fā)人員擴(kuò)展添加新功能疆偿,更新現(xiàn)有的功能咱筛,并且可以自動(dòng)執(zhí)行一些管理任務(wù),這些自定義的控制器就像 Kubernetes 原生的組件一樣杆故,Operator 直接使用 Kubernetes API進(jìn)行開發(fā)迅箩,也就是說他們可以根據(jù)這些控制器內(nèi)部編寫的自定義規(guī)則來監(jiān)控集群、更改 Pods/Services反番、對(duì)正在運(yùn)行的應(yīng)用進(jìn)行擴(kuò)縮容沙热。
Operator Framework
Operator Framework 同樣也是 CoreOS 開源的一個(gè)用于快速開發(fā) Operator 的工具包媒鼓,該框架包含兩個(gè)主要的部分:
- Operator SDK: 無需了解復(fù)雜的 Kubernetes API 特性侥加,即可讓你根據(jù)你自己的專業(yè)知識(shí)構(gòu)建一個(gè) Operator 應(yīng)用大猛。
- Operator Lifecycle Manager OLM: 幫助你安裝棉圈、更新和管理跨集群的運(yùn)行中的所有 Operator(以及他們的相關(guān)服務(wù))
Workflow
Operator SDK 提供以下工作流來開發(fā)一個(gè)新的 Operator:
- 使用 SDK 創(chuàng)建一個(gè)新的 Operator 項(xiàng)目
- 通過添加自定義資源(CRD)定義新的資源 API
- 指定使用 SDK API 來 watch 的資源
- 定義 Operator 的協(xié)調(diào)(reconcile)邏輯
- 使用 Operator SDK 構(gòu)建并生成 Operator 部署清單文件
Demo
我們平時(shí)在部署一個(gè)簡單的 Webserver 到 Kubernetes 集群中的時(shí)候姨蟋,都需要先編寫一個(gè) Deployment 的控制器馍惹,然后創(chuàng)建一個(gè) Service 對(duì)象策添,通過 Pod 的 label 標(biāo)簽進(jìn)行關(guān)聯(lián)白修,最后通過 Ingress 或者 type=NodePort 類型的 Service 來暴露服務(wù)息楔,每次都需要這樣操作寝贡,是不是略顯麻煩,我們就可以創(chuàng)建一個(gè)自定義的資源對(duì)象值依,通過我們的 CRD 來描述我們要部署的應(yīng)用信息圃泡,比如鏡像、服務(wù)端口愿险、環(huán)境變量等等颇蜡,然后創(chuàng)建我們的自定義類型的資源對(duì)象的時(shí)候,通過控制器去創(chuàng)建對(duì)應(yīng)的 Deployment 和 Service,是不是就方便很多了风秤,相當(dāng)于我們用一個(gè)資源清單去描述了 Deployment 和 Service 要做的兩件事情鳖目。
這里我們將創(chuàng)建一個(gè)名為 AppService 的 CRD 資源對(duì)象,然后定義如下的資源清單進(jìn)行應(yīng)用部署:
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 資源對(duì)象去創(chuàng)建副本數(shù)為2的 Pod缤弦,然后通過 nodePort=30002 的端口去暴露服務(wù)领迈,接下來我們就來一步一步的實(shí)現(xiàn)我們這里的這個(gè)簡單的 Operator 應(yīng)用。
開發(fā)環(huán)境
環(huán)境需求
要開發(fā) Operator 自然 Kubernetes 集群是少不了的碍沐,還需要 Golang 的環(huán)境狸捅,這里的安裝就不多說了,然后還需要一個(gè) Go 語言的依賴管理工具包:dep抢韭,由于 Operator SDK 是使用的 dep 該工具包薪贫,所以需要我們提前安裝好,可以查看資料:https://github.com/golang/dep刻恭,另外一個(gè)需要說明的是瞧省,由于 dep 去安裝的時(shí)候需要去谷歌的網(wǎng)站拉取很多代碼,所以正常情況下的話是會(huì)失敗的鳍贾,需要做什么工作大家應(yīng)該清楚吧鞍匾?要科學(xué)。
安裝 operator-sdk
operator sdk 安裝方法非常多骑科,我們可以直接在 github 上面下載需要使用的版本橡淑,然后放置到 PATH 環(huán)境下面即可,當(dāng)然也可以將源碼 clone 到本地手動(dòng)編譯安裝即可咆爽,如果你是 Mac梁棠,當(dāng)然還可以使用常用的 brew 工具進(jìn)行安裝:
$ 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
演示
創(chuàng)建新項(xiàng)目
環(huán)境準(zhǔn)備好了斗埂,接下來就可以使用 operator-sdk 直接創(chuàng)建一個(gè)新的項(xiàng)目了符糊,命令格式為:
operator-sdk new <project-name>
按照上面我們預(yù)先定義的 CRD 資源清單,我們這里可以這樣創(chuàng)建:
# 創(chuàng)建項(xiàng)目目錄
$ mkdir -p operator-learning
# 設(shè)置項(xiàng)目目錄為 GOPATH 路徑
$ cd operator-learning && export GOPATH=$PWD
$ mkdir -p $GOPATH/src/github.com/cnych
$ cd $GOPATH/src/github.com/cnych
# 使用 sdk 創(chuàng)建一個(gè)名為 opdemo 的 operator 項(xiàng)目
$ operator-sdk new opdemo
......
# 該過程需要科學(xué)上網(wǎng)呛凶,需要花費(fèi)很長時(shí)間男娄,請(qǐng)耐心等待
......
$ 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
到這里一個(gè)全新的 Operator 項(xiàng)目就新建完成了。
項(xiàng)目結(jié)構(gòu)
使用operator-sdk new
命令創(chuàng)建新的 Operator 項(xiàng)目后漾稀,項(xiàng)目目錄就包含了很多生成的文件夾和文件模闲。
- Gopkg.toml Gopkg.lock?—?Go Dep 清單,用來描述當(dāng)前 Operator 的依賴包崭捍。
- cmd - 包含 main.go 文件尸折,使用 operator-sdk API 初始化和啟動(dòng)當(dāng)前 Operator 的入口。
- deploy - 包含一組用于在 Kubernetes 集群上進(jìn)行部署的通用的 Kubernetes 資源清單文件殷蛇。
- pkg/apis - 包含定義的 API 和自定義資源(CRD)的目錄樹翁授,這些文件允許 sdk 為 CRD 生成代碼并注冊(cè)對(duì)應(yīng)的類型拣播,以便正確解碼自定義資源對(duì)象。
- pkg/controller - 用于編寫所有的操作業(yè)務(wù)邏輯的地方
- vendor - golang vendor 文件夾收擦,其中包含滿足當(dāng)前項(xiàng)目的所有外部依賴包,通過 go dep 管理該目錄谍倦。
我們主要需要編寫的是pkg目錄下面的 api 定義以及對(duì)應(yīng)的 controller 實(shí)現(xiàn)塞赂。
添加 API
接下來為我們的自定義資源添加一個(gè)新的 API,按照上面我們預(yù)定義的資源清單文件昼蛀,在 Operator 相關(guān)根目錄下面執(zhí)行如下命令:
$ operator-sdk add api --api-version=app.example.com/v1 --kind=AppService
添加完成后宴猾,我們可以看到類似于下面的這樣項(xiàng)目結(jié)構(gòu):
添加控制器
上面我們添加自定義的 API,接下來可以添加對(duì)應(yīng)的自定義 API 的具體實(shí)現(xiàn) Controller叼旋,同樣在項(xiàng)目根目錄下面執(zhí)行如下命令:
$ operator-sdk add controller --api-version=app.example.com/v1 --kind=AppService
這樣整個(gè) Operator 項(xiàng)目的腳手架就已經(jīng)搭建完成了仇哆,接下來就是具體的實(shí)現(xiàn)了。
自定義 API
打開源文件pkg/apis/app/v1/appservice_types.go
夫植,需要我們根據(jù)我們的需求去自定義結(jié)構(gòu)體 AppServiceSpec讹剔,我們最上面預(yù)定義的資源清單中就有 size、image详民、ports 這些屬性延欠,所有我們需要用到的屬性都需要在這個(gè)結(jié)構(gòu)體中進(jìn)行定義:
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"`
}
代碼中會(huì)涉及到一些包名的導(dǎo)入,由于包名較多沈跨,所以我們會(huì)使用一些別名進(jìn)行區(qū)分由捎,主要的包含下面幾個(gè):
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"
中定義的結(jié)構(gòu)體狞玛,而且需要注意的是我們這里使用的是ServicePort
,而不是像傳統(tǒng)的 Pod 中定義的 ContanerPort涧窒,這是因?yàn)槲覀兊馁Y源清單中不僅要描述容器的 Port心肪,還要描述 Service 的 Port。
然后一個(gè)比較重要的結(jié)構(gòu)體AppServiceStatus
用來描述資源的狀態(tài)杀狡,當(dāng)然我們可以根據(jù)需要去自定義狀態(tài)的描述蒙畴,我這里就偷懶直接使用 Deployment 的狀態(tài)了:
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"`
}
定義完成后,在項(xiàng)目根目錄下面執(zhí)行如下命令:
$ operator-sdk generate k8s
改命令是用來根據(jù)我們自定義的 API 描述來自動(dòng)生成一些代碼呜象,目錄pkg/apis/app/v1/
下面以zz_generated
開頭的文件就是自動(dòng)生成的代碼膳凝,里面的內(nèi)容并不需要我們?nèi)ナ謩?dòng)編寫。
這樣我們就算完成了對(duì)自定義資源對(duì)象的 API 的聲明恭陡。
實(shí)現(xiàn)業(yè)務(wù)邏輯
上面 API 描述聲明完成了蹬音,接下來就需要我們來進(jìn)行具體的業(yè)務(wù)邏輯實(shí)現(xiàn)了,編寫具體的 controller 實(shí)現(xiàn)休玩,打開源文件pkg/controller/appservice/appservice_controller.go
著淆,需要我們?nèi)ジ牡牡胤揭膊皇呛芏嘟俸荩诵牡木褪?code>Reconcile方法,該方法就是去不斷的 watch 資源的狀態(tài)永部,然后根據(jù)狀態(tài)的不同去實(shí)現(xiàn)各種操作邏輯独泞,核心代碼如下:
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
}
// 如果不存在,則創(chuàng)建關(guān)聯(lián)資源
// 如果存在苔埋,判斷是否需要更新
// 如果需要更新懦砂,則直接更新
// 如果不需要更新,則正常返回
deploy := &appsv1.Deployment{}
if err := r.client.Get(context.TODO(), request.NamespacedName, deploy); err != nil && errors.IsNotFound(err) {
// 創(chuàng)建關(guān)聯(lián)資源
// 1. 創(chuàng)建 Deploy
deploy := resources.NewDeploy(instance)
if err := r.client.Create(context.TODO(), deploy); err != nil {
return reconcile.Result{}, err
}
// 2. 創(chuàng)建 Service
service := resources.NewService(instance)
if err := r.client.Create(context.TODO(), service); err != nil {
return reconcile.Result{}, err
}
// 3. 關(guān)聯(lián) 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) {
// 更新關(guān)聯(lián)資源
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
}
上面就是業(yè)務(wù)邏輯實(shí)現(xiàn)的核心代碼组橄,邏輯很簡單荞膘,就是去判斷資源是否存在,不存在玉工,則直接創(chuàng)建新的資源羽资,創(chuàng)建新的資源除了需要?jiǎng)?chuàng)建 Deployment 資源外,還需要?jiǎng)?chuàng)建 Service 資源對(duì)象遵班,因?yàn)檫@就是我們的需求屠升,當(dāng)然你還可以自己去擴(kuò)展,比如在創(chuàng)建一個(gè) Ingress 對(duì)象费奸。更新也是一樣的弥激,去對(duì)比新舊對(duì)象的聲明是否一致,不一致則需要更新愿阐,同樣的微服,兩種資源都需要更新的。
另外兩個(gè)核心的方法就是上面的resources.NewDeploy(instance)
和resources.NewService(instance)
方法缨历,這兩個(gè)方法實(shí)現(xiàn)邏輯也很簡單以蕴,就是根據(jù) CRD 中的聲明去填充 Deployment 和 Service 資源對(duì)象的 Spec 對(duì)象即可。
NewDeploy 方法實(shí)現(xiàn)如下:
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 對(duì)應(yīng)的方法實(shí)現(xiàn)如下:
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,
},
},
}
}
這樣我們就實(shí)現(xiàn)了 AppService 這種資源對(duì)象的業(yè)務(wù)邏輯辛孵。
調(diào)試
如果我們本地有一個(gè)可以訪問的 Kubernetes 集群丛肮,我們也可以直接進(jìn)行調(diào)試,在本地用戶~/.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 對(duì)象:
$ 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>
......
當(dāng)我們通過kubectl get crd
命令獲取到我們定義的 CRD 資源對(duì)象,就證明我們定義的 CRD 安裝成功了冶匹。其實(shí)現(xiàn)在只是 CRD 的這個(gè)聲明安裝成功了习劫,但是我們這個(gè) CRD 的具體業(yè)務(wù)邏輯實(shí)現(xiàn)方式還在我們本地,并沒有部署到集群之中嚼隘,我們可以通過下面的命令來在本地項(xiàng)目中啟動(dòng) Operator 的調(diào)試:
$ 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}
上面的命令會(huì)在本地運(yùn)行 Operator 應(yīng)用诽里,通過~/.kube/config
去關(guān)聯(lián)集群信息,現(xiàn)在我們?nèi)ヌ砑右粋€(gè) AppService 類型的資源然后觀察本地 Operator 的變化情況飞蛹,資源清單文件就是我們上面預(yù)定義的(deploy/crds/app_v1_appservice_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
直接創(chuàng)建這個(gè)資源對(duì)象:
$ kubectl create -f deploy/crds/app_v1_appservice_cr.yaml
appservice "nginx-app" created
我們可以看到我們的應(yīng)用創(chuàng)建成功了谤狡,這個(gè)時(shí)候查看 Operator 的調(diào)試窗口會(huì)有如下的信息出現(xiàn):
......
{"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"}
......
然后我們可以去查看集群中是否有符合我們預(yù)期的資源出現(xiàn):
$ 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>
看到了吧灸眼,我們定義了兩個(gè)副本(size=2),這里就出現(xiàn)了兩個(gè) Pod墓懂,還有一個(gè) NodePort=30002 的 Service 對(duì)象焰宣,我們可以通過該端口去訪問下應(yīng)用:
如果應(yīng)用在安裝過程中出現(xiàn)了任何問題,我們都可以通過本地的 Operator 調(diào)試窗口找到有用的信息拒贱,然后調(diào)試修改即可宛徊。
清理:
$ kubectl delete -f deploy/crds/app_v1_appservice_crd.yaml
$ kubectl delete -f deploy/crds/app_v1_appservice_cr.yaml
部署
自定義的資源對(duì)象現(xiàn)在測試通過了,但是如果我們將本地的operator-sdk up local
命令終止掉逻澳,我們可以猜想到就沒辦法處理 AppService 資源對(duì)象的一些操作了,所以我們需要將我們的業(yè)務(wù)邏輯實(shí)現(xiàn)部署到集群中去暖呕。
執(zhí)行下面的命令構(gòu)建 Operator 應(yīng)用打包成 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.
鏡像構(gòu)建成功后斜做,推送到 docker hub:
$ docker push cnych/opdemo
鏡像推送成功后,使用上面的鏡像地址更新 Operator 的資源清單:
$ sed -i 's|REPLACE_IMAGE|cnych/opdemo|g' deploy/operator.yaml
# 如果你使用的是 Mac 系統(tǒng)湾揽,使用下面的命令
$ sed -i "" 's|REPLACE_IMAGE|cnych/opdemo|g' deploy/operator.yaml
現(xiàn)在 Operator 的資源清單文件準(zhǔn)備好了瓤逼,然后創(chuàng)建對(duì)應(yīng)的 RBAC 的對(duì)象:
# 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
權(quán)限相關(guān)聲明已經(jīng)完成,接下來安裝 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 實(shí)現(xiàn)都已經(jīng)安裝成功了库物。
現(xiàn)在我們?cè)賮聿渴鹞覀兊?AppService 資源清單文件霸旗,現(xiàn)在的業(yè)務(wù)邏輯就會(huì)在上面的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 這個(gè) NodePort 端口去訪問應(yīng)用戚揭,到這里應(yīng)用就部署成功了诱告。
清理
有資源清單文件,直接刪除即可:
$ 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
開發(fā)
Operator SDK 為我們創(chuàng)建了一個(gè)快速啟動(dòng)的代碼和相關(guān)配置民晒,如果我們要開始處理相關(guān)的邏輯精居,我們可以在項(xiàng)目中搜索TODO(user)
這個(gè)注釋來實(shí)現(xiàn)我們自己的邏輯,比如在我的 VSCode 環(huán)境中潜必,看上去是這樣的:
本篇文章示例代碼地址:https://github.com/cnych/opdemo