Private Docs

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 ใน namespace superappdev แหล่งข้อมูล: 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-5edd7fcPR #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:

  1. Zscaler root CA — อยู่ที่ docker/zscaler-root-ca.crt ใน repo แล้ว (จำเป็นเพราะเครือข่ายองค์กร TLS-inspect ทำให้ dotnet restore เจอ cert chain พัง)
  2. 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_Iacsrc/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 เข้า CoreDNSConfigMap 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 ให้ VNetInfra teamถูกต้องที่สุดทีมเดียวกับที่ต้องจัดการ private endpoint · รอคิว
D. 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 หายแล้ว” จะกลายเป็นข้อมูลเท็จภายในไม่กี่นาที