技术漫谈 | 自己动手写Kubernetes Operator

在本文中,我们将了解如何使用Operator SDK构建和部署您的第一个Kubernetes operator。
Kubernetes是一个流行的开源平台,用于管理容器化工作负载和服务。随着1.7版本中自定义资源定义(CRD)的引入,该平台也变得可扩展:例如,管理员可以定义自己的资源类型,并提供比deployment或service更适合特定领域的模式。但请放心,这并不一定需要更长的YAML!
但CRD只定义了配置,群集仍需要控制器来监视其状态并协调资源以匹配声明。这便是operator发挥作用的地方。
Operator是与自定义资源相关联的控制器,以执行那些需要“人工操作”的任务。设想部署一套数据库集群,需要设定节点数量、升级和执行备份操作。通过自定义资源可以指定数据库版本、要部署的节点、备份频率以及后端存储。而控制器将执行这些操作所需的业务逻辑。例如,etcd-operator。
然而,编写CRD及其附带控制器是一项艰巨的任务。值得庆幸的是,operator SDK随时为您提供帮助。让我们看看如何用它构建一个可以指定pod镜像并实现扩缩容的operator。换句话说,让我们编写一个基本的Kubernetes ReplicaSet!
请注意,在撰写本文时,Operator SDK的版本为v0.4.0,因此之后可能会发生变化。我们将构建一个Go Operator,但operator也可以开发成Ansible playbooks或Helm chart。
一、一级资源和二级资源
在深入了解Operator SDK和源代码之前,我们再讨论一下operator。正如前面所见,operator是CRD相关的控制器,它观察并根据配置变化或集群状态变化来采取行动。但实际上控制器需要监测两种资源,一级资源和二级资源。
对于ReplicaSet,一级资源是ReplicaSet本身,指定运行的Docker镜像以及pod的副本数。二级资源则是pod。当ReplicaSet 的定义发生改变(例如镜像版本或pod数量)或者其中的pod发生改变时(例如某个pod被删除),控制器则会通过推出新版本来协调集群状态,或是对pod数量扩缩容。
对于DaemonSet,一级资源是DaemonSet本身,而二级资源是pod和node。不同之处在于,控制器也会监测集群node变化,以便在群集增大或缩小时添加或删除pod。
明确识别出Operator的主要和二级资源是至关重要的,因为他们将决定其控制器的行为。

二、构建Potset Operator
讲了足够的理论,现在深入代码构建一个名叫PodSet的operator,它启动busybox容器执行sleep 3600,并管理pod数量的扩容和缩容。样例并不复杂,因为本文的重点是教大家构建operator,而不是深入某个应用复杂的设置。

1、安装Operator SDK
首先下载并安装SDK:

$ mkdir -p $GOPATH/src/github.com/operator-framework

$ cd $GOPATH/src/github.com/operator-framework

$ git clone https://github.com/operator-framework/operator-sdk

$ cd operator-sdk

$ git checkout v0.4.0

$ make dep

$ make install

2、初始化GO项目
接下来使用operator-sdk初始化项目:

# bootstrap the project and run "deps ensure" and "git init"

$ operator-sdk new podset-operator

check the project structure

$ tree -I vendor
.
├── Gopkg.lock
├── Gopkg.toml
├── build
│   └── Dockerfile
├── cmd
│   └── manager
│       └── main.go
├── deploy
│   ├── operator.yaml
│   ├── role.yaml
│   ├── role_binding.yaml
│   └── service_account.yaml
├── pkg
│   ├── apis
│   │   └── apis.go
│   └── controller
│       └── controller.go
└── version
└── version.go
完成后,我们有了项目的基本布局,它不仅包含运行operator的go代码(cmd/manager/main.go),还包含将二进制文件打包成Docker镜像的Dockerfile,以及一组YAML清单:
1)部署podset-operator的deployment;
2)Operator操作所需的Service Account以及role和role bindings(比如添加或删除pod等);

3、添加CRD和控制器
现在,让我们为PodSet Operator创建一个CRD和一个关联控制器。由于这是Operator的第一个版本,因此将版本设置为v1alpha1:

# Add a new API for the custom resource PodSet

$ operator-sdk add api --api-version=app.example.com/v1alpha1 --kind=PodSet

Add a new controller that watches for PodSet

$ operator-sdk add controller –api-version=app.example.com/v1alpha1 –kind=PodSet
这两条命令会生成另一组YAML文件和GO代码,最明显的变化如下:
1、新生成的deploy/crds包含PodSet的CRD和CR样例:

$ cat deploy/crds/app_v1alpha1_podset_crd.yaml

apiVersion: apiextensions.k8s.io/v1beta1

kind: CustomResourceDefinition

metadata:

name: podsets.app.example.com

spec:

group: app.example.com

names:

kind: PodSet

listKind: PodSetList

plural: podsets

singular: podset

scope: Namespaced

version: v1alpha1

$ cat deploy/crds/app_v1alpha1_podset_cr.yaml

apiVersion: app.example.com/v1alpha1

kind: PodSet

metadata:

