【微服务】——k8s Service Catalog

一、前言

      Service Catalog是kubernetes的一种API扩展,方便kubernetes集群内部应用访问集群外部、由第三方管理、提供的服务,如由云供应商提供的数据库服务。Service Catalog通过Service Brokers使集群内应用能够列出外部服务、提供实例、将集群内应用与实例绑定,使集群内应用不必关心外部服务的实现、管理细节。总结起来就是Service Broker代表外部服务,集群内应用通过Service Catalog使用Service Broker。

二、架构设计

Service Catalog 项目主要的工作是,将kubernetes资源翻译为对Service Broker的OSB API调用,基于此,kubernetes的用户可以通过创建kubernetes资源的方式来使用外部服务。

它有如下几个特性:

  • 服务目录用户可以向kubernetes注册serice broker
  • 服务目录可以从服务目录获取服务和服务计划(即catalog),并提供给kubernetes的用户
  • kubernetes用户向服务目录请求一个ServiceInstance资源,从而向broker请求一个服务实例
  • kubernetes用户通过创建ServiceBinding资源,将应用与服务绑定

arch

Service Catalog 的架构和kubernetes的架构类似,也是分为API Server和controller;其中API Server接收RESTful请求,写入etcd;而Controller监听资源,并执行实际的工作:通过OSB API调用Service Broker的服务。Service Catalog 目前是通过Aggregated API的方式,将自身的API作为API 扩展注册到了kubernetes API server。值得注意的是,Service Catalog项目计划改为基于CRD来实现。

三、应用架构

Service Catalog在kubernetes架构中的位置如下图。

 

Service Catalog通过API Server对用户暴露如下API资源:

  • ClusterServiceBroker,对接Service Broker,封装了该服务的连接细节。集群管理员管理。
  • ClusterServiceClass,某Service Broker提供的服务,例如mysql,redis等。集群管理员管理。
  • ClusterServicePlan,某服务的具体Plan(可以理解为套餐),例如mysql 5.7,redis-5-0-5
  • ServiceInstance,ClusterServiceClass供给的服务实例,例如一个mysql数据库实例
  • ServiceBinding,服务实例的访问凭证,例如mysql的用户名、密码、IP地址、端口号

 

四、部署Service Catalog

参考官网,使用helm可以很方便的部署。

     1、Kubernetes cluster版本>=v1.12;

      2、需要Kubernetes 集群安装在DNS允许的情况下,常用的minikube;

      3、安装Service Catalog需要通过Helm,且Helm版本大于v2.7.0  Helm

       4、添加chart源;

       5、获取安装包到本地(也可以直接安装)

       6、安装svc-cat

$ helm repo add svc-cat https://svc-catalog-charts.storage.googleapis.com
$ helm search service-catalog
NAME                	CHART VERSION	APP VERSION	DESCRIPTION                                                 
svc-cat/catalog     	0.3.0-beta.2 	           	service-catalog webhook server and controller-manager hel...
svc-cat/catalog-v0.2	0.2.2        	           	service-catalog API server and controller-manager helm chart

#拉取安装包到本地
$ helm fetch svc-cat/catalog --version 0.3.0-beta.2


$ ls
catalog-0.3.0-beta.2.tgz

$ helm install catalog-0.3.0-beta.2.tgz --name catalog --namespace service-catalog
NAME:   catalog
LAST DEPLOYED: Sun Apr 12 18:30:13 2020
NAMESPACE: service-catalog
STATUS: DEPLOYED

RESOURCES:
==> v1/ClusterRole
NAME                                             AGE
servicecatalog.k8s.io:controller-manager         1s
servicecatalog.k8s.io:service-catalog-readiness  1s
servicecatalog.k8s.io:webhook                    1s

==> v1/ClusterRoleBinding
NAME                                             AGE
servicecatalog.k8s.io:controller-manager         1s
servicecatalog.k8s.io:service-catalog-readiness  1s
servicecatalog.k8s.io:webhook                    1s

==> v1/Deployment
NAME                                READY  UP-TO-DATE  AVAILABLE  AGE
catalog-catalog-controller-manager  0/1    1           0          1s
catalog-catalog-webhook             0/1    1           0          1s

==> v1/Pod(related)
NAME                                                 READY  STATUS             RESTARTS  AGE
catalog-catalog-controller-manager-76c5dcdd67-74n2l  0/1    ContainerCreating  0         0s
catalog-catalog-webhook-685cf755b7-6dbn9             0/1    ContainerCreating  0         0s

==> v1/Role
NAME                                                     AGE
servicecatalog.k8s.io:cluster-info-configmap             1s
servicecatalog.k8s.io:leader-locking-controller-manager  1s

