Skip to content

บทที่ 9 — Traefik ลึก (Cloud-native Edge Router)

← บทที่ 8 | สารบัญ DevOps

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 — เลือกตัวไหน

แง่มุมNginxTraefik
รูปแบบ 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) จะใช้ key exposedByDefault (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 จุดสำคัญตั้งแต่ตัวอย่างแรก:

  1. --api.insecure=true ที่เคยเห็นในตัวอย่างเก่า ๆ จะเปิด dashboard ที่ port :8080 แบบไม่มี auth เลย ใครยิงเข้ามาก็เห็นทุก config — รวมโดเมน, service ภายใน, middleware ลับ ห้ามใช้บน server จริง วิธีถูกคือเปิดเป็น router แล้วใส่ middleware basicauth + ipallowlist (ดูส่วนที่ 12)
  2. 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 ที่ค่าตรง regex
  • HostRegexp(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 ที่เจอบ่อยมาก:

  1. acme.json ต้อง mount แบบ persistent (volume) — ถ้าหายต้องขอใหม่ทุก restart และจะโดน Let's Encrypt rate limit (ปัจจุบันจำกัด 50 ใบ/สัปดาห์/registered domain และ duplicate certificate 5 ใบ/สัปดาห์ — ตัวเลขนี้เปลี่ยนได้ ตรวจสอบค่าล่าสุดที่ https://letsencrypt.org/docs/rate-limits/ ก่อนใช้จริง)
  2. acme.json ต้องเป็น permission chmod 600 (อ่าน/เขียนได้แค่ owner) — ถ้า world-readable Traefik จะ ปฏิเสธไม่ start เลย ทำครั้งแรกบน host ด้วย touch traefik/letsencrypt/acme.json && chmod 600 traefik/letsencrypt/acme.json
  3. ตอนทดสอบให้ใช้ 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 → reject

Headers (แต่ง 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 (บังคับ HTTPHTTPS):

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=true

Chain 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@dockermiddleware จาก Docker label
  • name@file — จากไฟล์ dynamic
  • name@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 รองรับผ่าน provider kubernetesGateway (ดูส่วนที่ 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: letsencrypt

CRD ให้ 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 ปกติแล้วใส่ middleware basicAuth (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: drop

Metrics (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/traces

Traefik ใส่ 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: true

dynamic/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/16

docker-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:

bash
docker 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 (กับดักที่เจอบ่อย)

  1. exposedByDefault: true — ทุก container เปิดออกอินเทอร์เน็ตหมด (รวม DB ด้วย!) → ตั้งเป็น false แล้วเปิดทีละตัวด้วย traefik.enable=true
  2. acme.json ไม่ persistent — ทุก restart จะขอใบรับรองใหม่ พอเกิน rate limit (ราว 50 ใบ/สัปดาห์/โดเมน — ตรวจค่าล่าสุดที่ letsencrypt.org/docs/rate-limits) จะขอไม่ได้อีกเป็นวัน ๆ → mount เป็น volume
  3. acme.json ไม่ได้ chmod 600 → Traefik ปฏิเสธไม่ start (ขึ้น error "permission of file is not 600") → chmod 600 acme.json ก่อน start ครั้งแรก
  4. ใช้ challenge ผิดประเภท — ขอ wildcard (*.example.com) ต้องใช้ DNS-01 เท่านั้น; domain เดี่ยวจะ HTTP-01, DNS-01 หรือ TLS-ALPN-01 ก็ได้
  5. Dashboard เปิดที่ port 8080 ผ่าน --api.insecure=true ใน production → config หลุดสู่อินเทอร์เน็ต ใส่ basicAuth + ipAllowList เสมอ
  6. tls.passthrough=true แต่ Traefik ก็ตั้ง TLS เอง → ขัดกัน (passthrough = ไม่ terminate แต่ตั้ง cert = terminate) → เลือกอย่างใดอย่างหนึ่ง
  7. Network ของ Traefik กับ service ไม่ตรงกัน → ขึ้น "service not found" หรือ 502 → ใส่ traefik.docker.network=traefik-net ที่ service และเช็คว่าทั้ง Traefik กับ service join network เดียวกัน
  8. Port ของ loadbalancer.server.port ไม่ตรงกับ port ที่ container เปิดจริง → ขึ้น 502 Bad Gateway → เช็ค EXPOSE ใน Dockerfile หรือ port ที่ app listen
  9. อ้างชื่อ middleware ผิด provider — Docker label ต้องเป็น name@docker, ไฟล์เป็น name@file, k8s CRD เป็น namespace-name@kubernetescrd (รวม namespace ด้วย)
  10. ลืมใส่ entryPoints ใน router → router match path แล้วแต่ Traefik ไม่ตอบเพราะไม่รู้ว่า listen port ไหน
  11. Forward auth ไม่ตอบกลับด้วย 2xx → ทุก request ถูก reject — เช็คว่า endpoint ภายในตอบ 200 OK เมื่อ auth ผ่าน
  12. ใช้ underscore (_) ในชื่อ middleware → Traefik reject ตอน parse — ใช้ตัวอักษร/ตัวเลข/ขีดกลาง (-) เท่านั้น
  13. Rate limit หลัง CDN ไม่ทำงานตามคาดratelimit default ดู IP ของ client แต่หลัง CDN จะเห็น IP ของ CDN เหมือนกันหมด → ตั้ง sourceCriterion.requestHeaderName: X-Forwarded-For
  14. ใช้ syntax v2 บนภาพ v3apiVersion: 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 — ลงมือทำ

  1. ตั้ง Traefik + 2 web service (whoami + Nginx static) ด้วย docker-compose
  2. ขอ TLS จาก Let's Encrypt staging server (ไม่ rate limit) สำหรับ subdomain คุณ
  3. เพิ่ม middleware chain: sec-headers + compress + rate-limit 50 r/s
  4. scale whoami เป็น 5 replica → curl 10 ครั้ง → ดูว่า hostname เปลี่ยน (round-robin)
  5. ลอง canary: 90% v1, 10% v2 ผ่าน weighted service
  6. ดู dashboard ที่ traefik.example.com (ใส่ basic auth)
  7. expose Prometheus metric → scrape → ดู traefik_router_requests_total{code="200"}
  8. ลอง forward auth: middleware ส่งไป /verify endpoint ของ 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

กลับสารบัญ DevOps