โหมดมืด
บทที่ 9 — Traefik ลึก (Cloud-native Edge Router)
TL;DR: บทนี้สอนตั้ง Traefik ซึ่งเป็น reverse proxy (ตัวกลางรับ request แทน backend) แบบใหม่ที่ตั้งง่ายกว่า Nginx สำหรับ Docker/Kubernetes — เพิ่ม service ใหม่ได้โดยไม่ต้องแก้ config ทุกครั้ง แบบ "ตั้งครั้งเดียวแล้วลืมไป" (set-and-forget). หัวข้อที่ครอบคลุม: Traefik vs Nginx, config สอง layer (static = ตั้งตอน start / dynamic = เปลี่ยนสดไม่ต้อง restart), provider (แหล่งดึง config เช่น Docker labels / K8s / ไฟล์), router + middleware (ตัวจับ request + ตัวกรองแต่ง), ACME auto-TLS (ขอใบรับรอง HTTPS อัตโนมัติ), IngressRoute CRD (resource พิเศษของ Traefik บน Kubernetes) และ observability (log + metric + trace). ข้ามได้ถ้า: ใช้ Nginx/HAProxy หรือ K8s Ingress controller ตัวอื่นเป็นหลัก
Traefik ([เทรฟิก]) คือ reverse proxy / load balancer ที่ออกแบบมาสำหรับยุค container โดยมี 3 จุดเด่นหลัก คือ ค้นหา service อัตโนมัติ (auto-discovery), เปลี่ยน config ได้สดโดยไม่ต้อง restart, และขอใบรับรอง TLS อัตโนมัติ
ด้วยจุดเด่นเหล่านี้ Traefik จึงเป็นที่นิยมที่สุดใน Docker Swarm และ Kubernetes แบบ "ตั้งครั้งเดียวแล้วลืมไป" (set-and-forget)
1. Traefik vs Nginx — เลือกตัวไหน
| แง่มุม | Nginx | Traefik |
|---|---|---|
| รูปแบบ config | ไฟล์ static (.conf) | static + dynamic (label, CRD) |
| ค้นหา service เอง | ❌ ต้องตั้งมือ | ✅ อัตโนมัติ (Docker, k8s, Consul, ...) |
| โหลด config ใหม่ตอนแก้ | สั่งเอง nginx -s reload | อัตโนมัติ |
| ขอ TLS อัตโนมัติ (Let's Encrypt) | ต้องใช้ certbot ช่วย | ✅ มีในตัว |
| Dashboard (UI ดูสถานะ) | ❌ (มีในรุ่นเสียเงิน หรือต้องใช้ exporter) | ✅ ฟรี |
| Plugin (ส่วนเสริม) | ngx_module (ต้อง compile) | Yaegi runtime / middleware (ดูรายละเอียดด้านล่าง) |
| Throughput (จำนวน request ต่อวินาที) | มักสูงกว่าเล็กน้อยใน HTTP/1.1 + TLS handshake (C, ไม่มี GC) | ช่องว่างแคบลงมากใน v3.x (Go) แต่ยังขึ้นกับ workload จริง — วัดของตัวเองก่อนใช้เป็นตัวตัดสินใจหลัก |
| ความชันการเรียนรู้ | กลาง | ต่ำกว่าในสภาพแวดล้อม container |
| ความ mature สำหรับ production | สูงมาก | สูง |
💡 ในยุค 2026 ทางเลือก edge ฝั่ง Kubernetes ไม่ได้มีแค่ Nginx/Traefik — Envoy (พื้นฐานของ Istio/Linkerd2/Consul/Gloo) และ Cilium ingress (ใช้ eBPF บน kernel) ก็เป็นทางเลือกหลัก หากต้องการเทคโนโลยีระดับ service mesh
ส่วน plugin ของ Nginx (
ngx_module) เป็น native binary ที่ต้อง compile รวมกับ Nginx ก่อนถึงใช้ได้ ต่างจาก Yaegi ของ Traefik ที่เป็น interpreter (ตัวแปลคำสั่งสด) โหลด plugin ที่เขียนด้วย Go เข้ามาใช้ตอน runtime ได้เลยโดยไม่ต้อง compile ใหม่ — ถ้าเทียบง่าย ๆ Yaegi ใกล้เคียงแนวคิด Wasm filter ของ Envoy (ส่วนเสริมที่โหลดสดตอน runtime เหมือนกัน) มากกว่าngx_module(รายละเอียดเชิงลึกข้ามได้ ถ้ายังไม่ได้ใช้ Envoy)
เลือกตามสภาพแวดล้อม:
- VPS / VM ตรง ๆ, ทีมคุ้น Nginx → Nginx
- Docker Compose / Kubernetes → Traefik (DX = Developer Experience หรือประสบการณ์นักพัฒนา ดีกว่า)
- ต้องการ service mesh + กระจาย traffic ขั้นสูง → Istio/Linkerd (service mesh = ชั้น proxy ที่แทรกข้าง ๆ ทุก service เพื่อจัดการการสื่อสารระหว่างกัน — ใหญ่กว่าและซับซ้อนกว่า edge proxy)
2. Concepts — Static vs Dynamic Config
Traefik แยก config 2 ชั้น:
Static configuration (อ่านตอน startup — ต้อง restart ถึงจะเปลี่ยน)
- entry points — port ที่ Traefik เปิดรับ traffic (เช่น
:80,:443) - providers — แหล่งดึง config มาจากไหน (Docker labels, k8s CRD, ไฟล์, ...)
- certificates resolver — ตัวจัดการขอใบรับรอง TLS (เช่น Let's Encrypt)
- logs, metrics, API/dashboard — ส่วนสังเกตการณ์และ UI จัดการ
Dynamic configuration (อ่านและใช้งานทันทีโดยไม่ต้อง restart)
- routers — กฎตัดสินใจว่า request ไหนไป service ไหน (match rule)
- services — backend ปลายทางที่ส่ง request ไปให้
- middlewares — ตัวแต่ง request/response (auth, rate limit, header, ฯลฯ)
- TLS options/stores — ค่าและคลังเก็บใบรับรอง TLS
เปรียบ: Static = "ที่ทำงานของ Traefik" / Dynamic = "งานที่ Traefik ต้องทำ" → เปลี่ยน service เพิ่ม-ลดได้สดโดยไม่ต้อง restart proxy
3. ติดตั้ง + Hello world
📌 ตัวอย่างด้านล่างใช้สำหรับเล่นบนเครื่องตัวเอง (localhost) เท่านั้น — อย่าเอา config ชุดนี้ไป deploy ตรง ๆ บน VPS / production เพราะยังไม่ได้ปิด dashboard และ mount docker socket แบบ raw ดูส่วนที่ 14 สำหรับ production config
yaml
# docker-compose.yml (สำหรับเล่นบนเครื่อง local เท่านั้น)
services:
traefik:
image: traefik:v3.1
command:
# dashboard เปิดผ่าน router + basicAuth (ไม่ใช้ --api.insecure ที่เปิด :8080 เปลือย ๆ)
- --api.dashboard=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
ports: ["80:80"]
volumes:
# local เล่น ๆ ใช้ socket ตรงพอได้ — production ให้ใช้ docker-socket-proxy (ดูหมายเหตุด้านล่าง)
- /var/run/docker.sock:/var/run/docker.sock:ro
whoami:
image: traefik/whoami
labels:
- traefik.enable=true # เปิดให้ Traefik อ่าน label ของ service นี้
- traefik.http.routers.whoami.rule=Host(`whoami.localhost`) # ตัวเข้ารหัส match: ขอเข้าด้วยโฮสต์ whoami.localhost
- traefik.http.routers.whoami.entrypoints=web # ใช้ entryPoint ชื่อ web (port 80)docker compose up -d → เปิด http://whoami.localhost → ตอบกลับด้วยข้อมูล container
💡 สังเกต casing ต่างกัน: ใน
command:ข้างบนใช้--providers.docker.exposedbydefault=false(ตัวเล็กล้วน คั่นด้วยจุด) เพราะเป็น CLI flag ของ Traefik ซึ่งเขียนแบบ lowercase-with-dots เสมอ — แต่พอไปเจอไฟล์ config แบบ YAML (เช่นส่วนที่ 5) จะใช้ keyexposedByDefault(camelCase) แทน สองรูปแบบนี้หมายถึงค่าเดียวกัน แค่ syntax ต่างกันตามที่มา ถ้าเผลอเอาexposedbydefault(ตัวเล็ก) ไปใส่ในไฟล์ YAML จะ parse error ทันที
💡
whoami.localhostไม่ทำงานบางเครื่อง — บน Windows/macOS เบราว์เซอร์อาจหา hostname นี้ไม่เจอ ให้เพิ่มบรรทัดนี้ใน hosts file ก่อน:127.0.0.1 whoami.localhost
- Windows: เปิด
C:\Windows\System32\drivers\etc\hostsด้วย Notepad แบบ Run as Administrator แล้วเพิ่มบรรทัดข้างบน- macOS / Linux: รัน
sudo sh -c 'echo "127.0.0.1 whoami.localhost" >> /etc/hosts'
✅ ไม่ต้องเขียน Nginx config — แค่ใส่ label → Traefik จัดการให้
🔴 ระวัง 2 จุดสำคัญตั้งแต่ตัวอย่างแรก:
--api.insecure=trueที่เคยเห็นในตัวอย่างเก่า ๆ จะเปิด dashboard ที่ port:8080แบบไม่มี auth เลย ใครยิงเข้ามาก็เห็นทุก config — รวมโดเมน, service ภายใน, middleware ลับ ห้ามใช้บน server จริง วิธีถูกคือเปิดเป็น router แล้วใส่ middlewarebasicauth+ipallowlist(ดูส่วนที่ 12)- mount
/var/run/docker.sockตรงเข้า Traefik — แม้จะเป็น:ro(read-only) ก็ยังอ่าน env ของทุก container ได้ (รวม secret) ถ้าใครเจาะ Traefik สำเร็จเท่ากับได้สิทธิ์เทียบเท่า root ของ host ที่ปลอดภัยกว่าคือใช้tecnativa/docker-socket-proxyเปิด endpoint แค่เท่าที่ Traefik ต้องใช้ (ดูส่วนที่ 14)
4. Routers — ตัวจับ request กับกฎ (Match + Rule)
Router คือกฎที่ Traefik ใช้ตัดสินใจว่า request ที่เข้ามาจะ "ตรงกับ" service ปลายทางตัวไหน
ไวยากรณ์กฎ (rule) ที่ใช้บ่อย:
text
Host(`example.com`) # ตรงกับโฮสต์เป๊ะ ๆ
Host(`example.com`) || Host(`www.example.com`) # || = หรือ (อันใดอันหนึ่ง)
Host(`example.com`) && PathPrefix(`/api`) # && = และ (ต้องตรงทั้งคู่)
PathPrefix(`/api/v1`) # path ขึ้นต้นด้วย
Path(`/health`) # path ตรงเป๊ะ
Method(`POST`) # ดู HTTP method
HeadersRegexp(`X-Custom`, `^value`) # ดู header X-Custom ว่าตรง regex `^value` ไหม
Query(`mobile`) # มี query string ?mobile=...
HostRegexp(`^[a-z]+\.example\.com$`) # โฮสต์ตรง regex (v3 ใช้ regex ดิบ ไม่ใช่ {name:pattern} แบบ v2)
ClientIP(`10.0.0.0/8`) # IP ของ client อยู่ในช่วงนี้💡 คำอธิบายฟังก์ชันที่อาจใหม่:
HeadersRegexp(name, regex)— match ถ้า request มี header ชื่อnameที่ค่าตรง regexHostRegexp(regex)— เหมือนHostแต่ใช้ regex; Traefik v3 ใช้ Go regex ดิบ (เช่น`^api\..*$`) ไม่ใช้ pattern{name:...}แบบ v2 อีกแล้วClientIP(cidr)— match ตาม IP ของ client (ระวัง: ถ้า Traefik อยู่หลัง CDN/LB อื่น ต้องตั้งforwardedHeaders.trustedIPsก่อน ไม่อย่างนั้น IP ที่เห็นจะเป็นของ CDN)HostSNI(...)(ใช้กับ TCP router) — ดู SNI ของ TLS handshake แทนการดู host header ของ HTTP💡 ถ้ายังไม่คุ้น regex (regular expression — ภาษาย่อสำหรับบอก pattern ของข้อความ เช่น
^แปลว่า "ขึ้นต้นด้วย",[a-z]+แปลว่า "ตัวอักษรเล็ก 1 ตัวขึ้นไป") ไม่ต้องกังวล — กฎแบบHost()/PathPrefix()ธรรมดาก็เพียงพอสำหรับเริ่มต้นแล้ว ค่อยกลับมาเรียน regex ทีหลังตอนต้องใช้จริง
ลำดับความสำคัญ (Priority) = router ที่ match แบบเฉพาะเจาะจงกว่า (เช่น path ยาวกว่า, มีเงื่อนไขมากกว่า) จะถูกเลือกก่อน
ตัวอย่างใน Docker label:
yaml
labels:
- traefik.http.routers.api.rule=Host(`api.example.com`) && PathPrefix(`/v1`)
- traefik.http.routers.api.entrypoints=websecure
- traefik.http.routers.api.tls.certresolver=letsencrypt
- traefik.http.services.api.loadbalancer.server.port=3000 # backend listen port (ต้องตรงกับที่ container เปิดจริง — ไม่ตรง = 502)⚠️ ถ้า service ของคุณเชื่อมต่อหลาย Docker network ให้เพิ่ม label
- traefik.docker.network=traefik-netระบุชัด ๆ ว่าให้ Traefik คุยกับ container ผ่าน network ไหน ไม่อย่างนั้น Traefik จะเดาตัวแรกที่เจอ ซึ่งบางทีไม่ใช่ตัวที่ reach ได้⚠️ ใน YAML ของ Docker Compose label ที่มี backtick (`) ต้องครอบด้วย quote เผื่อ shell หรือ parser บางตัวแปลผิด เช่น
"- traefik.http.routers.api.rule=Host(\api.example.com`)"ถ้าเขียนเป็น list แบบ- ...` มักไม่มีปัญหา แต่ถ้าใช้รูปแบบ map ให้ใส่ quote เสมอ
5. Providers — ค่ามาจากไหน
💡 CRD (Custom Resource Definition) = วิธีที่ Kubernetes ให้ "เพิ่มชนิด resource ใหม่" ของตัวเอง — Traefik นิยาม
IngressRouteเป็น CRD ของตัวเอง ทำให้ใช้ feature ของ Traefik ได้ครบกว่า Ingress มาตรฐาน
yaml
# traefik.yml (static)
providers:
docker:
endpoint: unix:///var/run/docker.sock
exposedByDefault: false # default ไม่ expose ทุก container — ต้องใส่ label เปิดเอง
network: traefik-net
file:
directory: /etc/traefik/dynamic
watch: true # เปลี่ยนไฟล์แล้วโหลดสด
kubernetesIngress: {} # ใช้ Ingress resource มาตรฐานของ k8s
kubernetesCRD: {} # ใช้ IngressRoute — CRD ของ Traefik (มี feature มากกว่า)
# kubernetesGateway: {} # Gateway API — มาตรฐานใหม่ของ k8s (แนะนำสำหรับ cluster ใหม่ตั้งแต่ปี 2026)
consulCatalog: {} # Consul = service registry ของ HashiCorp (ใช้กับ VM/bare-metal เป็นหลัก)
ecs: {} # ECS = Amazon Elastic Container Service (managed container runtime ของ AWS)หลาย provider ใช้พร้อมกันได้ — Traefik รวม config จากทุก source
💡 บน Kubernetes ในปี 2026 ทางเลือกเรียงตามแนวโน้ม:
- kubernetesGateway (Gateway API) — มาตรฐานใหม่ของ K8s ที่กำลังกลายเป็น default แนะนำสำหรับ cluster ใหม่
- kubernetesCRD (IngressRoute) — ใช้ feature ของ Traefik ได้ครบ เหมาะกับโปรเจกต์ที่ผูกกับ Traefik
- kubernetesIngress — มาตรฐานเดิม รองรับน้อย feature แต่ portable ที่สุด
⚠️ เปิด
kubernetesIngressกับkubernetesCRDพร้อมกันได้ แต่ถ้าโดเมนซ้ำกันใน 2 resource อาจ conflict — แนะนำเลือกอย่างใดอย่างหนึ่งเป็นหลัก
6. TLS Automatic (Let's Encrypt)
💡 ACME (Automatic Certificate Management Environment) = โปรโตคอลมาตรฐานที่ให้เครื่องเรา "ขอ + ต่ออายุใบรับรอง TLS (HTTPS)" จาก Let's Encrypt แบบอัตโนมัติ โดยพิสูจน์ว่าเราเป็นเจ้าของโดเมนจริงผ่าน "challenge" (HTTP-01 = วางไฟล์ให้เขามาเช็ค, DNS-01 = ใส่ record ใน DNS)
💡 HTTP-01 = Let's Encrypt ขอให้เราวางไฟล์ที่
http://<โดเมน>/.well-known/acme-challenge/...ให้เขามาดึงดู (ต้องเปิด port 80 ออกอินเทอร์เน็ตได้) DNS-01 = ให้เราใส่ TXT record ใน DNS แล้วเขามาเช็ค (จำเป็นถ้าต้องการใบรับรอง wildcard*.example.comหรือเครื่องไม่เปิด port 80) TLS-ALPN-01 = ใช้ port 443 อย่างเดียวก็พอ — ตอบกลับผ่าน TLS handshake (เหมาะถ้าเปิดได้แค่ port 443) ตั้งใน Traefik ว่าtlsChallenge: {}
yaml
# traefik.yml
certificatesResolvers:
letsencrypt:
acme:
email: ops@example.com
storage: /letsencrypt/acme.json
# ตอนทดสอบให้ uncomment บรรทัดถัดไป — staging server ของ Let's Encrypt ไม่มี rate limit เข้มและออกใบรับรองที่ browser ไม่ trust (ลองได้ปลอดภัย)
# caServer: https://acme-staging-v02.api.letsencrypt.org/directory
# 1) HTTP-01 — วางไฟล์ตอบ challenge ผ่าน port 80
httpChallenge:
entryPoint: web
# 2) DNS-01 — ต้องใช้ถ้าขอ wildcard (*.example.com) หรือเครื่องไม่เปิด port 80
# dnsChallenge:
# provider: cloudflare
# delayBeforeCheck: 0
# 3) TLS-ALPN-01 — ใช้แค่ port 443 (เหมาะถ้าเปิดได้แค่ port เดียว)
# tlsChallenge: {}ผูกใน router:
yaml
labels:
- traefik.http.routers.app.tls=true
- traefik.http.routers.app.tls.certresolver=letsencrypt
- traefik.http.routers.app.tls.domains[0].main=example.com
- traefik.http.routers.app.tls.domains[0].sans=*.example.com # SAN = Subject Alternative Name (โดเมนเพิ่มในใบเดียวกัน)Wildcard TLS ต้อง DNS-01 challenge — provider ที่ Traefik รองรับ เช่น Cloudflare, Route53, Gandi
⚠️ Pitfall ที่เจอบ่อยมาก:
acme.jsonต้อง mount แบบ persistent (volume) — ถ้าหายต้องขอใหม่ทุก restart และจะโดน Let's Encrypt rate limit (ปัจจุบันจำกัด 50 ใบ/สัปดาห์/registered domain และ duplicate certificate 5 ใบ/สัปดาห์ — ตัวเลขนี้เปลี่ยนได้ ตรวจสอบค่าล่าสุดที่ https://letsencrypt.org/docs/rate-limits/ ก่อนใช้จริง)acme.jsonต้องเป็น permissionchmod 600(อ่าน/เขียนได้แค่ owner) — ถ้า world-readable Traefik จะ ปฏิเสธไม่ start เลย ทำครั้งแรกบน host ด้วยtouch traefik/letsencrypt/acme.json && chmod 600 traefik/letsencrypt/acme.json- ตอนทดสอบให้ใช้ staging server ก่อน (uncomment
caServer:ด้านบน) — ไม่ใช่ rate limit จะหมดก่อนได้ใบจริง
7. Middlewares — ตัวแต่ง request/response
Middleware ของ Traefik ทำงานเหมือนต่อท่อ filter หลายตัวเข้าด้วยกัน — request วิ่งผ่านทีละชั้นก่อนถึง service
💡 ชื่อ middleware ต้องเป็นตัวอักษร/ตัวเลข/ขีดกลางเท่านั้น (
a-z,0-9,-) — ห้ามมี underscore_ไม่อย่างนั้น Traefik จะ reject ตอน parse config
7.1 Built-in ที่ใช้บ่อย
Rate limit (จำกัดอัตรา request):
yaml
labels:
- traefik.http.middlewares.api-ratelimit.ratelimit.average=100 # เฉลี่ย 100 req/วินาที (ต่อ source — default ดูจาก IP)
- traefik.http.middlewares.api-ratelimit.ratelimit.burst=200 # พุ่งสูงสุดได้ 200 req ในช่วงสั้น
- traefik.http.routers.api.middlewares=api-ratelimit⚠️ ถ้า Traefik อยู่หลัง CDN (Cloudflare, CloudFront)
ratelimitจะนับจาก IP ของ CDN ซึ่งเหมือนกันหมด — จำกัดทั้งระบบเป็นกองเดียว ต้องตั้งsourceCriterion.requestHeaderName: X-Forwarded-For(หรือ header ที่ CDN ตั้ง IP จริง) แทน
Strip prefix (ตัด path นำหน้าออก):
yaml
- traefik.http.middlewares.strip-api.stripprefix.prefixes=/api
- traefik.http.routers.api.middlewares=strip-api
# request /api/users → backend ได้ /users (ตัด /api ออก)Basic auth (ถามรหัสผ่านแบบเบสิก):
bash
# ติดตั้ง htpasswd ก่อน (ส่วนใหญ่อยู่ใน package apache2-utils)
# Ubuntu/Debian: sudo apt-get install apache2-utils
# RHEL/Fedora: sudo dnf install httpd-tools
# macOS: brew install httpd (มีในตัว)
# v3 แนะนำใช้ bcrypt (-B) แทน apr1 (default ของ -nb) ที่ brute force ได้เร็ว
htpasswd -nbB admin 'รหัสที่แข็งแรง' | sed -e 's/\$/\$\$/g'
# ผู้ใช้: admin / รหัสผ่าน: รหัสที่แข็งแรง
# sed คู่ที่ขึ้นต้นด้วย \$ คือการ escape เครื่องหมาย $ ให้ docker-compose ไม่แปลเป็น variable
# (ยังไม่คุ้น sed ก็ไม่ต้องเข้าใจ syntax ลึก — ก๊อปทั้งคำสั่งไปรันได้เลย มันแค่แทนที่ $ ตัวเดียวด้วย $$ สองตัวในผลลัพธ์)
# (หมายเหตุ: ตัวอย่าง sed นี้ใช้กับ bash/zsh ใน Linux/macOS — บน Windows PowerShell ต้องเปลี่ยน quote)yaml
- "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$05$$<PASTE_YOUR_BCRYPT_HASH_HERE>" # hash ที่ขึ้นต้น $2y$ คือ bcrypt — ต้องแทนที่ด้วย hash จริงจากคำสั่ง htpasswd ด้านบน ห้ามก๊อปไปใช้ตรง ๆ
- traefik.http.routers.app.middlewares=auth💡 ทำไมต้อง bcrypt?
htpasswd -nb(ไม่มี-B) จะให้ hash แบบapr1(MD5 base) ที่ GPU brute force ได้ภายในไม่กี่ชั่วโมง — bcrypt (-B) ปรับ cost ได้และทนทานกว่ามาก
Forward auth (เรียก service auth ภายนอกให้ตัดสินใจ):
yaml
- traefik.http.middlewares.fwd-auth.forwardauth.address=http://auth-svc/verify
- traefik.http.middlewares.fwd-auth.forwardauth.authResponseHeaders=X-User,X-Role
# Traefik ส่ง request ไปที่ /verify → ถ้าตอบ 2xx ให้ผ่าน + แนบ X-User/X-Role ต่อ; ถ้าไม่ใช่ 2xx → rejectHeaders (แต่ง response header เพื่อ security):
yaml
- traefik.http.middlewares.sec-headers.headers.framedeny=true # ห้าม embed ใน iframe (กัน clickjacking)
- traefik.http.middlewares.sec-headers.headers.contenttypenosniff=true # เบราว์เซอร์อย่าเดา MIME type
- traefik.http.middlewares.sec-headers.headers.stsincludesubdomains=true # HSTS รวม subdomain
- traefik.http.middlewares.sec-headers.headers.stsseconds=31536000 # HSTS อายุ 1 ปีRedirect (บังคับ HTTP → HTTPS):
yaml
- traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
- traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
# router กลาง: รับทุก host บน entrypoint web (port 80) แล้ว redirect ทั้งหมดไป HTTPS
- "traefik.http.routers.http-catchall.rule=HostRegexp(`.+`)" # v3: HostRegexp ตัวพิมพ์ใหญ่ + regex ดิบ (ไม่ใช่ {any:.+} แบบ v2)
- traefik.http.routers.http-catchall.entrypoints=web
- traefik.http.routers.http-catchall.middlewares=https-redirect⚠️ Traefik v3 เปลี่ยน syntax จาก v2 — ใช้
HostRegexp(H ใหญ่) + regex Go ดิบ เช่น`.+`; ของเดิมhostregexp(`{any:.+}`)จะถูก reject ตอน start
Compress (บีบอัด response):
yaml
- traefik.http.middlewares.compress.compress=trueChain middleware (รวมหลายตัวเป็นชุดเดียว เรียกใช้ทีเดียว):
yaml
- traefik.http.middlewares.app-chain.chain.middlewares=sec-headers,compress,api-ratelimit
- traefik.http.routers.app.middlewares=app-chain💡 อ้างชื่อ middleware ข้าม provider — ต้องเติม suffix บอก provider:
name@docker— middleware จาก Docker labelname@file— จากไฟล์ dynamicname@kubernetescrd— จาก K8s CRD (ในระดับ k8s ยังต้องเติม namespace ด้านหน้าด้วย เช่นdefault-sec-headers@kubernetescrd)
7.2 Plugin (Yaegi)
💡 Yaegi = ตัว interpret (ล่าม) ภาษา Go ที่ฝังอยู่ใน Traefik — โหลด plugin ที่เขียนด้วย Go มาใช้ได้ทันทีโดยไม่ต้อง compile Traefik ใหม่ (จึงเพิ่ม/ถอด plugin ได้ง่าย แต่แลกกับความเร็วที่ช้าลงเล็กน้อยเพราะรันแบบ interpret ไม่ใช่ native code)
ติดตั้ง plugin จาก Plugin Catalog:
yaml
# static
experimental:
plugins:
blockpath:
moduleName: github.com/traefik/plugindemo
version: v0.2.1ข้อดี: ขยาย feature ของ Traefik ได้โดยไม่ต้องรอ release ใหม่ ข้อระวัง: plugin วิ่งบน interpreter — request ที่ผ่าน plugin จะช้าลงเล็กน้อย (ไม่กี่ไมโครวินาที แต่ถ้า traffic หนัก ๆ ก็สะสมได้)
8. Load Balancing
Traefik default = round-robin (วนเท่า ๆ กัน) ระหว่าง replica
yaml
labels:
- traefik.http.services.api.loadbalancer.server.port=3000
- traefik.http.services.api.loadbalancer.sticky.cookie=true # เปิด sticky session (ผูก client เดิมกับ replica เดิมด้วย cookie)
- traefik.http.services.api.loadbalancer.sticky.cookie.name=lb
- traefik.http.services.api.loadbalancer.healthcheck.path=/health
- traefik.http.services.api.loadbalancer.healthcheck.interval=10s
- traefik.http.services.api.loadbalancer.healthcheck.timeout=3sขยายจำนวน replica ด้วย:
bash
docker compose up -d --scale api=3
# Traefik ค้นพบทั้ง 3 replica อัตโนมัติ → กระจาย traffic ทันทีWeighted round-robin (กระจายไม่เท่ากันตามน้ำหนัก — ทำผ่าน TraefikService ใน k8s CRD):
yaml
apiVersion: traefik.io/v1alpha1 # v3: ใช้ traefik.io/v1alpha1 (ของเก่า traefik.containo.us ใช้กับ v2 เท่านั้น)
kind: TraefikService
metadata: { name: weighted }
spec:
weighted:
services:
- name: api-v1
weight: 9
- name: api-v2 # canary 10%
weight: 1ใช้สำหรับ canary deploy (ค่อย ๆ ปล่อย version ใหม่ทีละน้อยเพื่อสังเกตปัญหาก่อนเปลี่ยนเต็ม — ดูบทที่ 5 ส่วน CI/CD)
⚠️ Traefik v3 เปลี่ยน apiVersion จาก
traefik.containo.us/v1alpha1(v2) เป็นtraefik.io/v1alpha1ถ้า apply ของเก่าบน cluster ที่ติดตั้ง CRD v3 ใหม่จะ error ทันที
9. Mirror — copy traffic (shadow traffic)
🚀 โซนขั้นสูง — ข้ามได้ Mirror (ก๊อป traffic จริงไปยิงใส่ version ใหม่เงียบ ๆ), TCP/UDP routers และ weighted canary เป็นเทคนิคของทีมที่ทำ deployment ขั้นสูง มือใหม่ตั้ง Traefik ให้ route + TLS ทำงานก่อนก็พอ แล้วค่อยกลับมาส่วนนี้ทีหลัง
yaml
# TraefikService kind: Mirroring (v3 ใช้ apiVersion: traefik.io/v1alpha1)
apiVersion: traefik.io/v1alpha1
kind: TraefikService
metadata: { name: api-mirror }
spec:
mirroring:
name: api-v1 # service หลักที่ตอบ response กลับ user จริง ๆ
mirrors:
- name: api-v2-shadow
percent: 50 # ก็อปครึ่งหนึ่งของ request ยิงซ้ำให้ตัวใหม่ (response ของตัว mirror จะถูกทิ้ง user ไม่เห็น)ใช้ทดสอบ version ใหม่ด้วย traffic จริงโดยไม่กระทบ user — ดู error/latency ของ shadow ผ่าน metric/log
⚠️ Mirror เป็น feature ของ Traefik IngressRoute/TraefikService CRD เท่านั้น — ใน K8s Ingress มาตรฐานไม่มี ถ้าจะใช้ต้องอยู่บน CRD ของ Traefik
10. TCP / UDP Routers
นอก HTTP — Traefik รองรับ raw TCP/UDP:
yaml
labels:
- traefik.tcp.routers.postgres.rule=HostSNI(`db.example.com`)
- traefik.tcp.routers.postgres.entrypoints=postgres # ต้องประกาศ entryPoint ชื่อ postgres ใน static config ก่อน (เช่น :5432)
- traefik.tcp.routers.postgres.tls.passthrough=true
- traefik.tcp.services.postgres.loadbalancer.server.port=5432 # port ของ container Postgres ภายใน💡
traefik.tcp.services.<name>.loadbalancer.server.portของ TCP ใช้ตัวเลขล้วน (port เดียว) — ถ้าจะกระจายไปหลาย backend แบบไม่เท่ากันต้องใช้ TraefikService weighted ใน CRD เหมือน HTTP
HostSNI ใช้กับ TLS — Traefik ดู SNI ของ TLS handshake โดยไม่ต้องถอดรหัสเนื้อหา
💡 SNI (Server Name Indication) = ส่วนหนึ่งของ TLS handshake ที่ client บอก "ฉันจะคุยกับโดเมนชื่ออะไร" ตั้งแต่ก่อนเข้ารหัสจริง — ทำให้ proxy เลือกปลายทาง/ใบรับรองที่ถูกต้องได้โดยไม่ต้องถอดรหัสเนื้อหาข้างใน
tls.passthrough=true = Traefik forward TLS ไม่ terminate → end-to-end encryption (ข้อมูลเข้ารหัสตลอดทางจนถึงปลายทางจริง ไม่มีจุดกลางทางที่ถอดรหัสได้ — Traefik แค่ส่งต่อ packet ที่ยังเข้ารหัสอยู่ ไม่แกะดูเนื้อหาข้างใน)
11. Traefik ใน Kubernetes
📌 ส่วนนี้สมมติว่าผู้อ่านพื้นฐาน Kubernetes (Ingress, CRD, namespace) มาแล้ว — ถ้ายัง ลองอ่าน Kubernetes Book ก่อนแล้วค่อยกลับมา
11.1 ผ่าน Ingress (มาตรฐาน)
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: default-sec-headers@kubernetescrd
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: app-svc, port: { number: 80 } }
tls:
- hosts: [app.example.com]
secretName: app-tls💡 annotation
traefik.ingress.kubernetes.io/...ยังใช้ได้ใน Traefik v3 (compatible) แต่ในปี 2026 Kubernetes แนะนำ Gateway API เป็นมาตรฐานใหม่สำหรับ cluster ใหม่ — Traefik รองรับผ่าน providerkubernetesGateway(ดูส่วนที่ 5)
11.2 ผ่าน IngressRoute CRD (แนะนำสำหรับงานที่ผูกกับ Traefik)
yaml
apiVersion: traefik.io/v1alpha1 # v3: traefik.io/v1alpha1 (ไม่ใช่ traefik.containo.us อีกแล้ว)
kind: IngressRoute
metadata:
name: app
spec:
entryPoints: [websecure]
routes:
- match: Host(`app.example.com`) && PathPrefix(`/api`)
kind: Rule
services:
- name: app-svc
port: 80
middlewares:
- name: sec-headers
- name: rate-limit
tls:
certResolver: letsencryptCRD ให้ flexibility มาก — ทุก feature ของ Traefik ใช้ได้
ดูเสริม kubernetes/06-ingress.md
12. Dashboard + API
Dashboard = web UI ที่แสดง router/service/middleware ทั้งหมด
yaml
# static
api:
dashboard: true
insecure: false # production = false (ใช้ basic auth + tls)
# router for dashboard
labels:
- traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls.certresolver=letsencrypt
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.middlewares=auth⚠️ ห้ามเปิด
api.insecure=trueใน production — Dashboard เห็น config ทั้งหมด (โดเมน, service ภายใน, middleware ลับ) ถ้าหลุดออกอินเทอร์เน็ตเท่ากับยื่นแผนที่ระบบให้ผู้บุกรุกเดินตามทีละขั้น ต้องเปิดเป็น router ปกติแล้วใส่ middlewarebasicAuth(bcrypt) +ipAllowListทุกครั้ง
13. Observability
จุดเด่นของ Traefik คือ observability มาในตัว — เปิดได้ครบทั้ง access log (JSON พร้อม drop header ลับอย่าง Authorization), metric สำหรับ Prometheus และ distributed tracing ผ่าน OpenTelemetry โดยตั้งค่าไม่กี่บรรทัด ทำให้เห็น traffic ที่ผ่าน proxy ได้ละเอียดตั้งแต่แรก:
Access log (JSON)
yaml
# static
accessLog:
filePath: /var/log/traefik/access.log
format: json
fields:
headers:
defaultMode: keep
names:
Authorization: dropMetrics (Prometheus)
yaml
metrics:
prometheus:
addEntryPointsLabels: true
addServicesLabels: true
addRoutersLabels: true
buckets: [0.1, 0.3, 1.2, 5.0]Endpoint: /metrics — scrape ด้วย Prometheus
Tracing (OpenTelemetry)
💡 OpenTelemetry (OTel) = มาตรฐานกลางสำหรับ collect log + metric + trace ของระบบ distributed ส่วน OTLP (OpenTelemetry Protocol) คือ wire protocol ที่ใช้ส่งข้อมูลเหล่านี้ไป backend (Tempo, Jaeger, Honeycomb, ฯลฯ) Traefik v3 มี OTel มาในตัว ไม่ต้องลง agent เพิ่ม
yaml
tracing:
serviceName: traefik-edge # ตั้งชื่อ service ของ Traefik ใน trace — ถ้าไม่ตั้ง จะขึ้นเป็น "unknown_service" ใน Tempo/Jaeger
otlp:
http:
endpoint: http://tempo:4318/v1/tracesTraefik ใส่ trace context header (traceparent) → app forward ต่อ → distributed trace ครบทั้งสาย
14. Production Config ตัวอย่าง (Docker Compose)
ปิดท้ายด้วยตัวอย่าง config ที่ใช้งานจริงบน Docker Compose — รวมทุกอย่างที่เรียนมา: entryPoint พร้อม redirect HTTP→HTTPS, dashboard ที่มี basicAuth + IP allowlist, Let's Encrypt อัตโนมัติ, docker-socket-proxy แยกชั้น และ provider แบบ Docker labels ก๊อปไปปรับใช้เป็นจุดเริ่มของ stack จริงได้เลย:
💡
accessLog.bufferingSizeเป็น trade-off: ตั้งเป็น 100 คือให้ Traefik เก็บ log ไว้ในหน่วยความจำก่อนแล้วค่อยเขียนดิสก์ทีเดียว 100 บรรทัด — ลดภาระ I/O ดิสก์ แต่ถ้า Traefik crash กะทันหัน log ที่ยังไม่ถูก flush (สูงสุด 100 บรรทัดล่าสุด) จะหายไป ถ้า traffic หนักและต้อง audit log ให้ครบทุกบรรทัด ให้ตั้งเป็น0แทน (เขียนทันทีทุก request แลกกับ I/O ที่สูงขึ้น)
yaml
# traefik/traefik.yml
global:
checkNewVersion: false
sendAnonymousUsage: false
api:
dashboard: true
insecure: false # production = false เสมอ — เปิดผ่าน router ที่มี auth แทน
entryPoints:
web:
address: :80
http:
redirections:
entryPoint: { to: websecure, scheme: https, permanent: true }
websecure:
address: :443
http:
tls: { certResolver: letsencrypt }
middlewares: [sec-headers@file, compress@file]
metrics:
address: :8082 # entryPoint สำหรับ /metrics — ต้องประกาศที่นี่ก่อน ไม่อย่างนั้นบล็อก metrics.prometheus จะ start ไม่ติด
providers:
docker:
# ชี้ไปที่ docker-socket-proxy แทน socket ตรง ๆ (ปลอดภัยกว่ามาก ดูบล็อก compose ด้านล่าง)
endpoint: tcp://dockerproxy:2375
exposedByDefault: false
network: traefik-net
file:
directory: /etc/traefik/dynamic
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: ops@example.com
storage: /letsencrypt/acme.json
# caServer: https://acme-staging-v02.api.letsencrypt.org/directory # uncomment ตอนเทสต์เพื่อหลบ rate limit (50/สัปดาห์/domain)
httpChallenge: { entryPoint: web }
log:
level: INFO
format: json
accessLog:
format: json
bufferingSize: 100 # buffer 100 บรรทัดก่อนเขียนดิสก์ (ลด I/O)
metrics:
prometheus:
entryPoint: metrics # ตรงกับ entryPoint ที่ประกาศข้างบน
addEntryPointsLabels: true
addServicesLabels: true
addRoutersLabels: truedynamic/middlewares.yml:
yaml
http:
middlewares:
sec-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
frameDeny: true
contentTypeNosniff: true
# browserXssFilter: true ← ห้ามใช้ (deprecated) — X-XSS-Protection ถูกถอดจาก Chrome ปี 2019
# และ Firefox ไม่เคย implement เลย OWASP แนะนำให้ตั้งเป็น 0 หรือไม่ส่ง header นี้เลย
# ให้ใช้ Content-Security-Policy แทน (ดูหัวข้อ CSP ในบทที่ 8)
customResponseHeaders:
X-XSS-Protection: "0" # ปิดชัดเจน ดีกว่าไม่ส่งเลยในบาง browser เก่า
referrerPolicy: no-referrer
compress:
compress: {}
rate-limit:
rateLimit:
average: 100
burst: 200เพิ่ม middleware สำหรับล็อก dashboard:
yaml
# ใน dynamic/middlewares.yml (เพิ่มเข้าไป)
http:
middlewares:
dashboard-auth:
basicAuth:
# สร้างด้วย: htpasswd -nbB admin 'รหัสที่แข็งแรง' (bcrypt — ไม่ใช่ apr1)
users:
- "admin:$2y$05$EXAMPLE_BCRYPT_HASH_REPLACE_ME"
dashboard-allowlist:
ipAllowList: # v3 เปลี่ยนชื่อจาก ipWhiteList → ipAllowList
sourceRange:
- 10.0.0.0/8 # vpn/office range
- 192.168.0.0/16docker-compose.yml:
yaml
services:
# ชั้นกลางที่กรอง endpoint ของ docker socket ให้ Traefik อ่านได้แค่ที่จำเป็น
# ปลอดภัยกว่า mount /var/run/docker.sock เข้า Traefik ตรง ๆ มาก
dockerproxy:
image: tecnativa/docker-socket-proxy:0.3.0 # ตรึง version ไว้ เช่นเดียวกับ traefik:v3.1 — ตรวจ release ใหม่ที่ https://github.com/Tecnativa/docker-socket-proxy/releases
restart: unless-stopped
environment:
CONTAINERS: 1 # อนุญาตอ่านเฉพาะ container info (ที่ Traefik จำเป็นต้องใช้)
SERVICES: 1
TASKS: 1
NETWORKS: 1
# endpoint อื่น ๆ default = ปิด (เช่น POST, exec, build — Traefik ไม่ได้ใช้)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks: [traefik-net]
traefik:
image: traefik:v3.1 # ตรึง version หลัก (3.x) — ทบทวน release notes ก่อน bump minor (v3.x มี cadence ประมาณทุก 3-6 เดือน)
restart: unless-stopped
depends_on: [dockerproxy]
networks: [traefik-net]
ports: ["80:80", "443:443"]
volumes:
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- ./traefik/dynamic:/etc/traefik/dynamic:ro
# ⚠️ acme.json ต้อง chmod 600 ก่อน start ครั้งแรก:
# touch ./traefik/letsencrypt/acme.json && chmod 600 ./traefik/letsencrypt/acme.json
- ./traefik/letsencrypt:/letsencrypt
labels:
- traefik.enable=true
# router สำหรับ dashboard — ใส่ basicAuth + ip allowlist ครบทั้งสองชั้น
- traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls.certresolver=letsencrypt
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.middlewares=dashboard-auth@file,dashboard-allowlist@file
app:
image: my-app:1.0
networks: [traefik-net]
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`app.example.com`)
- traefik.http.services.app.loadbalancer.server.port=3000 # ต้องตรงกับ port ที่ container เปิดจริง (EXPOSE)
- traefik.docker.network=traefik-net # ระบุ network ชัด ๆ เผื่อ container อยู่หลาย network
- traefik.http.routers.app.middlewares=rate-limit@file
networks:
traefik-net:
external: true # ต้องสร้างก่อนรัน: docker network create traefik-net⚠️ ขั้นตอนตั้งครั้งแรกที่ต้องทำมือก่อน
docker compose up:bashdocker network create traefik-net # ไม่อย่างนั้น "network not found" mkdir -p traefik/letsencrypt traefik/dynamic touch traefik/letsencrypt/acme.json chmod 600 traefik/letsencrypt/acme.json # Traefik จะ refuse ถ้าไม่ใช่ 600
15. Common Pitfalls (กับดักที่เจอบ่อย)
exposedByDefault: true— ทุก container เปิดออกอินเทอร์เน็ตหมด (รวม DB ด้วย!) → ตั้งเป็นfalseแล้วเปิดทีละตัวด้วยtraefik.enable=trueacme.jsonไม่ persistent — ทุก restart จะขอใบรับรองใหม่ พอเกิน rate limit (ราว 50 ใบ/สัปดาห์/โดเมน — ตรวจค่าล่าสุดที่ letsencrypt.org/docs/rate-limits) จะขอไม่ได้อีกเป็นวัน ๆ → mount เป็น volumeacme.jsonไม่ได้ chmod 600 → Traefik ปฏิเสธไม่ start (ขึ้น error "permission of file is not 600") →chmod 600 acme.jsonก่อน start ครั้งแรก- ใช้ challenge ผิดประเภท — ขอ wildcard (
*.example.com) ต้องใช้ DNS-01 เท่านั้น; domain เดี่ยวจะ HTTP-01, DNS-01 หรือ TLS-ALPN-01 ก็ได้ - Dashboard เปิดที่ port 8080 ผ่าน
--api.insecure=trueใน production → config หลุดสู่อินเทอร์เน็ต ใส่ basicAuth + ipAllowList เสมอ tls.passthrough=trueแต่ Traefik ก็ตั้ง TLS เอง → ขัดกัน (passthrough = ไม่ terminate แต่ตั้ง cert = terminate) → เลือกอย่างใดอย่างหนึ่ง- Network ของ Traefik กับ service ไม่ตรงกัน → ขึ้น "service not found" หรือ 502 → ใส่
traefik.docker.network=traefik-netที่ service และเช็คว่าทั้ง Traefik กับ service join network เดียวกัน - Port ของ
loadbalancer.server.portไม่ตรงกับ port ที่ container เปิดจริง → ขึ้น 502 Bad Gateway → เช็คEXPOSEใน Dockerfile หรือ port ที่ app listen - อ้างชื่อ middleware ผิด provider — Docker label ต้องเป็น
name@docker, ไฟล์เป็นname@file, k8s CRD เป็นnamespace-name@kubernetescrd(รวม namespace ด้วย) - ลืมใส่
entryPointsใน router → router match path แล้วแต่ Traefik ไม่ตอบเพราะไม่รู้ว่า listen port ไหน - Forward auth ไม่ตอบกลับด้วย 2xx → ทุก request ถูก reject — เช็คว่า endpoint ภายในตอบ 200 OK เมื่อ auth ผ่าน
- ใช้ underscore (
_) ในชื่อ middleware → Traefik reject ตอน parse — ใช้ตัวอักษร/ตัวเลข/ขีดกลาง (-) เท่านั้น - Rate limit หลัง CDN ไม่ทำงานตามคาด —
ratelimitdefault ดู IP ของ client แต่หลัง CDN จะเห็น IP ของ CDN เหมือนกันหมด → ตั้งsourceCriterion.requestHeaderName: X-Forwarded-For - ใช้ syntax v2 บนภาพ v3 —
apiVersion: traefik.containo.us/v1alpha1(v2) ต้องเปลี่ยนเป็นtraefik.io/v1alpha1;hostregexp(`{any:.+}`)(v2) ต้องเปลี่ยนเป็นHostRegexp(`.+`)(v3)
16. Traefik vs Nginx — สรุปการเลือก
ใช้ Traefik เมื่อ:
- Docker Compose / Kubernetes
- Service เพิ่ม-ลดบ่อย
- ต้องการ TLS automatic ที่ทำงานเอง
- ต้องการ dashboard
- ทีมไม่อยาก maintain Nginx config
ใช้ Nginx เมื่อ:
- VPS / static infra
- ต้องการ throughput สูงสุด
- มี config ที่ซับซ้อน (regex matching, rewrite)
- caching ที่ require fine-tune
- ทีมคุ้นเคยอยู่แล้ว
ใช้ทั้งคู่:
- Nginx ที่ขอบสุด (terminate TLS, ทำ caching) → Traefik อยู่ภายใน cluster (ทางเข้า service)
- หรือใช้ Traefik แทน Nginx ไปเลย — ส่วนใหญ่ทำได้
💡 ในยุค K8s ปี 2026 ทางเลือกขอบ cluster ไม่ได้มีแค่ Nginx/Traefik อีกแล้ว — ลองพิจารณา:
- Envoy (เดี่ยว ๆ หรือผ่าน Istio/Linkerd2/Consul Connect/Gloo) — ถ้าจะไปทาง service mesh เต็มรูปแบบ
- Cilium ingress (ใช้ eBPF บน kernel) — ถ้าต้องการ performance ระดับเดียวกับ kernel networking และ observability ละเอียดมาก
- Gateway API — มาตรฐาน K8s ใหม่ที่ proxy หลายตัวเริ่มรองรับ (รวม Traefik) — ถ้าตั้ง cluster ใหม่ในปี 2026 ควรเริ่มที่ Gateway API ก่อน
🛠️ Checkpoint 9 — ลงมือทำ
- ตั้ง Traefik + 2 web service (
whoami+ Nginx static) ด้วย docker-compose - ขอ TLS จาก Let's Encrypt staging server (ไม่ rate limit) สำหรับ subdomain คุณ
- เพิ่ม middleware chain: sec-headers + compress + rate-limit 50 r/s
- scale
whoamiเป็น 5 replica → curl 10 ครั้ง → ดูว่า hostname เปลี่ยน (round-robin) - ลอง canary: 90% v1, 10% v2 ผ่าน weighted service
- ดู dashboard ที่
traefik.example.com(ใส่ basic auth) - expose Prometheus metric → scrape → ดู
traefik_router_requests_total{code="200"} - ลอง forward auth: middleware ส่งไป
/verifyendpoint ของ app → reject ถ้าไม่มี cookie
สรุปบทที่ 9
- Traefik = reverse proxy ที่ขอบของระบบสำหรับยุค container — ค้นหา service อัตโนมัติและเปลี่ยน config ได้สดโดยไม่ต้อง restart
- Config 2 ชั้น: Static (entry point, provider, cert resolver — อ่านตอน start) + Dynamic (router, service, middleware — มาจาก label/CRD/ไฟล์ เปลี่ยนได้สด)
- ขอใบรับรอง TLS อัตโนมัติผ่าน ACME (HTTP-01, DNS-01 สำหรับ wildcard, หรือ TLS-ALPN-01 สำหรับเปิดเฉพาะ :443)
- Middleware = สายโซ่ตัวกรอง (auth, rate limit, header, redirect, ฯลฯ) ที่ห่อหุ้ม request ก่อนถึง service
- Observability ครบในตัว: access log (JSON), Prometheus metric, distributed trace ผ่าน OpenTelemetry — ตั้งไม่กี่บรรทัด
- บน Kubernetes แนะนำใช้ IngressRoute CRD แทน Ingress มาตรฐาน (หรือใช้ Gateway API ถ้าเป็น cluster ใหม่ปี 2026)
- จุดที่ต้องระวังที่สุดในการ deploy production: ปิด
api.insecure, ใช้ docker-socket-proxy, chmod 600 acme.json, ใช้ bcrypt สำหรับ basicAuth