==> v1/RoleBinding
NAME                                                AGE
service-catalog-controller-manager-cluster-info     1s
service-catalog-controller-manager-leader-election  1s

==> v1/Secret
NAME                          TYPE    DATA  AGE
catalog-catalog-webhook-cert  Opaque  2     1s

==> v1/Service
NAME                                TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)        AGE
catalog-catalog-controller-manager  ClusterIP  10.111.251.101  <none>       443/TCP        1s
catalog-catalog-webhook             NodePort   10.101.196.33   <none>       443:31443/TCP  1s

==> v1/ServiceAccount
NAME                                SECRETS  AGE
clean-job-account                   1        1s
service-catalog-controller-manager  1        1s
service-catalog-webhook             1        1s

==> v1beta1/ClusterRole
NAME               AGE
clean-job-account  1s

==> v1beta1/ClusterRoleBinding
NAME               AGE
clean-job-account  1s

==> v1beta1/CustomResourceDefinition
NAME                                         AGE
clusterservicebrokers.servicecatalog.k8s.io  1s
clusterserviceclasses.servicecatalog.k8s.io  1s
clusterserviceplans.servicecatalog.k8s.io    1s
servicebindings.servicecatalog.k8s.io        1s
servicebrokers.servicecatalog.k8s.io         1s
serviceclasses.servicecatalog.k8s.io         1s
serviceinstances.servicecatalog.k8s.io       1s
serviceplans.servicecatalog.k8s.io           1s

==> v1beta1/MutatingWebhookConfiguration
NAME                     AGE
catalog-catalog-webhook  0s

==> v1beta1/ValidatingWebhookConfiguration
NAME                                AGE
catalog-catalog-validating-webhook  0s

部署后会在namespace catalog 下创建 catalog-catalog-apiserver 和 catalog-catalog-controller-manager两个Deployment,其中catalog-apiserver会向kubernetes apiserver注册如下api:

$ kubectl api-resources | grep servicecatalog.k8s.io
clusterservicebrokers                          servicecatalog.k8s.io                                    false        ClusterServiceBroker
clusterserviceclasses                          servicecatalog.k8s.io                                    false        ClusterServiceClass
clusterserviceplans                            servicecatalog.k8s.io                                    false        ClusterServicePlan
servicebindings                                servicecatalog.k8s.io                                    true         ServiceBinding
servicebrokers                                 servicecatalog.k8s.io                                    true         ServiceBroker
serviceclasses                                 servicecatalog.k8s.io                                    true         ServiceClass
serviceinstances                               servicecatalog.k8s.io                                    true         ServiceInstance
serviceplans                                   servicecatalog.k8s.io                                    true         ServicePlan

kubernetes管理员通过clusterservicebrokers / clusterserviceclasses / clusterserviceplans注册第三方Service Broker服务;kubernetes用户通过serviceinstances / servicebindings 的api向k8s请求、使用第三方服务。

五、部署一个简单的service broker

kubernetes社区提供了一个service broker的demo,叫做minibroker,这个项目管理了一些helm charts,例如mysql,redis;例如,当service catalog通过OSB API向minibroker请求创建新的mysql实例时,minibroker其实是通过helm部署了一个mysql。

当然,minibroker实现了OSB API。

minibroker本身通过helm部署。

 

helm repo add minibroker https://minibroker.blob.core.windows.net/charts
helm install --name minibroker --namespace minibroker minibroker/minibroker

部署后,会在namespace minibroker里创建Deployment minibroker controller。helm部署时还会向kubernetes创建如下资源:

一个clusterservicebrokers,用来将OSB API请求转发给实际的broker服务(即minibroer controller):

$ kubectl get clusterservicebrokers.servicecatalog.k8s.io
NAME         URL                                                         STATUS   AGE
minibroker   http://minibroker-minibroker.minibroker.svc.cluster.local   Ready    2d

以及注册如下几个 clusterserviceclasses:

$ kubectl get clusterserviceclasses
NAME         EXTERNAL-NAME   BROKER       AGE
mariadb      mariadb         minibroker   5m50s
mongodb      mongodb         minibroker   5m50s
mysql        mysql           minibroker   5m50s
postgresql   postgresql      minibroker   5m50s
redis        redis           minibroker   5m50s

以及若干clusterserviceplans:

$ kubectl get clusterserviceplans.servicecatalog.k8s.io
NAME                       EXTERNAL-NAME      BROKER       CLASS        AGE
mariadb-10-1-26            10-1-26            minibroker   mariadb      47h
mariadb-10-1-28            10-1-28            minibroker   mariadb      47h
mariadb-10-1-29            10-1-29            minibroker   mariadb      47h

