Traefik là gì
Traefik (pronounced traffic) is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy. Traefik integrates with your existing infrastructure components (Docker, Swarm mode, Kubernetes, Consul, Etcd, Rancher v2, Amazon ECS, ...) and configures itself automatically and dynamically. Pointing Traefik at your orchestrator should be the only configuration step you need.
Traefik là một HTTP reverse proxy và load balancer giúp triển khai microservices dễ dàng. Traefik có tính năng tương tự NGINX, với điểm mạnh lớn nhất là khả năng tích hợp, cấu hình tự động với hệ thống đã chạy như Kubernetes, Docker, ...
Traefik được viết bằng Go, về hiệu năng chưa có gì vượt trội so với NGINX, nhưng có nhiều tính năng hiện đại như: tích hợp HTTPS dễ dàng, hỗ trợ sẵn dashboard, metrics, rate limit,...
Build Traefik từ source
Tạo file build.sh
git clone https://github.com/traefik/traefik --depth 1 --branch v3.5
cd traefik
touch webui/static/index.html
make dist
/usr/bin/time -v make binary
./dist/linux/amd64/traefik version
Chạy để build với go1.24:
bash build.sh
Cloning into 'traefik'...
remote: Enumerating objects: 2215, done.
remote: Counting objects: 100% (2215/2215), done.
remote: Compressing objects: 100% (1713/1713), done.
remote: Total 2215 (delta 654), reused 1107 (delta 427), pack-reused 0 (from 0)
Receiving objects: 100% (2215/2215), 12.89 MiB | 9.15 MiB/s, done.
Resolving deltas: 100% (654/654), done.
fatal: No names found, cannot describe anything.
mkdir -p dist
fatal: No names found, cannot describe anything.
SHA: 27a820950a316451da3d52091cdfac7138e4ee8a cheddar 2025-09-14_04:31:45AM
CGO_ENABLED=0 GOGC= GOOS=linux GOARCH=amd64 go build -ldflags "-s -w \
-X github.com/traefik/traefik/v3/pkg/version.Version=27a820950a316451da3d52091cdfac7138e4ee8a \
-X github.com/traefik/traefik/v3/pkg/version.Codename=cheddar \
-X github.com/traefik/traefik/v3/pkg/version.BuildDate=2025-09-14_04:31:45AM" \
-installsuffix nocgo -o "./dist/linux/amd64/traefik" ./cmd/traefik
Command being timed: "make binary"
User time (seconds): 5.59
System time (seconds): 1.02
Percent of CPU this job got: 150%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:04.40
Maximum resident set size (kbytes): 2106624
...
Exit status: 0
Version: 27a820950a316451da3d52091cdfac7138e4ee8a
Codename: cheddar
Go version: go1.24.0
Built: 2025-09-14_04:31:45AM
OS/Arch: linux/amd64
Sửa code router in ra chi tiết priority
Thêm 1 dòng print
diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go
index 44f6950..12c6589 100644
--- a/pkg/server/router/router.go
+++ b/pkg/server/router/router.go
@@ -142,6 +142,8 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
routerConfig.Priority = httpmuxer.GetRulePriority(routerConfig.Rule)
}
+ fmt.Printf("FML priority name: %v rule: '%s' len: %d\n", routerName, routerConfig.Rule, routerConfig.Priority)
if routerConfig.Priority > maxUserPriority && !strings.HasSuffix(routerName, "@internal") {
err = fmt.Errorf("the router priority %d exceeds the max user-defined priority %d", routerConfig.Priority, maxUserPriority)
routerConfig.AddError(err, true)
rồi build lại.
Cấu hình Prometheus metrics cho Traefik
# traefik.yml
entryPoints:
web:
address: :2280
log:
level: DEBUG
accessLog:
addInternals: true
metrics:
addInternals: true
prometheus:
manualRouting: true
entryPoint: web
providers:
file:
filename: ./traefik_provider.yml
watch: true
# traefik_provider.yml
http:
routers:
router1:
entryPoints:
- web
service: prometheus@internal
rule: PathPrefix(`/metrics`)
observability:
accessLogs: true
regexrouter2:
entryPoints:
- web
service: prometheus@internal
rule: PathRegexp(`/me.*`)
observability:
accessLogs: true
Chạy:
./traefik/dist/linux/amd64/traefik --configfile traefik.yml
2025-09-14T11:40:01+07:00 DBG traefik/cmd/traefik/traefik.go:114 > Static configuration loaded [json] staticConfiguration={"accessLog":{"addInternals":true,"fields":{"defaultMode":"keep","headers":{"defaultMode":"drop"}},"filters":{},"format":"common"},"entryPoints":{"web":{"address":":2280","forwardedHeaders":{},"http":{"maxHeaderBytes":1048576,"sanitizePath":true},"http2":{"maxConcurrentStreams":250},"transport":{"lifeCycle":{"graceTimeOut":"10s"},"respondingTimeouts":{"idleTimeout":"3m0s","readTimeout":"1m0s"}},"udp":{"timeout":"3s"}}},"global":{"checkNewVersion":true},"log":{"format":"common","level":"DEBUG"},"metrics":{"addInternals":true,"prometheus":{"addEntryPointsLabels":true,"addServicesLabels":true,"buckets":[0.1,0.3,1.2,5],"entryPoint":"web","manualRouting":true}},"providers":{"file":{"filename":"./traefik_provider.yml","watch":true},"providersThrottleDuration":"2s"},"serversTransport":{"maxIdleConnsPerHost":200},"tcpServersTransport":{"dialKeepAlive":"15s","dialTimeout":"30s"}}
...
FML priority name: router1@file rule: 'PathPrefix(`/metrics`)' len: 22
...
FML priority name: regexrouter2@file rule: 'PathRegexp(`/me.*`)' len: 19
...
127.0.0.1 - - [14/Sep/2025:04:40:06 +0000] "GET /metrics HTTP/1.1" 200 2065 "-" "-" 1 "router1@file" "-" 1ms
127.0.0.1 - - [14/Sep/2025:04:41:31 +0000] "GET /metrics_but_longer HTTP/1.1" 200 2057 "-" "-" 1 "router1@file" "-" 1ms
Traefik ưu tiên các route có rule dài hơn
Khi truy cập curl localhost:2280/metrics_but_longer
thấy Traefik log đã match router1@file
:
router1
chứa rule có độ dài 22 ký tự nên được ưu tiên match trước.regexrouter2
chứa rule chỉ có 19 kí tự
To avoid path overlap, routes are sorted, by default, in descending order using rules length. The priority is directly equal to the length of the rule, and so the longest length has the highest priority. source
Trong code https://github.com/traefik/traefik/blob/27a820950a316451da3d52091cdfac7138e4ee8a/pkg/muxer/http/mux.go#L73-L88 thực hiện sort các router theo priority:
// AddRoute add a new route to the router.
func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler http.Handler) error {
matchers, err := m.parser.parse(syntax, rule)
if err != nil {
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
}
m.routes = append(m.routes, &route{
handler: handler,
matchers: matchers,
priority: priority,
})
sort.Sort(m.routes)
return nil
}
...
// routes implements sort.Interface.
type routes []*route
// Len implements sort.Interface.
func (r routes) Len() int { return len(r) }
// Swap implements sort.Interface.
func (r routes) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
// Less implements sort.Interface.
func (r routes) Less(i, j int) bool { return r[i].priority > r[j].priority }
Sort
interface sắp xếp theo thứ tự tăng dần:
Sort sorts data in ascending order as determined by the Less method. It makes one call to data.Len to determine n and
O(n*log(n))
calls to data.Less and data.Swap. The sort is not guaranteed to be stable.
Nhưng function Less
ở đây trả về true khi phần tử r[i].priority > r[j].priority
, tức priority càng cao thì xem như càng đứng trước, vì vậy route có priority cao nhất sẽ đứng đầu.
Khi ServeHTTP
nhận HTTP request, nó duyệt qua các routes, route có priority cao hơn được duyệt trước:
// ServeHTTP forwards the connection to the matching HTTP handler.
// Serves 404 if no handler is found.
func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
...
for _, route := range m.routes {
if route.matchers.match(req) {
route.handler.ServeHTTP(rw, req)
return
}
}
m.defaultHandler.ServeHTTP(rw, req)
}
Một ngày đẹp trời mà xấu số, người dùng thêm điều kiện kiểm tra Host vào regexrouter2
:
rule: PathRegexp(`/me.*`) && Host(`localhost`)
vô tình khiến route regexrouter2
có priority là 40 và được match trước:
> FML priority name: regexrouter2@file rule: 'PathRegexp(`/me.*`) && Host(`localhost`)' len: 40
...
> 127.0.0.1 - - [14/Sep/2025:08:26:40 +0000] "GET /metrics_but_longer HTTP/1.1" 200 8682 "-" "-" 1 "regexrouter2@file" "-" 0ms
Kết luận
Người dùng nên chỉ định priority cụ thể cho từng route trong config để tránh bất ngờ khi thay đổi config tưởng chừng vô hại lại có thể đảo lộn thứ tự match route:
http:
routers:
router1:
entryPoints:
- web
service: prometheus@internal
rule: PathPrefix(`/metrics`)
priority: 200
regexrouter2:
entryPoints:
- web
service: prometheus@internal
rule: PathRegexp(`/me.*`) && Host(`localhost`)
priority: 100
Hết.
HVN at https://pymi.vn and https://www.familug.org.