跳转至

developers guide

1.背景

为了让更多开发者快速融入slime社区,本文将提供一份开发指南,指引开发者在slime项目中创建子模块、功能开发、编译构建、部署,功能验证

2.创建模块

slime提供了一套脚手架, 通过该脚手架,使用者可以快速创建一个包含了reconcile功能的子模块,这样开发人员就可以将更多的精力放在应用程序的逻辑实现上,加速了开发流程。

2.1 创建子模块

运行以下命令,在slime的staging/src/slime.io/slime/modules目录下创建子模块foo,开发者可自行替换子模块名称

bash bin/gen_module.sh foo

新创建的子模块目录树如下:

|-- Dockerfile
|-- PROJECT
|-- README.md
|-- api
|   |-- config
|   |   |-- foo_module.pb.go
|   |   |-- foo_module.proto
|   |   `-- foo_module_deepcopy.gen.go
|   `-- v1alpha1
|       |-- foo.pb.go
|       |-- foo.proto
|       |-- foo_deepcopy.gen.go
|       |-- foo_types.go
|       |-- groupversion_info.go
|       `-- zz_generated.deepcopy.go
|-- charts
|   `-- values.yaml
|-- controllers
|   `-- foo_controller.go
|-- go.mod
|-- go.sum
|-- install
|   `-- samples
|       `-- slimeboot_foo.yaml
|-- main.go
|-- model
|   `-- model.go
|-- module
|   `-- module.go
`-- publish.sh

其中的一些目录含义如下:

  • api:
    • config:子模块的启动参数定义,例如,limiter定义的disableGlobalRateLimit字段,在启动时根据该参数关闭全局共享限流功能
    • v1alpha1:子模块的CRD定义,默认情况下生成的CRD的gvk是microservice.slime.io/v1alpha1/Foo,其中group和version固定不变,kind等同于用户的子模块名称
  • controller: 核心的controller逻辑,开发者需要关注的目录
  • install: 根据该内容部署你的子模块
  • publish: 镜像构建脚本

2.2 修改api

在2.1中,我们创建了一个子模块foo,其api定义位于api/v1alpha1,默认情况下其定义如下

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

    Spec   FooSpec   `json:"spec,omitempty"`
    Status FooStatus `json:"status,omitempty"`
}