六、使用

先创建namespace test-ns,假设用户是在test-ns里。

1、创建一个服务实例

$ kubectl create -f https://raw.githubusercontent.com/kubernetes-sigs/service-catalog/master/contrib/examples/walkthrough/mini-instance.yaml
$ kubectl get serviceinstances.servicecatalog.k8s.io -n test-ns
NAME            CLASS                         PLAN      STATUS   AGE
mini-instance   ClusterServiceClass/mariadb   10-1-26   Ready    3d3h
$ kubectl get pods -n test-ns
NAME                                     READY   STATUS    RESTARTS   AGE
virulent-camel-mariadb-d65d9dfb4-5bp4v   1/1     Running   0          3d3h

可以看到service catalog+minibroker会在用户的namespace test-ns下创建一个mariadb的Deployment,使用的也是用户自己的资源。相对用户直接helm install,service catalog更方便一些。

2、绑定服务实例

$ kubectl create -f https://raw.githubusercontent.com/kubernetes-sigs/service-catalog/master/contrib/examples/walkthrough/mini-binding.yaml
$ kubectl get servicebindings.servicecatalog.k8s.io -n test-ns
NAME           SERVICE-INSTANCE   SECRET-NAME    STATUS   AGE
mini-binding   mini-instance      mini-binding   Ready    3d3h
$ svcat describe binding mini-binding
  Name:        mini-binding
  Namespace:   test-ns
  Status:      Ready - Injected bind result @ 2019-09-16 10:03:33 +0000 UTC
  Secret:      mini-binding
  Instance:    mini-instance

Parameters:
  No parameters defined

Secret Data:
  Protocol                5 bytes
  host                    48 bytes
  mariadb-password        10 bytes
  mariadb-root-password   10 bytes
  password                10 bytes
  port                    4 bytes
  uri                     77 bytes
  username                4 bytes
$ kubectl get secrets mini-binding -o yaml -n test-ns

binding是什么意思呢?其实就是把访问服务的一些信息,通过secret提供给用户。

比如对于mariadb来说,其信息包括uri,port,用户名密码等等信息,用户可以通过这些信息来使用mariadb服务(例如将secret以volume的形式挂到Pod里)。

...
          env:
          - name: "PASSWORD"
            valueFrom:
                secretKeyRef:
                   name: mini-binding
                   key: password

 

七、开发

    开发SB,就是要实现Open service broker API规定的接口,结合实际业务场景来动态的提供托管服务。Go语言下的OSB库包括:

说明
brokerapi用于构建SB的库
osb-broker-lib提供了OSB API的REST实现
go-open-service-broker-client一个SB客户端库

可以从osb-starter-pack这个示例项目开始,构建你的Service Broker。该项目使用了上面的osb-broker-lib、go-open-service-broker-client库。

1、克隆此项目:

cd $GOPATH/src
mkdir -p github.com/pmorie
cd github.com/pmorie
git clone git://github.com/pmorie/osb-starter-pack

 

2、主要代码分析

(1)主函数

func runWithContext(ctx context.Context) error {
 
    addr := ":" + strconv.Itoa(options.Port)
    // 下面的businessLogic是broker.Interface接口的实现
    // broker.Interface为SB的核心接口
    businessLogic, _ := broker.NewBusinessLogic(options.Options)
 
    // 用于暴露Prometheus指标
    reg := prom.NewRegistry()
    osbMetrics := metrics.New()
    reg.MustRegister(osbMetrics)
 
    // 创建一个APISurface,此对象表示OSB REST API的接口
    // 它能够将REST请求转换为对broker.Interface的调用
    api, err := rest.NewAPISurface(businessLogic, osbMetrics)
 
    // 创建HTTP服务器,
    s := server.New(api, reg)
 
    // 如果需要验证K8S令牌
    if options.AuthenticateK8SToken {
        // K8S客户端
        k8sClient, err := getKubernetesClient(options.KubeConfig)
        if err != nil {
            return err
        }
        // 创建用户信息验证器
        authz := middleware.SARUserInfoAuthorizer{
            SAR: k8sClient.AuthorizationV1().SubjectAccessReviews(),
        }
        tr := middleware.TokenReviewMiddleware{
            TokenReview: k8sClient.Authentication().TokenReviews(),
            Authorizer:  authz,
        }
        // 并作为路由的中间件(请求拦截器)
        s.Router.Use(tr.Middleware)
    }
 
    glog.Infof("Starting broker!")
 
    // 启动服务,如果使用TLS则需要一些额外的处理
    if options.Insecure {
        err = s.Run(ctx, addr)
    } else {
        if options.TLSCert != "" && options.TLSKey != "" {
            // 使用直接提供的证书数据
            err = s.RunTLS(ctx, addr, options.TLSCert, options.TLSKey)
        } else {
            if options.TLSCertFile == "" || options.TLSKeyFile == "" {
                return nil
            }
            // 使用证书文件
            err = s.RunTLSWithTLSFiles(ctx, addr, options.TLSCertFile, options.TLSKeyFile)
        }
    }
    return err
}

 可以看到,HTTP/REST相关的技术细节,直接拷贝此示例的代码就可以了。我们需要做的,就是实现broker.Interface接口。