name: example-podset

spec:

# Add fields here

size: 3

CRD的完整名称是podsets.app.example.com,但也有与之相关联的名称(PodSet, PodSetList, podsets, podset)。它们将成为扩展集群API的一部分,并在kubectl命令行中提供。我们稍后在部署和运行Operator时会看到。
2、pkg/apis/app/v1alpha1/podset_types.go定义了PodSet预期状态的结构体,该结构体由前面所讲的deploy/crds/app_v1alpha1_podset_cr.yaml文件中指定。它还定义了在执行kubectl describe查看状态时,PodSetStatus的结构体。稍后会详细介绍。
3、脚手架文件
pkg/controller/podset/podset_controller.go是放置控制器的业务逻辑的地方。
其余的更改是注册CRD和控制器所必需的。

4、实现控制器的业务逻辑
开箱即用的方式,如果不存在给定app标签的pod,则由SDK生成的控制器代码会自动创建一个。虽然这不是我们想要的,但它仍然是一个很好的起点,因为它展示了如何使用k8s API,无论是列出pod还是创建新的。 我们要做的第一件事是通过添加字段来存储PodSetSpec的副本数量和PodSetStatus的pod名称:

type PodSetSpec struct {

Replicas int32 `json:"replicas"`

}

type PodSetStatus struct {

PodNames []string `json:"podNames"`

}

每次对结构体进行更改后,都需要运行operator-sdk generate k8s命令来相应地更新
pkg/apis/app/v1alpha1/zz_generated.deepcopy.go文件。
之后,我们需要配置控制器在namespace中监测的一级资源和二级资源。对于podset Operator,一级资源是podset,二级资源是namespace中的pod。我们无需做任何事情,因为已经在生成的代码中实现了。默认情况下,控制器在podset资源上运行并创建一个pod。
最后,我们需要实现扩缩容和通过pod名称更新自定义资源状态的逻辑。所有这些都发生在控制器的Reconcile函数中。
在协调期间,控制器获取当前命名空间中的PodSet资源,并将其Replica字段的值与匹配标签的实际Pod数进行比较,以确定是否需要创建或删除pod。 想深入了解控制器Reconcile函数细节,请查看代码
https://github.com/xcoulon/pod … 23L86
让我们专注记住的几个关键点:
1、每次更改PodSet资源或在属于PodSet的pod中发生更改时,都会调用reconcile函数。
2、如果需要Reconcile添加或删除pod,则该功能应该一次只添加或删除一个pod,返回并等待下一次调用(因为它将在创建或删除pod后被调用)。
3、使用controllerutil.SetControllerReference()函数确保PodSet一级资源“拥有”pod 。拥有从属关系意味着删除PodSet资源时,其所有“从属”pod也将被删除。
三、 构建并发布Operator
让我们使用Operator SDK构建包含控制器的Docker镜像,然后将其推到镜像仓库。我们将使用Quay.io,但其他镜像仓库也一样可以使用:

# build the Docker image using the Operator SDK

$ operator-sdk build quay.io/xcoulon/podset-operator

login to Quay.io

$ docker login -u xcoulon quay.io

push the image to Docker Hub

$ docker push quay.io/xcoulon/podset-operator
此外,我们需要更新operator.yaml以使用Quay.io上的新Docker镜像:

# On Linux:

$ sed -i 's|REPLACE_IMAGE|quay.io/xcoulon/podset-operator|g' deploy/operator.yaml

On OSX:

$ sed -i “” ‘s|REPLACE_IMAGE|quay.io/xcoulon/podset-operator|g’ deploy/operator.yaml

1、配置Minishift
安装Minishift并开启admin-user插件:

# create a new profile to test the operator

$ minishift profile set operator

enable the admin-user add-on

$ minishift addon enable admin-user

optionally, configure the VM

$ minishift config set cpus 4
$ minishift config set memory 8GB
$ minishift config set vm-driver virtualbox

start the instance

$ minishift start

login with the admin account

$ oc login -u system:admin

2、在Minishift上部署Operator
在部署Operator控制器之前,我们需要创建service account并为其分配一个具有适当管理权限的role:

# Setup Service Account

$ oc create -f deploy/service_account.yaml

Setup RBAC

$ oc create -f deploy/role.yaml
$ oc create -f deploy/role_binding.yaml
完成后,我们可以创建CRD并部署opertaor控制器:

# Setup the CRD

$ oc create -f deploy/crds/app_v1alpha1_podset_crd.yaml

Deploy the podset-operator

$ oc create -f deploy/operator.yaml
创建CRD后,它不仅可以通过集群API端点访问,还可以从命令行工具访问。如前所述,这就是Kubernetes作为可扩展平台的亮点:

# check the CRD

$ oc get crd podsets.app.example.com

NAME                      

podsets.app.example.com   

check the operator controller

$ oc get pods
NAME                               READY     STATUS   
podset-operator-685bbbc858-d4gf7   1/1       Running   

check if there’s a CR using the CRD fullname…

$ oc get podsets.app.example.com
No resources found.

