Kong Ingress Controller 使用指南&开发

1,313 阅读5分钟

一、Kong Ingress Controller是什么

源码:github.com/Kong/kubern…

​ Kong官方提供了一键安装的命令:kubectl apply -f https://bit.ly/k4k8s,该命令主要安装了CRD/Deployment/ClusterRole等配置到kubernetes的集群中,Controller通过kubernetes的Apiserver监听(watch)集群的事件,如果有事件变化,Controller会调用Kong的Admin api完成kong集群的资源配置。整个控制架构如下:

high-level-design

​ 整个部署的清单如下:

  • 创建namespace: kong
  • 创建CRD:
    • ingressclassparameterses.configuration.konghq.com
    • kongclusterplugins.configuration.konghq.com
    • kongconsumers.configuration.konghq.com
    • kongingresses.configuration.konghq.com
    • kongplugins.configuration.konghq.com
    • tcpingresses.configuration.konghq.com
    • udpingresses.configuration.konghq.com
  • serviceaccount(后续的Deployment会用到):kong-serviceaccount
  • secret: kong-serviceaccount-token是基于kong-serviceaccount创建出来的,用于挂载到ingress-controller的contianer中,提供kubernetes的必要权限给ingress-controller完成watch apiserver的逻辑。
  • role和rolebinding:
    • role: kong-leader-election
    • rolebinding: kong-leader-election
  • clusterrole和clusterrolebinding
    • clusterrole: kong-ingress/ kong-ingress-gateway/kong-ingress-knative
    • clusterrolebinding: kong-ingress/ kong-ingress-gateway/kong-ingress-knative
  • ingress class: kong , 让v1版本的Ingress可以指定ingressClassName为kong
  • service: kong-proxy 和 kong-validation-webhook
  • deployment: kong,其中包括了两个容器,分别是proxy(运行kong,并且暴露数据面端口和管控面端口)和ingress-controller

部署成功的截图:

image-20221018115051354

二、怎么开发

​ 编译二进制的过程: make build,生成的二进制是./bin/manager 。make build依赖controller-gen 和 client-gen的工具生成controller和对应CRD的client的代码。代码自动生成后,在执行编译的过程。manager的代码入口在internal/cmd/main.go 中。

​ 整个编译流程分为:(1)controllers代码自动生成 ;(2) clients代码自动生成 (3)manager二进制编译

Makefile的依赖图如下:

image-20221020172216033

1. Controllers代码自动生成

kong ingress依赖的Controllers自动代码生成步骤如下:

  1. 下载controller-gen的二进制(利用的是go generate的机制安装controller-gen): go generate -tags=third_party ./controller-gen.go

    image-20221018173009562
  2. 根据CRD的定义struct,生成对应deepcopy相关接口代码:controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...

    生成的deepcopy相关接口代码如下:

    pkg/apis/configuration/v1/zz_generated.deepcopy.go pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go /pkg/apis/configuration/v1beta1/zz_generated.deepcopy.go

    CRD定义的struct的文件列表如下(这些都是kong ingress controller定义的CRD,kubernetes自带的Ingress不包括在内)

    image-20221018201202898
  3. 执行go generate internal/cmd, 在internal/cmd的main.go中,执行go run github.com/kong/kubernetes-ingress-controller/v2/hack/generators/controllers/networking 生成controller对应的代码。

    image-20221018171251865

​ 第3步生成的controller的代码路径是:internal/controllers/configuration/zz_generated_controllers.go, 生成的Controller如下图:

image.png

​ 以NetV1IngressClassReconciler为例,生成的Controller控制代码如下

// Reconcile processes the watched objects
func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("NetV1IngressClass", req.NamespacedName)

	// get the relevant object
	obj := new(netv1.IngressClass)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "IngressClass", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

2. Clients代码自动生成

  1. 下载client-gen的二进制工具: go generate -tags=third_party ./client-gen.go

  2. 使用client-gen生成客户端代码:

    client-gen \
                    --go-header-file ./hack/boilerplate.go.txt \
                    --logtostderr \
                    --clientset-name clientset \
                    --input-base github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/  \
                    --input configuration/v1,configuration/v1beta1,configuration/v1alpha1 \
                    --input-dirs github.com/kong/kubernetes-ingress-controller/pkg/apis/configuration/v1alpha1/,github.com/kong/kubernetes-ingress-controller/pkg/apis/configuration/v1beta1/,github.com/kong/kubernetes-ingress-controller/pkg/apis/configuration/v1/ \
                    --output-base pkg/ \
                    --output-package github.com/kong/kubernetes-ingress-controller/v2/pkg/ \
                    --trim-path-prefix pkg/github.com/kong/kubernetes-ingress-controller/v2/
    

