本地启动
在第三篇自定义中间件的前提下,已经说了很多关于本地通过 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 方法。
![null e9d9f201f696efe85f34bb46c7c78c8c.png](https://i-blog.csdnimg.cn/blog_migrate/1d0b2a9b911001b56264df3d73530a14.png)
我们在本地 DEBUG 就是启动这个 main 方法,首先进行配置的初始化,然后定义了 3 个资源加载的方式,和官方描述的一样,可以从配置文件、命令行参数和环境变量读取配置。
之后就是添加启动命令,包括健康检测、版本检查,最后回去调用cli.Execute
方法,这个方法跟进去看也挺简单的,最终其实都会调用到Command
的Run
方法,也就是会执行runCmd
方法。
![null b043e612d10a087b4c817a2c9601c0c5.png](https://i-blog.csdnimg.cn/blog_migrate/a6d72706fe20145671cdb14528918bc7.png)
进入 Execute 方法,我们启动是没有其他参数的,只是启动命令,所以进入第一个判断,参数长度为1。
![null caf2f5c84781d617d6a87956332c7df5.png](https://i-blog.csdnimg.cn/blog_migrate/fe4efea56bc539819b9aba24c5735434.png)
然后最终跟我们上面说的一样,调用到了cmd.Run
。
![null e59786b208ecdff26ff40bad871b69fc.png](https://i-blog.csdnimg.cn/blog_migrate/63472e8de6b2bffb534cad6b415879f1.png)
然后直接进入runCmd
方法中看服务如何启动的。
这里干了几件事情,首先是初始化日志的配置、静态配置,然后解析静态配置为 json,然后就是很关键的两个步骤,一个是setupServer
初始化 server,之后是 server 的启动。
![null 145c972f407869a993bce8d60f7ee043.png](https://i-blog.csdnimg.cn/blog_migrate/0c545061d85359616326c126abd185c7.png)
这里可以看一下静态配置到底都有些什么东西,因为我们没有配置 AcessLog、Metric 这些东西,所以都是空的,其实关键就两个东西,一个是 EntryPoints,另外一个是 Provider。
![null 2cfd9a919a1606b18bacefb14d33af7b.png](https://i-blog.csdnimg.cn/blog_migrate/1882acb84a6ac55d2ee9fc77b3ab20d7.png)
这里我们本地启动通过 CRD 的方式,入口点只是默认的配置。
![null 1081263f98a48b6be06e04aab467a8cc.png](https://i-blog.csdnimg.cn/blog_migrate/028a9f48b851d83121571ad022917f16.png)
Server初始化
![null 3b95b3a17d01f361816aa817f1b0c174.png](https://i-blog.csdnimg.cn/blog_migrate/c604f3ff2b0c16b7feab40cd7690eb50.png)
然后我们直接看 setupServer
都干了些什么,这个方法非常长,我们忽略调一些无关的细节,只看重点的部分。
首先创建 provider 的聚合器,其实就是服务发现的提供者,我们知道有很多,像是文件、Docker、K8S、ECS、Consul 等等很多,在之前我们就提到过这个,这里我们主要是 CRD。
ACME 是 HTTPS 证书相关的东西,可以忽略他。
然后是关于server.NewTCPEntryPoints
,用于构建入口点的逻辑,这个很关键。
![null cf825008f1bf1bf9fb9f799cb25c4ace.png](https://i-blog.csdnimg.cn/blog_migrate/8938d0695f84269f8b5cdb99dc3fcd98.png)
接着是创建 Provider 的插件,这个X509Source
又是关于证书的一些逻辑,主要是使用 SPIFFE (Secure Production Identity Framework For Everyone) 获取证书,以便与其他服务进行安全通信,这个不是重点,忽略。
![null 4a584fd978c8a7dcc28448382e6fc0d2.png](https://i-blog.csdnimg.cn/blog_migrate/63ec7d72b9866743ed325f596305413f.png)
接着往下走是几个比较关键的 Factory,一个是managerFactory
,另外一个则是routerFactory
,这两个工厂会构建出后续请求的流程链路,放到后面再说。
![null db3c65309e6b3c72bdae975e6f05f5d2.png](https://i-blog.csdnimg.cn/blog_migrate/38346a5136e5f856c5701691c621e884.png)
这里的 watcher 也是一个关键点,添加了关于 TLS、Metrics、Server Transports 和 Switch Router 的动态配置的监听。
这里的switchRouter
是一个很重要的方法,待会儿再细看。
![null c573b9a2fd5bdee470dfb3bbd705e24e.png](https://i-blog.csdnimg.cn/blog_migrate/ee688eb985f599d3c4878a66a138d9d7.png)
最终流程走完到最终执行NewServer
方法创建 Server,配置初始化 Server 的流程结束。
![null 577790209836f203bb80bcbbb27881cf.png](https://i-blog.csdnimg.cn/blog_migrate/8e2ada503977e5faf7befd3486b671fc.png)
配置监听
![null 0ab33d3d2cddb124225224fca0b312cb.png](https://i-blog.csdnimg.cn/blog_migrate/95552024f5ed10fb29b158d66d7c2c50.png)
这个流程看完之后,回到最开始的地方,开始启动 Server。
![null 01ca195a230c44a7b2401d9a0b3d6bf6.png](https://i-blog.csdnimg.cn/blog_migrate/9a7ed03eda2cbb8f954582fa0105a787.png)
这里主要关注两个方法,一个是 TCP 入口点的启动,另外一个则是监听 watcher 的启动,首先看tcpEntryPoints.Start
。
这里可以看到入口点默认是两个,一个是 traefik 自己默认的,另外一个是处理 web 请求的入口点,之后开启协程进入 serverEntryPoint.Start(ctx)
。
![null 94c3f95f34d2edd0dc17ca9d1c64143d.png](https://i-blog.csdnimg.cn/blog_migrate/8bfc0cf52f1cfcf57d67b98e682e2f6c.png)
这里主要是监听连接,当有新连接到达时,创建一个新的 writeCloser
对象,并在其上设置读/写超时。接下来,使用 e.switcher.ServeTCP
处理新连接,如果出现错误,则会记录错误日志并将错误转发到 e.httpServer
和 e.httpsServer
的 channel。
![null 83d94b893b6c5646e9e31d0c2ad8dc5c.png](https://i-blog.csdnimg.cn/blog_migrate/51a098196105f8d059938d5e82b3a8fe.png)
之后就进入 watcher 监听的启动方法,主要实现了 3 个方法。
![null 17d7bbf7edf5a7e2c81bafe74f4f7d8a.png](https://i-blog.csdnimg.cn/blog_migrate/c94e6a91e62b3820f5ff563e978da4a8.png)
1. receiveConfigurations 是用于接收配置的变化,并且发送到消费者
2. applyConfigurations 是合并配置、应用配置
3. 启动 Provider 的聚合器,做服务发现
![null d4e302d1c949c56ae6be89db6f7fc955.png](https://i-blog.csdnimg.cn/blog_migrate/4138751e868d71b5dcb594ed2c71b25d.png)
receiveConfigurations
主要是死循环监听配置的变更,新的配置会发送到 output 的 channel。
![null 8bd2844cc6741c2a1086c1065c9a918c.png](https://i-blog.csdnimg.cn/blog_migrate/a2d9d8b687ac42f57f70d20d2c17182a.png)
applyConfigurations
则是收到新的配置之后,合并然后应用配置,具体合并的细节就不看了,忽略他。
![null d45cebad9f2c493e3e27460dcffcb0c1.png](https://i-blog.csdnimg.cn/blog_migrate/1ecbe84a21cdf925bd0e12bdabe7ffc4.png)
provider 的聚合器是根据不同的 provider 提供了不同的实现,这里我们是使用 K8S CRD 的方式,所以待会儿进去看这个实现即可。
![null a20f39f66146603bdfa9b28489d865ba.png](https://i-blog.csdnimg.cn/blog_migrate/7445d55e2ff5836967d517b6f13cca58.png)
首先创建一个 K8S client 客户端,用于和 K8S 集群进行通信,然后使用该客户端创建一个事件通道。
接下来,使用WatchAll
监听所有配置的 namespace 的变化。
![null e4e822832a504f0a90737b85fdb51eed.png](https://i-blog.csdnimg.cn/blog_migrate/fc403b098a419e51791c899ec5015d29.png)
这里 debug 能看到现在我们只配置了一个默认的 namespace。
![null f908269d37f900f8397e89cecb9dc17e.png](https://i-blog.csdnimg.cn/blog_migrate/476ba47272aedf72c65ffac167b95d3c.png)
在第一次启动的时候,默认调用loadConfigurationFromCRD
方法从 CRD 加载配置,之后如果配置发生变化,走到 default 分支,变更的配置会发送到 configurationChan
。
![null b67734eecac0f0560d8da3a12a5f245d.png](https://i-blog.csdnimg.cn/blog_migrate/f58f4f1604913cf6f1ee94ba7ddc7716.png)
这个 channel 的定义其实就是在方法入口的ConfigurationWatcher
。
![null 8a690d00fdaf390a4f0a04ba20f5024a.png](https://i-blog.csdnimg.cn/blog_migrate/a7ab9b66956afef5b1b45afe1e05c10b.png)
查看 ConfigurationWatcher 结构体的定义,找到allProvidersConfigs
,定义的就是我们发送到的 channel 了。
![null c565ccb1d0833df2c8e20a1d3bd88d95.png](https://i-blog.csdnimg.cn/blog_migrate/1deea778eb1473510caf6431b892e01e.png)
看到这里,会有疑问,那么发送的配置在什么地方进行消费了呢?
答案就是上面我们已经讲过的receiveConfigurations
,而receiveConfigurations
消费到配置变更之后,又会发送消息到newConfigs
,然后applyConfigurations
方法进行消费,然后处理配置、进行合并、应用。
服务发现
然后让我们回到 loadConfigurationFromCRD
这个方法中,找到loadIngressRouteConfiguration
方法,这里就是启动的时候去做服务发现的地方。
![null 79c490790df36f7cc2d79c5fcc788953.png](https://i-blog.csdnimg.cn/blog_migrate/ee1aaaaceddc603ae0100347a3f847a9.png)
进入这个方法就能看到通过 K8S 的客户端去获取配置的 IngressRoute。
![null 95500b0314a0c4920e95089c98ba6ba6.png](https://i-blog.csdnimg.cn/blog_migrate/11acde7c5782826eda6ccb0e5d69cee2.png)
进入这个方法,发现也是确实如此。
![null e55d9b8440c3333fb37ad1520ca9d670.png](https://i-blog.csdnimg.cn/blog_migrate/e81068761e0a0e0c4fc3f2d853e9ae9c.png)
这里 DEBUG 可以看到我们日志的 IngressRoute 的信息。
![null 6aef02c5ece17959b81ebfd363a5c5c0.png](https://i-blog.csdnimg.cn/blog_migrate/208b8a0434e6b86b8d301bfe6d1f8aa6.png)
往下看还有一个比较有意思的地方,如果说路由中配置的 service 数量超过 1,那么会默认会变成权重负载均衡。
![null 16b6631b81ef647b82a578252bf6bfa6.png](https://i-blog.csdnimg.cn/blog_migrate/b8d7459c94a33fcd4295736aaf9a3acf.png)
接着往下看buildServicesLB
的方法,其实和 service==1 的情况一样,只是循环去调用了nameAndService
方法而已。
![null c8d9087fe99335273b79dc6bd04b314d.png](https://i-blog.csdnimg.cn/blog_migrate/e4389f623e0d189f155909c007f60466.png)
进入这个方法这里有两个判断,针对类型是 Service 和 TraefikService的,Service 类型是 K8S 的类型,所以会去直接获取 server 的信息,而如果配置的是 TraefikService,实际上只是根据 TraefikService 获取 Service 名字,最终还是会走到 Service 的判断逻辑上。
![null a2fca593f552ca9fa8f6605bd7eadf07.png](https://i-blog.csdnimg.cn/blog_migrate/cebb8e829983bd484f3b2974c557a32e.png)
创建负载均衡代码的关键在于第一行loadServers
。
![null 31ad8e26e67a97bb498c393cfb6b327d.png](https://i-blog.csdnimg.cn/blog_migrate/e060d8694f3892503db083d0adf9e43f.png)
进入这个方法可以看到其实就是通过 K8S 的客户端去调用 API 接口,获取到了 service 的信息,还有端口、后端服务的地址。
![null f2b1bb20b70701cc7003db81b028993e.png](https://i-blog.csdnimg.cn/blog_migrate/3d9c2e40f6d49564ee5db347e56b1705.png)
![null a88e21b02462934b9c961345069512eb.png](https://i-blog.csdnimg.cn/blog_migrate/9113b45e52ba9560648f98eebb222ed2.png)
那么,到这里基本服务发现的逻辑就结束了。
在这里顺便说点事情,最近可能大家觉得公共号没啥更新了,其实情况并不是这样。
首先一点确实是公众号目前情况不佳,阅读下滑,这个也就那样吧。
另外一点是最近几个月的重心有工作、生活两方面吧。
工作研究性质的东西会比较多一点,比如最近在学 GO,研究 Traefik,所以就写了 Traefik 的一个专栏,这个玩意儿如果想在生产使用看我这个肯定没啥问题,你在网上几乎都搜不到像样的文章,这东西我们研究了好几个月了,所以我才能写出来。
而文章更新因为有了星球,所以内容会偏向星球居多一点。
当然你要说就想免费的也行啊,但是大家都知道免费的其实最贵。
还有一点是包括抖音等平台的视频更新,虽然不多,但是也有快2万粉丝了,这个我几乎佛系更新的情况下居然这么猛是我没想到的,也欢迎大家关注下。
SO,我欢迎大家不缺个100的话,可以加入星球,查看完整 Traefik 云原生网关系列,目前我已经更新了两个专栏,Traefik 系列也即将进入尾声,Q4还会有持续的更新计划。
其他的一些内容是我之前写的文章重新整理,还有后续的补充,都会在里面。
嗯,大概就是这样,我始终相信真正有价值的东西不需要太多的营销话术,仅仅是信任就行了,你懂我不会吹。
OK,就这样,感谢大家。
另外,大家也可以关注一下我的抖音,内容不多,粉丝到是有快2万了。