扩展kubernetes两个最常用最需要掌握的东西:自定义资源CRD 和 adminsion webhook, 本文教你如何十分钟掌握CRD开发.

kubernetes允许用户自定义自己的资源对象,就如同deployment statefulset一样,这个应用非常广泛,比如prometheus opterator就自定义Prometheus对象,再加上一个自定义的controller监听到kubectl create Prometheus时就去创建Pod组成一个pormetheus集群。rook等等同理。

我需要用kubernetes调度虚拟机,所以这里自定义一个 VirtualMachine 类型

# [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)
kubebuilder能帮我们节省大量工作,让开发CRD和adminsion webhook变得异常简单。

## 安装
通过源码安装:
```
git clone https://github.com/kubernetes-sigs/kubebuilder
cd kubebuilder
make build
cp bin/kubebuilder $GOPATH/bin
```

或者下载二进制:
```
os=$(go env GOOS)
arch=$(go env GOARCH)

# download kubebuilder and extract it to tmp
curl -sL https://go.kubebuilder.io/dl/2.0.0-beta.0/${os}/${arch} | tar -xz -C /tmp/

# move to a long-term location and put it on your path
# (you'll need to set the KUBEBUILDER_ASSETS env var if you put it somewhere else)
sudo mv /tmp/kubebuilder_2.0.0-beta.0_${os}_${arch} /usr/local/kubebuilder
export PATH=$PATH:/usr/local/kubebuilder/bin
```

