kubernetes CRD学习笔记
前言
最近在极客时间订阅了kubernetes的专栏,这篇文章是想记录一下自己学习CRD(custom resource definition)的过程,加深一下记忆。
准备工作
首先安装一下我们用的go依赖:
cd $GOPATH/src/
mkdir resouer
git clone https://github.com/resouer/k8s-controller-custom-resource.git
cd k8s-controller-custom-resource
godep restore
然后参照github地址,我自己也照着抄了一个,这是我的那个git地址,没有注释,加深一下印象,以下代码还有目录都是我自己创建的,创建目录和文件如下(当前目录):
hongzhi:k8s-crd hongzhi.wang$ tree
.
├── controller.go
├── crd
│ └── network.yaml
├── example
│ └── example-network.yaml
├── main.go
└── pkg
└── apis
└── samplecrd
├── register.go
└── v1
├── doc.go
├── register.go
└── types.go
并且参考原文填写这些文件,接着安装
go get -u k8s.io/code-generator
cd $GOPATH/src/k8s.io/code-generator
godep restore
然后生成代码
ROOT_PACKAGE="k8s-crd"
CUSTOM_RESOURCE_NAME="samplecrd"
CUSTOM_RESOURCE_VERSION="v1"
cd $GOPATH/src/k8s.io/code-generator
./generate-groups.sh all "$ROOT_PACKAGE/pkg/client" "$ROOT_PACKAGE/pkg/apis" "$CUSTOM_RESOURCE_NAME:$CUSTOM_RESOURCE_VERSION"
脚本运行结果:
hongzhi:code-generator hongzhi.wang$ ./generate-groups.sh all "$ROOT_PACKAGE/pkg/client" "$ROOT_PACKAGE/pkg/apis" "$CUSTOM_RESOURCE_NAME:$CUSTOM_RESOURCE_VERSION"
Generating deepcopy funcs
Generating clientset for samplecrd:v1 at k8s-crd/pkg/client/clientset
Generating listers for samplecrd:v1 at k8s-crd/pkg/client/listers
Generating informers for samplecrd:v1 at k8s-crd/pkg/client/informers
之后代码如下:(自己的types.go:37行的代码注释写错了,然后在生成代码之后,goland 只有 networklist 提示networklist 不是 runtime.Object 类型,而 network 在生成代码之前也提示了,生成之后错误提示就消失了。我仔细看了一下 runtime.Object 是一个 interface 类型,在 k8s.io/apimachinery/pkg/runtime 的目录下的 interface.go 文件中,goland 报错应该是没生成代码之前我们自定义的 type 没有完全实现 runtime.Object 这个接口。)
hongzhi:k8s-crd hongzhi.wang$ tree
.
├── controller.go
├── crd
│ └── network.yaml
├── example
│ └── example-network.yaml
├── main.go
└── pkg
├── apis
│ └── samplecrd
│ ├── register.go
│ └── v1
│ ├── doc.go
│ ├── register.go
│ ├── types.go
│ └── zz_generated.deepcopy.go
├── client
│ ├── clientset
│ │ └── versioned
│ │ ├── clientset.go
│ │ ├── doc.go
│ │ ├── fake
│ │ │ ├── clientset_generated.go
│ │ │ ├── doc.go
│ │ │ └── register.go
│ │ ├── scheme
│ │ │ ├── doc.go
│ │ │ └── register.go
│ │ └── typed
│ │ └── samplecrd
│ │ └── v1
│ │ ├── doc.go
│ │ ├── fake
│ │ │ ├── doc.go
│ │ │ ├── fake_network.go
│ │ │ └── fake_samplecrd_client.go
│ │ ├── generated_expansion.go
│ │ ├── network.go
│ │ └── samplecrd_client.go
│ ├── informers
│ │ └── externalversions
│ │ ├── factory.go
│ │ ├── generic.go
│ │ ├── internalinterfaces
│ │ │ └── factory_interfaces.go
│ │ └── samplecrd
│ │ ├── interface.go
│ │ └── v1
│ │ ├── interface.go
│ │ └── network.go
│ └── listers
│ └── samplecrd
│ └── v1
│ ├── expansion_generated.go
│ └── network.go
└── signals
├── signal.go
├── signal_posix.go
└── signal_windows.go
23 directories, 31 files
signals 目录和里面的内容需要自己创建。接下来创建crd和crd对象
cd $GOPATH/src/k8s-crd
hongzhi:k8s-crd hongzhi.wang$ kubectl apply -f crd/network.yaml
customresourcedefinition.apiextensions.k8s.io/networks.samplecrd.k8s.io created
hongzhi:k8s-crd hongzhi.wang$ kubectl apply -f example/example-network.yaml
network.samplecrd.k8s.io/example-network created
hongzhi:k8s-crd hongzhi.wang$ kubectl get crd
NAME CREATED AT
networks.samplecrd.k8s.io 2018-10-22T07:17:56Z
hongzhi:k8s-crd hongzhi.wang$ kubectl get network
NAME AGE
example-network 18s
整个 cunstom controller 的流程图(张磊在AS深圳2018分享ppt中的图片):

