เอกสาร CI/CD Pipeline
อ้างอิง TOR: ข้อ 4.6.3 (CI/CD บน GitHub พร้อมการย้อนกลับ — Rollback)
ไฟล์: .github/workflows/ci-cd.yml (GitHub Actions) ร่วมกับ Helm chart infra/helm/mbu-buddhist-ai/
ฉบับ: 1.0 — วันที่ 3 ตุลาคม พ.ศ. 2569
สถานะ: ไฟล์ workflow ส่งมอบแล้วและผ่านการตรวจโครงสร้างอัตโนมัติ (tests/unit/infra-yaml.test.ts — parse YAML, ลำดับ job, trigger, Node 22, deploy-prod ตรวจ tag บน main) แต่ ยังไม่ได้รันบน GitHub Actions จริง ในรอบสาธิต เพราะ sandbox ไม่ได้ push ไปยัง GitHub repository ที่ตั้งค่า secrets/environments และไม่มีคลัสเตอร์ Kubernetes ข้อ 4.6.3 จึงมีสถานะ partial ในหน้า /tor ข้อสังเกตที่ต้องแก้ก่อนเปิดใช้อยู่ในหัวข้อ 8
1. ภาพรวม
flowchart LR
T["push / pull_request / workflow_dispatch / tag v*"] --> L["1. lint"]
L --> TE["2. test<br/>(vitest + coverage gate)"]
TE --> S["3. security<br/>(npm audit + pentest-lite)"]
S --> B["4. build<br/>(next build + images*)"]
B --> E["5. e2e<br/>(Playwright Chromium)"]
E --> D1["6. deploy-dev<br/>develop → mbu-dev"]
E --> D2["7. deploy-test<br/>release/* → mbu-test"]
E --> D3["8. deploy-prod<br/>tag v* บน main → mbu-prod<br/>(ต้องอนุมัติ)"]
D1 & D2 & D3 -. ล้มเหลว .-> RB["helm --atomic / helm rollback"]
* build/push container image และงาน deploy ทำงานเฉพาะเมื่อเป็น push และตั้ง repository variable DEPLOY_ENABLED = "true"
2. Trigger และ Branch
| เหตุการณ์ | เงื่อนไข | งานที่รัน |
|---|
push | branch main, develop, release/** หรือ tag v* | lint → test → security → build → e2e (+ deploy ตาม branch/tag) |
pull_request | เข้า main หรือ develop | lint → test → security → build → e2e (ไม่ deploy) |
workflow_dispatch | สั่งรันด้วยมือ | lint → test → security → build → e2e |
Concurrency: ci-cd-${{ github.ref }} — ยกเลิกรอบเก่าเฉพาะ pull request; งาน deploy แต่ละ environment มี concurrency group ของตัวเอง (deploy-dev/deploy-test/deploy-prod) และไม่ยกเลิกงานที่กำลัง deploy
Permissions เริ่มต้น: contents: read (งาน build เพิ่ม packages: write สำหรับ ghcr.io)
3. ตัวแปรสภาพแวดล้อมระดับ workflow
| ตัวแปร | ค่า | หมายเหตุ |
|---|
NODE_VERSION | 22 | |
NEXT_TELEMETRY_DISABLED | 1 | |
DATABASE_URL | file:./test.db | SQLite แยกไฟล์สำหรับ CI |
AUTH_SECRET | ค่าเฉพาะ CI (ไม่ใช่ความลับจริง) | |
LLM_PROVIDER | mock | งานทดสอบทุกงานใช้ Mock LLM — pipeline ไม่อ่าน/ไม่เก็บคีย์ LLM |
ADMIN_IP_ALLOWLIST | localhost | |
NEXT_PUBLIC_BASE_URL | http://localhost:3000 | |
HELM_VERSION | v3.16.4 | |
HELM_RELEASE / HELM_CHART | mbu-buddhist-ai / infra/helm/mbu-buddhist-ai | |
IMAGE_REGISTRY | ghcr.io | |
DATA_ENCRYPTION_KEY สร้างใหม่แบบสุ่มในแต่ละงานด้วย openssl rand -base64 32 (step “Generate CI-only encryption key”)
4. รายละเอียดทุก Job และ Step
4.1 Job lint — Lint (timeout 15 นาที)
| # | Step | คำสั่ง/การทำงาน |
|---|
| 1 | Checkout | actions/checkout@v4 |
| 2 | Setup Node.js 22 | actions/setup-node@v4 (cache npm) |
| 3 | Install dependencies | npm ci |
| 4 | Generate Prisma client | npx prisma generate |
| 5 | ESLint | npm run lint |
| 6 | TypeScript type check | npx tsc --noEmit |
| 7 | Setup Helm | azure/setup-helm@v4 (v3.16.4) |
| 8 | Helm lint + template (dev / test / prod) | วนทั้ง 3 environment: helm lint และ helm template ด้วย values.yaml + values-<env>.yaml |
4.2 Job test — Test (unit + API, coverage gate) (needs: lint, 30 นาที)
| # | Step | คำสั่ง/การทำงาน |
|---|
| 1–3 | Checkout / Setup Node / Install | เหมือน lint |
| 4 | Generate CI-only encryption key | เขียน DATA_ENCRYPTION_KEY ลง $GITHUB_ENV |
| 5 | Prepare database (migrate + seed) | npx prisma generate && npx prisma migrate deploy && npx prisma db seed |
| 6 | Vitest + coverage gate (≥ 80%) | npx vitest run --coverage — เกณฑ์ใน vitest.config.ts: lines/functions/statements ≥80%, branches ≥75% ของ src/domain + src/application; ต่ำกว่าเกณฑ์ = งานล้มเหลว |
| 7 | Upload coverage report | actions/upload-artifact@v4 → artifact coverage-report (coverage/, เก็บ 14 วัน, ทำเสมอ) |
หมายเหตุ: step นี้เรียก npx vitest run --coverage โดยตรง จึงไม่ได้ตั้ง OPENAPI_COVERAGE=1 ชั้นตรวจการเรียก API จริงขณะรัน (runtime) จึงไม่ทำงานใน CI — ชั้น static (tests/api/openapi-coverage.test.ts) ยังทำงาน (ดูข้อเสนอในหัวข้อ 8)
4.3 Job security — Security (npm audit + pentest-lite) (needs: test, 30 นาที)
| # | Step | คำสั่ง/การทำงาน |
|---|
| 1–3 | Checkout / Setup Node / Install | |
| 4 | npm audit (fail on High/Critical) | npm audit --audit-level=high |
| 5 | Generate CI-only encryption key | |
| 6 | Prepare database (migrate + seed) | |
| 7 | Start dev server (background) | PORT=3000 nohup npm run dev, รอ GET /api/health สูงสุด 120 วินาที (ไม่ขึ้น = แสดง log และล้มเหลว) |
| 8 | Vulnerability scan report | npx tsx scripts/security/npm-audit-report.ts → docs/deliverables/vulnerability-scan-report.md |
| 9 | Pentest-lite (8 เคส OWASP) | npx tsx scripts/security/pentest-lite.ts (env BASE_URL=http://localhost:3000) → docs/deliverables/pentest-report.md — ดูหัวข้อ 8: ไฟล์สคริปต์นี้ไม่มีใน repository |
| 10 | Stop dev server | kill ตาม dev-server.pid (ทำเสมอ) |
| 11 | Upload security reports | artifact security-reports (รายงาน 2 ฉบับ + dev-server.log, เก็บ 30 วัน) |
4.4 Job build — Build (needs: security, 30 นาที, packages: write)
| # | Step | คำสั่ง/การทำงาน |
|---|
| 1–3 | Checkout / Setup Node / Install | |
| 4 | Generate CI-only encryption key | |
| 5 | Prepare database (migrate + seed) | |
| 6 | Next.js production build | npm run build |
| 7 | Log in to container registry (ghcr.io) | docker/login-action@v3 ด้วย GITHUB_TOKEN — เฉพาะ push + DEPLOY_ENABLED == 'true' |
| 8 | Build & push images (8 services) | วน identity chat ai-orchestrator retrieval ingestion evaluation config report: docker build -f services/<svc>/Dockerfile → push ghcr.io/mbu-ac-th/mbu-<svc>-service:<SHA> และ tag ตาม git tag เมื่อเป็น release — เงื่อนไขเดียวกับ step 7 (ยังไม่มี Dockerfile — หัวข้อ 8) |
4.5 Job e2e — E2E (Playwright / Chromium) (needs: build, 45 นาที)
| # | Step | คำสั่ง/การทำงาน |
|---|
| 1–3 | Checkout / Setup Node / Install | |
| 4 | Install Chromium for Playwright | npx playwright install --with-deps chromium แล้วตั้ง CHROMIUM_PATH เป็น executable ของ Playwright (runner ของ CI เป็นเครื่องเปล่า ต่างจาก sandbox ที่ใช้ Chromium ของระบบ) |
| 5 | Generate CI-only encryption key | |
| 6 | Prepare database (migrate + seed) | |
| 7 | Playwright tests | npx playwright test — playwright.config.ts เปิด dev server เอง (webServer), 3 viewport, retry 1 ครั้งเมื่อ CI |
| 8 | Upload Playwright report | artifact playwright-report (playwright-report/, test-results/, เก็บ 14 วัน, ทำเสมอ) |
หมายเหตุ: playwright.config.ts ใช้พอร์ต E2E_PORT (ค่าเริ่มต้น 3210) และ job นี้ไม่ได้ตั้ง E2E_PORT จึงเปิด dev server ที่ 3210
4.6 Job deploy-dev — Deploy → dev (mbu-dev) (needs: build, e2e, 30 นาที)
เงื่อนไข: push + refs/heads/develop + DEPLOY_ENABLED == 'true'; environment dev; env KUBE_NAMESPACE=mbu-dev, VALUES_FILE=values-dev.yaml, IMAGE_TAG=<commit SHA>, SMOKE_URL=vars.SMOKE_URL
| # | Step | การทำงาน |
|---|
| 1 | Checkout | |
| 2 | Setup Helm | v3.16.4 |
| 3 | Configure kubeconfig (secret KUBE_CONFIG) | ไม่มี secret = ล้มเหลวพร้อมข้อความ; ถอด base64 ลง $RUNNER_TEMP/kube/config (chmod 600) แล้วตั้ง KUBECONFIG |
| 4 | Record current Helm revision (ใช้ย้อนกลับ) | helm status -o json → .version เก็บเป็น output revision (ว่าง = ติดตั้งครั้งแรก) |
| 5 | Helm upgrade (atomic) | helm upgrade --install … --create-namespace -f values.yaml -f values-dev.yaml --set global.image.tag=$IMAGE_TAG --atomic --wait --timeout 10m --history-max 10 |
| 6 | Smoke test | kubectl get deploy,hpa,ingress -l app.kubernetes.io/instance=… และ curl -fsS --retry 10 ไปที่ SMOKE_URL (ไม่ตั้ง = ข้ามการเรียก health) |
| 7 | Rollback (helm rollback) on failure | เมื่อ step ก่อนหน้าล้มเหลวและ upgrade ไม่ถูกข้าม: แสดง helm history; ติดตั้งครั้งแรก = ไม่มีอะไรย้อน (--atomic ถอนให้แล้ว); ถ้า upgrade สำเร็จแต่ smoke test ล้ม หรือสถานะ release ไม่ใช่ deployed → helm rollback <release> <revision เดิม> --wait --timeout 5m |
4.7 Job deploy-test — Deploy → test (mbu-test)
เหมือน deploy-dev ทุก step ต่างกันที่เงื่อนไข startsWith(github.ref, 'refs/heads/release/'), environment test, KUBE_NAMESPACE=mbu-test, VALUES_FILE=values-test.yaml — ใช้สำหรับ SIT/UAT ร่วมกับมหาวิทยาลัย
4.8 Job deploy-prod — Deploy → prod (mbu-prod) (45 นาที)
เงื่อนไข: push + startsWith(github.ref, 'refs/tags/v') + DEPLOY_ENABLED == 'true'; environment prod (ตั้ง Required reviewers ให้มหาวิทยาลัยอนุมัติก่อนขึ้นระบบจริง); IMAGE_TAG=<ชื่อ tag>
| # | Step | การทำงาน |
|---|
| 1 | Checkout (full history) | fetch-depth: 0 |
| 2 | Verify tag is on main | git merge-base --is-ancestor $GITHUB_SHA origin/main — ไม่อยู่บน main = ยกเลิก |
| 3–7 | Setup Helm / Configure kubeconfig / Record revision / Helm upgrade (atomic, timeout 15m) / Smoke test | เหมือน deploy-dev |
| 8 | Rollback (helm rollback) on failure | เหมือน deploy-dev |
5. Secrets และ Variables ที่ต้องตั้งใน GitHub
| ชื่อ | ประเภท | ระดับ | ใช้ใน | หมายเหตุ |
|---|
DEPLOY_ENABLED | Variable | Repository | build (images), deploy-* | ตั้ง "true" เพื่อเปิดงาน build image และ deploy (ค่าเริ่มต้นปิด) |
KUBE_CONFIG | Secret | Environment dev / test / prod (แยกกัน) | deploy-* | kubeconfig เข้ารหัส base64 ควรใช้ ServiceAccount ที่มีสิทธิ์เฉพาะ namespace นั้น |
| (ทางเลือก) OIDC | — | Environment | deploy-* | แนะนำสำหรับ prod: เพิ่ม permissions: id-token: write และแทน step kubeconfig ด้วย action ของผู้ให้บริการ (AWS EKS / Azure AKS / GCP GKE) — ไม่ต้องเก็บ credential ระยะยาว |
SMOKE_URL | Variable | Environment (ไม่บังคับ) | deploy-* | เช่น https://dhammaverse.mbu.ac.th/api/health |
GITHUB_TOKEN | Secret อัตโนมัติ | — | build | push ไป ghcr.io (ไม่ต้องตั้งเพิ่ม) |
คีย์ LLM (Typhoon/อื่น ๆ) ไม่อยู่ใน GitHub — มหาวิทยาลัยใส่เองใน Kubernetes Secret mbu-buddhist-ai-secrets ของแต่ละ namespace (TOR 4.6.1)
6. Artifacts
| Artifact | Job | เนื้อหา | เก็บ |
|---|
coverage-report | test | coverage/ (text-summary, json-summary, HTML) | 14 วัน |
security-reports | security | vulnerability-scan-report.md, pentest-report.md, dev-server.log | 30 วัน |
playwright-report | e2e | playwright-report/, test-results/ (รวม e2e-results.json) | 14 วัน |
7. กลไกการย้อนกลับ (Rollback)
| ระดับ | กลไก |
|---|
| อัตโนมัติขณะ deploy | helm upgrade --atomic --wait — pod ไม่พร้อมภายใน timeout หรือ upgrade ล้มเหลว Helm ย้อน revision เดิมให้ (ติดตั้งครั้งแรกจะถอนการติดตั้ง) |
| อัตโนมัติหลัง deploy | step “Rollback (helm rollback) on failure” ย้อนไป revision ที่บันทึกก่อน deploy เมื่อ smoke test ล้มเหลวหรือ release ไม่อยู่สถานะ deployed |
| ด้วยมือ | helm history mbu-buddhist-ai -n <ns> → helm rollback mbu-buddhist-ai <rev> -n <ns> --wait (เก็บ 10 revision) หรือ re-run pipeline ของ tag/commit ก่อนหน้า (image tag = commit SHA/ชื่อ tag จึงย้อนเวอร์ชันได้แน่นอน) |
| ระดับการตั้งค่าในแอป | snapshot ของ AI config และเวอร์ชัน prompt ย้อนกลับได้ทันทีโดยไม่ต้อง deploy |
| ระดับข้อมูล | กู้คืนจากไฟล์สำรอง (/documents/backup-restore-guide) เมื่อ migration ทำให้ข้อมูลเสีย |
8. ข้อสังเกตจากการตรวจ repository (ต้องดำเนินการก่อนเปิดใช้ pipeline จริง)
| # | ประเด็น | ผลกระทบ | แนวทาง |
|---|
| 1 | step Pentest-lite เรียก scripts/security/pentest-lite.ts แต่ ไม่มีไฟล์นี้ใน repository (scripts/security/ มีเพียง lib.ts, npm-audit-report.ts) — สอดคล้องกับที่การทดสอบเจาะระบบไม่ได้ดำเนินการในรอบนี้ตามคำสั่งของผู้ว่าจ้าง | job security จะล้มเหลวที่ step นี้ และหยุด job ถัดไป | จัดทำสคริปต์ทดสอบความปลอดภัยอัตโนมัติก่อนเปิดใช้ หรือนำ step ออก/ตั้ง continue-on-error ชั่วคราวพร้อมบันทึกเหตุผล |
| 2 | step Build & push images อ้าง services/<svc>/Dockerfile ซึ่ง ยังไม่มี (ระบบสาธิตเป็น Next.js แอปเดียว) | เมื่อตั้ง DEPLOY_ENABLED=true งาน build จะล้มเหลว | แยก service และเพิ่ม Dockerfile ตาม /documents/deployment-guide หัวข้อ 5.5 (หรือเริ่มจาก image แอปเดียว) |
| 3 | job test ไม่ได้ตั้ง OPENAPI_COVERAGE=1 | ชั้น runtime ของ openapi-coverage ไม่ทำงานใน CI | เปลี่ยน step เป็น npm run test:coverage |
| 4 | Prisma datasource ยังเป็น SQLite | prisma migrate deploy บน Production (PostgreSQL) ยังใช้ไม่ได้ | สร้าง migration สำหรับ PostgreSQL ก่อน deploy production |
| 5 | ยังไม่มี step migrate ฐานข้อมูลใน deploy-* | schema ไม่อัปเดตอัตโนมัติ | เพิ่ม Helm hook/Job prisma migrate deploy ก่อนอัปเกรด |
9. วิธีเปิดใช้งาน (สรุป)
- push repository ไปยัง GitHub ขององค์กรมหาวิทยาลัย และตั้ง branch protection (
main, develop) ให้ต้องผ่าน workflow ก่อน merge
- แก้ข้อสังเกตในหัวข้อ 8
- สร้าง environments
dev, test, prod พร้อม secret KUBE_CONFIG (หรือ OIDC) และ variable SMOKE_URL; ตั้ง Required reviewers ที่ prod
- เตรียมคลัสเตอร์/Secret/DNS ตาม
/documents/deployment-guide
- ตั้ง repository variable
DEPLOY_ENABLED = "true"
- ทดสอบลำดับ: push
develop → ตรวจ mbu-dev; สร้าง release/x.y → UAT บน mbu-test; merge เข้า main แล้วสร้าง tag vX.Y.Z → อนุมัติ → mbu-prod
- ทดลอง rollback โดยจงใจให้ smoke test ล้มเหลวบน
mbu-dev อย่างน้อย 1 ครั้งและบันทึกผล
10. การตรวจไฟล์ pipeline ในเครื่อง
npx vitest run tests/unit/infra-yaml.test.ts # parse YAML + ตรวจโครงสร้าง workflow และ Helm chart
# ถ้ามี helm ในเครื่อง
for e in dev test prod; do helm lint infra/helm/mbu-buddhist-ai -f infra/helm/mbu-buddhist-ai/values.yaml -f infra/helm/mbu-buddhist-ai/values-$e.yaml; done