kubernetes 20、operator基础

目标:
掌握kubernetes operator的开发,并弄清楚operator的原理

1 安装
1.1 安装go开发环境
cd /etc
cp -rf yum.repos.d yum.repos.d_bak
cd /etc/yum.repos.d/
rm -rf ./*
wget http://mirrors.aliyun.com/repo/Centos-7.repo
mv Centos-7.repo CentOs-Base.repo

yum install epel-release -y
yum install  golang -y

1.2 导入go环境变量
mkdir -p $HOME/go
mkdir -p $HOME/go/bin
mkdir -p $HOME/go/src
mkdir -p $HOME/go/pkg

cat >>$HOME/.bash_profile<<EOF
export GOROOT=/usr/lib/golang
export GOPATH=$HOME/go
export GO111MODULE=on
export GOPROXY=https://goproxy.io
EOF

source $HOME/.bash_profile

然后修改
vim $HOME/.bash_profile
添加:
export PATH=$PATH:/usr/lib/golang/bin:$HOME/go/bin


source $HOME/.bash_profile

注意:
若无法安装epel-release,则
yum clean all
yum makecache
yum update

1.3 安装delve
mkdir -p $GOPATH/src/github.com/go-delve/delve
git clone https://github.com/go-delve/delve.git $GOPATH/src/github.com/go-delve/delve
cd $GOPATH/src/github.com/go-delve/delve
make install

1.4 安装operator-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.12.0
make dep
make install

1.5 验证go环境安装完成
[root@localhost ch1_dir]# go version
go version go1.13.3 linux/amd64

[root@localhost ch1_dir]# whereis go
go: /usr/bin/go

1.6 查看go环境变量
go env

1.7 验证go环境正常
cd $GOPATH/src
vim main.go
写入如下内容
package main
 
import "fmt"
 
func main() {
    fmt.Println("Hello, World!")
}

执行:
go run ./main.go

1.8 关于环境变量
GOROOT: golang的安装目录
GOPATH:是工作空间,用来存放包的目录。
包含3个子目录:
src:源码, .go等,go run, go install等命令的当前工作路径,在此路径下执行尚书命令
pkg:编译生成的中间文件,比如.a, golang编译包时
bin:编译后生成可执行文件
GOPATH下的src目录就是接下来开发程序的主要目录,所有的源码都是放在这个目录下面,那么一般我们的做法就是一个目录一个项目,
例如: $GOPATH/src/mymath 表示mymath这个应用包或者可执行应用

参考:
https://www.cnblogs.com/enduo/p/9078313.html

2 使用
2.1 创建operator
operator-sdk new dozer-operator

2.2 生成api
operator-sdk add api --api-version=timers.estimer.com/v1alpha1 --kind=Timer

operator-sdk add api --api-version=timers.estimer.com/v1alpha1 --kind=AuditRecord

operator-sdk add api --api-version=timers.estimer.com/v1alpha1 --kind=Action

operator-sdk add api --api-version=timers.estimer.com/v1alpha1 --kind=Execution

2.2 生成controller
operator-sdk add controller --api-version=timers.estimer.com/v1alpha1 --kind=Timer

2.3 修改各个的types.go
例如修改:
pkg/apis/timers/v1alpha1/timer_types.go
修改如下内容:
......
type TimerSpec struct {
    Name map[string]interface{}   `json:"name"`
    Labels []map[string]interface{} `json:"labels"`
}

// TimerStatus defines the observed state of Timer
// +k8s:openapi-gen=true
type TimerStatus struct {
    Nodes []string `json:"nodes"`
}
......

可根据实际内容修改。
注意:
Trigger map[string]interface{}   `json:"trigger"`
中的`json:"trigger"` 表示从json中的trigger中取出内容赋予Trigger字段。

//可以选择的控制字段有三种:
// -:不要解析这个字段
// omitempty:当字段为空(默认值)时,不要解析这个字段。比如 false、0、nil、长度为 0 的 array,map,slice,string
// FieldName:当解析 json 的时候,使用这个名字
type StudentWithOption struct {
    StudentId      string //默认使用原定义中的值
    StudentName    string `json:"sname"`           // 解析(encode/decode) 的时候,使用 `sname`,而不是 `Field`
    StudentClass   string `json:"class,omitempty"` // 解析的时候使用 `class`,如果struct 中这个值为空,就忽略它
    StudentTeacher string `json:"-"`               // 解析的时候忽略该字段。默认情况下会解析这个字段,因为它是大写字母开头的
}

参考:
https://www.cnblogs.com/fengxm/p/9917686.html

2.4 更新代码
执行:
operator-sdk generate k8s

2.5 编写controller逻辑
1) 修改协调逻辑
具体在:
pkg/controller/timer/timer_controller.go中的
func (r *ReconcileTimer) Reconcile(request reconcile.Request) (reconcile.Result, error) 
方法,实现自己的协调逻辑。

2)修改事件过滤
具体在:
pkg/controller/timer/timer_controller.go中的
func add(mgr manager.Manager, r reconcile.Reconciler)

2.6 调试operator
在一个安装了上述golang环境的pod中,开启一个窗口执行:
operator-sdk up local --namespace timed-task --enable-delve --verbose
在一个安装了上述golang环境的pod中,开启另一个窗口执行:
dlv connect 0.0.0.0:2345

加断点:
b main.main
b timer_controller.go:41
b timer_controller.go:86

注意:
请将41,86替换为自己真正想要打断点的代码行号。

注意:
执行operator-sdk up local --namespace timed-task --enable-delve --verbose
命令之前,确保2345和8333端口没有被占用。

2.7 制作镜像
operator-sdk build qingyuanluo/dozer-operator:v0.0.1
sed -i 's|REPLACE_IMAGE|qingyuanluo/dozer-operator:v0.0.1|g' deploy/operator.yaml
docker push qingyuanluo/dozer-operator:v0.0.1

3 原理
3.1 operator作用与组成
作用: 简化复杂有状态应用管理,其通过CRD扩展 Kubernetes API 来自动创建、管理和配置应用实例。
但群集仍需要控制器来监视其状态并协调资源以匹配声明。
通过扩展Kubernetes定义Custom Controller,观察应用并根据实际状态执行自定义任务
部署: Operator以deployment的形式部署到K8S中。
构建Operator:
为了创建自定义Operator,我们需要如下资源:
Custom Resource(CR)spec,定义我们要观测的应用对象,以及为CR定义的API
Custom Controller,用来观测CR
Custom code,决定Custom Controller如何协调CR
Operator,管理Custom Controller
Deployment,定义Operator和自定义资源

参考:
http://dockone.io/article/8769

3.2 operator框架
3.2.1) Operator Framework作用
用于快速开发 Operator 的工具包,该框架包含两个主要的部分:
Operator SDK: 根据自己需求来构建operator应用
Operator Lifecycle Manager OLM: 安装管理operator

3.2.2) Operator Framework开发工作流
1)使用 SDK 创建一个新的 Operator 项目
2)通过添加自定义资源(CRD)定义新的资源 API
3)指定使用 SDK API 来 watch 的资源
4)定义 Operator 的协调(reconcile)逻辑
5)使用 Operator SDK 构建并生成 Operator 部署清单文件

3.3 operator监控的资源
控制器检测的资源:
一级资源和二级资源。
对于replicaSet,一级资源是ReplicaSet本身,指定运行的
Docker镜像以及Pod的副本数。
二级资源是Pod,当ReplicaSet定义发生改变(例如镜像版本或Pod数量),
或者Pod发生改变(例如某个Pod被删除),控制器则会通过推出新版本来协调集群状态,
或者对Pod数量扩缩容。
对于DaemonSet,一级资源是DaemonSet本身,而二级资源是Pod和Node。不同之处在于,控制器也会监测集群Node变化,以便在群集增大或缩小时添加或删除Pod。
明确识别出Operator的主要和二级资源是至关重要的,因为他们将决定其控制器的行为。

3.4 operator的原理
Operator 实际上作为kubernetes自定义扩展资源注册到controller-manager,通过list and watch的方式监听对应资源的变化,然后在周期内的各个环节做相应的协调处理。所谓的处理就是operator实现由状态的应用的核心部分,当然不同应用其处理的方式也不同。


参考:
https://github.com/operator-framework/operator-sdk/blob/master/doc/user/install-operator-sdk.md
https://feisky.gitbooks.io/kubernetes/apps/operator.html
http://dockone.io/article/8769
https://github.com/operator-framework/operator-sdk
http://dockone.io/article/8733
https://www.jianshu.com/p/628aac3e6758
http://www.imooc.com/article/294186
https://www.cnblogs.com/liabio/p/11723809.html

4 sample-controller源码分析
1)代码主入口
sample-controller/main.go
代码如下:
func main() {
    klog.InitFlags(nil)
    flag.Parse()

    // set up signals so we handle the first shutdown signal gracefully
    stopCh := signals.SetupSignalHandler()

    cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
    if err != nil {
        klog.Fatalf("Error building kubeconfig: %s", err.Error())
    }

    kubeClient, err := kubernetes.NewForConfig(cfg)
    if err != nil {
        klog.Fatalf("Error building kubernetes clientset: %s", err.Error())
    }

    exampleClient, err := clientset.NewForConfig(cfg)
    if err != nil {
        klog.Fatalf("Error building example clientset: %s", err.Error())
    }

    kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Second*30)
    exampleInformerFactory := informers.NewSharedInformerFactory(exampleClient, time.Second*30)

    controller := NewController(kubeClient, exampleClient,
        kubeInformerFactory.Apps().V1().Deployments(),
        exampleInformerFactory.Samplecontroller().V1alpha1().Foos())

    // notice that there is no need to run Start methods in a separate goroutine. (i.e. go kubeInformerFactory.Start(stopCh)
    // Start method is non-blocking and runs all registered informers in a dedicated goroutine.
    kubeInformerFactory.Start(stopCh)
    exampleInformerFactory.Start(stopCh)

    if err = controller.Run(2, stopCh); err != nil {
        klog.Fatalf("Error running controller: %s", err.Error())
    }
}

分析:
大体流程就是:
s1)读取 kubeconfig 配置,构造用于事件监听的 Kubernetes Client
s2)这里创建了两个,一个监听普通事件,一个监听 Foo 事件
s3)基于 Client 构造监听相关的 informer
s4)基于 Client、Informer 初始化自定义 Controller,监听 Deployment 以及 Foos 资源变化
s5)开启 Controller

接下来分析Controller是如何处理事件的

2) 查看Controller的定义
代码在:
sample-controller/controller.go
代码如下:
// Controller is the controller implementation for Foo resources
type Controller struct {
    // kubeclientset is a standard kubernetes clientset
    kubeclientset kubernetes.Interface
    // sampleclientset is a clientset for our own API group
    sampleclientset clientset.Interface

    deploymentsLister appslisters.DeploymentLister
    deploymentsSynced cache.InformerSynced
    foosLister        listers.FooLister
    foosSynced        cache.InformerSynced

    // workqueue is a rate limited work queue. This is used to queue work to be
    // processed instead of performing it as soon as a change happens. This
    // means we can ensure we only process a fixed amount of resources at a
    // time, and makes it easy to ensure we are never processing the same item
    // simultaneously in two different workers.
    workqueue workqueue.RateLimitingInterface
    // recorder is an event recorder for recording Event resources to the
    // Kubernetes API.
    recorder record.EventRecorder
}

分析:
Controller 的关键成员即两个事件的 Listener(appslisters.DeploymentLister、listers.FooLister)
这两个成员将由 main 函数传入参数进行初始化
此外,为了缓冲事件处理,这里使用队列暂存事件,相关成员即为 workqueue.RateLimitingInterface
record.EventRecorder 用于记录事件

3) 分析Controller的构造过程
// NewController returns a new sample controller
func NewController(
    kubeclientset kubernetes.Interface,
    sampleclientset clientset.Interface,
    deploymentInformer appsinformers.DeploymentInformer,
    fooInformer informers.FooInformer) *Controller {

    // Create event broadcaster
    // Add sample-controller types to the default Kubernetes Scheme so Events can be
    // logged for sample-controller types.
    utilruntime.Must(samplescheme.AddToScheme(scheme.Scheme))
    klog.V(4).Info("Creating event broadcaster")
    eventBroadcaster := record.NewBroadcaster()
    eventBroadcaster.StartLogging(klog.Infof)
    eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})
    recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})

    controller := &Controller{
        kubeclientset:     kubeclientset,
        sampleclientset:   sampleclientset,
        deploymentsLister: deploymentInformer.Lister(),
        deploymentsSynced: deploymentInformer.Informer().HasSynced,
        foosLister:        fooInformer.Lister(),
        foosSynced:        fooInformer.Informer().HasSynced,
        workqueue:         workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Foos"),
        recorder:          recorder,
    }

    klog.Info("Setting up event handlers")
    // Set up an event handler for when Foo resources change
    fooInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc: controller.enqueueFoo,
        UpdateFunc: func(old, new interface{}) {
            controller.enqueueFoo(new)
     

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值