Route Resolution & Priority — Khi nhiều route cùng khớp, cái nào thắng?
Why this matters in production
Câu hỏi "route nào được chọn" nghe có vẻ tầm thường cho tới khi bạn gặp một trong các tình huống sau ở production:
- Bạn cài một route
0.0.0.0/0qua một firewall appliance (next-hop VM), nhưng traffic vẫn ra thẳng internet. Vì sao? - Bạn học một route
/16qua BGP từ on-prem, nhưng một static route/24của ai đó cài từ năm ngoái vẫn thắng dù BGP "mới hơn". - Bạn có hai VPN tunnel song song, kỳ vọng load-balance, nhưng traffic chỉ đi một tunnel.
- Một route peering và một route nội bộ cùng đích, cùng prefix length — và bạn cần biết chắc cái nào win trước khi go-live.
Tất cả những điều này được quyết định bởi một thuật toán xác định (deterministic) mà GCP áp dụng cho mọi packet. Không có yếu tố ngẫu nhiên, không có "best effort". Nếu bạn thuộc thuật toán này, bạn có thể dự đoán hành vi routing trước khi deploy và truy nguyên hành vi bất thường sau khi deploy. Nếu không, mọi sự cố routing đều là điều bí ẩn. File này mổ xẻ thuật toán đó đến từng bước tie-break, theo đúng tài liệu routing order của GCP.
Internal model — thuật toán route selection từng bước
Thuật toán resolution chạy theo các tầng (stage) có thứ tự nghiêm ngặt. Quan trọng: một số tầng, khi đã khớp, sẽ dừng toàn bộ quá trình (terminating); các tầng khác chỉ thu hẹp tập ứng viên rồi chuyển xuống tầng sau. Hiểu tầng nào terminating là chìa khóa.
┌─────────────────────────────────────────────────────────────┐
│ STAGE 0: SPECIAL ROUTING PATHS (không nằm trong route table)│ ← khớp = DỪNG
│ GFE 35.191.0.0/16, 130.211.0.0/22; IAP 35.235.240.0/20; │
│ health check ranges; Cloud DNS 35.199.192.0/19; │
│ external passthrough NLB (Maglev)... │
└─────────────────────────────────────────────────────────────┘
│ không khớp
▼
┌─────────────────────────────────────────────────────────────┐
│ STAGE 1: POLICY-BASED ROUTES (đánh giá CHỈ theo priority) │ ← khớp = DỪNG (hoặc skip)
└─────────────────────────────────────────────────────────────┘
│ không khớp
▼
┌─────────────────────────────────────────────────────────────┐
│ STAGE 2: SUBNET ROUTES │ ← khớp = DỪNG
│ (dải subnet trong VPC + peering subnet routes) │
└─────────────────────────────────────────────────────────────┘
│ không khớp
▼
┌─────────────────────────────────────────────────────────────┐
│ STAGE 3: CUSTOM ROUTES (static + dynamic + peering) │
│ 3a. Longest prefix match (most-specific destination) │
│ 3b. Priority thấp nhất thắng (0 = cao nhất) │
│ 3c. Preference category (next-hop type) │
│ 3d. Route-type hierarchy (local > NCC > peering) │
│ 3e. ECMP nếu vẫn hòa (5-tuple / 3-tuple hash) │
└─────────────────────────────────────────────────────────────┘Ta đi qua từng tầng.
Stage 0 — Special routing paths: vì sao bạn không thể "chặn" health check bằng route
Trước cả route table, GCP có những đường đi đặc biệt không xuất hiện trong route collection và không thể bị ghi đè bằng route thường. Theo tài liệu routes, các dải này gồm:
- Google Front Ends (GFE) cho load balancing & health check:
35.191.0.0/16,130.211.0.0/22(IPv4);2600:2d00:1:1::/64(IPv6). - Identity-Aware Proxy (IAP):
35.235.240.0/20. - Cloud DNS / Service Directory:
35.199.192.0/19. - Serverless VPC Access:
35.199.224.0/19. - External passthrough Network Load Balancer / protocol forwarding (hệ thống Maglev).
Ý nghĩa kiến trúc: traffic trả lời health check hay đi qua IAP không tuân theo route 0.0.0.0/0 của bạn. Đây là lý do bạn không thể vô tình blackhole health check bằng cách đổi default route — và cũng là lý do một số kỹ sư bối rối khi thấy traffic tới/từ các dải này "phớt lờ" route table. Những đường này được Andromeda xử lý trước khi tra route table thường. Khớp ở stage này là terminating: quá trình dừng, không tra tiếp.
Bài học: route table không phải là "luật tối cao" cho mọi packet. Có một tầng đường đặc biệt nằm trên route table, phục vụ các dịch vụ hạ tầng của Google. Khi debug traffic tới một trong các dải
35.x/130.211.x, hãy nhớ stage 0 trước khi soi route.
Stage 1 — Policy-based routes: chỉ priority, không longest-prefix
Policy-based routes (PBR) là loại route đặc biệt cho phép chuyển hướng traffic dựa trên 5-tuple (kể cả khi đích đã có route cụ thể), thường dùng để ép traffic qua một appliance L7. Điểm khác biệt cơ chế quan trọng: PBR được đánh giá chỉ theo priority, không theo longest-prefix match. Theo tài liệu, "policy-based routes are evaluated solely by their priority" — priority cao nhất (số nhỏ nhất) khớp trước.
Hệ quả tinh tế: với route thường, một /24 luôn thắng một /16 bất kể priority (longest-prefix trước). Với PBR, một rule "rộng" có priority cao có thể thắng một rule "hẹp" có priority thấp. Nếu nhiều PBR cùng priority cao nhất khớp, GCP chọn một theo thuật toán nội bộ (có thể nhập nhằng) — vì vậy tránh để PBR trùng priority. Một PBR còn có thể được cấu hình skip các PBR khác (loại bỏ toàn bộ PBR còn lại để rơi xuống routing thường). Khớp PBR (không skip) là terminating.
Stage 2 — Subnet routes: most-specific và bất khả ghi đè bởi static
Nếu đích của packet nằm trong một subnet route (dải primary hoặc secondary của một subnet trong VPC, hoặc subnet route nhập qua peering), subnet route thắng và quá trình dừng. Đây là lý do bạn không thể tạo một static/dynamic route trùng hoặc nằm gọn trong một subnet route: theo tài liệu, GCP từ chối tạo static route có đích "trùng khớp hoặc nằm trong" subnet route, và Cloud Router không tạo dynamic route khớp/nằm trong subnet route. Subnet route là "luật nhà" — nội bộ VPC luôn ưu tiên trước mọi định tuyến tùy chỉnh.
Implication thiết kế: bạn không thể "chuyển hướng" traffic nội-VPC tới một appliance bằng static route thường (vì subnet route luôn thắng). Để làm việc đó bạn cần policy-based routes (stage 1, chạy trước subnet route) — đó chính là lý do PBR tồn tại.
Stage 3 — Custom routes: nơi mọi tie-break thực sự diễn ra
Nếu không khớp stage 0–2, ta vào tập custom routes (static + dynamic + peering). Đây là tầng phức tạp nhất, gồm nhiều bước tie-break tuần tự. Mỗi bước thu hẹp tập ứng viên; chỉ khi còn hòa mới chuyển bước sau.
3a — Longest prefix match (most-specific destination)
Bước đầu tiên và quan trọng nhất: chỉ giữ lại các route có prefix dài nhất (cụ thể nhất) khớp đích. Theo tài liệu, route 10.240.1.0/24 luôn được ưu tiên hơn 10.240.0.0/16 cho một packet tới 10.240.1.5, bất kể priority.
Đây là điểm phá vỡ trực giác "priority quyết định tất cả". Priority chỉ được xét trong cùng một độ dài prefix. Một /24 priority 1000 vẫn thắng một /16 priority 0. Hệ quả production trực tiếp:
Anti-pattern kinh điển: bạn học
0.0.0.0/0qua BGP để ép toàn bộ egress qua on-prem (default route via VPN), nhưng quên rằng một static route10.50.0.0/16 → default-internet-gatewaycũ vẫn tồn tại. Mọi traffic tới10.50.xvẫn ra internet vì/16cụ thể hơn/0— longest-prefix thắng, priority không cứu được bạn. Để override, bạn phải advertise/ tạo route cụ thể bằng hoặc hơn route cũ.
3b — Priority (số nhỏ = ưu tiên cao)
Trong các route cùng độ dài prefix, route có priority value nhỏ nhất thắng (thang 0–65535; 0 là cao nhất, 65535 thấp nhất). Đây là cơ chế "metric" mà người dùng quen thuộc từ routing truyền thống, nhưng lưu ý nó chỉ là bước thứ hai sau longest-prefix.
Với dynamic route học qua BGP, priority được suy ra từ MED (Multi-Exit Discriminator) của route nhận được — file 05 mổ xẻ ánh xạ MED → priority. Đây là cách bạn điều khiển ưu tiên failover giữa nhiều BGP path: advertise cùng prefix với MED khác nhau.
3c — Preference category (theo loại next-hop)
Nếu vẫn hòa sau prefix + priority, GCP phân tách theo preference category dựa trên loại next-hop. Theo tài liệu, thứ tự ưu tiên (cao → thấp) là:
- Static route với next-hop là instance hoặc Classic VPN tunnel
- Dynamic routes (một category duy nhất)
- Static route với next-hop là internal passthrough Network Load Balancer
- Static route với next-hop
default-internet-gateway
Điểm cực kỳ quan trọng về cơ chế: GCP không ECMP xuyên các category. Nghĩa là nếu bạn có một static-route-next-hop-instance và một dynamic route cùng đích/priority, static-instance thắng tuyệt đối, không chia tải. Đây giải thích một lớp sự cố hay gặp:
Vì sao "appliance route" luôn thắng BGP: bạn cài static route
0.0.0.0/0 → next-hop firewall-VM(category 1) song song với default route học qua BGP (category 2). Bạn kỳ vọng BGP đôi khi thắng. Không bao giờ — category 1 luôn đứng trên category 2 khi prefix/priority hòa. Traffic luôn qua appliance. Nếu appliance chết mà route không bị rút, bạn blackhole. Đây là lý do appliance HA cần health-based route removal, không thể dựa vào "BGP sẽ tiếp quản".
3d — Route-type hierarchy: local > NCC > peering
Trong cùng category, còn một tầng ưu tiên theo nguồn của route. Theo tài liệu: local custom route (của chính VPC) được ưu tiên hơn NCC dynamic route; NCC dynamic route được ưu tiên hơn peering custom route. Đây là cơ chế đảm bảo VPC luôn "tin" định tuyến nội bộ của mình hơn định tuyến nhập từ ngoài (qua NCC hay peering). Implication: khi bạn nhập cùng một route qua nhiều con đường (nội bộ, NCC, peering), bạn biết chắc thứ tự thắng mà không cần thử.
3e — ECMP: chỉ khi mọi thứ khác hòa
Cuối cùng, Equal-Cost Multi-Path chỉ kích hoạt giữa các route có cùng đích, cùng priority, và cùng preference category. Theo tài liệu, khi đủ điều kiện ECMP, GCP chia traffic bằng hash:
- 5-tuple hash (src IP, dst IP, src port, dst port, protocol) nếu packet có thông tin port.
- 3-tuple hash (src IP, dst IP, protocol) nếu không có port.
Đặc tính quan trọng: hash là per-flow, không per-packet. Mọi packet của cùng một flow (cùng 5-tuple) đi cùng một path → không reorder trong một connection. Đây là lý do hai VPN tunnel ECMP cho bạn ~2× throughput tổng hợp qua nhiều flow, nhưng một flow đơn lẻ vẫn bị giới hạn ở một tunnel (và bởi giới hạn băng thông/throughput của một tunnel đó). Một kỹ sư test bằng một luồng iperf đơn sẽ kết luận sai rằng "ECMP không hoạt động".
Bài học cơ chế: ECMP trên GCP là load-balancing theo flow bằng hash, không phải round-robin theo packet. Throughput của một kết nối không tăng nhờ ECMP; chỉ tổng throughput qua nhiều kết nối mới tăng. Thiết kế test và capacity planning phải dựa trên phân bố flow thực tế.
Tổng hợp thuật toán dưới dạng pseudo-code
def resolve(packet):
if matches_special_path(packet.dst): # Stage 0
return special_path # TERMINATING
pbr = best_policy_based_route(packet) # Stage 1: chỉ theo priority
if pbr and not pbr.skips_all_pbr:
return pbr # TERMINATING
if matches_subnet_route(packet.dst): # Stage 2
return subnet_route # TERMINATING
# Stage 3: custom routes
candidates = custom_routes_matching(packet.dst)
candidates = keep_longest_prefix(candidates) # 3a
candidates = keep_lowest_priority(candidates) # 3b
candidates = keep_best_preference_category(candidates)# 3c (KHÔNG ECMP xuyên category)
candidates = keep_best_route_type(candidates) # 3d local>NCC>peering
if len(candidates) > 1:
return ecmp_hash(packet, candidates) # 3e 5-tuple/3-tuple
return candidates[0]Mọi hành vi routing "kỳ lạ" mà bạn từng gặp đều là một dòng cụ thể trong pseudo-code này hoạt động đúng như thiết kế.
Constraints, trade-offs & failure modes
Next-hop validity: route hợp lệ về cú pháp vẫn có thể bị bỏ qua
Một static route được bỏ qua hoàn toàn (như thể không tồn tại) nếu next-hop không khả dụng. Theo tài liệu: next-hop VM bị stopped/deleted, IP next-hop không hợp lệ, hoặc Classic VPN tunnel không có Phase 1 IKE SA hoạt động → route bị disregarded. Đây là cơ chế quan trọng cho failover dựa trên appliance:
- Nếu appliance VM của bạn stopped (không phải chỉ "unhealthy ở tầng app"), route next-hop-VM tự động bị bỏ → traffic rơi xuống route kế tiếp.
- Nhưng nếu VM vẫn running mà app bên trong chết, route vẫn được coi là hợp lệ → blackhole. Đây là khoảng cách giữa "VM up" và "service up" mà thiết kế HA phải bịt bằng health check chủ động + tự động xóa route.
Longest-prefix là con dao hai lưỡi
Vì longest-prefix đứng trên priority, nó vừa là công cụ mạnh (override một đường rộng bằng một đường hẹp) vừa là cái bẫy (một route hẹp cũ vô hiệu hóa chủ ý global của bạn). Quy tắc vận hành: khi muốn ép một đường default mới, hãy audit mọi route cụ thể hơn /0 đang tồn tại; chúng sẽ thắng default route bất kể bạn đặt priority thế nào.
Nhập nhằng khi PBR trùng priority
Vì PBR chỉ xét priority và "internal algorithm selects one" khi trùng priority cao nhất, hai PBR cùng priority khớp cùng traffic tạo ra hành vi không xác định về mặt vận hành (bạn không kiểm soát cái nào thắng). Luôn gán priority duy nhất cho các PBR có khả năng chồng lấn.
Anti-pattern: tin rằng "priority quyết định tất cả"
- Vì sao xảy ra: kinh nghiệm từ routing truyền thống nơi metric/priority thường là yếu tố quyết định chính, bỏ qua việc longest-prefix và preference-category đứng trước.
- Hệ quả ở scale: thiết kế failover/egress-control dựa trên priority thất bại âm thầm — traffic không đi đường bạn nghĩ, và sự cố chỉ lộ ra khi có lỗi (blackhole hoặc rò rỉ egress).
- Cách tư duy đúng: ghi nhớ thứ tự special → PBR(priority) → subnet → [longest-prefix → priority → category → route-type → ECMP]. Priority chỉ là một bước, và chỉ trong cùng prefix length. Trước khi tin một route sẽ thắng, hãy chạy qua đủ các bước tie-break.
GCP-native implementation guidance
# Liệt kê route theo đích để kiểm tra chồng lấn prefix (phát hiện route hẹp che default)
gcloud compute routes list \
--filter="network=prod-vpc" \
--format="table(destRange, priority, nextHopGateway, nextHopInstance, nextHopVpnTunnel)" \
--sort-by=destRange
# Xem dynamic route đã được Cloud Router program (kèm priority suy từ MED)
gcloud compute routers get-status router-us-central1 --region=us-central1 \
--format="table(result.bestRoutes[].destRange, result.bestRoutes[].priority)"Để xác định thực sự route nào thắng cho một packet 5-tuple cụ thể, dùng Connectivity Tests: nó chạy đúng thuật toán resolution ở trên trên forwarding state đã biên dịch và cho bạn route được chọn cùng lý do, thay vì để bạn tự tính tie-break bằng tay.
Tóm tắt mental model
- Route selection là thuật toán xác định nhiều tầng, không phải "best effort".
- Thứ tự: special paths → policy-based (chỉ priority) → subnet routes → custom routes. Ba stage đầu (khi khớp) là terminating.
- Trong custom routes: longest-prefix → priority → preference category → route-type hierarchy → ECMP. Priority chỉ là bước 2, dưới longest-prefix.
- ECMP không xuyên preference category; static-next-hop-instance luôn thắng dynamic khi prefix/priority hòa.
- ECMP là hash theo flow, không tăng throughput của một kết nối đơn.
- Next-hop không hợp lệ ⇒ route bị bỏ qua; "VM up" ≠ "service up".
References
- VPC Routes — Routing Order — thuật toán selection đầy đủ: special paths, PBR, subnet, longest-prefix, preference category, ECMP, next-hop validity
- Policy-based Routes — đánh giá theo priority, skip semantics
- Routes — Special return paths — dải GFE/IAP/DNS/health check
- Connectivity Tests — mô phỏng route được chọn cho 5-tuple