3、broker.Interface

package broker
 
import (
    "net/http"
    osb "github.com/pmorie/go-open-service-broker-client/v2"
)
 
// 此接口定义了SB需要实现的所有业务逻辑
type Interface interface {
 
    // 校验发送给此SB的OSB API版本
    ValidateBrokerAPIVersion(version string) error
 
    // 获取此SB提供的服务目录,K8S的ServiceCatalog组件第一次和此SB通信时会调用该方法
    GetCatalog(c *RequestContext) (*CatalogResponse, error)
 
    // 提供一个服务实例
    //
    // 参数说明:
    // - osb.ProvisionRequest 请求对象,基于HTTP请求封装
    // -RequestContext 用于获取原始HTTP请求、响应对象
    Provision(request *osb.ProvisionRequest, c *RequestContext) (*ProvisionResponse, error)
 
    // 回收(deprovision)一个服务实例
    //
    // 参数说明:
    // - a osb.DeprovisionRequest 请求对象,基于HTTP请求封装 
    // -RequestContext 用于获取原始HTTP请求、响应对象
    Deprovision(request *osb.DeprovisionRequest, c *RequestContext) (*DeprovisionResponse, error)
    
    // 当K8S需要确认一个正在处理的异步操作的状态时,调用此方法
    LastOperation(request *osb.LastOperationRequest, c *RequestContext) (*LastOperationResponse, error)
    
    // 执行绑定操作,该操作会创建供客户端使用(服务实例)的凭证,并非所有服务支持绑定操作
    Bind(request *osb.BindRequest, c *RequestContext) (*BindResponse, error)
 
    // 删除绑定
    Unbind(request *osb.UnbindRequest, c *RequestContext) (*UnbindResponse, error)
 
    // 更新服务实例的配置
    Update(request *osb.UpdateInstanceRequest, c *RequestContext) (*UpdateInstanceResponse, error)
}
 
 
type RequestContext struct {
    // 用于细粒度的控制响应
    Writer  http.ResponseWriter
    // 原始的HTTP请求头
    Request *http.Request
}

4、BusinessLogic 

此结构即样例项目osb-starter-pack的 broker.Interface 实现:

package broker
 
import (
    "net/http"
    "sync"
 
    "github.com/golang/glog"
    "github.com/pmorie/osb-broker-lib/pkg/broker"
 
    osb "github.com/pmorie/go-open-service-broker-client/v2"
    "reflect"
)
 
// 参数是命令行选项
func NewBusinessLogic(o Options) (*BusinessLogic, error) {
    return &BusinessLogic{
        async:     o.Async,
        instances: make(map[string]*exampleInstance, 10),
    }, nil
}
 
type BusinessLogic struct {
    // 是否应当异步的处理请求
    async bool
    // 互斥锁
    sync.RWMutex
    // 所有已经Provision的实例(请求)列表
    instances map[string]*exampleInstance
}
 
var _ broker.Interface = &BusinessLogic{}
 
func truePtr() *bool {
    b := true
    return &b
}
 
