0. 项目源码 (下载参考)
1.CodeGenerator
1.1 安装及生产
-
常用的几个生成功能 代码生成务必在 Linux 上执行
- deepcopy-gen CustomResources 实现runtime.Object 接口对应的 DeepCopy 方法
- client-gen clientsets 相关代码
- informer-gen 创建 informers( watch 对应 CR变化产生的事件)
- lister-gen 对 GET/List 请求提供只读的缓存层
-
需明确以下操作
go
命令可直接使用,版本>= 1.15export GOPATH=/opt/gopath
GOPATH的设置export GOPROXY=https://goproxy.cn
GOPROXY的设置
-
项目代码结构
# tree /opt/operator-db/ /opt/operator-db/ ├── go.mod ├── go.sum ├── main.go └── pkg └── apis └── dbconfig //最终要生成的类型目录 └── v1 ├── doc.go ├── register.go ├── types.go # cat go.mod module github.com/shenyisyn/dbcore go 1.16 require ( k8s.io/apimachinery v0.20.2 )
-
在$GOPATH 中下载 code-generator
# mkdir -p $GOPATH/src # cd $GOPATH/src # go get -u k8s.io/code-generator@0.20.15 # mv code-generator@0.20.15 code-generator # cd code-generator # chmod a+x generate-groups.sh # ls ./code-generator/ -l 总用量 92 dr-xr-xr-x 14 root root 4096 1月 22 15:54 cmd -r--r--r-- 1 root root 148 1月 22 15:54 code-of-conduct.md -r--r--r-- 1 root root 750 1月 22 15:54 CONTRIBUTING.md dr-xr-xr-x 2 root root 4096 1月 22 15:54 examples dr-xr-xr-x 6 root root 4096 1月 22 15:54 _examples -r-xr-xr-x 1 root root 3979 1月 22 15:54 generate-groups.sh -r-xr-xr-x 1 root root 5834 1月 22 15:54 generate-internal-groups.sh -r--r--r-- 1 root root 651 1月 22 15:54 go.mod -r--r--r-- 1 root root 13536 1月 22 15:54 go.sum dr-xr-xr-x 2 root root 4096 1月 22 15:54 hack -r--r--r-- 1 root root 11358 1月 22 15:54 LICENSE -r--r--r-- 1 root root 184 1月 22 15:54 OWNERS dr-xr-xr-x 4 root root 4096 1月 22 15:54 pkg -r--r--r-- 1 root root 1440 1月 22 15:54 README.md -r--r--r-- 1 root root 550 1月 22 15:54 SECURITY_CONTACTS dr-xr-xr-x 3 root root 4096 1月 22 15:54 third_party -r--r--r-- 1 root root 1247 1月 22 15:54 tools.go // 保障起见下载好需要用的库 # go mod download
-
在 $GOPATH 中创建同样的项目文件
# cd $GOPATH/src # mkdir -p $GOPATH/src/github.com/shenyisyn/dbcore // copy 项目到GOPATH下 # cp -av /opt/operator-db/* $GOPATH/src/github.com/shenyisyn/dbcore/ # cd $GOPATH/src/github.com/shenyisyn/dbcore/ // 执行生成,正常下是慢慢生成的 # $GOPATH/src/k8s.io/code-generator/generate-groups.sh all github.com/shenyisyn/dbcore/pkg/client github.com/shenyisyn/dbcore/pkg/apis dbconfig:v1 Generating deepcopy funcs Generating clientset for dbconfig:v1 at github.com/shenyisyn/dbcore/pkg/client/clientset Generating listers for dbconfig:v1 at github.com/shenyisyn/dbcore/pkg/client/listers Generating informers for dbconfig:v1 at github.com/shenyisyn/dbcore/pkg/client/informers // 生成成功后便会出现 zz_generated.deepcopy.go # ls $GOPATH/src/github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1/ doc.go register.go types.go zz_generated.deepcopy.go // 生成成功 # ls $GOPATH/src/github.com/shenyisyn/dbcore/pkg/client/ clientset informers listers
2. CRD
-
vim /opt/operator-db/crd.yaml
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: # 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>' name: dbconfigs.api.jtthink.com spec: # 分组名,在REST API中也会用到的,格式是: /apis/分组名/CRD版本 group: api.jtthink.com # 范围是属于namespace的 ,可以是 Namespaced 或 Cluster scope: Namespaced # 列举此 CustomResourceDefinition 所支持的版本 versions: - name: v1 # 是否有效 served: true storage: true # 额外打印或暴露字段 additionalPrinterColumns: # - jsonPath: .status.readyReplicas # name: Ready # type: string - jsonPath: .status.readyReplicas name: Ready type: string - name: Age type: date jsonPath: .metadata.creationTimestamp schema: openAPIV3Schema: type: object properties: # 状态字段字段 status: type: object properties: readyReplicas: type: string replicas: type: integer spec: type: object properties: replicas: type: integer # 字段合法性检测 # https://kubernetes.io/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation # https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#schemaObject minimum: 1 maximum: 10 dsn: type: string # 字段合法性检测 required: - replicas - dsn # https://kubernetes.io/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#subresources subresources: # 当启用了 status 子资源时,对应定制资源的 /status 子资源会被暴露出来。 status: {} # 当启用了 scale 子资源时,定制资源的 /scale 子资源就被暴露出来。 针对 /scale 所发送的对象是 autoscaling/v1.Scale。 # 可以使用 kubectl scale 控制资源的副本数量 # kubectl scale --replicas=3 dc/my-dbconfig scale: # specReplicasPath 定义定制资源中对应 scale.spec.replicas 的 JSON 路径 specReplicasPath: .spec.replicas # statusReplicasPath 定义定制资源中对应 scale.status.replicas 的 JSON 路径 statusReplicasPath: .status.replicas names: # 复数名 plural: dbconfigs singular: dbconfig kind: DbConfig listKind: DbConfigList shortNames: - dc
# kubectl create -f crd/crd.yaml # kubectl get crd |grep dbconfig
-
vim /opt/operator-db/test_crd.yaml
kind: DbConfig apiVersion: api.jtthink.com/v1 metadata: name: dbcore-my-dbconfig spec: replicas: 1 dsn: mysql dsn
3. 开发Controller
3.1 配置连接
- /opt/opearotr-db/k8sConfig/initConfig.go
package k8sConfig import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "log" "os" ) const ( NSFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" ) //POD里 体内 func K8sRestConfigInPod() *rest.Config { config, err := rest.InClusterConfig() if err != nil { log.Fatal(err) } return config } func K8sRestConfig() *rest.Config { if os.Getenv("release") == "1" { //自定义环境 log.Println("run in cluster") return K8sRestConfigInPod() } log.Println("run outside cluster") config, err := clientcmd.BuildConfigFromFlags("", "/Users/liangyedong/.kube/config") if err != nil { log.Fatal(err) } config.Insecure = false return config }
3.2 Builders (核心代码)
-
/opt/opearotr-db/pkg/builders/deploy_tpl.go
package builders const deployTpl = ` apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Name }} namespace: {{ .Namespace}} spec: selector: matchLabels: app: {{ .Namespace}}-{{ .Name }} replicas: 1 template: metadata: labels: app: {{ .Namespace}}-{{ .Name }} version: v1 spec: containers: - name: {{ .Namespace}}-{{ .Name }}-container image: docker.io/shenyisyn/dbcore:v1 imagePullPolicy: IfNotPresent ports: - containerPort: 8081 - containerPort: 8090 `
-
/opt/opearotr-db/pkg/builders/deploy_builder.go
package builders import ( "bytes" "context" "fmt" configv1 "github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1" v1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" "text/template" ) type DeployBuilder struct { client.Client config *configv1.DbConfig deploy *v1.Deployment } //目前软件的 命名规则 func deployName(name string ) string { return name + "-deployment" } func NewDeployBuilder(config *configv1.DbConfig, c client.Client) (*DeployBuilder, error) { deploy := &v1.Deployment{} err := c.Get(context.Background(), types.NamespacedName{ Name: deployName(config.Name), Namespace: config.Namespace, }, deploy) if err != nil { deploy.Name, deploy.Namespace = deployName(config.Name), config.Namespace tpl, err := template.New("deploy").Parse(deployTpl) if err != nil { return nil, err } var doc bytes.Buffer err = tpl.Execute(&doc, deploy) if err != nil { return nil, err } err = yaml.Unmarshal(doc.Bytes(), deploy) if err != nil { return nil, err } } return &DeployBuilder{deploy: deploy, Client: c, config: config}, nil } func (this *DeployBuilder) apply() *DeployBuilder { *this.deploy.Spec.Replicas = int32(this.config.Spec.Replicas) return this } // 设置属主,用作资源关联,删除时可连带删除 func (this *DeployBuilder) setOwner() *DeployBuilder { this.deploy.OwnerReferences = append(this.deploy.OwnerReferences, metav1.OwnerReference{ APIVersion: this.config.APIVersion, Kind: this.config.Kind, Name: this.config.Name, UID: this.config.UID, }, ) return this } func (this *DeployBuilder) Build(ctx context.Context) error { // 如果没有creationTime 是nil,则创建deploy if this.deploy.CreationTimestamp.IsZero() { this.apply().setOwner() if err := this.Create(ctx, this.deploy); err != nil { return err } } else { // 合并又有对象 patch := client.MergeFrom(this.deploy.DeepCopy()) fmt.Printf("=== Patch: %+v \n", patch) this.apply() if err := this.Patch(ctx, this.deploy, patch); err != nil { return err } // 查看状态 // 获取ready replicas readyReplicas := this.deploy.Status.ReadyReplicas this.config.Status.ReadyReplicas = fmt.Sprintf("%d/%d",readyReplicas, this.config.Spec.Replicas) this.config.Status.Replicas = *this.deploy.Spec.Replicas if err := this.Status().Update(ctx, this.config); err != nil { return err } } return nil }
3.3 controllers
-
/opt/opearotr-db/pkg/controllers/DbConfigController.go
package controllers import ( "context" "fmt" v1 "github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1" "github.com/shenyisyn/dbcore/pkg/builders" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const ( ResourceKind = "DbConfig" ResourceApiGroupVersion = "api.jtthink.com/v1" ) type DbConfigController struct { client.Client } func NewDbConfigController() *DbConfigController { return &DbConfigController{} } // 捕获更新事件 func (r *DbConfigController) OnUpdate(updateEvent event.UpdateEvent, wq workqueue.RateLimitingInterface) { for _, ref := range updateEvent.ObjectNew.GetOwnerReferences() { if ref.Kind == ResourceKind && ref.APIVersion == ResourceApiGroupVersion { wq.Add(reconcile.Request{ types.NamespacedName{ Namespace: updateEvent.ObjectNew.GetNamespace(), Name: ref.Name, }, }) } } } // 捕获删除事件 func (r *DbConfigController) OnDelete(event event.DeleteEvent, wq workqueue.RateLimitingInterface) { for _, ref := range event.Object.GetOwnerReferences() { if ref.Kind == ResourceKind && ref.APIVersion == ResourceApiGroupVersion { wq.Add(reconcile.Request{ NamespacedName: types.NamespacedName{ Namespace: event.Object.GetNamespace(), Name: event.Object.GetName(), }, }) } } } func (r *DbConfigController) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { config := &v1.DbConfig{} err := r.Get(ctx, req.NamespacedName, config) if err != nil { return reconcile.Result{}, err } bd, err := builders.NewDeployBuilder(config, r.Client) if err != nil { return reconcile.Result{}, err } if err = bd.Build(ctx); err != nil { return reconcile.Result{}, err } fmt.Println(config) return reconcile.Result{}, err } func (r *DbConfigController) InjectClient(c client.Client) error { r.Client = c return nil }
3.4 Init Controller Manger
- /opt/opearotr-db/k8sConfig/initRuntime.go
package k8sConfig
import (
"fmt"
v1 "github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1"
"github.com/shenyisyn/dbcore/pkg/controllers"
appv1 "k8s.io/api/apps/v1"
"log"
"os"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
"sigs.k8s.io/controller-runtime/pkg/source"
)
func InitManager() {
logf.SetLogger(zap.New())
mgr, err := manager.New(K8sRestConfig(), manager.Options{
Logger: logf.Log.WithName("dbcore"),
})
if err != nil {
log.Fatal(fmt.Sprintf("unable to setup manager, err: %s", err.Error()))
}
if err = v1.SchemeBuilder.AddToScheme(mgr.GetScheme()); err != nil {
mgr.GetLogger().Error(err, "unable add scheme")
}
dbconfigController:=controllers.NewDbConfigController()
if err = builder.ControllerManagedBy(mgr).
For(&v1.DbConfig{}).
Watches(&source.Kind{Type: &appv1.Deployment{}},
handler.Funcs{
UpdateFunc: dbconfigController.OnUpdate,
DeleteFunc: dbconfigController.OnDelete,
},
).
Complete(controllers.NewDbConfigController()); err != nil {
mgr.GetLogger().Error(err, "unable to create manager.")
os.Exit(1)
}
if err = mgr.Start(signals.SetupSignalHandler()); err != nil {
mgr.GetLogger().Error(err, "unable to start manager")
os.Exit(1)
}
}
3.5 启动Controller
- /opt/opearotr-db/main.go
package main
import "github.com/shenyisyn/dbcore/k8sConfig"
func main() {
//k8sCfg := k8sConfig.K8sRestConfig()
//client, err := clientv1.NewForConfig(k8sCfg)
//
//if err != nil {
// log.Fatal(err)
//}
//
//dcList, _ := client.DbConfigs("default").List(context.Background(), metav1.ListOptions{})
//fmt.Println(dcList)
// 初始化并启动manager
k8sConfig.InitManager()
}
3.6 当前阶段功能演示
// 创建dbconfig
# kubectl apply -f crd/test-crd.yaml
// 获取 dbconfig 资源,可响应test-crd.yaml 中的replicas
# kubectl get dc
NAME READY AGE
dbcore-my-dbconfig 2/2 51s
// 查看启动的deploy 对应的pod, 可联级删除资源,deploy资源删除后可重建
kubectl get deploy
4. ConfigMap 的使用
4.1 ConfigMap创建与映射
-
vim /opt/operator-db/pkg/builders/configmap_builder.go
package builders import ( "bytes" "context" configv1 "github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "log" "sigs.k8s.io/controller-runtime/pkg/client" "text/template" //metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ConfigMapBuilder struct { cm *corev1.ConfigMap config *configv1.DbConfig client.Client DataKey string } func NewConfigMapBuilder(config *configv1.DbConfig, client client.Client) (*ConfigMapBuilder, error) { cm := &corev1.ConfigMap{} err := client.Get(context.Background(), types.NamespacedName{ Namespace: config.Name, Name: deployName(config.Name), },cm) // 没取到则拼接一个空的data if err != nil { cm.Name, cm.Namespace = deployName(config.Name), config.Namespace cm.Data = make(map[string]string) } return &ConfigMapBuilder{cm: cm, config: config, Client: client}, nil } // 设置连带关系 func (this *ConfigMapBuilder) setOwner() *ConfigMapBuilder { this.cm.OwnerReferences = append(this.cm.OwnerReferences, metav1.OwnerReference{ APIVersion: this.config.APIVersion, Kind: this.config.Kind, Name: this.config.Name, UID: this.config.UID, }) return this } const configMapKey = "app.yml" //把configmap里面的 key=app.yml的内容 取出变成md5, func (this *ConfigMapBuilder) parseKey() *ConfigMapBuilder { if appData, ok := this.cm.Data[configMapKey]; ok { this.DataKey = Md5(appData) return this } this.DataKey = "" return this } func (this *ConfigMapBuilder) apply() *ConfigMapBuilder { // 解析模版并赋值 tpl, err := template.New(configMapKey).Delims("[[", "]]").Parse(cmtpl) if err != nil { log.Println(err) return this } var tplBuffer bytes.Buffer if err = tpl.Execute(&tplBuffer, this.config.Spec); err != nil { log.Println(err) return this } this.cm.Data[configMapKey] = tplBuffer.String() return this } func (this *ConfigMapBuilder) Build(ctx context.Context) error { if this.cm.CreationTimestamp.IsZero() { this.apply().setOwner().parseKey() if err := this.Create(ctx, this.cm); err != nil { return err } } else { patch := client.MergeFrom(this.cm.DeepCopy()) this.apply().parseKey() if err := this.Patch(ctx, this.cm, patch); err != nil { return err } } return nil }
-
vim /opt/dbcore-db/pkg/builders/deploy_tpl.go
package builders const cmtpl=` dbConfig: dsn: "[[ .Dsn ]]" maxOpenConn: [[ .MaxOpenConn ]] maxLifeTime: [[ .MaxLifeTime ]] maxIdleConn: [[ .MaxIdleConn ]] appConfig: rpcPort: 8081 httpPort: 8090 apis: - name: test sql: "select * from test" ` // md5 设置在template 的 annotation中,所以当annotation发生变化时,所有的pod 信息都会进行更新为你声明的内容 ,从而更新了/app/app.yml const deployTpl = ` apiVersion: apps/v1 kind: Deployment metadata: name: dbcore-{{ .Name }} namespace: {{ .Namespace}} spec: selector: matchLabels: app: dbcore-{{ .Namespace}}-{{ .Name }} replicas: 1 template: metadata: annotations: dbcore.config/md5: '' labels: app: dbcore-{{ .Namespace}}-{{ .Name }} version: v1 spec: containers: - name: dbcore-{{ .Namespace}}-{{ .Name }}-container image: docker.io/shenyisyn/dbcore:v1 imagePullPolicy: IfNotPresent volumeMounts: - name: configdata mountPath: /app/app.yml subPath: app.yml ports: - containerPort: 8081 - containerPort: 8090 volumes: - name: configdata configMap: defaultMode: 0644 name: dbcore-{{ .Name }} `
-
vim /opt/dbcore-db/pkg/buidlers/deploy_builder.go, 把configmap buider 组合进来进行调用;
package builders import ( "bytes" "context" "fmt" configv1 "github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1" v1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/yaml" "log" "sigs.k8s.io/controller-runtime/pkg/client" "text/template" ) type DeployBuilder struct { client.Client config *configv1.DbConfig cmBuilder *ConfigMapBuilder deploy *v1.Deployment } //目前软件的 命名规则 func deployName(name string) string { return "dbcore-" + name } func NewDeployBuilder(config *configv1.DbConfig, c client.Client) (*DeployBuilder, error) { deploy := &v1.Deployment{} err := c.Get(context.Background(), types.NamespacedName{ Name: deployName(config.Name), Namespace: config.Namespace, }, deploy) if err != nil { deploy.Name, deploy.Namespace = config.Name, config.Namespace tpl, err := template.New("deploy").Parse(deployTpl) if err != nil { return nil, err } var doc bytes.Buffer err = tpl.Execute(&doc, deploy) if err != nil { return nil, err } err = yaml.Unmarshal(doc.Bytes(), deploy) if err != nil { return nil, err } } cmBuilder, err := NewConfigMapBuilder(config, c) if err != nil { log.Println("cm error:", err) return nil, err } return &DeployBuilder{ deploy: deploy, Client: c, config: config, cmBuilder: cmBuilder}, nil } func (this *DeployBuilder) apply() *DeployBuilder { *this.deploy.Spec.Replicas = int32(this.config.Spec.Replicas) return this } const CMAnnotation = "dbcore.config/md5" func (this *DeployBuilder) setCMAnnotation(configStr string) { this.deploy.Spec.Template.Annotations[CMAnnotation] = configStr } // 设置属主,用作资源关联,删除时可连带删除 func (this *DeployBuilder) setOwner() *DeployBuilder { this.deploy.OwnerReferences = append(this.deploy.OwnerReferences, metav1.OwnerReference{ APIVersion: this.config.APIVersion, Kind: this.config.Kind, Name: this.config.Name, UID: this.config.UID, }, ) return this } func (this *DeployBuilder) Build(ctx context.Context) error { // 如果没有creationTime 是nil,则创建deploy if this.deploy.CreationTimestamp.IsZero() { this.apply().setOwner() if err := this.cmBuilder.Build(ctx); err != nil { log.Printf("Build ConfigMap Error: %+v \n", err) } //设置 config md5 this.setCMAnnotation(this.cmBuilder.DataKey) if err := this.Create(ctx, this.deploy); err != nil { log.Printf("Create Deployment Error: %+v \n", err) return err } } else { if err := this.cmBuilder.Build(ctx); err != nil { log.Printf("Build ConfigMap Error: %+v \n", err) } // 合并原有对象 patch := client.MergeFrom(this.deploy.DeepCopy()) fmt.Printf("=== Patch: %+v \n", patch) this.apply() //设置 config md5 this.setCMAnnotation(this.cmBuilder.DataKey) if err := this.Patch(ctx, this.deploy, patch); err != nil { log.Printf("Update Deployment Error: %+v \n", err) } } // 查看状态 // 获取ready replicas readyReplicas := this.deploy.Status.ReadyReplicas this.config.Status.ReadyReplicas = fmt.Sprintf("%d/%d", readyReplicas, this.config.Spec.Replicas) this.config.Status.Replicas = *this.deploy.Spec.Replicas if err := this.Status().Update(ctx, this.config); err != nil { return err } return nil }
5. Dashboard
5.1 定义Dashboard
package dashboard
import (
"context"
"github.com/gin-gonic/gin"
v1 "github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1"
"io/ioutil"
appv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/rest"
metricclient "k8s.io/metrics/pkg/client/clientset/versioned"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func isChildDeploy(cfg *v1.DbConfig, objectRef corev1.ObjectReference, c client.Client) bool {
deploy := &appv1.Deployment{}
err := c.Get(context.Background(), types.NamespacedName{
Name: "dbcore-" + cfg.Name, Namespace: cfg.Namespace,
}, deploy)
if err != nil {
return false
}
if objectRef.UID == deploy.UID {
return true
}
return false
}
type AdminUi struct {
r *gin.Engine
c client.Client
config *rest.Config
}
func NewAdminUi(c client.Client, config *rest.Config) *AdminUi {
r := gin.New()
r.Use(errorHandler())
r.GET("/", func(context *gin.Context) {
context.JSON(200, gin.H{"message": "ok"})
})
return &AdminUi{r: r, c: c, config: config}
}
func (this *AdminUi) tops () {
mc, err := metricclient.NewForConfig(this.config)
Error(err)
this.r.GET("/tops", func(c *gin.Context) {
list, err := mc.MetricsV1beta1().NodeMetricses().List(c, metav1.ListOptions{})
Error(err)
c.JSON(200, list.Items)
})
}
func (this *AdminUi) usage () {
mc, err := metricclient.NewForConfig(this.config)
Error(err)
this.r.GET("/top-usage", func(c *gin.Context) {
nodeName := c.Query("name")
nm, err := mc.MetricsV1beta1().NodeMetricses().Get(c, nodeName, metav1.GetOptions{})
Error(err)
c.JSON(200, gin.H{"usage": nm.Usage})
})
}
func (this *AdminUi) events() {
this.r.GET("/events/:ns/:name", func(c *gin.Context) {
var ns, name =c.Param("ns"),c.Param("name")
cfg := &v1.DbConfig{}
Error(this.c.Get(c, types.NamespacedName{
Namespace: ns, Name: name,
}, cfg))
eList := &corev1.EventList{}
Error(this.c.List(c, eList, &client.ListOptions{}))
retEvents := []corev1.Event{}
for _, e := range eList.Items {
//这是匹配 自定义资源 对应的 event
//&& e.InvolvedObject.UID==cfg.UID
if e.InvolvedObject.UID == cfg.UID && e.InvolvedObject.Name == cfg.Name {
retEvents = append(retEvents, e)
continue
}
//代表判断,当前资源是否是dbconfig 创建出来的 deployment
if isChildDeploy(cfg, e.InvolvedObject, this.c) {
retEvents = append(retEvents, e)
}
}
c.JSON(200, retEvents)
})
}
// 创建资源
func (this *AdminUi) postResource () {
this.r.POST("/resource", func(c *gin.Context) {
b, err := ioutil.ReadAll(c.Request.Body)
Error(err)
cfg := &v1.DbConfig{}
Error(yaml.Unmarshal(b, cfg))
if cfg.Namespace == "" {
cfg.Namespace = "default"
}
Error(this.c.Create(c, cfg))
c.JSON(200, gin.H{"message": "success"})
})
}
// 删除资源
func (this *AdminUi) deleteResource () {
this.r.DELETE("/resource/:ns/:name", func(c *gin.Context) {
cfg := &v1.DbConfig{}
Error(this.c.Get(c, types.NamespacedName{
Name: c.Param("name"),
Namespace: c.Param("ns"),
}, cfg))
Error(this.c.Delete(c, cfg))
c.JSON(200, gin.H{"message": "OK"})
})
}
func (this *AdminUi) total () {
this.r.GET("/top-total", func(c *gin.Context) {
nodeName := c.Query("name")
nodeObj := &corev1.Node{}
err := this.c.Get(c, types.NamespacedName{
Name: nodeName,
}, nodeObj)
Error(err)
c.JSON(200, gin.H{
"cpu": float64(nodeObj.Status.Capacity.Cpu().MilliValue()),
"mem": nodeObj.Status.Capacity.Name(corev1.ResourceMemory, resource.DecimalSI),
})
})
}
func (this *AdminUi) Start(ctx context.Context) error {
this.r.GET("/configs", func(c *gin.Context) {
list := &v1.DbConfigList{}
Error(this.c.List(ctx, list, &client.ListOptions{}))
c.JSON(200, list.Items)
})
this.tops()
this.usage()
this.total()
this.postResource()
this.deleteResource()
this.events()
return this.r.Run(":9003")
}
6. 所有功能补充
- event recorder;
- 通过 mgr 启动 dashboard;
- 开启控制器选主;
- 加入自定义指标;
- 定义 metrics address;
package k8sConfig
import (
"fmt"
v1 "github.com/shenyisyn/dbcore/pkg/apis/dbconfig/v1"
"github.com/shenyisyn/dbcore/pkg/controllers"
"github.com/shenyisyn/dbcore/pkg/dashboard"
"github.com/shenyisyn/dbcore/pkg/mymetrics"
appv1 "k8s.io/api/apps/v1"
"log"
"os"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/controller-runtime/pkg/source"
)
func InitManager() {
logf.SetLogger(zap.New())
mgr, err := manager.New(K8sRestConfig(), manager.Options{
Logger: logf.Log.WithName("dbcore"),
// 开启控制器选主
LeaderElection: true,
LeaderElectionID: "dbcore-lock",
LeaderElectionNamespace: "default",
// 定义metrics address
MetricsBindAddress: ":8082",
})
//加入自定义指标
metrics.Registry.MustRegister(mymetrics.MyReconcileTotal)
if err != nil {
log.Fatal(fmt.Sprintf("unable to setup manager, err: %s", err.Error()))
}
if err = v1.SchemeBuilder.AddToScheme(mgr.GetScheme()); err != nil {
mgr.GetLogger().Error(err, "unable add scheme")
}
// 获取event recorder
dbconfigController:=controllers.NewDbConfigController(mgr.GetEventRecorderFor("dbconfig"))
if err = builder.ControllerManagedBy(mgr).
For(&v1.DbConfig{}).
Watches(&source.Kind{Type: &appv1.Deployment{}},
handler.Funcs{
UpdateFunc: dbconfigController.OnUpdate,
DeleteFunc: dbconfigController.OnDelete,
},
).
Complete(dbconfigController); err != nil {
mgr.GetLogger().Error(err, "unable to create manager.")
os.Exit(1)
}
// 启动管理器,只要实现Start接口皆可以通过mgr 启动
if err = mgr.Add(dashboard.NewAdminUi(mgr.GetClient(), K8sRestConfig())); err != nil {
mgr.GetLogger().Error(err, "unable to start dashboard")
os.Exit(1)
}
if err = mgr.Start(signals.SetupSignalHandler()); err != nil {
mgr.GetLogger().Error(err, "unable to start manager")
os.Exit(1)
}
}