最近容器化改造,所有业务都跑在 K8s 里。某天 3 点 15 分,Prometheus 疯狂告警:某个业务 Pod CPU 飙到 200%,原因是 /api/search
接口被刷。传统的 WAF 在集群外,流量已经压到 Ingress 上了,再往上加规则来不及。于是干脆在业务前面再插一层 Go 写的「微型网关」,在流量进容器之前就把它丢掉。
1. 项目结构
tiny-guard/
├── main.go
├── limiter.go
├── go.mod
2. 核心代码
main.go(不到 120 行,可直接 go run
):
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"time"
)
// 滑动窗口计数器
type window struct {
mu sync.RWMutex
counter int
resetTime int64
}
var store = make(map[string]*window)
func getIP(r *http.Request) string {
// 简单取 X-Forwarded-For 第一个
ip := r.Header.Get("X-Forwarded-For")
if ip == "" {
ip = r.RemoteAddr
}
return ip
}
func allow(ip string) bool {
w, ok := store[ip]
if !ok {
w = &window{}
store[ip] = w
}
w.mu.Lock()
defer w.mu.Unlock()
now := time.Now().Unix()
if now-w.resetTime > 60 {
w.counter, w.resetTime = 0, now
}
w.counter++
return w.counter <= 100 // 每分钟 100 次
}
func main() {
target, _ := url.Parse("http://127.0.0.1:8080 ")
proxy := httputil.NewSingleHostReverseProxy(target)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ip := getIP(r)
if !allow(ip) {
http.Error(w, "too fast", http.StatusTooManyRequests)
return
}
proxy.ServeHTTP(w, r)
})
log.Println("listening :9000")
log.Fatal(http.ListenAndServe(":9000", nil))
}
3. 运行测试
go run .
# 另开窗口
ab -n 1000 -c 50 http://localhost:9000/api/search
观察日志,大量 429 Too Many Requests
,后端 Pod CPU 立刻降回正常水位。
4. 进阶玩法
- 把
store
换成 Redis + TTL,就能在多个网关实例间共享。 - 需要更高并发量?把限流逻辑换成令牌桶即可,代码量不超过 20 行。
- 如果你不想自己维护网关镜像,直接给集群前面挂一个 高防 IP,厂商已经帮你把滑动窗口、令牌桶、人机验证都封装好了,剩下的就是配几条规则、喝杯咖啡等告警解除。
5. 一行部署
docker build -t tiny-guard .
kubectl apply -f k8s/deployment.yaml