Skip to content

Watcher

实现动态配置的更新.

监听各种 Provider, 收集从 Provider 收集的动态配置, 并更新.

  • Start
    • receiveConfigurations
    • applyConfigurations
    • startProviderAggregator

ReceiveConfigurations

接收动态配置文件

  1. 监听 Server Context Done.
  2. 监听 allProvidersConfigs
  3. newConfigurations, 写入 output, 重置 output
  4. logConfiguration 输出动态配置到日志.
  5. reflect.DeepEqual 对比本地动态配置与刚接收到的动态配置是否一致
  6. 将收到的动态配置写入到本地变量newConfigurations
  7. 将 output 绑定到ConfigurationWatcher

ApplyConfigurations

更新动态配置

  1. 监听ConfigurationWatcher.newConfigs
  2. 更新 ConfigurationWatcher.requiredProvider 对应的Privider 写入的动态配置信息.
  3. 合并动态配置和ConfigurationWatcher.defaultEntryPoints
  4. applyModel 实例化动态路由配置
  5. 传递动态配置, 由 listener 执行更新逻辑

StartProviderAggregator

启动聚合提供者, 定时收集 Provider 内的动态配置信息.

  1. 调用 (ProviderAggregator).Provide, 执行每个 provider 的 provide 方法收集动态配置
    1. launchProvider 执行对应 Provider
    2. RemoveCredentials 根据 tag 信息中的loggable 来重置敏感字段为零值
    3. 去敏后的动态配置信息输出到日志
    4. maybeThrottledProvide 执行 Provider 的 Provide 方法

注意事项

DefaultEntryPoints

ConfigurationWatcher.defaultEntryPoints 是对静态配置执行 getDefaultsEntrypoints 获得的所有 name 不为 traefik 且 protocol 为 tcp 的 EntryPoints

func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string {
 var defaultEntryPoints []string
 for name, cfg := range staticConfiguration.EntryPoints {
  protocol, err := cfg.GetProtocol()
  if err != nil {
   // Should never happen because Traefik should not start if protocol is invalid.
   log.WithoutContext().Errorf("Invalid protocol: %v", err)
  }

  if protocol != "udp" && name != static.DefaultInternalEntryPointName {
   defaultEntryPoints = append(defaultEntryPoints, name)
  }
 }

 sort.Strings(defaultEntryPoints)
 return defaultEntryPoints
}

RequiredProvider

ApplyConfigurations 在监听动态配置时, 仅当判断 requiredProvider 对应的配置存在时才会进入下一步.

目前 requiredProvider 对应的 Provider 是 internal .

logConfiguration

仅当 log level 为 DEBUG 的情况下, 才会将动态配置的信息输出到日志.

而且 logConfiguration 发生在接收到 Provider 写入动态配置消息之后, 实际应用到全局之前.

在日志文件看到输出到对应动态配置生效是有一定间隔的, 比如在后续可能发生异常, 或者在传递给消费任务之前, Provider 又传递了新的动态配置, 导致看到的动态配置可能实际并未生效.

即在日志文件中看到动态配置的记录不代表动态配置已经生效.

在 logConfiguration 中, 会深拷贝一份动态配置, 并且重写清空涉敏信息.

涉敏信息包括证书相关信息.

所以不用担心动态配置的修改导致日志中出现关于证书的敏感信息.

但是其余信息如果涉敏, 就需要考虑是否要开启 DEBUG Mode了.

mergeConfiguration

在更新动态配置时, 可能会对多个 Provider 写入的配置无序遍历后写入.

所有多个 Provider 如果存在冲突的配置项, 最终使用的配置项完全是随机的.

并且每次写入后, 冲突的部分, 都可能使用随机的配置项.

traefik 并没有对配置被覆盖的情况进行检测, 所以需要用户想办法留意.

Provide

Provider 的执行顺序按 FileProvider -> Others -> InternalProvider 执行.

其中, InternalProvider 必须在最后执行, 因为需要知晓其他 providers 的加载情况.

同样的, Internal 作为 RequiredProvider , 动态配置必须加载到此条后, 才会进入处理流程.

MaybeThrottledProvide

当 Provider 实现了 throttled 接口且返回的 time.Duration 不为 0 时, 会包装一层限流功能.

限流功能启用后, 会实例化一个 RingChannel , Provider 的所有写入都是基于 RingChannel , 并且保证每次写入都不会阻塞.

每次从 RingChannel 读到消息, 在写入 configurationChan , 会挂起 ThrottleDuration 对应的时间间隔.

合理利用限流功能, 可以减少 Provider 对 CPU 的占用.

Provider 何时执行?

providerAggregator 会在 watch 启动时被调用, 然后调用 FileProvider -> Others Provider -> TraefikProvider.

每次 Provider 只会被providerAggregator调用一次.

FileProvider 在执行 Provide 时会通过fsnotify 监听目录变化来传递配置信息.

DockerProvider 在执行 Provide 后会根据Watch决定是否订阅, 根据SwarmMode 决定订阅的逻辑.

一些想法

动态配置同步生效机制

receiveConfigurations 在接收 Provider 传递的动态消息后, 并不会直接通过channel 传递给 applyConfigurations , 而是通过 output 变量, 在下一次 output 可写的时候, 将缓存的多个 Provider 动态配置一次写入.

一方面, applyConfigurations 生效配置需要时间, 而这个时候, 如果 receiveConfigurations 不通过 output 的方式而是直接写入, 可能会导致阻塞, 无法接收 Provider 传递的新到的动态配置消息.

另一方面, receiveConfigurations 到 applyConfigurations 传递的动态配置, 是各个 Provider 动态配置的集合. 并且是 ConfigurationWatcher.newConfigs 可写入时的最新集合. 那么当 applyConfigurations 在处理完一次的期间, 有 Provider 重复发现多个动态配置信息时, 只会使用最新的动态配置. 这种机制也让 applyConfigurations 一次尽可能处理多个 Provider 并且尽可能不处于已经需要被抛弃的动态配置信息.

RingChannel

RingChannel 是为了在限流功能下, 每次挂起间隔达到后, 尽可能的只消费最新的消息, 丢弃旧消息.

因此, RingChannel 基于 in、out 读写 channel, 以及 buffer 缓存每次写入的消息.

Provider 向 in channel 写入消息后, 消息会被转移到 buffer, 而保证 in channel 可以持续消费来自 Provider 的消息.

buffer 始终能获得来自 Provider 的最新消息.

所以当 output 可写的时候, next 所指向的 buffer, 始终保持新值.