func (b *BusinessLogic) GetCatalog(c *broker.RequestContext) (*broker.CatalogResponse, error) {
    // 这里仅仅简单的返回一个osb.Service对象,Service Catalog根据此对象创建ClusterServiceClass、ClusterServicePlan
    response := &broker.CatalogResponse{}
    osbResponse := &osb.CatalogResponse{
        Services: []osb.Service{
            {
                Name:          "example-starter-pack-service",
                ID:            "4f6e6cf6-ffdd-425f-a2c7-3c9258ad246a",
                Description:   "The example service from the osb starter pack!",
                Bindable:      true,
                PlanUpdatable: truePtr(),
                Metadata: map[string]interface{}{
                    "displayName": "Example starter pack service",
                    "imageUrl":    "https://avatars2.githubusercontent.com/u/19862012?s=200&v=4",
                },
                Plans: []osb.Plan{
                    {
                        Name:        "default",
                        ID:          "86064792-7ea2-467b-af93-ac9694d96d5b",
                        Description: "The default plan for the starter pack example service",
                        Free:        truePtr(),
                        Schemas: &osb.Schemas{
                            ServiceInstance: &osb.ServiceInstanceSchema{
                                Create: &osb.InputParametersSchema{
                                    // 前端应该根据类型来展示这些参数,供用户选择
                                    Parameters: map[string]interface{}{
                                        "type": "object",
                                        "properties": map[string]interface{}{
                                            "color": map[string]interface{}{
                                                "type":    "string",
                                                "default": "Clear",
                                                "enum": []string{
                                                    "Clear",
                                                    "Beige",
                                                    "Grey",
                                                },
                                            },
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
    }
 
    response.CatalogResponse = *osbResponse
    return response, nil
}
 
func (b *BusinessLogic) Provision(request *osb.ProvisionRequest, c *broker.RequestContext) (*broker.ProvisionResponse, error) {
    // 全局锁
    b.Lock()
    defer b.Unlock()
 
    response := broker.ProvisionResponse{}
 
    // 将请求转换为代表Instance的结构
    exampleInstance := &exampleInstance{
        ID:        request.InstanceID,
        ServiceID: request.ServiceID,
        PlanID:    request.PlanID,
        Params:    request.Parameters,
    }
 
    // 检查是否重复请求
    if i := b.instances[request.InstanceID]; i != nil {
        if i.Match(exampleInstance) {
            // 完全重复请求,提示已经存在
            response.Exists = true
            return &response, nil
        } else {
            // InstanceID已经被占用,这是个错误
            description := "InstanceID in use"
            return nil, osb.HTTPStatusCodeError{
                StatusCode: http.StatusConflict,
                Description: &description,
            }
        }
    }
    b.instances[request.InstanceID] = exampleInstance
 
    // 进行实际的Provision操作,创建出外部服务
 
 
    if request.AcceptsIncomplete {
        // 可是Service Catalog这是个未完成的异步请求
        response.Async = b.async
    }
 
    return &response, nil
}
 
func (b *BusinessLogic) Deprovision(request *osb.DeprovisionRequest, c *broker.RequestContext) (*broker.DeprovisionResponse, error) {
    // 销毁外部服务
    b.Lock()
    defer b.Unlock()
 
    response := broker.DeprovisionResponse{}
 
    delete(b.instances, request.InstanceID)
 
    if request.AcceptsIncomplete {
        response.Async = b.async
    }
 
    return &response, nil
}
 
func (b *BusinessLogic) LastOperation(request *osb.LastOperationRequest, c *broker.RequestContext) (*broker.LastOperationResponse, error) {
    return nil, nil
}
 
func (b *BusinessLogic) Bind(request *osb.BindRequest, c *broker.RequestContext) (*broker.BindResponse, error) {
    b.Lock()
    defer b.Unlock()
 
    instance, ok := b.instances[request.InstanceID]
    if !ok {
        return nil, osb.HTTPStatusCodeError{
            StatusCode: http.StatusNotFound,
        }
    }
 
    response := broker.BindResponse{
        BindResponse: osb.BindResponse{
            // 返回访问服务实例需要的凭证信息
            Credentials: instance.Params,
        },
    }
    if request.AcceptsIncomplete {
        response.Async = b.async
    }
 
    return &response, nil
}
 
func (b *BusinessLogic) Unbind(request *osb.UnbindRequest, c *broker.RequestContext) (*broker.UnbindResponse, error) {
    return &broker.UnbindResponse{}, nil
}
 
func (b *BusinessLogic) Update(request *osb.UpdateInstanceRequest, c *broker.RequestContext) (*broker.UpdateInstanceResponse, error) {
    response := broker.UpdateInstanceResponse{}
    if request.AcceptsIncomplete {
        response.Async = b.async
    }
 
    return &response, nil
}
 
func (b *BusinessLogic) ValidateBrokerAPIVersion(version string) error {
    return nil
}
 
type exampleInstance struct {
    ID        string
    ServiceID string
    PlanID    string
    Params    map[string]interface{}
}
 
func (i *exampleInstance) Match(other *exampleInstance) bool {
    return reflect.DeepEqual(i, other)
}

 【参考文章】

1、官网安装文档:https://github.com/kubernetes-sigs/service-catalog/blob/master/docs/install.md

2、https://blog.gmem.cc/service-catalog-of-kubernetes

3、https://github.com/pmorie/osb-starter-pack

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值