接下来分析一下 main.go 和 controller.go :
main.go 中首先需要 kube apiserver 的配置,连上我们k8s集群,然后就是主要的几行代码
networkInformerFactory := informers.NewSharedInformerFactory(networkClient, time.Second*30)
controller := NewController( kubeClient, networkClient,
networkInformerFactory.Smaplecrd().V1().Networks())
go networkInformerFactory.Start(stopCh)
if err = controller.Run(2, stopCh); err != nil{
glog.Fatalf("Error running controller: %s", err.Error())
}
informers.NewSharedInformerFactory 这个函数返回的是SharedInformerFactory这个interface,
然后controller := NewController( kubeClient, networkClient,networkInformerFactory.Smaplecrd().V1().Networks()),这里面主要是把针对这个 crd的增,删,改注册到 eventhandler 中。
这里需要说下,这个增删改是针对 reflector 从 apiserver 接到事件的时候发出的,time.Second*30这个时间是 local cache 和 apiserver 的定时同步时间,同步完成会触发 updateFunc ,这个 update 操作是对所有的network对象的,这时需要对比一下r esource version 如果一样就不需要进入 workqueue。这个增删改正常只是 apiserver 接到请求后传给 reflector 的,比如我们通过 kubectl 删了一个 network 对象,这个删除的操作会通过 apiserver 然后触发 DeleteFunc。
然后调用 SharedInformerFactory 这个 interface 的 Start 方法,其实这个只是启动 reflector 监听 apiserver 中n etwork 对象的变化。
然后调用 func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) 方法启动 controller 。
for i:=0 ; i < threadiness; i++ {
go wait.Until(c.runWorker, time.Second, stopCh)
}
这是要启动 threadiness 个 go 的协程,来进行 reconcile loop。其中期望状态是从 cache 中获取的,实际状态是从 k8s 集群是实时获取的。整个流程是 apiserver 接到请求(增删改),然后发给 reflector ,reflector 调用对应的方法,把这个 key(这个 key 就是 namespace/name 的字符串)加到 workqueue 中,然后根据对应的请求操作 cache ,reconcile loop 从 workqueue 拿到这个 key ,然后从 cache 中取这个 key 如果报 notfound error,reconcile loop 接着就会从实际的集群中删除这个 network 对象。如果 cache 中有这个 key ,那么就看实际状态有没有,没有就创建,有就对比,不一样就更新。
参考代码
main.go
package main
import (
clientset "k8s-crd/pkg/client/clientset/versioned"
informers "k8s-crd/pkg/client/informers/externalversions"
"flag"
"k8s-crd/pkg/signals"
"k8s.io/client-go/tools/clientcmd"
"github.com/golang/glog"
"k8s.io/client-go/kubernetes"
"time"
)
var (
masterURL string
kubeconfig string
)
func main() {
flag.Parse()
stopCh := signals.SetupSignalHandler()
cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
if err != nil {
glog.Fatalf("Error building kubecofnig: %s", err.Error())
}
kubeClient, err := kubernetes.NewForConfig(cfg)
if err != nil {
glog.Fatalf("Error building kubernetes clientset: %s", err.Error())
}
networkClient, err := clientset.NewForConfig(cfg)
if err != nil {
glog.Fatalf("Error building example clientset: %s", err.Error())
}
networkInformerFactory := informers.NewSharedInformerFactory(networkClient, time.Second*30)
controller := NewController( kubeClient, networkClient,
networkInformerFactory.Smaplecrd().V1().Networks())
go networkInformerFactory.Start(stopCh)
if err = controller.Run(2, stopCh); err != nil{
glog.Fatalf("Error running controller: %s", err.Error())
}
}
func init() {
flag.StringVar(&kubeconfig, "kubeconfig","","Path to kubeconfig")
flag.StringVar(&masterURL, "master", "","The address of the kubernetes api server")
}
controller.go
package main
import (
corev1 "k8s.io/api/core/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
samplecrdv1 "k8s-crd/pkg/apis/samplecrd/v1"
clientset "k8s-crd/pkg/client/clientset/versioned"
networkscheme "k8s-crd/pkg/client/clientset/versioned/scheme"
informers "k8s-crd/pkg/client/informers/externalversions/samplecrd/v1"
listers "k8s-crd/pkg/client/listers/samplecrd/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/kubernetes/scheme"
"github.com/golang/glog"
"github.com/contrib/service-loadbalancer/Godeps/_workspace/src/k8s.io/kubernetes/pkg/util/runtime"
"fmt"
"k8s.io/apimachinery/pkg/util/wait"
"time"
"k8s.io/apimachinery/pkg/api/errors"
)
const controllerAgentName = "network-controller"
const (
SuccessSynced = "Synced"
MessageResourceSynced = "Network synced successfully"
)
type Controller struct {
kubeclientset kubernetes.Interface
networkclientset clientset.Interface
networksLister listers.NetworkLister
networksSynced cache.InformerSynced
workqueue workqueue.RateLimitingInterface
recorder record.EventRecorder
}
func NewController(
kubeclientset kubernetes.Interface,
networkclientset clientset.Interface,
networkInformer informers.NetworkInformer) *Controller {
utilruntime.Must(networkscheme.AddToScheme(scheme.Scheme))
glog.V(4).Info("Creating event broadcaster")
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface:kubeclientset.CoreV1().Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
controller := &Controller{
kubeclientset: kubeclientset,
networkclientset: networkclientset,
networksLister: networkInformer.Lister(),
networksSynced: networkInformer.Informer().HasSynced,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(),"Networks"),
recorder: recorder,
}
glog.Info("Setting up event handlers")
networkInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.enqueueNetwork,
UpdateFunc: func(old, new interface{}) {
oldNetwork := old.(*samplecrdv1.Network)
newNetwork := new.(*samplecrdv1.Network)
if oldNetwork.ResourceVersion == newNetwork.ResourceVersion {
return
}
controller.enqueueNetwork(new)
},
DeleteFunc: controller.enqueueNetworkForDelete,
})
return controller
}
func (c *Controller) Run(threadiness int, stopCh <- chan struct{}) error {
defer runtime.HandleCrash()
defer c.workqueue.ShutDown()
glog.Info("Starting Network control loop")
glog.Info("Waiting for informer caches to sync")
if ok := cache.WaitForCacheSync(stopCh, c.networksSynced); !ok {
return fmt.Errorf("failed to wait for caches to sync")
}
glog.Info("Starting workers")
for i:=0 ; i < threadiness; i++ {
go wait.Until(c.runWorker, time.Second, stopCh)
}
glog.Info("Started workers")
<-stopCh
glog.Info("shutting down workers")
return nil
}
func (c *Controller) runWorker() {
for c.processNextWorkItem(){
}
}
func (c *Controller) processNextWorkItem() bool {
obj, shutdown := c.workqueue.Get()
if shutdown {
return false
}
err := func(obj interface{}) error {
defer c.workqueue.Done(obj)
var key string
var ok bool
if key,ok = obj.(string); !ok {
c.workqueue.Forget(obj)
runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
return nil
}
if err := c.syncHandler(key); err != nil {
return fmt.Errorf("error syncing '%s' : %s", key, err.Error())
}
c.workqueue.Forget(obj)
glog.Infof("Successfully synced '%s'", key)
return nil
}(obj)
if err != nil {
runtime.HandleError(err)
return true
}
return true
}
func (c *Controller) syncHandler(key string) error {
namespace, name , err := cache.SplitMetaNamespaceKey(key)
if err != nil {
runtime.HandleError(fmt.Errorf("invalid resource key: %s", key))
return nil
}
network, err := c.networksLister.Networks(namespace).Get(name)
if err != nil {
if errors.IsNotFound(err){
glog.Warningf("Network: %s/%s does not exist in local cache, will delete it from Neutron ...",
namespace, name)
glog.Infof("[Neutron] Deleting network: %s/%s ...", namespace, name)
return nil
}
runtime.HandleError(fmt.Errorf("failed to list network by : %s/%s", namespace, name))
return nil
}
glog.Infof("[Neutron] Try to process network: %#v ...", network)
c.recorder.Event(network, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
return nil
}
func (c *Controller) enqueueNetwork(obj interface{}) {
var key string
var err error
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil{
runtime.HandleError(err)
return
}
c.workqueue.AddRateLimited(key)
}
func (c *Controller) enqueueNetworkForDelete(obj interface{}) {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err != nil {
runtime.HandleError(err)
return
}
c.workqueue.AddRateLimited(key)
}
kubernetes CRD学习笔记的更多相关文章
- 【转】Kubernetes scheduler学习笔记
简介 Kubernetes是一个强大的编排工具,可以用来很方便的管理许多台机器,为了使机器的资源利用率提高,同时也尽可能的把压力分摊到各个机器上,这个职责就是由scheduler来完成的. Kuber ...
- Contour 学习笔记(一):使用 Contour 接管 Kubernetes 的南北流量
原文链接:Contour 学习笔记(一):使用 Contour 接管 Kubernetes 的南北流量 在 Kubernetes 中运行大规模以 Web 为中心的工作负载,最关键的需求之一就是在 L7 ...
- Kubernetes 学习笔记(一):基础概念
个人笔记,仅本人查阅使用,不保证正确. 零.微服务 微服务架构专注于应用解耦合,通过将应用彻底地组件化和服务化,每个微服务只包含一个非常小的功能,比如权限管理.日志收集等等.由这一组微服务组合起来,提 ...
- Kubernetes学习笔记(四):服务
服务介绍 服务是一种为一组相同功能的pod提供单一不变接入点的资源.当服务存在时,他的IP和端口不会改变.客户端通过IP和端口建立连接,这些连接会被路由到任何一个pod上.如此,客户端不需要知道每个单 ...
- Kubernetes学习笔记(八):Deployment--声明式的升级应用
概述 本文核心问题是:如何升级应用. 对于Pod的更新有两种策略: 一是删除全部旧Pod之后再创建新Pod.好处是,同一时间只会有一个版本的应用存在:缺点是,应用有一段时间不可用. 二是先创建新Pod ...
- Kubernetes学习笔记之认识Kubernetes组件
前言:笔记知识点来源于Kubernetes官方文档说明,链接:https://kubernetes.io/docs/concepts/overview/components/ ,本记录仅仅是学习笔记记 ...
- Beego学习笔记——Logs
日志处理 这是一个用来处理日志的库,它的设计思路来自于database/sql,目前支持的引擎有file.console.net.smtp,可以通过如下方式进行安装: go get github.co ...
- TCP/IP协议学习之实例ping命令学习笔记
TCP/IP协议学习之实例ping命令学习笔记(一) 一. 目的为了让网络协议学习更有效果,在真实网络上进行ping命令前相关知识的学习,暂时不管DNS,在内网中,进行2台主机间的ping命令的整个详 ...
- Flink学习笔记-新一代Flink计算引擎
说明:本文为<Flink大数据项目实战>学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程: Flink大数据项目实战:http://t.cn/EJtKh ...
随机推荐
- 【GitHub】的基本使用
GitHub是一个常用的版本管理工具,之前安装了window版的git但是一直没使用过,今天尝试一下,去百度了一下使用方法: 引用https://www.cnblogs.com/paulwhw/p/9 ...
- windows10安装JIRA
windows10安装MySQL数据库 一.问题现象: cmd执行“mysql”命令,提示:ERROR 2003 (HY000): Can't connect to MySQL server on ' ...
- Jquery+H5验证数据(不是表单验证啊 )
啥也不说了 直接上代码 1.我将所有需要验证的控件都加上了 required(类名自己定吧没啥讲究) class 2.所有的控件都加上了 data-vname的H5自定义属性(名称自个定义吧) ...
- bind和on的区别
bind方法与on方法都是事件绑定,但是两者却又有着一个大区别:事件委托 jquery文档中bind和on函数绑定事件的用法: .bind(events [,eventData], handler) ...
- java基础 ----- 程序的调试
--- -- 什么是程序调试 当程序出错时,我们希望可以这样 逐条语句执行程序 ----- 观察程序的执行情况 ------ 发现问题 ----- 解决问题 但是,程序一闪就运行结束 ...
- Mybatis之批量操作
首先批量操作的优点是:大大的提高查询的效率. 举个简单的例子:如果在程序中遍历来执行sql的话,这种情况就是有多少行数据就要执行多少条sql,这样导致的效率将是非常低. 如下可能需要40s inser ...
- Linux 内存占用大排查
用命令 top 查看发现内存使用很高,可用内存很少,导致有些服务无法正常启动. 这时,可以用下面的命令查看占用内存前10的进程,改变 10 的数字,可以调整前几的个数. ps -aux | sort ...
- laravel安装laravel-ide-helper扩展进行代码提示(二)
一.扩展的地址 https://github.com/barryvdh/laravel-ide-helper 二.安装扩展 1.引入库: composer require barryvdh/larav ...
- JavaSE基础知识(5)—面向对象(5.7 final关键字)
一.说明 final属于一种修饰符,可以用于修饰类和属性.方法.局部变量 二.特点 1.修饰类 该类不能被继承,如String.Integer等 2.修饰方法 该方法不能被重写 3.修饰变量(属性和局 ...
- 7I - 数塔
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的: 有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少? 已经告诉你了,这是个DP的题目 ...