traefik源码解析(1)配置解析

1,907 阅读2分钟

traefik

go语言实现的api网关、具备路由热更新、熔断、限流的功能

github: traefik

本次源码分析基于2.1.3

traefik 配合分为静态配置和动态配置

  • 静态配置:在运行期间不会改变的配置,比如监听的端口、日志配置等
  • 动态配置:在运行期间会改变的配置,traefik会定时拉取更新,比如路由配置、限流和熔断的配置等

静态配置加载解析

cmd/traefik/traefik.go

func main() {
    // traefik config inits
    tConfig := cmd.NewTraefikConfiguration()

    // 这里支持三种静态配置的加载方式:file、flag、env 我们这里以文件方式来举例分析,其他的两种方式大家可以自己阅读源码,相对难度较小
    loaders := []cli.ResourceLoader{&cli.FileLoader{}, &cli.FlagLoader{}, &cli.EnvLoader{}}

    cmdTraefik := &cli.Command{
        Name: "traefik",
        Description: `Traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
Complete documentation is available at https://traefik.io`,
        Configuration: tConfig,
        Resources:     loaders,
        Run: func(_ []string) error {
            return runCmd(&tConfig.Configuration)
        },
    }

    err := cmdTraefik.AddCommand(healthcheck.NewCmd(&tConfig.Configuration, loaders))
    if err != nil {
        stdlog.Println(err)
        os.Exit(1)
    }

    err = cmdTraefik.AddCommand(cmdVersion.NewCmd())
    if err != nil {
        stdlog.Println(err)
        os.Exit(1)
    }

    // 这里traefik的启动是以命令的方式来执行的
    err = cli.Execute(cmdTraefik)
    if err != nil {
        stdlog.Println(err)
        logrus.Exit(1)
    }

    logrus.Exit(0)
}

pkg/cli/commands.go

func run(cmd *Command, args []string) error {
    ...

    // 这里面会遍历三种静态配置加载方式,如果一个加载成功,则返回,下面我们主要看下file 方式静态配置的加载方式
    // resource.Load 最终调用pkg/cli/loader_file.go(file方式)
    for _, resource := range cmd.Resources {
        done, err := resource.Load(args, cmd)
        if err != nil {
            return err
        }
        if done {
            break
        }
    }

    return cmd.Run(args)
}

pkg/cli/loader_file.go

func (f *FileLoader) Load(args []string, cmd *Command) (bool, error) {
    ...

    // 这个地方读取配置文件, 这里返回的是配置文件的路径,同时在内部实现了文件内容解析
    // 具体实现在下面的loadConfigFiles函数
    configFile, err := loadConfigFiles(ref[configFileFlag], cmd.Configuration)
    if err != nil {
        return false, err
    }

    f.filename = configFile

    if configFile == "" {
        return false, nil
    }

    logger := log.WithoutContext()
    logger.Printf("Configuration loaded from file: %s", configFile)

    content, _ := ioutil.ReadFile(configFile)
    logger.Debug(string(content))

    return true, nil
}

// loadConfigFiles tries to decode the given configuration file and all default locations for the configuration file.
// It stops as soon as decoding one of them is successful.
func loadConfigFiles(configFile string, element interface{}) (string, error) {
    // 静态文件搜索目录和顺序
    finder := Finder{
        BasePaths:  []string{"/etc/traefik/traefik", "$XDG_CONFIG_HOME/traefik", "$HOME/.config/traefik", "./traefik"},
        Extensions: []string{"toml", "yaml", "yml"},
    }

    filePath, err := finder.Find(configFile)
    if err != nil {
        return "", err
    }

    if len(filePath) == 0 {
        return "", nil
    }

    // 这里是解析文件内容生成配置的地方
    // 最终调用pkg/config/file/file.go
    if err = file.Decode(filePath, element); err != nil {
        return "", err
    }
    return filePath, nil
}

pkg/config/file/file.go

这里是文件解析的具体代码,已toml文件为例 首先解析toml文件为map, 然后使用反射将配置解析出来,这块代码如果大家感兴趣,可以自己阅读,难度不大。 同时,因为配置解析是很多项目的必备模块,大家以后设计配置解析的时候,可以参考这块的实现方式

func Decode(filePath string, element interface{}) error {
    if element == nil {
        return nil
    }

    filters := getRootFieldNames(element)

    root, err := decodeFileToNode(filePath, filters...)
    if err != nil {
        return err
    }

    err = parser.AddMetadata(element, root)
    if err != nil {
        return err
    }

    return parser.Fill(element, root)
}

动态配置

篇幅有点长,下一篇单独写吧

编程、面试交流

章鱼编程