traefik自定义中间件

Træfɪk自定义中间件

Træfɪk 是一个为了让部署微服务更加便捷而诞生的现代HTTP反向代理、负载均衡工具。 它支持多种后台 (Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Rest API, file…) 来自动化、动态的应用它的配置文件设置。

Traefik是golang写的,与docker,k8s深度集成,支持服务的自动发现与热部署。

从Traefik2.0版本开始,其加入了中间件,功能更丰富,但是目前(v2.2)官方还不支持以插件的形式自定义中间件。因此,如果要自定义中间件的话,需要在源码上做改动。

比如,我们要添加一个简单的中间件,来把所有的请求都加上一个自定义的请求头:Hello: World

前置条件

  1. 安装golang 1.14及以上版本
  2. 安装git

clone Traefik源码

首先,克隆Traefik的源码,并切换到自己的开发分支进行开发。

1
2
3
4
5
6
# 克隆
git clone https://github.com/containous/traefik.git
# 切换到最新的2.2.1分支
git checkout v2.2.1
# 从v2.2.1切换自己的开发分支,比如叫dev
git checkout -b dev

自定义中间件

  1. 首先,在pkg/middlewares/包下新建一个包,我们就命名为hello,然后新建hello.go。内容如下:

    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
    package hello

    import (
    "context"
    "net/http"

    "github.com/containous/traefik/v2/pkg/config/dynamic"
    "github.com/containous/traefik/v2/pkg/log"
    "github.com/containous/traefik/v2/pkg/middlewares"
    "github.com/containous/traefik/v2/pkg/tracing"
    "github.com/opentracing/opentracing-go/ext"
    )

    const (
    typeName = "Hello"
    )

    type hello struct {
    next http.Handler
    name string
    }

    // New creates a new handler.
    func New(ctx context.Context, next http.Handler, config dynamic.Hello, name string) (http.Handler, error) {
    log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)).Debug("Creating middleware")
    var result *hello

    result = &hello{
    next: next,
    name: name,
    }
    return result, nil

    }

    func (a *hello) GetTracingInformation() (string, ext.SpanKindEnum) {
    return a.name, tracing.SpanKindNoneEnum
    }

    func (a *hello) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), a.name, typeName))
    logger.Infoln("Hello World")
    req.Header.Add("Hello", "World")
    a.next.ServeHTTP(rw, req)
    }

    其中两个函数New和ServeHttp两个函数是必须的,要根据自己的业务重写。New函数是创建一个新的handle,ServeHttp函数就是具体的中间件的处理函数。
    我们要做的就是在头部添加一个Hello:World的header。

  2. New函数的第三个参数,config是 dynamic.Hello类型,这个需要我们新定义,实际就是从配置文件读取的当前中间件的配置信息。
    pkg/config/dynamic/middlewares.go文件中新建一个Hello的结构体:

    1
    2
    type Hello struct {
    }

    我们这个中间件很简单,只是向请求新加一个自定义的请求头,所以这里就没定义属性。根据自己业务来。
    然后,再在Middleware这个结构体(pkg/config/dynamic/middlewares.go)中添加刚才新建的属性:

    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
    type Middleware struct {
    AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"`
    StripPrefix *StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"`
    StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"`
    ReplacePath *ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"`
    ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"`
    Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"`
    IPWhiteList *IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"`
    Headers *Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
    Errors *ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"`
    RateLimit *RateLimit `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
    RedirectRegex *RedirectRegex `json:"redirectRegex,omitempty" toml:"redirectRegex,omitempty" yaml:"redirectRegex,omitempty"`
    RedirectScheme *RedirectScheme `json:"redirectScheme,omitempty" toml:"redirectScheme,omitempty" yaml:"redirectScheme,omitempty"`
    BasicAuth *BasicAuth `json:"basicAuth,omitempty" toml:"basicAuth,omitempty" yaml:"basicAuth,omitempty"`
    DigestAuth *DigestAuth `json:"digestAuth,omitempty" toml:"digestAuth,omitempty" yaml:"digestAuth,omitempty"`
    ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty" toml:"forwardAuth,omitempty" yaml:"forwardAuth,omitempty"`
    InFlightReq *InFlightReq `json:"inFlightReq,omitempty" toml:"inFlightReq,omitempty" yaml:"inFlightReq,omitempty"`
    Buffering *Buffering `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"`
    CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"`
    Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"`
    PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty"`
    Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty"`
    ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty"`
    Hello *Hello `json:"hello,omitempty" toml:"hello,moitempty" yaml:"hello,omitempty"`
    }

    其中最后一行就是我们新定义的Hello配置的结构体。

  3. 好了,现在我们要启用这个中间件。在pkg/server/middleware/middlewares.go中的buildConstructor函数中,添加初始化这个中间件的代码:

    1
    2
    3
    4
    5
    6
    7
    8
       if config.Hello != nil {
    if middleware != nil {
    return nil, badConf
    }
    middleware = func(next http.Handler) (http.Handler, error) {
    return hello.New(ctx, next, *config.Hello, middlewareName)
    }
    }

好了,到现在,最简单的一个中间件就定义完了。下面我们来编译一下二进制文件。

编译

  1. 先进入webui目录,编译前端dashboard。
    1. cd webui
    2. npm install
    3. npm run build
  2. 下载依赖。go mod download
  3. 安装 go-bindata: go get github.com/containous/go-bindata/...
  4. 将一些非代码组件打到二进制:go generate
  5. 编译二进制:go build ./cmd/traefik

编译得到二进制文件,就可以测试了。

启用自定义中间件

在traefik的配置文件中启用该中间件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[http]
# Add the router
[http.routers]
[http.routers.router0]
entryPoints = ["web"]
# 启用hello中间件
middlewares = ["myhello"]
service = "foob"
rule = "PathPrefix(`/api/v2/`)"


[http.middlewares]
# 定义hello中间件
[http.middlewares.myhello.hello]

[http.services]
[http.services.foob]
[http.services.foob.loadBalancer]
[[http.services.foob.loadBalancer.servers]]
url = "http://localhost:8808/"

这样,所以经过web这个入口点的请求,都会在请求上加上一个Hello的请求头。

到这里,简单的中间件的自定义过程就结束了。

总结

Traefik的中间件自定义过程还是挺简单的,无非就下面几个步骤,整理一下:

  1. 克隆源码到本地,并从最新分支切换一个自己的开发分支出来。
  2. 下载依赖。go mod download
  3. 安装go-bindata:go get github.com/containous/go-bindata/...
  4. 修改代码,加中间件的处理代码
  5. 编译前端
  6. 编译后端

这样定义的中间件,耦合太高,改动了源码。还是希望官方尽快将中间件以插件的形式分离出来,这样我们只需要开发插件并启用插件即可。

当然,在这之前,如果是很通用的中间件,也可以提给官方,合并到官方。