还需要装下[kustomize](https://github.com/kubernetes-sigs/kustomize) 这可是个渲染yaml的神器,让helm颤抖。
```
go install sigs.k8s.io/kustomize/v3/cmd/kustomize
```

## 使用
注意你得先有个kubernetes集群,[一步安装走你](https://github.com/fanux/sealos)

> 创建CRD

```
kubebuilder init --domain sealyun.com --license apache2 --owner "fanux"
kubebuilder create api --group infra --version v1 --kind VirtulMachine
```

> 安装CRD并启动controller

```
make install # 安装CRD
make run # 启动controller
```
然后我们就可以看到创建的CRD了
```
# kubectl get crd
NAME AGE
virtulmachines.infra.sealyun.com 52m
```

来创建一个虚拟机:
```
# kubectl apply -f config/samples/
# kubectl get virtulmachines.infra.sealyun.com
NAME AGE
virtulmachine-sample 49m
```
看一眼yaml文件:
```
# cat config/samples/infra_v1_virtulmachine.yaml
apiVersion: infra.sealyun.com/v1
kind: VirtulMachine
metadata:
name: virtulmachine-sample
spec:
# Add fields here
foo: bar
```

这里仅仅是把yaml存到etcd里了,我们controller监听到创建事件时啥事也没干。

> 把controller部署到集群中

```
make docker-build docker-push IMG=fanux/infra-controller
make deploy
```
我是连的远端的kubenetes, make docker-build时test过不去,没有etcd的bin文件,所以先把test关了。

修改Makefile:
```
# docker-build: test
docker-build:
```
Dockerfile里的`gcr.io/distroless/static:latest` 这个镜像你也可能拉不下来,随意改改就行,我改成了`golang:1.12.7`

也有可能构建时有些代码拉不下来,启用一下go mod vendor 把依赖打包进去
```
go mod vendor
如果你本地有些代码拉不下来,可以用proxy:
```
export GOPROXY=https://goproxy.io
```
```
再改下Dockerfile, 注释掉download:

修改后:
```
# Build the manager binary
FROM golang:1.12.7 as builder

WORKDIR /go/src/github.com/fanux/sealvm
# Copy the Go Modules manifests
COPY . .

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o manager main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
# FROM gcr.io/distroless/static:latest
FROM golang:1.12.7
WORKDIR /
COPY --from=builder /go/src/github.com/fanux/sealvm/manager .
ENTRYPOINT ["/manager"]
```

`make deploy` 时报错: `Error: json: cannot unmarshal string into Go struct field Kustomization.patches of type types.Patch`

把 `config/default/kustomization.yaml` 中的 `patches:` 改成 `patchesStrategicMerge:` 即可

`kustomize build config/default` 这个命令就渲染出了controller的yaml文件,可以体验下

看 你的controller已经跑起来了:
```
kubectl get deploy -n sealvm-system
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
sealvm-controller-manager 1 1 1 0 3m
kubectl get svc -n sealvm-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
sealvm-controller-manager-metrics-service ClusterIP 10.98.71.199 8443/TCP 4m
```

## 开发

### 增加对象数据参数
看下config/samples下面的yaml文件:
```
apiVersion: infra.sealyun.com/v1
kind: VirtulMachine
metadata:
name: virtulmachine-sample
spec:
# Add fields here
foo: bar
```
这里参数里有`foo:bar`, 那我们来加个虚拟CPU,内存信息:

直接`api/v1/virtulmachine_types.go`即可
```
// VirtulMachineSpec defines the desired state of VirtulMachine
// 在这里加信息
type VirtulMachineSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
CPU string `json:"cpu"` // 这是我增加的
Memory string `json:"memory"`
}

// VirtulMachineStatus defines the observed state of VirtulMachine
// 在这里加状态信息,比如虚拟机是启动状态,停止状态啥的
type VirtulMachineStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}
```
然后make一下:
```
make && make install && make run
```
这时再去渲染一下controller的yaml就会发现CRD中已经带上CPU和内存信息了:

`kustomize build config/default`
```
properties:
cpu:
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
type: string
memory:
type: string
```

修改一下yaml:
```
apiVersion: infra.sealyun.com/v1
kind: VirtulMachine
metadata:
name: virtulmachine-sample
spec:
cpu: "1"
memory: "2G"
```

```
# kubectl apply -f config/samples
virtulmachine.infra.sealyun.com "virtulmachine-sample" configured
# kubectl get virtulmachines.infra.sealyun.com virtulmachine-sample -o yaml
apiVersion: infra.sealyun.com/v1
kind: VirtulMachine
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"infra.sealyun.com/v1","kind":"VirtulMachine","metadata":{"annotations":{},"name":"virtulmachine-sample","namespace":"default"},"spec":{"cpu":"1","memory":"2G"}}
creationTimestamp: 2019-07-26T08:47:34Z
generation: 2
name: virtulmachine-sample
namespace: default
resourceVersion: "14811698"
selfLink: /apis/infra.sealyun.com/v1/namespaces/default/virtulmachines/virtulmachine-sample
uid: 030e2b9a-af82-11e9-b63e-5254bc16e436
spec: # 新的CRD已生效
cpu: "1"
memory: 2G
```
Status 同理,就不再赘述了,比如我把status里加一个Create, 表示controller要去创建虚拟机了(主要一些控制层面的逻辑),创建完了把状态改成Running

### Reconcile 唯一需要实现的接口
controller把轮训与事件监听都封装在这一个接口里了.你不需要关心怎么事件监听的.

#### 获取虚拟机信息
```
func (r *VirtulMachineReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx = context.Background()
_ = r.Log.WithValues("virtulmachine", req.NamespacedName)

vm := &v1.VirtulMachine{}
if err := r.Get(ctx, req.NamespacedName, vm); err != nil { # 获取VM信息
log.Error(err, "unable to fetch vm")
} else {
fmt.Println(vm.Spec.CPU, vm.Spec.Memory) # 打印CPU内存信息
}

return ctrl.Result{}, nil
}
```
`make && make install && make run`这个时候去创建一个虚拟机`kubectl apply -f config/samples`,日志里就会输出CPU内存了. List接口同理,我就不赘述了

```
r.List(ctx, &vms, client.InNamespace(req.Namespace), client.MatchingField(vmkey, req.Name))
```

#### 更新状态
在status结构体中加入状态字段:
```
type VirtulMachineStatus struct {
Status string `json:"status"`
}
```

controller里去更新状态:
```
vm.Status.Status = "Running"
if err := r.Status().Update(ctx, vm); err != nil {
log.Error(err, "unable to update vm status")
}
```

如果出现:`the server could not find the requested resource` 这个错误,那么在CRD结构体上需要加个注释 `// +kubebuilder:subresource:status`:

```
// +kubebuilder:subresource:status
// +kubebuilder:object:root=true

type VirtulMachine struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec VirtulMachineSpec `json:"spec,omitempty"`
Status VirtulMachineStatus `json:"status,omitempty"`
}
```
这样就好了

编译启动后再去apply发现状态已经变成running:
```
# kubectl get virtulmachines.infra.sealyun.com virtulmachine-sample -o yaml
...
status:
status: Running
```

#### 删除
```
time.Sleep(time.Second * 10)
if err := r.Delete(ctx, vm); err != nil {
log.Error(err, "unable to delete vm ", "vm", vm)
}
```
10s之后我们将GET不到

### 删除回收器 Finalizers
如果不使用Finalizers,kubectl delete 时直接就删了etcd数据,controller再想去拿CRD时已经拿不到了:
```
ERRO[0029] VirtulMachine.infra.sealyun.com "virtulmachine-sample" not foundunable to fetch vm source="virtulmachine_controller.go:48"
```

所以在创建时我们需要给CRD加上Finalizer:
```
vm.ObjectMeta.Finalizers = append(vm.ObjectMeta.Finalizers, "virtulmachine.infra.sealyun.com")
```
然后删除时就只会给CRD打上一个删除时间戳,供我们做后续处理, 处理完了我们删除掉Finalizers:
```
如果 DeleteionTimestamp不存在
如果没有Finalizers
加上Finalizers,并更新CRD
要不然,说明是要被删除的
如果存在Finalizers,删除Finalizers,并更新CRD
```
看个完整的代码示例:
```
if cronJob.ObjectMeta.DeletionTimestamp.IsZero() {
if !containsString(cronJob.ObjectMeta.Finalizers, myFinalizerName) {
cronJob.ObjectMeta.Finalizers = append(cronJob.ObjectMeta.Finalizers, myFinalizerName)
if err := r.Update(context.Background(), cronJob); err != nil {
return ctrl.Result{}, err
}
}
} else {
if containsString(cronJob.ObjectMeta.Finalizers, myFinalizerName) {
if err := r.deleteExternalResources(cronJob); err != nil {
return ctrl.Result{}, err
}

cronJob.ObjectMeta.Finalizers = removeString(cronJob.ObjectMeta.Finalizers, myFinalizerName)
if err := r.Update(context.Background(), cronJob); err != nil {
return ctrl.Result{}, err
}
}
}
```

### webhook
kuberentes有三种webhook,admission webhook, authorization webhook and CRD conversion webhook.

这里比如我们要给CRD设置一些默认值,又或者是用户创建时少填了一些参数,那么我们得禁止创建等等这些事。

使用webhook也非常的简单,只需给定义的结构体实现 `Defaulter` 和 `Validator`接口即可.

#### 其它接口
Reconcile结构体聚合了Client接口,所以client的所有方法都是可以直接调用,大部分是对CRD object的相关操作
```
type Client interface {
Reader
Writer
StatusClient
}
```
```
// Reader knows how to read and list Kubernetes objects.
type Reader interface {
// Get retrieves an obj for the given object key from the Kubernetes Cluster.
// obj must be a struct pointer so that obj can be updated with the response
// returned by the Server.
Get(ctx context.Context, key ObjectKey, obj runtime.Object) error

// List retrieves list of objects for a given namespace and list options. On a
// successful call, Items field in the list will be populated with the
// result returned from the server.
List(ctx context.Context, list runtime.Object, opts ...ListOptionFunc) error
}

// Writer knows how to create, delete, and update Kubernetes objects.
type Writer interface {
// Create saves the object obj in the Kubernetes cluster.
Create(ctx context.Context, obj runtime.Object, opts ...CreateOptionFunc) error

// Delete deletes the given obj from Kubernetes cluster.
Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error

// Update updates the given obj in the Kubernetes cluster. obj must be a
// struct pointer so that obj can be updated with the content returned by the Server.
Update(ctx context.Context, obj runtime.Object, opts ...UpdateOptionFunc) error

// Patch patches the given obj in the Kubernetes cluster. obj must be a
// struct pointer so that obj can be updated with the content returned by the Server.
Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOptionFunc) error
}

// StatusClient knows how to create a client which can update status subresource
// for kubernetes objects.
type StatusClient interface {
Status() StatusWriter
}
```
扫码关注sealyun
![](https://sealyun.com/img/qrcode1.jpg)

探讨可加QQ群:98488045

kuberenetes CRD开发指南的更多相关文章

  1. kubernetes CRD 开发指南

    扩展kubernetes两个最常用最需要掌握的东西:自定义资源CRD 和 adminsion webhook, 本文教你如何十分钟掌握CRD开发. kubernetes允许用户自定义自己的资源对象,就 ...

  2. kubernetes CRD开发指南

    扩展kubernetes两个最常用最需要掌握的东西:自定义资源CRD 和 adminsion webhook, 本文教你如何十分钟掌握CRD开发. kubernetes允许用户自定义自己的资源对象,就 ...

  3. ASP.NET Aries 开源开发框架:开发指南(一)

    前言: 上周开源了Aries开发框架后,好多朋友都Download了源码,在运行过程里,有一些共性的问题会问到. 所以本篇打算写一下简单的开发指南,照顾一下不是太看的懂源码的同学,同时也会讲解一下框架 ...

  4. FreeMarker模板开发指南知识点梳理

    freemarker是什么? 有什么用? 怎么用? (问得好,这些都是我想知道的问题) freemarker是什么? FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生 ...

  5. Jetty使用教程(四:21-22)—Jetty开发指南

    二十一.嵌入式开发 21.1 Jetty嵌入式开发HelloWorld 本章节将提供一些教程,通过Jetty API快速开发嵌入式代码 21.1.1 下载Jetty的jar包 Jetty目前已经把所有 ...

  6. JVM 平台上的各种语言的开发指南

    JVM 平台上的各种语言的开发指南 为什么我们需要如此多的JVM语言? 在2013年你可以有50中JVM语言的选择来用于你的下一个项目.尽管你可以说出一大打的名字,你会准备为你的下一个项目选择一种新的 ...

  7. iOS原生地图开发指南续——大头针与自定义标注

    iOS原生地图开发指南续——大头针与自定义标注 出自:http://www.sxt.cn/info-6042-u-7372.html 在上一篇博客中http://my.oschina.net/u/23 ...

  8. Angularjs中文版本开发指南发布

    从本人开始在写关于Angularjs的文章开始,也算是见证了Angularjs在国内慢慢的火起来,如今的Angularjs正式如日中天.想知道为什么Angularjs会这么火,请移步angularjs ...

  9. nodejs开发指南读后感

    nodejs开发指南读后感 阅读目录 使用nodejs创建http服务器; supervisor的使用及nodejs常见的调式代码命令了解; 了解Node核心模块; ejs模板引擎 Express 理 ...

随机推荐

  1. 安装metasploitable3的经验总结

    一个月没有写学习记录了,但是这一个月一直没闲着,抽空写写自己装metasploitable3的经验教训(提示:只要你的环境配置没错,步骤没错,多试几次就会成功了,我总共测试了15次,,短的耗时30分钟 ...

  2. 使用LinkedList模拟一个堆栈或者队列数据结构。

    堆栈:先进后出 First in last out filo 队列:先进先出 First in last out filo使用LinkedList的方法,addFirst addLast getFir ...

  3. 解决Mac下sed命令报错的问题

    在Mac上准备批量替换一些文字,使用sed命令,如下: sed -i 's/xxx/yyy/g' file 同样的命令在Linux上是可以成功运行的,注意Mac下man sed中-i参数的说明: 原来 ...

  4. Angular4.0从入门到实战打造在线竞拍网站学习笔记之一--组件

    Angular4.0基础知识之组件 Angular4.0基础知识之路由 Angular4.0依赖注入 Angular4.0数据绑定&管道 最近搞到手了一部Angular4的视频教程,这几天正好 ...

  5. 曹工说Tomcat3:深入理解 Tomcat Digester

    一.前言 我写博客主要靠自己实战,理论知识不是很强,要全面介绍Tomcat Digester,还是需要一定的理论功底.翻阅了一些介绍 Digester 的书籍.博客,发现不是很系统,最后发现还是官方文 ...

  6. Programming In Lua 第八章

    1, 也就是说,lua虽然会把代码预编译成中间码,以提高运行速度.但其会在程序运行过程中需要编译器,所以其仍然是解释型语言.loadfile会加载一个文件并将其编译成中间码,并返回一个函数. 2, 3 ...

  7. WIN7下vs2010滑轮滚动不正确的解决方法

    win7下vs2010在滚动滑轮时文档滚动条不滚动而是解决方案的滚动条滚动的解决方法, 控制面板>设备和打印机>鼠标设置>滚轮选项卡里面将滚轮功能设置设为只使用office97预设的 ...

  8. python学习 -女神或者男神把微信消息撤回后好慌,有了这个妈妈再也不担心你看不到女神或者男神撤回的消息了(超详解)

    简介 有时候在忙工作,女朋友发了一个消息,就撤回了,但是人天生的都有一颗好奇心,而且在当今这个时代找个女朋友不容易,一个程序猿找一个女朋友更是不容易的.人家好不容易跟你,你还不得把人家当老佛爷侍候着, ...

  9. CodeForces 691D:Swaps in Permutation(并查集)

    http://codeforces.com/contest/691/problem/D D. Swaps in Permutation   You are given a permutation of ...

  10. 【Mysql】索引简介

    本文口味:番茄炒蛋,预计阅读:10分钟. 博客又停更了两个月,在这期间,对人生和世界多了许多思考.在人生的不同阶段,会对生活和世界有着不一样的认知,而认知的改变也会直接反应在行为模式之中. 对于生活的 ...