---
title: "เอกสาร CI/CD Pipeline"
titleEn: "CI/CD Pipeline"
clauses: ["4.6.3"]
phase: 2
category: "ส่งมอบ"
generated: false
---

# เอกสาร 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. ภาพรวม

```mermaid
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 |

| Branch/Tag | สภาพแวดล้อม | Namespace | values | URL ของ environment |
|---|---|---|---|---|
| `develop` | dev | `mbu-dev` | `values-dev.yaml` | https://dev.dhammaverse.mbu.ac.th |
| `release/*` | test (SIT/UAT) | `mbu-test` | `values-test.yaml` | https://test.dhammaverse.mbu.ac.th |
| tag `v*` (ต้องอยู่บน `main`) | prod | `mbu-prod` | `values-prod.yaml` | https://dhammaverse.mbu.ac.th |

**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. วิธีเปิดใช้งาน (สรุป)

1. push repository ไปยัง GitHub ขององค์กรมหาวิทยาลัย และตั้ง branch protection (`main`, `develop`) ให้ต้องผ่าน workflow ก่อน merge
2. แก้ข้อสังเกตในหัวข้อ 8
3. สร้าง environments `dev`, `test`, `prod` พร้อม secret `KUBE_CONFIG` (หรือ OIDC) และ variable `SMOKE_URL`; ตั้ง Required reviewers ที่ `prod`
4. เตรียมคลัสเตอร์/Secret/DNS ตาม `/documents/deployment-guide`
5. ตั้ง repository variable `DEPLOY_ENABLED = "true"`
6. ทดสอบลำดับ: push `develop` → ตรวจ `mbu-dev`; สร้าง `release/x.y` → UAT บน `mbu-test`; merge เข้า `main` แล้วสร้าง tag `vX.Y.Z` → อนุมัติ → `mbu-prod`
7. ทดลอง rollback โดยจงใจให้ smoke test ล้มเหลวบน `mbu-dev` อย่างน้อย 1 ครั้งและบันทึกผล

## 10. การตรวจไฟล์ pipeline ในเครื่อง

```bash
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
```
