ผ่าตัดกลไกการจับคู่คำขอ (Routing Engine) ของ Kong Gateway: เจาะลึกความแตกต่างระหว่าง strip_path,
การรักษาโดเมนด้วย preserve_host, กฎลำดับความสำคัญของ Prefix Tree และการใช้ Regular Expressions
ในโลกของ Microservices สถาปัตยกรรม URL ของผู้ใช้ภายนอก (Public API) มักถูกออกแบบให้เป็นระเบียบ เช่น:
https://api.company.com/v1/orders/123
แต่เซิร์ฟเวอร์หลังบ้าน (Internal Order Service) กลับถูกเขียนให้รับ Request แค่:
http://order-pod:8080/orders/123 หรือแม้กระทั่ง http://order-pod:8080/123
หากไม่มีตัวแปลง Path ที่ชาญฉลาด วิศวกรจะต้องเขียนโค้ดหลังบ้านเพื่อรองรับ Prefix ของทุกเวอร์ชัน หรือต้องคอยแก้ Route ในโค้ดทุกครั้งที่มีการเปลี่ยนโดเมน นอกจากนี้ หากระบบหลังบ้านเป็นระบบที่อาศัยโดเมนเนมในการแยกแยะ Tenant (Multi-tenant SaaS) แต่ Gateway ดันส่ง Hostname ภายในไปแทน ระบบหลังบ้านจะตอบกลับเป็น Error 404 หรือ Access Denied ทันที
นี่คือเหตุผลที่ Kong Gateway มีพารามิเตอร์แกนกลาง 2 ตัว ได้แก่ strip_path และ preserve_host เพื่อทำหน้าที่เป็น "ตัวเชื่อมและแปลงสาร" อย่างสมบูรณ์แบบ
เพื่อความแม่นยำทางวิศวกรรม ตารางด้านล่างนี้นิยามพารามิเตอร์และกลไกของ Router ตามข้อกำหนด Kong Core Routing Specification:
| พารามิเตอร์ / กลไก | นิยามทางการ (Formal Definition) | หน้าที่หลัก (Primary Purpose) | ขอบเขตและความแตกต่าง (Distinction) |
|---|---|---|---|
| strip_path | Boolean flag บน Route Entity ที่ควบคุมการตัด Prefix Path ส่วนที่ Match ออกจาก URI | ซ่อนพาธภายนอก (Public Prefix) ไม่ให้ถูกส่งต่อเข้าสู่ Internal Path ของ Service หลังบ้าน | หากเป็น true Prefix ที่ Match จะหายไป แล้วนำเศษที่เหลือไปต่อท้าย Service.path; หากเป็น false Path เดิมของ Client จะถูกส่งไปทั้งยวง |
| preserve_host | Boolean flag บน Route Entity ที่กำหนดว่าให้รักษา HTTP Host Header เดิมของ Client หรือไม่ |
ช่วยให้ระบบ Multi-Tenant หรือ Virtual Hosting หลังบ้าน สามารถแยกแยะ Domain เดิมของผู้ใช้ได้ | หากเป็น false (Default) Kong จะแทนที่ Host ด้วย Hostname ของ Service/Upstream; หากเป็น true จะส่ง Host เดิมของ Client เสมอ |
| Prefix Tree Router | โครงสร้างข้อมูล Radix Tree ในหน่วยความจำของ Kong Router Engine สำหรับจับคู่ Path ตามความยาว | ค้นหา Route ที่ตรงกับคำขอด้วยความเร็วระดับ $O(k)$ (เมื่อ $k$ คือความยาวของ Path) | จับคู่แบบ Longest Prefix Match เสมอ โดย Route ที่มี Path ยาวและเจาะจงกว่าจะชนะ Route ที่สั้นกว่า |
| Regex Path (~) | รูปแบบ Path ที่ขึ้นต้นด้วยสัญลักษณ์ตัวหนอน (~) สั่งให้ใช้ PCRE Engine ในการจับคู่ |
รองรับการจับคู่แบบ Dynamic Pattern เช่น ตัวเลขรหัสบัตร, UUID, หรือ Path Variables | ประมวลผลช้ากว่า Prefix Match เล็กน้อย และต้องระวังเรื่อง Regular Expression Denial of Service (ReDoS) |
| Route Priority | ลำดับการประเมินผลคำขอเมื่อมี Route หลายตัวที่ Match ได้พร้อมกัน | กำหนดว่า Request จะถูกส่งเข้า Service ใดอย่างแม่นยำ (Deterministic) ไม่มีการสุ่ม | เรียงจาก: จำนวน Match Rules (Headers + Hosts + Paths) ➔ Longest Path Prefix ➔ Regex Evaluation Order |
จุดเริ่มต้นของความผิดพลาด:
ทีมงานทำการย้ายระบบ (Migration) จาก Nginx แบบเดิมมาใช้ Kong Gateway เพื่อจัดการ Route เข้าสู่ระบบแกนกลาง Multi-tenant Core Service สถาปัตยกรรมหลังบ้านใช้ HTTP Host Header ในการตรวจสอบว่าคำขอนั้นเป็นของบริษัทใด เช่น:
• acme.hr-saas.com ➔ ตรวจสอบเข้าฐานข้อมูลของบริษัท ACME Corp
• globex.hr-saas.com ➔ ตรวจสอบเข้าฐานข้อมูลของบริษัท Globex
วินาทีที่ระบบล่ม:
ในการเขียนคอนฟิก `kong.yml` ทีมวิศวกรลืมกำหนดพารามิเตอร์ preserve_host: true (ซึ่ง Kong มีค่าเริ่มต้นเป็น false) ส่งผลให้ Kong ทำการเขียนทับ (Overwrite) Header Host ของทราฟฟิกทั้งหมดให้กลายเป็นชื่อ Upstream ภายใน:
Host: hr-core-upstream.internal:8080
เมื่อคำขอวิ่งไปถึง Backend Service ระบบหลังบ้านหาชื่อบริษัท hr-core-upstream.internal ในฐานข้อมูลไม่พบ จึงตอบกลับเป็น HTTP 404 Tenant Not Found ไปยังพนักงานกว่า 120,000 คนทั่วโลก พร้อมทั้งบางส่วนที่ระบบ Fallback ดึงคอนฟิก Default Tenant ส่งผลให้พนักงานเห็นข้อมูลแผนกของบริษัทอื่น!
การกู้ภัยด้วย Kong Configuration:
ทีม Incident Commander สั่งปรับแก้คอนฟิก Declarative `kong.yml` โดยเติม preserve_host: true และสั่ง deck sync ภายใน 3 วินาที Traffic ทั้งหมดได้รับการฟื้นฟู และนี่คือบทเรียนราคาแพงว่า การเข้าใจค่า Default ของ Host และ Path Routing คือหัวใจของความปลอดภัยระดับองค์กร
ลองเปรียบเทียบการทำงานของ Kong Router กับขั้นตอนการคัดแยกพัสดุ:
To: tenantA.company.com/v1/shipments/tracking/999strip_path: true: เจ้าหน้าที่คัดแยกจะ "ฉีกส่วนนำทางภายนอก /v1/shipments ทิ้งไป" เหลือเฉพาะส่วนสำคัญคือ /tracking/999 แล้วนำไปแปะต่อท้ายแผนกจัดส่งภายใน (Service.path)preserve_host: true: เหมือนการ "คงตราประทับขององค์กรผู้ส่งต้นทางไว้" เพื่อให้แผนกภายในรู้ว่านี่เป็นพัสดุของบริษัท Tenant ใด หากตั้งเป็น false เจ้าหน้าที่จะเอาป้ายชื่อคลังสินค้าของตัวเอง (Host: internal-warehouse) แปะทับลงไปแทน_format_version: "3.0"
services:
- name: inventory-service
url: http://inventory-backend:8080/v2/items # Base URL และ Default Path
routes:
- name: inventory-route
paths:
- /store/inventory # [1] URI Prefix ที่ต้องการดักจับ
strip_path: true # [2] ตัด /store/inventory ออกก่อนส่งต่อ
preserve_host: true # [3] ส่ง Host Header เดิมของ Client ไปยัง Backend
methods:
- GET # [4] ดักเฉพาะ HTTP Methods ที่ระบุ
- POST
hosts:
- "*.company.com" # [5] Wildcard Host Matching
protocols:
- http
- https
strip_pathการแมปเบื้องต้นเพื่อตัด Prefix ภายนอกออกจาก API โดยส่งต่อไปยัง Backend ที่ไม่มี Prefix:
_format_version: "3.0"
services:
- name: simple-catalog-service
url: http://catalog-pod:8080/products
routes:
- name: catalog-public-route
paths:
- /api/catalog
strip_path: true # /api/catalog/detail/10 ➔ http://catalog-pod:8080/products/detail/10
การแยกเส้นทางตามโดเมนเนม (Host-based Routing) ควบคู่กับ Path โดยเปิดใช้งาน preserve_host:
_format_version: "3.0"
services:
- name: enterprise-tenant-service
url: http://tenant-cluster:8080
routes:
- name: tenant-alpha-route
hosts:
- alpha.saas-platform.com
paths:
- /portal
strip_path: false # ส่ง /portal ไปยังหลังบ้านด้วย
preserve_host: true # Backend ได้รับ Host: alpha.saas-platform.com นำไป Query DB ต่อได้ถูกบริษัท
Anti-Pattern ที่พบบ่อย: วาง Route ที่เป็น General Prefix (เช่น /api) ไว้โดยไม่คุม Priority
ทำให้ไปบังคับกลืน Route ที่เป็น Regex หรือ Route ที่มีความเจาะจงสูงกว่า
Production Solution: ใช้งาน Regex Matching ด้วยตัวหนอน ~ พร้อมจัดสรร Priority และดักจับ Query Parameters:
_format_version: "3.0"
services:
- name: legacy-api-service
url: http://legacy-core:8080/app
routes:
# Route เจาะจง: Regex จับคู่เฉพาะ Version 1 ถึง Version 3 ที่มีตัวเลข ID
- name: strict-versioned-route
paths:
- ~/api/v[1-3]/accounts/\d+
strip_path: false
preserve_host: true
protocols:
- https
- name: fallback-api-service
url: http://fallback-handler:8080
routes:
# Route ทั่วไป: จะถูกเลือกเฉพาะเมื่อไม่ตรงกับ Regex ด้านบน (Longest Prefix Match Rules)
- name: general-api-route
paths:
- /api
strip_path: true
protocols:
- https
ทดลองป้อน Request ของ Client และปรับแต่งค่า strip_path, preserve_host
เพื่อดูผลลัพธ์แบบเรียลไทม์ว่า Kong Router จะส่ง Request หน้าตาอย่างไรไปถึง Backend จริง:
แบบทดสอบจำนวน 20 ข้อ จัดสัดส่วนตามหลัก Spaced Repetition: ข้อ 1-15 เจาะลึก Path & Host Routing Mechanics ของ Day 02 และ ข้อ 16-20 คือ Spaced Repetition Review ดึงทบทวน 4 เสาหลักและ Health Checks จาก Day 01 เพื่อตอกย้ำความจำระยะยาว