GO 学了一个月,代码不会写,先看个源码解析(文末说点事儿)

Golang
213
0
0
2024-01-06

本地启动

在第三篇自定义中间件的前提下,已经说了很多关于本地通过 CRD 或者 File 作为 provider 的启动方式,这里想要补充一下关于本地 DEBUG 3.0.0 版本代码的问题,后续的源码分析也都会基于目前最新的 3.0 版本。

在写文章的时候,目前 3.0 版本还是 beta 版本。

按照上述文章的方式安装之后其实会发现本地启动会报一个关于*v1alpha1.ServersTransportTCP相关的错误,这是因为之前我们安装的 CRD 资源定义和 RBAC 都是 2.10 版本的,需要重新安装一下 3.0.0 版本的资源定义。

# Install Traefik Resource Definitions:
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml

# Install RBAC for Traefik:
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml

启动流程

Traefik 启动的源码在cmd/traefik/traefik.go中,进入到 main 方法。

我们在本地 DEBUG 就是启动这个 main 方法,首先进行配置的初始化,然后定义了 3 个资源加载的方式,和官方描述的一样,可以从配置文件、命令行参数和环境变量读取配置。

之后就是添加启动命令,包括健康检测、版本检查,最后回去调用cli.Execute方法,这个方法跟进去看也挺简单的,最终其实都会调用到CommandRun方法,也就是会执行runCmd方法。

进入 Execute 方法,我们启动是没有其他参数的,只是启动命令,所以进入第一个判断,参数长度为1。

然后最终跟我们上面说的一样,调用到了cmd.Run

然后直接进入runCmd方法中看服务如何启动的。

这里干了几件事情,首先是初始化日志的配置、静态配置,然后解析静态配置为 json,然后就是很关键的两个步骤,一个是setupServer初始化 server,之后是 server 的启动。

这里可以看一下静态配置到底都有些什么东西,因为我们没有配置 AcessLog、Metric 这些东西,所以都是空的,其实关键就两个东西,一个是 EntryPoints,另外一个是 Provider。

这里我们本地启动通过 CRD 的方式,入口点只是默认的配置。

Server初始化

然后我们直接看 setupServer都干了些什么,这个方法非常长,我们忽略调一些无关的细节,只看重点的部分。

首先创建 provider 的聚合器,其实就是服务发现的提供者,我们知道有很多,像是文件、Docker、K8S、Consul 等等很多,在之前我们就提到过这个,这里我们主要是 CRD。

ACME 是 HTTPS 证书相关的东西,可以忽略他。

然后是关于server.NewTCPEntryPoints,用于构建入口点的逻辑,这个很关键。

接着是创建 Provider 的插件,这个X509Source又是关于证书的一些逻辑,主要是使用 SPIFFE (Secure Production Identity Framework For Everyone) 获取证书,以便与其他服务进行安全通信,这个不是重点,忽略。

接着往下走是几个比较关键的 Factory,一个是managerFactory,另外一个则是routerFactory,这两个工厂会构建出后续请求的流程链路,放到后面再说。

这里的 watcher 也是一个关键点,添加了关于 TLS、Metrics、Server Transports 和 Switch Router 的动态配置的监听。

这里的switchRouter是一个很重要的方法,待会儿再细看。

最终流程走完到最终执行NewServer方法创建 Server,配置初始化 Server 的流程结束。

配置监听

这个流程看完之后,回到最开始的地方,开始启动 Server。

这里主要关注两个方法,一个是 TCP 入口点的启动,另外一个则是监听 watcher 的启动,首先看tcpEntryPoints.Start

这里可以看到入口点默认是两个,一个是 traefik 自己默认的,另外一个是处理 web 请求的入口点,之后开启协程进入 serverEntryPoint.Start(ctx)

这里主要是监听连接,当有新连接到达时,创建一个新的 writeCloser 对象,并在其上设置读/写超时。接下来,使用 e.switcher.ServeTCP 处理新连接,如果出现错误,则会记录错误日志并将错误转发到 e.httpServere.httpsServer 的 channel。

之后就进入 watcher 监听的启动方法,主要实现了 3 个方法。

  1. 1. receiveConfigurations 是用于接收配置的变化,并且发送到消费者
  2. 2. applyConfigurations 是合并配置、应用配置
  3. 3. 启动 Provider 的聚合器,做服务发现

receiveConfigurations主要是死循环监听配置的变更,新的配置会发送到 output 的 channel。

applyConfigurations 则是收到新的配置之后,合并然后应用配置,具体合并的细节就不看了,忽略他。

provider 的聚合器是根据不同的 provider 提供了不同的实现,这里我们是使用 K8S CRD 的方式,所以待会儿进去看这个实现即可。

首先创建一个 K8S client 客户端,用于和 K8S 集群进行通信,然后使用该客户端创建一个事件通道。

接下来,使用WatchAll监听所有配置的 namespace 的变化。

这里 debug 能看到现在我们只配置了一个默认的 namespace。

在第一次启动的时候,默认调用loadConfigurationFromCRD方法从 CRD 加载配置,之后如果配置发生变化,走到 default 分支,变更的配置会发送到 configurationChan

这个 channel 的定义其实就是在方法入口的ConfigurationWatcher

查看 ConfigurationWatcher 结构体的定义,找到allProvidersConfigs,定义的就是我们发送到的 channel 了。

看到这里,会有疑问,那么发送的配置在什么地方进行消费了呢?

答案就是上面我们已经讲过的receiveConfigurations,而receiveConfigurations消费到配置变更之后,又会发送消息到newConfigs,然后applyConfigurations方法进行消费,然后处理配置、进行合并、应用。

服务发现

然后让我们回到 loadConfigurationFromCRD这个方法中,找到loadIngressRouteConfiguration方法,这里就是启动的时候去做服务发现的地方。

进入这个方法就能看到通过 K8S 的客户端去获取配置的 IngressRoute。

进入这个方法,发现也是确实如此。

这里 DEBUG 可以看到我们日志的 IngressRoute 的信息。

往下看还有一个比较有意思的地方,如果说路由中配置的 service 数量超过 1,那么会默认会变成权重负载均衡。

接着往下看buildServicesLB的方法,其实和 service==1 的情况一样,只是循环去调用了nameAndService方法而已。

进入这个方法这里有两个判断,针对类型是 Service 和 TraefikService的,Service 类型是 K8S 的类型,所以会去直接获取 server 的信息,而如果配置的是 TraefikService,实际上只是根据 TraefikService 获取 Service 名字,最终还是会走到 Service 的判断逻辑上。

创建负载均衡代码的关键在于第一行loadServers

进入这个方法可以看到其实就是通过 K8S 的客户端去调用 API 接口,获取到了 service 的信息,还有端口、后端服务的地址。

那么,到这里基本服务发现的逻辑就结束了。