快速入门
安装
更新helm
helm repo add traefik https://traefik.github.io/charts
helm repo update
下载最新的chart包并安装
h pull traefik/traefik --untar
h install traefik ./traefik -n traefik -f traefik/values.yaml
查看Dashboard
查看安装的 CRD
查看应用
其实就一个deployment,没有其他模块
修改上面部署的 helm-chart 的 values.yaml 文件
# values.yaml
ingressRoute:
dashboard:
enabled: true
matchRule: Host(`dashboard.localhost`)
entryPoints:
- web
providers:
kubernetesGateway:
enabled: true
gateway:
listeners:
web:
namespacePolicy:
from: All
将默认的 dashboard 暴露出来,升级完成之后查看
➜ ~ k get ingressroute traefik-dashboard -oyaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
annotations:
meta.helm.sh/release-name: traefik
meta.helm.sh/release-namespace: traefik
creationTimestamp: "2026-04-17T07:03:46Z"
generation: 1
labels:
app.kubernetes.io/instance: traefik-traefik
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: traefik
helm.sh/chart: traefik-39.0.7
name: traefik-dashboard
namespace: traefik
resourceVersion: "45525861"
uid: bb8a0425-09d8-4ec0-9149-0b18356b3d00
spec:
entryPoints:
- web
routes:
- kind: Rule
match: Host(`dashboard.localhost`)
services:
- kind: TraefikService
name: api@internal
已经暴露出了一个 dashboard 的 ingressroute,访问地址 dashboard.localhost/dashboard/
便能看到类似这样的展示信息
部署应用
我们创建一个应用,涉及 deployment 以及 service 和 ingressroute
# whoami.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
namespace: traefik
spec:
replicas: 2
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
ports:
- containerPort: 80
---
# whoami-service.yaml
apiVersion: v1
kind: Service
metadata:
name: whoami
namespace: traefik
spec:
ports:
- port: 80
selector:
app: whoami
---
# whoami-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: whoami
namespace: traefik
spec:
entryPoints:
- web
routes:
- match: Host(`whoami.localhost`)
kind: Rule
services:
- name: whoami
port: 80
测试访问
➜ ~ k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik LoadBalancer 10.105.235.195 <pending> 80:31799/TCP,443:32037/TCP 25m
whoami ClusterIP 10.96.42.170 <none> 80/TCP 3m
因为测试环境没有 LoadBalancer 的 IP,我们访问 clusterip 通过指定 Host 的方式能够得到预期的输出
部署方式对比
Traefik 支持两种部署方式
第一种:对接 k8s 的 Gateway 资源,利用 GatewayClass ➕ Gateway ➕ HTTPRoute
第二种:利用 Traefik 自带的 IngressRoute
区别显而言之,用 IngressRoute 会更加方便,相当于一个 IngressRoute 替换了上面 Gateway 的三个CRD
但是缺点也很明显,用了 Gateway 的话流量控制的力度会更细,比如用 IngressRoute 无法做到按照 header 来做粘性会话,而用 envoy-gateway 的 BackendTrafficPolicy 就能实现按照header的字段来做流量控制
路由策略
路由策略基本就包含两部分内容,分别是会话保持和负载均衡
整体链路如下
会话保持
基本逻辑
基本步骤如下
- 第一次客户端访问的时候在返回体上网关侧会带上cookie:value 相关的值返回
- 第二次客户端只需要带上上次相同的cookie值就能命中上一次同样的后端实例,以此来实现粘性会话
Traefik 目前只支持基于 cookie 的会话保持策略,只需要在指定的 ingressRoute 里面开启即可
services:
- name: whoami
port: 80
sticky:
cookie:
name: test-cookie
httpOnly: false
secure: false
比如我现在访问
则会返回一个 Set-Cookie 的选项,下次请求直接指定这个 cookie 的 header 的选项,则能实现粘性会话
curl -H 'Host: whoami.localhost' -b 'test-cookie=57532e7592b9a5ef' http://10.105.235.195:80/
多次访问会发现均会返回一个固定的访问地址,以此来实现粘性会话
实现原理
当给 IngressRoute 设置了开启 cookie 的粘性会话功能时,其实有个很有趣的问题:比如我现在网关是多实例,我第一次请求网关实例一处理,然后返回了一个cookie的值;那要是第二次请求到了网关实例二了,网关实例二怎么知道这个请求应该分发到对应的后端哪个实例呢?
就是网关侧有没有全局视野的存储,怎么做到分散的网关实例利用同一个值命中预期的实例呢?
看到代码实现如下,每个 LoadBalancer 其实都会存一个对应的 Value
func (b *DefaultLoadBalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if b.StickyCookie != nil {
cookie, err := req.Cookie(b.StickyCookie.Name)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
log.Warn().Err(err).Msg("Error while reading cookie")
}
if err == nil && cookie != nil {
b.HandlersMu.RLock()
// 这里会判断是否handlerMap里面是否有这个值
handler, ok := b.HandlerMap[cookie.Value]
b.HandlersMu.RUnlock()
if ok && handler != nil {
b.HandlersMu.RLock()
_, isHealthy := b.Status[handler.Name]
b.HandlersMu.RUnlock()
if isHealthy {
handler.ServeHTTP(w, req)
return
}
}
}
}
.......
if b.StickyCookie != nil {
cookie := &http.Cookie{
Name: b.StickyCookie.Name,
Value: Hash(server.Name), // 这里会将 server.Name 做哈希返回
Path: "/",
HttpOnly: b.StickyCookie.HttpOnly,
Secure: b.StickyCookie.Secure,
SameSite: convertSameSite(b.StickyCookie.SameSite),
MaxAge: b.StickyCookie.MaxAge,
}
http.SetCookie(w, cookie)
}
server.ServeHTTP(w, req)
}
哈希的代码如下
func Hash(input string) string {
hasher := fnv.New64()
// We purposely ignore the error because the implementation always returns nil.
_, _ = hasher.Write([]byte(input))
return strconv.FormatUint(hasher.Sum64(), 16)
}
看到这里就比较明确了,每个后端的 server 的 Name 通过哈希的方式存入到 traefik 的map里面,那只要 server 的名字不变,我任何网关实例同样哈希出来的值肯定就是一样的
另外在 k8s 集群部署的方式,这个 server.Name 其实就是每个后端service对应的 endpoint
负载均衡
Traefik 的负载均衡主要分为服务层面的四种负载均衡,已经提供了更为高级的service来做灰度发布以及蓝绿部署等,参考文档
服务层面负载均衡
Traefik 默认支持下面四种负载均衡策略
WRR
wrr 是加权轮询的策略,将请求轮询发给后端的实例,代码实现如下
var handler *namedHandler
for {
// Pick handler with closest deadline.
handler = heap.Pop(b).(*namedHandler)
// curDeadline should be handler's deadline so that new added entry would have a fair competition environment with the old ones.
b.curDeadline = handler.deadline
handler.deadline += 1 / handler.weight
heap.Push(b, handler)
if _, ok := b.status[handler.name]; ok {
if _, ok := b.fenced[handler.name]; !ok {
// do not select a fenced handler.
break
}
}
}
注意这里实现加权轮询的逻辑,是每个实例都会塞到到一个堆里面,比如我现在有两个实例
实例 A 的权重 weight 是 3,实例 B 的权重 weight 为 1
则初始的实例 A 的 deadline 为 1/3,实例 B 的 deadline 为 1/1,因为这个最小堆,默认每次都是选 deadline 最小的那个,所以请求模拟如下
第一次请求,选中 A,之后 A.deadline = 1/3 + 1/3 = 2/3, B.deadline = 1/1 = 1
第二次请求,选中 A,之后 A.deadline = 2/3 + 1/3 = 1, B.deadline = 1/1 = 1
第三次请求,因为 A,B deadline 的值一样,则随机选一个,无论选中 A 或者 B,则下一次一定是另外一个
所以 3:1 的权重,这四个请求,一定会有 3 个请求命中 A,1 个请求命中 B
p2c
p2c负载均衡则是随机选择两个后端 server,然后挑选其中一个链接数最少的 server
逻辑如下
func (b *Balancer) nextServer() (*namedHandler, error) {
.......
n1, n2 := b.rand.Intn(len(healthy)), b.rand.Intn(len(healthy))
b.randMu.Unlock()
// 如果随机发现选中的实例一样则错开
if n2 == n1 {
n2 = (n2 + 1) % len(healthy)
}
h1, h2 := healthy[n1], healthy[n2]
// Ensure h1 has fewer inflight requests than h2.
if h2.inflight.Load() < h1.inflight.Load() {
log.Debug().Msgf("Service selected by P2C: %s", h2.name)
return h2, nil
}
.....
}
注意这里链接数的计算方式如下
func (h *namedHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
h.inflight.Add(1)
defer h.inflight.Add(-1)
h.Handler.ServeHTTP(rw, req)
}
就是使用一个原子计数来计算有多少链接还存着
Hrw
Hrw 全称是 Highest Random Weight,使用 IP 来做一致性哈希以此进行会话保持
主要实现函数如下
func (b *Balancer) nextServer(key string) (*namedHandler, error) {
.....
var handler *namedHandler
score := 0.0
for _, h := range healthy {
s := getNodeScore(h, key)
if s > score {
handler = h
score = s
}
}
....
return handler, nil
}
// getNodeScore calculates the score of the couple of src and handler name.
// 这里的 src 就是客户端的 IP
func getNodeScore(handler *namedHandler, src string) float64 {
h := fnv.New64a()
h.Write([]byte(src + handler.name))
sum := h.Sum64()
score := float64(sum) / math.Pow(2, 64)
logScore := 1.0 / -math.Log(score)
return logScore * handler.weight
}
这里 getNodeScore 实际上就实现了一套一致性哈希的方案
确定性:同一个 Client (IP/Key) 总是被映射到同一台后端,无需任何状态存储
均匀性:不同 client 按权重比例分布在各后
最小扰动:增/删一台后端时,只有约 1/N 比例的 key 会被重新映射,其余 client 仍命中原后端(相比"取模 hash"全量重洗,差别巨大)
这种实现不需要传统的一致性哈希一样需要构造大量的虚拟节点,HRW 更加均匀
Leasttime
leasttime 选择响应时间最短且活动连接数最少的服务器路由
实现基本如下
// Score = (avgResponseTime × (1 + inflightCount)) / weight.
func (b *Balancer) nextServer() (*namedHandler, error) {
....
// Calculate scores and find minimum.
minScore := math.MaxFloat64
var candidates []*namedHandler
for _, h := range healthy {
avgRT := h.getAvgResponseTime()
inflight := float64(h.inflightCount.Load())
// 分数 = 响应延迟 * 链接数 / 权重
score := (avgRT * (1 + inflight)) / h.weight
if score < minScore {
minScore = score
candidates = []*namedHandler{h}
} else if score == minScore {
candidates = append(candidates, h)
}
}
if len(candidates) == 1 {
return candidates[0], nil
}
// Multiple servers with same score: use WRR (EDF) tie-breaking.
selected := b.selectWRR(candidates)
if selected == nil {
return nil, errNoAvailableServer
}
return selected, nil
}
// getAvgResponseTime returns the average response time in milliseconds.
// Returns 0 if no samples have been collected yet (cold start).
func (s *namedHandler) getAvgResponseTime() float64 {
....
return s.responseTimeSum / float64(s.sampleCount)
}
先选择每个实例的平均响应时间,然后打分;过滤出打分最低的,如果有相同的最低分的则,则再使用 WRR 轮询选择一个权重最高的
增强的服务类型
TraefikService 提供了比原生的 service 更为高级的实现;
注意 TraefikService 主要运行在 service 层面,而不是实例 server 层面
WRR 和 HRW 和上面的服务的 strategy 实现类似,不再赘述
Mirroring
mirroring 的核心用途是 复制请求到一个或多个“镜像后端”。Traefik 官方对它的定义就是:把发往某个 service 的请求镜像到其他 service
func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// 获取激活的Mirrors
mirrors := m.getActiveMirrors()
....
// 然后clone请求进行转发
m.routinePool.GoCtx(func(_ context.Context) {
for _, handler := range mirrors {
// prepare request, update body from buffer
r := rr.Clone(req.Context())
....
handler.ServeHTTP(m.rw, r.WithContext(contextStopPropagation{ctx}))
}
})
}
FailOver
FialOver 用于请求失败的兜底措施,当主服务响应错误配置中定义的特定 HTTP 状态代码时,故障转移服务会将所有请求转发到备用服务
实现逻辑如下
func (f *Failover) ServeHTTP(w http.ResponseWriter, req *http.Request) {
f.handlerStatusMu.RLock()
handlerStatus := f.handlerStatus
f.handlerStatusMu.RUnlock()
if handlerStatus {
if len(f.statusCode) == 0 {
f.handler.ServeHTTP(w, req)
return
}
....
f.handler.ServeHTTP(rw, rr.Clone(req.Context()))
if !rw.needFallback {
return
}
req = rr.Clone(req.Context())
}
....
}
而兜底的 handler 设置如下
// SetHandler sets the main http.Handler.
func (f *Failover) SetHandler(handler http.Handler) {
f.handlerStatusMu.Lock()
defer f.handlerStatusMu.Unlock()
f.handler = handler
f.handlerStatus = true
}
插件机制
Traefik 有两种类型的插件
- Middleware 插件 — 在请求链路中做处理(认证、限流、header 修改等)
- Provider 插件 — 提供路由配置来源(从自定义数据源发现服务和路由规则)
Middleware
JWT
Jwt 插件的话 Traefik 起始也有两种,一种就是自带的 JWT 的认证,参考文档;另外一种就是开源的插件,可以使用traefik 的 plugin 的功能加载进去使用
内置 JWT
Traefik 内置的 JWT 校验需要提供一个 Identity Provider(身份提供商),参考
建议之前使用开源的 JWT 插件,签发 JWT 的服务自己管理,校验 JWT 则直接使用开源的插件来进行
开源 JWT
配置使用插件,修改 traefik 的启动配置文件;这里我们 jwt 解析校验使用开源的插件
# Traefik experimental features
experimental:
.....
plugins:
jwt:
moduleName: github.com/agilezebra/jwt-middleware
version: v1.3.8
该插件的标注配置如下
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: my-jwt-middleware
namespace: my-namespace
spec:
plugin:
jwt-middleware:
issuers:
- https://auth.example.com # 这里指定颁发的实体方
require:
aud: test.example.com # 这里指定授信的主体
secret: ThisIsAPresharedSecret
我们声明中间件来使用该 jwt 插件
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: secure-api
namespace: traefik
spec:
plugin:
jwt:
issuers:
- my-issuer
skipPrefetch: "true"
secret: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGiavFFe7wfkecNernkl
jptJIqARMeJkpwtNpESqYfvhjwf5r7iFkGx+n3Y/GU7G6Y+3Tw8PDx2bzZSfKM7g
jbGD9cgvHvEfC0eUzg3AUsr2GG3hBmwxOcYIF6HKGn18m9tMNZh6Dh/7pWtdQLNh
hoQqPnorrA/pfAOlivIcBH7t7zBQDz0Ol9cAMtNpT1zva8fVxCzFDKWMDLG4wyE8
iGNVKsfKKNTSwejgP8U8AwDCYKjH++zPUC7HhB7W+YoojxdO3UBA5wLXZOgA8IN/
ddKs28J6T9jR/YStkFQQG1QINm3A6AR90L7n4po2hxIfNrZWCuEGMfcdzMZr51Hu
uwIDAQAB
-----END PUBLIC KEY-----
生成 jwt ,我们借助如下代码生成
// 1. 读取私钥
privPEM, err := os.ReadFile("private-key.pem")
if err != nil {
log.Fatal(err)
}
block, _ := pem.Decode(privPEM)
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
// 如果是 PKCS8 格式
key, err2 := x509.ParsePKCS8PrivateKey(block.Bytes)
if err2 != nil {
log.Fatalf("parse private key failed: %v / %v", err, err2)
}
privateKey = key.(*rsa.PrivateKey)
}
// 2. 生成 JWKS(公钥)
jwk := jose.JSONWebKey{
Key: &privateKey.PublicKey,
KeyID: "my-key-1",
Algorithm: string(jose.RS256),
Use: "sig",
}
jwks := jose.JSONWebKeySet{Keys: []jose.JSONWebKey{jwk}}
data, _ := json.MarshalIndent(jwks, "", " ")
fmt.Println("=== JWKS ===")
fmt.Println(string(data))
// 3. 签发 JWT
signer, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.RS256, Key: privateKey},
(&jose.SignerOptions{}).WithHeader("kid", "my-key-1"),
)
if err != nil {
log.Fatal(err)
}
exp := time.Now().AddDate(10, 0, 0).Unix()
payload := fmt.Sprintf(`{"iss":"my-issuer","sub":"user1","exp":%d}`, exp)
jws, err := signer.Sign([]byte(payload))
if err != nil {
log.Fatal(err)
}
token, _ := jws.CompactSerialize()
fmt.Println("\n=== JWT Token ===")
fmt.Println(token)
注意这里的公钥和私钥要匹配
测试不带 jwt 访问
直接提示没有 token
带上 jwt 访问
访问成功
ForwardAuth
思考一个问题:上面我们使用 JWT 鉴权直接使用的是 Traefik 的开源的插件,如果网关现在需要内部的一些认证措施,还能用插件来解决吗?
答案肯定是不能,即使能,插件利用的是 Go 的解释器来运行,势必会对性能造成损耗的情形
那这里 forwardAuth 这个插件就派上用场了,基本流程如下
# Forward authentication to example.com
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-auth
spec:
forwardAuth:
address: https://example.com/auth
自己编写一个 auth 服务,访问指定路由的时候带上这个 test-auth 的插件则会先处理鉴权的逻辑
RateLimit
限流是所有网关中逃都逃不过的一个重要组件,traefik 的限流是基于令牌桶实现
基本配置如下
这里注意下面这三个关键参数,average & period & burst
average 和 period 共同组成平均速率的表量
average = 100, period=1s 代表的是平均速率=100/1s=100req/s
burst 代表的就是通的容量
本地部署好 redis,使用中间件来连接 Redis 做限流,配置好限流的中间件参数如下
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: ratelimit
namespace: traefik
spec:
rateLimit:
average: 1
period: 2s
burst: 2
redis:
endpoints:
- "rate-limit-redis.traefik.svc.cluster.local:6379"
db: 0 # redis的数据库序号
poolSize: 50
minIdleConns: 10
maxActiveConns: 200
readTimeout: 3s
writeTimeout: 3s
dialTimeout: 5s
故意将 average 和 burst 设置的低一点,尝试请求接口,多请求几次就会有报错出现
源码解析
traefik 利用 Redis 实现分布式限流的基本方法也是使用 lua 脚本
func (r *redisLimiter) Allow(ctx context.Context, source string) (*time.Duration, error) {
ok, delay, err := r.evaluateScript(ctx, source)
...
}
func (r *redisLimiter) evaluateScript(ctx context.Context, key string) (bool, *time.Duration, error) {
....
params := []any{
float64(r.rate / 1000000),
r.burst,
r.ttl,
time.Now().UnixMicro(),
r.maxDelay.Microseconds(),
}
// 这里传递参数到下面的 Luna 脚本里面
v, err := AllowTokenBucketScript.Run(ctx, r.client, []string{redisPrefix + key}, params...).Result()
....
}
var AllowTokenBucketScript = redis.NewScript(AllowTokenBucketRaw)
redisLimiter 暴露出一个 Allow 方法,间接调用 evaluateScript ,再调用一个令牌桶限流的脚本如下
var AllowTokenBucketRaw = `
local key = KEYS[1]
local limit, burst, ttl, t, max_delay = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3]), tonumber(ARGV[4]),
tonumber(ARGV[5])
local bucket = {
limit = limit,
burst = burst,
tokens = 0,
last = 0
}
local rl_source = redis.call('hgetall', key)
if table.maxn(rl_source) == 4 then
-- Get bucket state from redis
bucket.last = tonumber(rl_source[2])
bucket.tokens = tonumber(rl_source[4])
end
local last = bucket.last
if t < last then
last = t
end
local elapsed = t - last
local delta = bucket.limit * elapsed // 计算该补多少令牌,limit就是速率(average/period)
local tokens = bucket.tokens + delta // 加到桶里
tokens = math.min(tokens, bucket.burst) // 令牌数不能大于最大的数量
tokens = tokens - 1 // 每次请求来了消耗一个令牌
local wait_duration = 0
if tokens < 0 then // 桶里令牌不足
wait_duration = (tokens * -1) / bucket.limit // 补回这些令牌需要多少时间
if wait_duration > max_delay then // 超过时间直接拒绝
tokens = tokens + 1 // 抵扣的令牌归还
tokens = math.min(tokens, burst)
end
end
redis.call('hset', key, 'last', t, 'tokens', tokens)
redis.call('expire', key, ttl)
return {tostring(true), tostring(wait_duration),tostring(tokens)}`
上面基本步骤如下
- 每个 key 用一个 Redis Hash 存两个字段:last(上次更新时间) 和 tokens(上次更新后的剩余令牌数);key的值类似这样:
rate:traefik-ratelimit@kubernetescrd:10.39.35.142,存储字段如下
127.0.0.1:6379> HGETALL rate:traefik-ratelimit@kubernetescrd:10.39.35.142
1) "last"
2) "1778038491850036"
3) "tokens"
4) "0.8289999000000002
2. 请求来了先判断这个 key 是否存在历史状态,长度为 4 说明两个字段都在,否则说明是首次访问
-
计算这段时间该补多少令牌,计算完成加到桶里;每次消耗一个令牌
-
如果令牌数够,直接放行
-
如果令牌数不够;算出补这些令牌需要多少时间
-
如果这个等待时间仍然在客户端可接受的范围内,则让客户端等待(返回一个等待时间,上游调用会等待)
-
如果等待时间超过 max_delay,则视为拒绝,把刚才-1 扣减的令牌归还,再保险地用
burst截断防止超容
-
maxDelay 的计算逻辑如下
if config.Average > 0 {
rtl = float64(config.Average*int64(time.Second)) / float64(period)
// maxDelay does not scale well for rates below 1,
// so we just cap it to the corresponding value, i.e. 0.5s, in order to keep the effective rate predictable.
// One alternative would be to switch to a no-reservation mode (Allow() method) whenever we are in such a low rate regime.
if rtl < 1 {
maxDelay = 500 * time.Millisecond
} else {
maxDelay = time.Second / (time.Duration(rtl) * 2)
}
}
maxDelay 的最大值是 500ms
Provider
Provider 也称为服务发现,作用是获取有关路由的相关信息,当 Traefik 检测到更改时,有动态更新路由的功能
目前 Provider 主要有下面四种
- 基于标签:每个部署的容器都有相关的标签
- 基于 kv 键值对:每个部署的容器都更新其相关的键值对信息
- 基于注解:每个部署的容器都有相关的注解定义区分
- 基于文件:使用文件来发现服务,顾名意思就是将配置信息(比如路由,中间件,后端服务等)全部配置在单个或者多个文件里面
目前路由配置来源主要来源以下三种,也是用的比较多的如下
- File provider:YAML、TOML、JSON
- KV stores:Consul、etcd、Redis、Zookeeper
- Kubernetes CRD (IngressRoute)
其中 Kubernetes CRD 是最常用的一种,更接近云原生的使用方式
插件编写
Traefik 的插件本质是用 Yaegi 这个 Go 语言的解释器来执行 Go 代码,既然是解释器,不是编译器,那么执行必定性能有损耗,并且解释器不支持以下用法
- 不能用 cgo
- 不能用 unsafe
- 大部分需要反射底层的库不能使用(比如 encoding/gob 等这些)
注:网关本身是需要很轻量,插件的逻辑不能重,推荐只进行一些header修改以及一些 JWT 的鉴权等这些旁路的逻辑构成插件,其他重逻辑都不推荐使用插件来进行
插件的编写也有严格的标准,一定要暴露下面这几个接口 参考文档
// Package example a example plugin.
package example
import (
"context"
"net/http"
)
// Config the plugin configuration.
type Config struct {
// ...
}
// CreateConfig creates the default plugin configuration.
func CreateConfig() *Config {
return &Config{
// ...
}
}
// Example a plugin.
type Example struct {
next http.Handler
name string
// ...
}
// New created a new plugin.
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
// ...
return &Example{
// ...
}, nil
}
func (e *Example) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// ...
e.next.ServeHTTP(rw, req)
}