​ 利用pkg/apis/configuration目录下,不同版本的CRD定义,生成对这些第三方资源的get/list/watch/create/update/patch/delete等标准操作方法。生成的客户端在目录pkg/clientset/typed/configuration下,以KongPlugins为例,生成如下的客户端

image-20221020155032226

2.1 kubernetes gateway-api的SIG定义的gateway CRD的客户端生成

make generate.gateway-api-urls生成gateway-api的定义相关的参数在文件test/consts/zz_generated_gateway.go

cat test/consts/zz_generated_gateway.go // This file is generated by test/internal/cmd/generate-gateway-urls. DO NOT EDIT.

package consts

const ( GatewayStandardCRDsKustomizeURL = "github.com/kubernetes-sigs/gateway-api/config/crd/?ref=v0.5.0" GatewayExperimentalCRDsKustomizeURL = "github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v0.5.0" GatewayRawRepoURL = "raw.githubusercontent.com/kubernetes-…" )

gateway-api的controller是直接在源码中,不是自动生成的。源码路径在internal/controllers/gateway中,里面定义了Gateway/UDPRoute/HTTPRoute/TLSRoute/ReferenceGrant/TCPRoute/GatewayClass等资源的控制逻辑。

有关Gateway Api的详细,可以参考Kubernetes官方介绍: gateway-api.sigs.k8s.io/

2.2 knative

kong ingress controller也支持了knative的Ingress配置,关于knative可参考:knative.dev/docs/servin… 。鉴于目前knative使用不多,不做过多介绍。

3. manager二进制的编译

manager是运行kong ingress controller的启动命令,ingress-controller容器里面运行的就是manager。

编译的过程如下,代码的入库是在internal/cmd/main.go中

go build -a -o bin/manager -ldflags "-s -w \
   -X $(REPO_URL)/v2/internal/metadata.Release=$(TAG) \
   -X $(REPO_URL)/v2/internal/metadata.Commit=$(COMMIT) \
   -X $(REPO_URL)/v2/internal/metadata.Repo=$(REPO_INFO)" internal/cmd/main.go

三、 manager如何运行

主要的逻辑是在internal/manager/run.go中,启动流程图如下:

image-20221020204129810

​ 完成了命令行参数的初始化后,读取k8s 的apiserver连接配置生成kubeconfig。接着,manager尝试连接kong的admin api,获取kong的版本以及是否使用了数据库部署(dbless也支持),再到初始化了同步组件Synchronizer和注册所有的controller到manger中,最后启动整个manager。

​ 配置从k8s集群同步到kong集群的逻辑如下:

image-20221020204947184

​ controller负责实现Reconcile的方法,使用r.DataplaneClient的对象更新Cache Store。Cache Store中维护了所有kong ingress的controllers所有同步的对象。以V1版本的Ingress为例,Ingress发生变化的时候,处理如下,调用r.DataplaneClient.UpdateObject(obj)更新Cache Store。

func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("NetV1IngressClass", req.NamespacedName)

	// get the relevant object
	obj := new(netv1.IngressClass)
	
	// 省略部分代码
	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

​ Synchronizer是负责从Cache Store中同步配置到Kong的任务。manager启动前,会调用setupDataplaneSynchronizer启动Synchronizer的逻辑。Synchronizer会定时执行dataplaneClient的Update方法完成配置从Cache Store到Kong的中,定时出发的机制如下:

func (p *Synchronizer) startUpdateServer(ctx context.Context) {
	var initialConfig sync.Once
	for {
		select {
		// 省略部分代码
		case <-p.syncTicker.C:
			if err := p.dataplaneClient.Update(ctx); err != nil {
				p.logger.Error(err, "could not update kong admin")
				break
			}
			initialConfig.Do(p.markConfigApplied)
		}
	}
}

总结: 这里可以看到,kong ingress的配置同步到kong上是有一个间隔(定时的逻辑)的,不是实时同步的,其实这里面可以有一个优化,就是Cache Store有更新的时候,通知Synchronizer执行更新,会更高效的完成配置的同步,但是如果频繁更新的话,会触发很多次配置的更新。所以定时和通知的机制各有好处。