Initial Guide — APIM: จาก service เปล่า ถึง state ปัจจุบัน

จุดประสงค์: หลัง provision API Management ขึ้นมาแล้ว ต้อง config อะไรต่อ เพื่อให้ request จาก app วิ่งทะลุ APIM เข้า AKS ได้เหมือน state ปัจจุบัน ตรวจสอบกับของจริงเมื่อ: 2026-09-21 — APIM exim-az-hub-apim (RG exim-azure-nonprd-hub-connectivity, subscription EximNonPrdHubSua = 9359feea-760c-4f7c-9115-30979c2c9c6d, region southeastasia) ปลายทาง: AKS aks-SupperApp-dev (sub 501c116b-…) — อ่านคู่กับ initial-guide-aks.md

การอ่าน tag ท้าย bullet:

⚠️ ข้อจำกัดของข้อมูล: account ที่ใช้ตรวจ (silak@exim.go.th) อ่าน Microsoft.Network/* และ resource group ใน hub subscription ไม่ได้ (AuthorizationFailed) → ทุกอย่างที่เป็น NSG / route table / DNS zone / peering ฝั่ง hub เป็น [assume] ทั้งหมด ต้องถาม owner ของ hub

az network private-dns zone list ใน hub sub คืน [] — แปลว่าไม่มีสิทธิ์อ่าน ไม่ได้แปลว่าไม่มี zone

ไม่พบ bicep/terraform ที่ provision APIM ตัวนี้ใน repo ที่มีในเครื่อง → ค่าทุกตัวใน §2 มาจาก az apim show / az rest ของ service ที่รันอยู่ ไม่ใช่ source of truth


0. ขอบเขต


1. Prerequisite นอก APIM (ขาดอันใดอันหนึ่ง = request ตาย)


2. ค่า service ปัจจุบัน (ใช้เป็น target ตอน provision)

หัวข้อ ค่าจริง tag
SKU Developer, capacity 1 (ไม่มี SLA, scale-out, zone) [live]
Platform stv2 [live]
VNet mode Internal — subnet APIM ใน exim-azure-nonprd-hub-vnet [live]
Private IP 172.29.166.4 [live]
Public IP (outbound) 20.247.201.129 (publicNetworkAccess: Enabled) [live]
Gateway hostname exim-az-hub-apim.azure-api.net — BuiltIn cert ตัวเดียว ไม่มี custom domain ใน APIM [live]
Regional gateway exim-az-hub-apim-southeastasia-01.regional.azure-api.net [live]
TLS SSL3 / TLS1.0 / TLS1.1 / 3DES ปิดหมด ทั้ง client และ backend; HTTP/2 ปิด [live]
Managed identity identity: null — ไม่มี MI → ดึง cert/secret จาก Key Vault ตรง ๆ ไม่ได้ [live]
Publisher exim / pongthepl@exim.go.th [live]
สร้างเมื่อ 2026-03-23 (instance ปัจจุบัน 2026-05-01 โดย parichat@fusionsol.com) [live]

สิ่งที่ "ไม่มี" และไม่ต้องสร้าง (state ปัจจุบันไม่ได้ใช้) [live]


3. Named values (ทำก่อน paste global policy ไม่งั้น policy save ไม่ผ่าน)


4. Global policy — หัวใจที่ทำให้ทะลุเข้า AKS

ตั้งที่ All APIs → Inbound processing → </> code editor (scope = service) — API ราย ๆ ตัวมีแค่ <base /> ทั้ง 4 section [live]

ลำดับใน policy จริง (ดูตัวเต็มด้วยคำสั่งใน §9):


5. สร้าง API ต่อ 1 service


6. WebSocket API (SignalR)


7. Logger / Diagnostics


8. ทางเข้าของ client (ต้นทาง → APIM)


9. Verification checklist

RG=exim-azure-nonprd-hub-connectivity; APIM=exim-az-hub-apim
SUB=9359feea-760c-4f7c-9115-30979c2c9c6d
az account set -s $SUB
B="https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.ApiManagement/service/$APIM"

# 1. service พร้อม + อยู่ใน VNet โหมด Internal + ได้ private IP
az apim show -g $RG -n $APIM \
  --query "{state:provisioningState,vnet:virtualNetworkType,ip:privateIpAddresses,sku:sku.name}"

# 2. DNS: ชื่อ gateway ต้องชี้ private IP (เมื่ออยู่ใน corp/VPN)
nslookup exim-az-hub-apim.azure-api.net     # ต้องได้ 172.29.166.4

# 3. named value ครบ
az rest --method get --url "$B/namedValues?api-version=2022-08-01" \
  --query "value[].properties.displayName"

# 4. global policy มี set-backend-service ครบ 3 env (ไฟล์มี BOM ต้องใช้ --output-file)
az rest --method get --url "$B/policies/policy?format=rawxml&api-version=2022-08-01" \
  --output-file global-policy.json && grep -c "set-backend-service" global-policy.json   # ต้องได้ 3

# 5. API + displayName prefix (ตัวกำหนด env routing)
az rest --method get --url "$B/apis?api-version=2022-08-01&\$top=60" \
  --query "value[].{id:name,disp:properties.displayName,path:properties.path}" -o tsv

# 6. operation ของ API ต้องเป็น path เต็มของ backend
az rest --method get --url "$B/apis/userservice-api/operations?api-version=2022-08-01" \
  --query "value[0:5].properties.urlTemplate" -o tsv

# 7. end-to-end จริง — ต้องได้ 200 + Set-Cookie (ทะลุถึง pod ใน AKS)
curl -sk -i https://exim-az-hub-apim.azure-api.net/sentinel-gateway-api/sentinel/antiforgery-token | head -5
curl -sk -i https://gateway-dev.exim.go.th/sentinel-gateway-api/sentinel/antiforgery-token | head -5

# 8. WebSocket ถึง backend จริง (401 missing_auth_cookie = ดี, 404 ของ APIM = path หาย)
curl -sk -i -N --max-time 10 -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
  -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
  https://exim-az-hub-apim.azure-api.net/notification-ws/hubs/app | head -8

ผลรันจริง 2026-09-21: ข้อ 7 ได้ HTTP 200 ทั้งสองทาง (body = antiforgery token, header Set-Cookie: __Host-csrf-sentinel-dev) → peering + DNS + policy + ingress + pod ครบสาย [live]


10. ⚠️ Gotcha ที่เจอจริงบน instance ปัจจุบัน


11. ลำดับสรุป (TL;DR)

  1. Network ก่อน — peering hub↔spoke, subnet APIM, NSG 3443/6390 + outbound, UDR exemption
  2. DNS — ทำให้ api.sua-az-<env>.internal resolve จาก hub VNet ไปที่ internal LB ของ nginx (172.29.88.5)
  3. AKS ต้องพร้อมก่อน — ingress + Sentinel Gateway รันได้ (ไม่งั้น request ที่มี cookie ได้ 502 ทั้งหมด)
  4. Provision APIM — Internal VNet mode, stv2, ปิด TLS เก่า
  5. Named values — sentinel-internal-api-key ให้ตรงกับ SecuritySettings:InternalAccess:ApiKey ของ Sentinel ทุก env
  6. Global policy (All APIs scope) — CORS → 403 internal → session façade → set-backend-service 3 ทาง → outbound strip Authorization
  7. สร้าง API ต่อ service — displayName DEV|UAT <x> API, path <x>-api / uat-<x>-api, import spec จาก /swagger/v1/swagger.json, subscriptionRequired=false, policy <base/>
  8. WebSocket API แยกต่างหาก พร้อม serviceUrl เต็ม path
  9. Logger + diagnostic (App Insights / Azure Monitor, mask query param)
  10. หน้าบ้าน — ชี้ gateway-dev.exim.go.th (Cloudflare) เข้า APIM พร้อม rewrite Host เป็น exim-az-hub-apim.azure-api.net
  11. เดิน checklist §9 — smoke test ต้องได้ 200 ทั้งจาก azure-api.net และจากโดเมนหน้าบ้าน

ลำดับ smoke test ตอน env ใหม่: ยิง request ไม่มี cookie ก่อน (ข้าม resolve-session) เพื่อแยกปัญหา routing ออกจากปัญหา Sentinel — ถ้า path นั้นผ่านแล้วค่อยทดสอบแบบมี cookie


12. คำถามที่ต้องถาม owner ของ hub (ยังค้าง)