traefik
# 1.概览
# 1.1.traefik
traefik是一款反向代理和负载均衡软件,支持自动负载均衡微服务实例,提供http/https/tcp/udp协议支持。作为云原生网关,traefik支持大量后端实现,具备服务发现、配置动态加载、中间件可拔插的特点,目前广泛用于云原生和容器化场景。
# 1.2.entrypoints
entrypoints是网络入口点,用于配置端口和协议,入口点需关联唯一标识符,支持划分不同的访问规则和路由策略。## Static configuration entryPoints: web: address: ":80" websecure: address: ":443" streaming: address: ":1704/udp"1
2
3
4
5
6
7
8
# 1.3.routers
routers是网络路由,负责将请求连接到对应的服务,转发过程可以基于一些中间件修改请求信息,或者将请求转发给服务前采取行动。## Dynamic configuration http: routers: my-router: rule: "Path(`/foo`)" service: service-foo1
2
3
4
5
6
# 1.4.middlewares
middlewares是中间件,用于转换或处理请求,类似拦截器,traefik内置了很多中间件——日志/重定向/身份认证/限流...,可以根据需要实现。# As a Docker Label whoami: # A container that exposes an API to show its IP address image: traefik/whoami labels: # `foo-add-prefix` 中间件 - "traefik.http.middlewares.foo-add-prefix.addprefix.prefix=/foo" - "traefik.http.routers.router1.middlewares=foo-add-prefix@docker"1
2
3
4
5
6
7
8
# 1.5.services
services是traefik服务,服务均关联唯一标识,可以配置不同的负载均衡规则和健康检查策略,负责声明请求如何到达实际服务。## Dynamic configuration http: services: my-service: loadBalancer: servers: - url: "http://<private-ip-server-1>:<private-port-server-1>/" - url: "http://<private-ip-server-2>:<private-port-server-2>/"1
2
3
4
5
6
7
8
# 2.核心流程
# 2.1.前言
由于本地调试,要求
traefik感知本地配置,将本地配置放在cmd目录,同时命令行参数指定配置位置--configFile=./cmd/traefik.yaml。# traefik.yaml # Traefik 启动配置文件 (静态配置) global: checkNewVersion: true sendAnonymousUsage: true # 定义入口点 entryPoints: web: address: ":80" websecure: address: ":443" # 日志 log: level: DEBUG # 提供者,指定动态配置文件 providers: file: filename: ./cmd/http.yaml watch: true # 开启热加载 --------------------------------------------- # http.yaml # Traefik 动态配置文件 (可热加载) http: routers: api: rule: "Host(`www.test.cn`)" service: svc1 entryPoints: - web services: svc1: loadBalancer: servers: - url: "http://localhost:8999"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40后端
后端采用
docker容器docker run -d -p 8999:80 -it containous/whoami
# 2.2.start
traefik由main函数作为入口,主要进行配置初始化及定义资源加载方式,声明版本检查、健康检查及命名回调,最终执行的就是命令回调。func main() { // 配置初始化 tConfig := cmd.NewTraefikConfiguration() // 资源加载器,由配置文件、命令行参数、环境变量加载配置,基于DeprecationLoader检测不兼容的废弃配置及阻断运行 loaders := []cli.ResourceLoader{&tcli.DeprecationLoader{}, &tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}} // 启动命令 cmdTraefik := &cli.Command{ Name: "traefik", ... Configuration: tConfig, Resources: loaders, Run: func(_ []string) error { // 核心逻辑,用于启动服务 return runCmd(&tConfig.Configuration) }, } // 添加健康检查命令 cmdTraefik.AddCommand(healthcheck.NewCmd(&tConfig.Configuration, loaders)) ... // 添加版本检查命令 cmdTraefik.AddCommand(cmdVersion.NewCmd()) ... // 执行命令 err = cli.Execute(cmdTraefik) ... } // Execute Executes a command. func Execute(cmd *Command) error { return execute(cmd, os.Args, true) } func execute(cmd *Command, args []string, root bool) error { // 由于未指定任何参数,仅有程序默认启动路径,会命中这里 if len(args) == 1 { run(cmd, args[1:]) ... return nil } ... return fmt.Errorf("command not found: %v", args) } func run(cmd *Command, args []string) error { ... // 加载配置(FileLoader->FlagLoader->EnvLoader) for _, resource := range cmd.Resources { done, err := resource.Load(args, cmd) ... // 根据优先级,加载到就退出 if done { break } } // 执行真正启动命令(runCmd) return cmd.Run(args) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63注意
核心逻辑是
traefik注册的runCmd服务,负责真正初始化server和启动server
# 2.3.runCmd
runCmd()负责初始化日志配置,解析静态配置,进而调用steupServer()初始化server及启动server,是traefik的核心逻辑。func runCmd(staticConfiguration *static.Configuration) error { // 初始化日志(输出位置或日志等级) setupLogger(staticConfiguration) // 设置请求代理为系统默认方式 http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment // 配置检查(填充及校验有效性) staticConfiguration.SetEffectiveConfiguration() staticConfiguration.ValidateConfiguration() ... // 版本检查 if staticConfiguration.Global.CheckNewVersion { // 间隔24小时触发一次检查 checkNewVersion() } // 匿名信息统计 stats(staticConfiguration) // 初始化服务 svr, err := setupServer(staticConfiguration) ... // 初始化关联系统信号的ctx ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) // 配置Ping上下文 if staticConfiguration.Ping != nil { staticConfiguration.Ping.WithContext(ctx) } // 启动服务 svr.Start(ctx) defer svr.Close() // 通知systemd服务就绪 sent, err := daemon.SdNotify(false, "READY=1") ... // 检查systemd启动watchdog(systemd基于此检查服务活跃) t, err := daemon.SdWatchdogEnabled(false) ... t /= 2 // 触发心跳检查 safe.Go(func() { // 半周期为定时器 tick := time.Tick(t) for range tick { // 执行健康检查 resp, errHealthCheck := healthcheck.Do(*staticConfiguration) ... // Ping未启用或健康检查通过 if staticConfiguration.Ping == nil || errHealthCheck == nil { // 向systemd发送心跳 daemon.SdNotify(false, "WATCHDOG=1") ... } ... } }) svr.Wait() return nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66注意
runCmd()围绕setupServer()和svr.Start()展开,是ingress外部流量处理的核心逻辑
# 2.4.setupServer
setupServer()会聚合服务发现的提供者,构建traefik的tcp和upd访问入口,构造中间件链及路由工厂,创建配置监听器。func setupServer(staticConfiguration *static.Configuration) (*server.Server, error) { // provider聚合器 providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers) ... // 协程池,用于异步任务管理 routinesPool := safe.NewPool(ctx) ... // TLS相关 // metric相关 // 创建tcp入口 serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints, staticConfiguration.HostResolver, metricsRegistry) ... // 创建udp入口 serverEntryPointsUDP, err := server.NewUDPEntryPoints(staticConfiguration.EntryPoints) ... // 插件构建器 pluginBuilder, err := createPluginBuilder(staticConfiguration) ... // 注册provider插件 for name, conf := range staticConfiguration.Providers.Plugin { ... // 构造插件提供者 p, _ := pluginBuilder.BuildProvider(name, conf) ... // 注册到聚合器 providerAggregator.AddProvider(p) ... } ... // http连接管理器 roundTripperManager := service.NewRoundTripperManager(spiffeX509Source) // tcp连接管理器 dialerManager := tcp.NewDialerManager(spiffeX509Source) // acme http handler acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider) // 服务管理器工厂(核心) managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, observabilityMgr, roundTripperManager, acmeHTTPHandler) // 路由器工厂(核心) routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager) // 服务配置监听器 watcher := server.NewConfigurationWatcher( routinesPool, providerAggregator, getDefaultsEntrypoints(staticConfiguration), "internal", ) // TLS监听器(证书轮转) watcher.AddListener(func(conf dynamic.Configuration) { ... // 更新证书 tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates) ... }) ... // http/tcp transport配置监听器 watcher.AddListener(func(conf dynamic.Configuration) { roundTripperManager.Update(conf.HTTP.ServersTransports) dialerManager.Update(conf.TCP.ServersTransports) }) // 路由监听器 watcher.AddListener(switchRouter(routerFactory, serverEntryPointsTCP, serverEntryPointsUDP)) ... // tls相关路由监听以更新验证逻辑 watcher.AddListener(tlsChallengeProvider.ListenConfiguration) ... // acme提供者的动态配置更新 for _, p := range acmeProviders { resolverNames[p.ResolverName] = struct{}{} watcher.AddListener(p.ListenConfiguration) } // tailscale提供者的动态配置更新 for _, p := range tsProviders { resolverNames[p.ResolverName] = struct{}{} watcher.AddListener(p.HandleConfigUpdate) } ... // server返回 return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95注意
setupServer()核心是创建provider聚合器、构造tcp/udp server及注册watcher,以监听配置变化、初始化入口点
# 2.5.srvStart
srv.Start()会启动tcp/udp入口点及watcher监听,以动态更新配置及处理请求,实现请求反向代理。// Start starts the server and Stop/Close it when context is Done. func (s *Server) Start(ctx context.Context) { go func() { <-ctx.Done() s.Stop() }() // 启动tcp入口 s.tcpEntryPoints.Start() // 启动udp入口 s.udpEntryPoints.Start() // 配置监听 s.watcher.Start() // 退出信号监听 s.routinesPool.GoCtx(s.listenSignals) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
注意
三个核心方法都是异步的,前两个用于处理
tcp/udp请求,watcher用于监听配置变化及同步