// FooSpec defines the desired state of Foo
type FooSpec struct {
    // Foo is an foo field of Foo.
    Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"`
    // Foo2 is an foo field of Foo.
    Foo2 string `protobuf:"bytes,2,opt,name=foo2,proto3" json:"foo2,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

如果使用者需要修改spec内容, 比如想给foo的spec加上foo3字段,你可以修改api/v1alpha1/foo.proto

样例如下:

message FooSpec {
  // Foo is an foo field of Foo.
  string foo = 1;

  // Foo2 is an foo field of Foo.
  string foo2 = 2;

  // Foo3 is an foo field of Foo.
  string foo3 = 3;
}

然后执行以下命令(需要docker),更新api内容, 其中MODULES的值需要替换成你创建时定义的模块名称

note: 每次修改proto后都需要执行该命令

MODULES=foo make generate-module

之后观察spec是否更新

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

    Spec   FooSpec   `json:"spec,omitempty"`
    Status FooStatus `json:"status,omitempty"`
}

type FooSpec struct {
    // Foo is an foo field of Foo.
    Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"`
    // Foo2 is an foo field of Foo.
    Foo2 string `protobuf:"bytes,2,opt,name=foo2,proto3" json:"foo2,omitempty"`
    // Foo3 is an foo field of Foo.
    Foo3                 string   `protobuf:"bytes,3,opt,name=foo3,proto3" json:"foo3,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}
note: proto中的oneof类型目前不能优雅支持,请使用者谨慎使用

2.3 生成CRD

在执行2.2后的,在charts/crds目录下生成了microservice.slime.io/v1alpha1/Foo

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.7.0
  creationTimestamp: null
  name: foos.microservice.slime.io
spec:
  group: microservice.slime.io
  names:
    kind: Foo
    listKind: FooList
    plural: foos
    singular: foo
  scope: Namespaced
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        description: Foo is the Schema for the foos API
        properties:
          apiVersion:
            description: 'APIVersion defines the versioned schema of this representation
              of an object. Servers should convert recognized schemas to the latest
              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
            type: string
          kind:
            description: 'Kind is a string value representing the REST resource this
              object represents. Servers may infer this from the endpoint the client
              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
            type: string
          metadata:
            type: object
          spec:
            description: FooSpec defines the desired state of Foo
            properties:
              foo:
                description: Foo is an foo field of Foo.
                type: string
              foo2:
                description: Foo2 is an foo field of Foo.
                type: string
              foo3:
                description: Foo3 is an foo field of Foo.
                type: string
            type: object
          status:
            description: FooStatus defines the observed state of Foo
            properties:
              bar:
                description: Bar is an foo field of FooStatus.
                type: string
            type: object
        type: object
    served: true
    storage: true
    subresources:
      status: {}
status:
  acceptedNames:
    kind: ""
    plural: ""
  conditions: []
  storedVersions: []

3.功能开发

子模块的核心处理逻辑在 staging/src/slime.io/slime/modules/foo/controllers/foo_controller.go

使用者可以编写自己的协调过程,一个简单的样例如下,用户可自行参考

import(
     "slime.io/slime/modules/foo/api/v1alpha1"
      log "github.com/sirupsen/logrus"
)

// 创建/更新/删除时 打印相关日志 
func (r *FooReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log.Infof("begin reconcile, get foo %+v", req)
    instance := &foov1alpha1.Foo{}
    if err := r.Client.Get(ctx, req.NamespacedName, instance); err != nil {
        if errors.IsNotFound(err) {
            instance = nil
            err = nil
            log.Infof("foo %v not found", req.NamespacedName)
        } else {
            log.Errorf("get foo %v err, %s", req.NamespacedName, err)
            return reconcile.Result{}, err
        }
    }
    // deleted
    if instance == nil {
        log.Infof("foo %v is deleted", req)
        return reconcile.Result{}, nil
    } else {
        // add or update
        log.Infof("foo %v is added or updated", req)
    }
    return reconcile.Result{}, nil
}

4.编译构建

  • 首先切换至子模块目录 slime/staging/src/slime.io/slime/modules/foo

  • 之后执行以下命令编译构建镜像

    ./publish.sh build image`
    
    也可以直接将编译构建镜像并推送至镜像仓库,需要指定HUB
    HUB=xxx ./publish.sh build image image-push
    
    note: slime模块的编译构建需要docker的buildx特性,一个简单的开启方式: 在 ~/.docker/config.json增加 "experimental": "enabled"

更多的编译构建信息请参考 build

5.部署

在slime中我们提供了一种slimeboot的部署方式,关于slimeboot的更多信息,可以参考 slime-boot

用户只需要提供 SlimeBoot CR(包含部署的模块信息),就可以部署你的子模块,前提是需要安装SlimeBoot相关CRD和Deployment

5.1 SlimeBoot安装

首先需要安装 SlimeBoot-CRD, SlimeBoot-deployment

5.2 创建Foo CRD

需要将microservice.slime.io_foos注册到k8s集群, 资源位于子模块chart/crds/microservice.slime.io_foos.yaml

5.3 部署应用

创建CRD后,使用者需要部署具体的应用,一般情况下使用的是chart, 但slime目前支持了SlimeBoot的部署方式

在新生成的子模块install/samples/simeboot_foo.yaml路径下,自动生成了的foo模块的SlimeBoot

使用者在apply该份yaml前,需要修改image信息,以及将name/kind修改成对应的模块名称

如果没有 mesh-operator 可以先创建一个ns

SlimeBoot的内容大致如下:

apiVersion: config.netease.com/v1alpha1
kind: SlimeBoot
metadata:
  name: foo
  namespace: mesh-operator
spec:
  image:
    pullPolicy: Always
    repository: docker.io/slimeio/slime-foo
    tag: ${IMAGE}
  module:
    - name: foo
      kind: foo
      enable: true
      global:
        log:
          logLevel: info

5.4 pods 验证

经过以上三步,你会在你的mesh-operator发现新的deployment

  • foo: 你创建的子模块
  • slime-boot: slimeboot deployment
kubectl get deploy -n mesh-operator
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
foo          1/1     1            1           81m
slime-boot   1/1     1            1           84m

5.4 功能验证

在之前的几个步骤中,我们注册了CRD,部署了服务,现在我们需要下发一份Foo CR验证服务是否正常。

note: CR需要使用者根据自己的api自行修改

apply以下CR之后再删除,观察你部署的pods日志

apiVersion: microservice.slime.io/v1alpha1
kind: Foo
metadata:
  labels:
  name: test
  namespace: default
spec:
  foo: a
  foo2: b
  foo3: c 

如果你按照第3步修改,将会出现以下日志,也证明你新创建的子模块正常work

// 创建
time=2022-12-08T07:33:52Z level=info msg=begin reconcile, get foo default/test
time=2022-12-08T07:33:52Z level=info msg=foo default/test is added or updated

// 删除
time=2022-12-08T07:34:52Z level=info msg=begin reconcile, get foo default/test
time=2022-12-08T07:34:52Z level=info msg=foo default/test not found
time=2022-12-08T07:34:52Z level=info msg=foo default/test is deleted

到这里我们介绍了子模块创建、功能开发、编译构建、部署使用,功能验证。

Back to top