sync-employee พังมานาน — image เก่าค้าง แล้วเจอ DNS ตันซ้อนอยู่ข้างหลัง
CronJob sync-employee ใน dev fail ทุกวัน สาเหตุไม่ใช่ config แต่เป็น image ที่ build ผิด base และ repo นั้นไม่มี pipeline เลย — แก้ image ผ่านแล้ว แต่โผล่บั๊กที่สองคือ cluster resolve ชื่อ empinfo ไม่ได้ ทั้งที่เส้นทางเครือข่ายถึงอยู่แล้ว
อัปเดต: 2026-08-23
ขอบเขต: CronJob
sync-employeeใน namespacesuperappdevแหล่งข้อมูล: log ของ pod จริง +docker --list-runtimesเทียบ 2 image + ทดสอบ DNS/TCP จากใน cluster · 23/08/2026
สรุปสั้น
| อาการ | CronJob รันทุกวัน 01 แล้ว fail ทิ้ง pod Error ค้างไว้ 3 ตัวทุกครั้ง |
| บั๊กที่ 1 — image build ผิด base | ✅ แก้แล้ว (PR #2462) |
| บั๊กที่ 2 — cluster resolve ชื่อ API ไม่ได้ | 🔶 ยังค้าง รอตัดสินใจเลือกวิธี |
| หนี้ที่โผล่มา | Console_SyncEmployee ไม่มี CI/CD เลยสักไฟล์ · และ hardcode credential ไว้ในโค้ด |
1. บั๊กที่ 1 — image build ผิด base image
pod ตายทันทีที่ start:
You must install or update .NET to run this application.
App: /app/Console_SyncEmployee.dll
Framework: 'Microsoft.AspNetCore.App', version '10.0.0'
No frameworks were found.
สาเหตุ
Console_SyncEmployee เป็น console app ก็จริง แต่ SupApp_util_lib ลาก FrameworkReference ของ Microsoft.AspNetCore.App เข้ามาด้วย
Dockerfile รุ่นเก่าใช้ base mcr.microsoft.com/dotnet/runtime:10.0 ซึ่งไม่มี framework ตัวนั้น → .NET หาไม่เจอตอน launch
ทำไมถึงค้างนานทั้งที่โค้ดแก้แล้ว
Dockerfile บน main | ✅ เปลี่ยนเป็น dotnet/aspnet:10.0 ตั้งแต่ 17/08 (commit 5edd7fc) พร้อม comment อธิบาย error นี้ตรงๆ |
Pipeline ใน repo Console_SyncEmployee | 🔴 ไม่มีเลยสักไฟล์ |
| ผลลัพธ์ | image ไม่เคยถูก build ใหม่ · ACR มีแค่ dev-889f3a3 จาก 31/07 ซึ่งยังใช้ base ตัวผิด |
โค้ดแก้แล้ว แต่ไม่มีใครพามันขึ้น — repo นี้ต้อง build มือทุกครั้ง (เห็นร่องรอยจาก tag เก่าอย่าง
dev-23a732c-tls1)
วิธีแก้ที่ทำไป
build จาก origin/main (5edd7fc) → push dev-5edd7fc → PR #2462 ชี้ manifest มา tag ใหม่ → ArgoCD sync ให้เอง
หลักฐาน — dotnet --list-runtimes ใน image ทั้งสองตัว:
dev-889f3a3 (เดิม) Microsoft.NETCore.App 10.0.10 <- มีตัวเดียว
dev-5edd7fc (ใหม่) Microsoft.NETCore.App 10.0.11
Microsoft.AspNetCore.App 10.0.11 <- ตัวที่ขาด
ลบ Job เก่าที่ fail ค้าง (sync-employee-29790360) แล้ว → pod Error 3 ตัวหายจาก namespace แล้ว
ขั้นตอน build มือ สำหรับครั้งหน้า
Dockerfile ต้องการของ 2 อย่างที่ไม่ได้อยู่ในrepo:
- Zscaler root CA — อยู่ที่
docker/zscaler-root-ca.crtใน repo แล้ว (จำเป็นเพราะเครือข่ายองค์กร TLS-inspect ทำให้dotnet restoreเจอ cert chain พัง) - NuGet.Config ที่มี PAT แบบ cleartext — mount เป็น BuildKit secret ไม่ให้ตกอยู่ใน image layer
# token สำหรับ Azure Artifacts
az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv
# เอาไปใส่เป็น ClearTextPassword ของ source eximth ในไฟล์ NuGet.Config ชั่วคราว
az acr login -n suaazcrdev
DOCKER_BUILDKIT=1 docker build \
--secret id=nuget_config,src=/path/to/NuGet.Config \
-t suaazcrdev-a2aqcveegedbdbbk.azurecr.io/sync-employee:dev-<sha> .
docker push suaazcrdev-a2aqcveegedbdbbk.azurecr.io/sync-employee:dev-<sha>
จากนั้นแก้ tag ที่ Backend_Iac → src/yamls/dev/superapp/cronjobs/sync-employee-cronjob.yaml
ลบไฟล์ NuGet.Config ชั่วคราวทิ้งทุกครั้ง — มี token อยู่ในนั้น
2. 🔶 บั๊กที่ 2 — cluster resolve ชื่อ empinfo ไม่ได้
พอ image ถูกแล้ว app start ได้ วิ่งเข้าไปถึงโค้ดจริง แล้วตายที่:
Cannot load library libgssapi_krb5.so.2 <- ดูหมายเหตุด้านล่าง
System.Net.Http.HttpRequestException: Name or service not known
(empinfo_api_sit.exim.go.th:443)
at Console_SyncEmployee.EmpInfo.EmpInfoClient.GetUsersAsync (EmpInfoClient.cs:26)
🔶 บรรทัดแรกยังไม่มีใครตาม — libgssapi_krb5.so.2 หายจาก base image ใหม่ (Npgsql probe หา Kerberos) · ยังไม่ fatal เพราะ app ตายที่ HTTP ก่อนจะแตะ Postgres แต่แปลว่า เส้นทางไปฐานข้อมูลยังไม่เคยถูกทดสอบบน image นี้เลย → ประโยค “แก้ DNS แล้วจบ” ยังไม่มีหลักฐานรองรับ
endpoint ถูก hardcode ไว้ในโค้ด — ไม่มี env ให้ override · และมี 2 ตัว ไม่ใช่ตัวเดียว
// Program.cs
const string endpoint = "https://empinfo_api_sit.exim.go.th/api/directory/users";
const string objectIdEndpoint = "https://empinfo_api_sit.exim.go.th/api/users/object-id/all";
⚠️ สำคัญกับทางเลือก B — แก้ตัวเดียวแล้วจะไปตายที่ตัวที่สอง ต้องแก้ทั้งคู่
สิ่งที่ทดสอบแล้ว
| ทดสอบ | ผล |
|---|---|
resolve exim.go.th จาก ใน cluster | ❌ ไม่ได้เลยสักชื่อ |
| resolve ชื่อสาธารณะ / Azure private link จากใน cluster | ✅ ผ่าน 4/4 (DNS ของ cluster ปกติดี) |
ConfigMap coredns-custom | มีอยู่แต่ ว่างเปล่า ไม่มี forward ของ exim.go.th |
TCP ไป 10.0.7.119:443 (IP จริงของ empinfo) จากใน cluster | ✅ ถึง |
TCP ไป 10.0.1.190:443 | ❌ timeout |
🔑 เส้นทางเครือข่ายมีอยู่แล้ว ขาดแค่ DNS อย่างเดียว
⚠️ กับดัก — exim.go.th มี wildcard DNS อย่าใช้ nslookup จากเครื่องตัวเองพิสูจน์ว่าชื่อมีจริง
nslookup จากเครื่องในเครือข่ายองค์กรตอบ ทุกชื่อ ที่ลงท้าย .exim.go.th รวมถึงชื่อมั่วๆ:
this-name-should-not-exist-xyz123.exim.go.th -> 10.0.1.190 (wildcard)
random_garbage_abc.exim.go.th -> 10.0.1.190 (wildcard)
วิธีแยกของจริงออกจาก wildcard คือดูว่าตอบ IP ต่างจาก wildcard หรือเปล่า:
empinfo_api_sit.exim.go.th -> 10.0.7.119 <- ของจริง (A record แท้)
empinfo_api_uat.exim.go.th -> 10.0.7.119 <- ของจริง
empinfo-api-sit.exim.go.th -> 10.0.1.190 <- wildcard = ไม่มีจริง
empinfo.exim.go.th -> 10.0.1.190 <- wildcard = ไม่มีจริง
→ ขีดล่างในชื่อเป็นของจริง ไม่ใช่พิมพ์ผิด และรูปแบบขีดกลางที่ “ดูถูกต้องกว่า” ต่างหากที่ไม่มีอยู่
ทางเลือกในการแก้ — ยังไม่เลือก
| ทาง | แก้ที่ | กระทบใคร | ข้อสังเกต |
|---|---|---|---|
A. เพิ่ม hosts เข้า CoreDNS | ConfigMap coredns-custom | 🔴 DNS ของทั้ง cluster (dev + uat) | เป็น extension point ที่ AKS ออกแบบมาให้ใช้ · แต่ถ้า Corefile พัง = DNS ล่มทั้ง cluster · hardcode IP |
| B. แก้โค้ดให้ยิง IP ตรง | Console_SyncEmployee | เฉพาะ job นี้ | โค้ด skip TLS validation ให้ host นี้อยู่แล้ว (CreateInsecureHttpClient) จึงไม่ติด cert · แต่ต้อง build มือ + bump tag อีกรอบ และ hardcode IP ลงโค้ด |
C. ให้ Infra ทำ DNS forwarding ของ exim.go.th ให้ VNet | Infra team | ถูกต้องที่สุด | ทีมเดียวกับที่ต้องจัดการ private endpoint · รอคิว |
hostAliases เฉพาะ pod | — | — | ❌ ใช้ไม่ได้ — Kubernetes ปฏิเสธ hostname ที่มีขีดล่าง (validate ตาม RFC 1123) ทดสอบแล้ว |
ยังไม่ลงมือ เพราะทาง A แตะ DNS ของทั้ง cluster ซึ่งขัดกับกติกา “อย่ากระทบ dev/uat”
3. 🔴 หนี้ที่โผล่มาระหว่างทาง
Console_SyncEmployee ไม่มี CI/CD
ไม่มีไฟล์ pipeline ใน repo เลย · ทุกครั้งที่แก้โค้ดต้อง build มือ + ไป bump tag ที่ Backend_Iac เอง
เรื่องนี้จะกลับมาเกิดซ้ำแน่นอน — บั๊กที่ 1 ก็เกิดจากเหตุนี้ล้วนๆ (โค้ดแก้ถูกตั้งแต่ 17/08 แต่ไม่มีใครพาขึ้น)
🔴 hardcode credential ไว้ในโค้ด — ข้อที่หนักที่สุดในหน้านี้
Program.cs บน main มีค่า default ที่เป็น รหัสผ่านฐานข้อมูล dev จริง และ API key ของ EmpInfo จริง เขียนเป็น const อยู่ในไฟล์ commit เข้า repo
const string defaultConnectionString = "Host=...;Username=sua-dev-admin;Password=<ค่าจริงอยู่ในโค้ด>;..."
const string defaultApiKey = "<ค่าจริงอยู่ในโค้ด>"
ทั้งคู่มี env var รองรับอยู่แล้ว (POSTGRES_CONNECTION_STRING, EMPINFO_API_KEY) และ CronJob ก็ส่งผ่าน secret มาให้จริง — ค่า default พวกนี้จึงไม่ได้ถูกใช้บน cluster แต่ยังอยู่ในประวัติ git
ต้องทำ: rotate ทั้งสองค่า แล้วถอด default ออกให้ throw แทนถ้าไม่มี env
4. สถานะปัจจุบัน
- ✅ pod
Errorหายจากsuperappdevแล้ว (ทั้ง Job เดิมsync-employee-29790360และ Job ทดสอบsync-employee-verifyที่เราสร้างเอง) - ✅ CronJob ชี้ image ที่ถูกต้องแล้ว (
dev-5edd7fc) - 🔶 รอบถัดไปที่ 01 จะยังไม่สำเร็จ — จะไปตายที่ DNS แทน (ข้อ 2) แต่จะ fail แบบเข้าใจสาเหตุแล้ว ไม่ใช่ตายตั้งแต่ start · และจะทิ้ง pod
Errorไว้ 3 ตัวทุกรอบเหมือนเดิม จนกว่าจะแก้ข้อ 2 - 🔶 ต้องเลือกทาง A / B / C ก่อนถึงจะรันผ่านจริง · และแก้ endpoint ทั้ง 2 ตัว ถ้าเลือกทาง B
- 🔶 ยังไม่รู้ว่า DNS เป็นด่านสุดท้ายจริงไหม —
libgssapiและเส้นทาง Postgres ยังไม่เคยถูกทดสอบ
📌 บทเรียนจากรอบนี้: การสั่งรัน Job ทดสอบเองก็ทิ้ง pod
Errorค้างแบบเดียวกับของจริง — ต้องลบ Job ทดสอบทุกครั้งหลังอ่าน log เสร็จ ไม่งั้นเอกสารที่เขียนว่า “pod Error หายแล้ว” จะกลายเป็นข้อมูลเท็จภายในไม่กี่นาที