… or one of its aliases

$ oc get podsets
No resources found.
在CRD和Operator控制器都创建好之后,终于可以创建3副本的PodSet资源了:

$ echo "apiVersion: app.example.com/v1alpha1

kind: PodSet

metadata:

name: example-podset

spec:

replicas: 3" | oc create -f -

现在查看一下namespace中的pod:

$ oc get pods -l app=example-podset

NAME                               READY     STATUS    

example-podset-podc2ckn            1/1       Running   

example-podset-podjnqqr            1/1       Running   

example-podset-podlx55r            1/1       Running   

正如我们对Operator控制器的期望,当删除一个pod时,新的pod会被创建,当扩容或者缩容时,pod数量也会相应增加或减少:

# let's delete a pod

$ oc delete pod example-podset-podlx55r

pod "example-podset-podlx55r" deleted

let’s check the pods again – a new pod was created to replace

the one we just deleted

$ oc get pods -l app=example-podset
NAME                      READY     STATUS    RESTARTS   AGE
example-podset-pod85zf5   1/1       Running   0          46s
example-podset-podc2ckn   1/1       Running   0          8m
example-podset-podjnqqr   1/1       Running   0          8m

let’s scale down the pod set

$ echo “apiVersion: app.example.com/v1alpha1
kind: PodSet
metadata:
name: example-podset
spec:
replicas: 2” | oc apply -f –

let’s check the pods

$ oc get pods -l app=example-podset
NAME                       READY     STATUS        RESTARTS   AGE
example-podset-pod85zf5    1/1       Terminating   0          39m
example-podset-podc2ckn    1/1       Running       0          46m
example-podset-podjnqqr    1/1       Running       0          46m

let’s scale up the pod set

$ echo “apiVersion: app.example.com/v1alpha1
kind: PodSet
metadata:
name: example-podset
spec:
replicas: 4” | oc apply -f –

let’s check the pods again

$ oc get pods -l app=example-podset
NAME                      READY     STATUS    RESTARTS   AGE
example-podset-pod5hj4r   1/1       Running   0          8s
example-podset-podc2ckn   1/1       Running   0          49m
example-podset-podjnqqr   1/1       Running   0          49m
example-podset-podlf7xm   1/1       Running   0          8s
此外,自定义资源状态会显示pod的名称:

$ oc describe podset/example-podset

Name:         example-podset

Namespace:    myproject

Labels:       

Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"app.example.com/v1alpha1","kind":"PodSet","metadata":{"annotations":{},"name":"example-podset","namespace":"myproject"},"spec":{"replica...

API Version:  app.example.com/v1alpha1

Kind:         PodSet

Metadata:

...

Spec:

Replicas:  4

Status:

Pod Names:

example-podset-pod5hj4r

example-podset-podc2ckn

example-podset-podjnqqr

example-podset-podlf7xm

Events:  

最后,当我们删除PodSet资源时,所有关联的(即“从属”)pod也会被删除:

# let's delete the PodSet resource

$ oc delete podset example-podset

podset.app.example.com "example-podset" deleted

let’s check the pods are deleted as well

$ oc get pods -l app=example-podset
NAME                      READY     STATUS        
example-podset-pod5hj4r   1/1       Terminating   
example-podset-podc2ckn   1/1       Terminating   
example-podset-podjnqqr   1/1       Terminating   
example-podset-podzdzrn   1/1       Terminating
至此,我们建立并部署了我们的第一个Kubernetes Operator!
有关Operator SDK的更多信息,请查看GitHub仓库和官网上的概述。
GitHub仓库:

https://github.com/operator-fr … -sdk/

CoreOS官网上:

https://coreos.com/operators/

本文中开发的opertaor代码也在GitHub上。
GitHub :

https://github.com/xcoulon/podset-operator

原文链接:

https://medium.com/devopslinks … 53234

关于睿云智合
深圳睿云智合科技有限公司成立于2012年,总部位于深圳,并分别在成都、深圳设立了研发中心,北京、上海设立了分支机构,核心骨干人员全部为来自金融、科技行业知名企业资深业务专家、技术专家。早期专注于为中国金融保险等大型企业提供创新技术、电子商务、CRM等领域专业咨询服务。
自2016年始,在率先将容器技术引进到中国保险行业客户后,公司组建了专业的容器技术产品研发和实施服务团队,旨在帮助中国金融行业客户将容器创新技术应用于企业信息技术支持业务发展的基础能力改善与提升,成为中国金融保险行业容器技术服务领导品牌。
此外,凭借多年来在呼叫中心领域的业务经验与技术积累,睿云智合率先在业界推出基于开源软交换平台FreeSwitch的微服务架构多媒体数字化业务平台,将语音、视频、webchat、微信、微博等多种客户接触渠道集成,实现客户统一接入、精准识别、智能路由的CRM策略,并以容器化治理来支持平台的全应用生命周期管理,显著提升了数字化业务处理的灵活、高效、弹性、稳定等特性,为帮助传统企业向“以客户为中心”的数字化业务转型提供完美的一站式整体解决方案。