一、Kong Ingress Controller是什么
Kong官方提供了一键安装的命令:kubectl apply -f https://bit.ly/k4k8s,该命令主要安装了CRD/Deployment/ClusterRole等配置到kubernetes的集群中,Controller通过kubernetes的Apiserver监听(watch)集群的事件,如果有事件变化,Controller会调用Kong的Admin api完成kong集群的资源配置。整个控制架构如下:
整个部署的清单如下:
- 创建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
部署成功的截图:
二、怎么开发
编译二进制的过程: 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的依赖图如下:
1. Controllers代码自动生成
kong ingress依赖的Controllers自动代码生成步骤如下:
-
下载controller-gen的二进制(利用的是go generate的机制安装controller-gen):
go generate -tags=third_party ./controller-gen.go -
根据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不包括在内)
-
执行
go generate internal/cmd, 在internal/cmd的main.go中,执行go run github.com/kong/kubernetes-ingress-controller/v2/hack/generators/controllers/networking生成controller对应的代码。
第3步生成的controller的代码路径是:internal/controllers/configuration/zz_generated_controllers.go, 生成的Controller如下图:
以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代码自动生成
-
下载client-gen的二进制工具:
go generate -tags=third_party ./client-gen.go -
使用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为例,生成如下的客户端
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中,启动流程图如下:
完成了命令行参数的初始化后,读取k8s 的apiserver连接配置生成kubeconfig。接着,manager尝试连接kong的admin api,获取kong的版本以及是否使用了数据库部署(dbless也支持),再到初始化了同步组件Synchronizer和注册所有的controller到manger中,最后启动整个manager。
配置从k8s集群同步到kong集群的逻辑如下:
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执行更新,会更高效的完成配置的同步,但是如果频繁更新的话,会触发很多次配置的更新。所以定时和通知的机制各有好处。