CI/CD
Phạm vi hệ thống
- CI/CD là yêu cầu hệ thống, không phải feature.
- Áp dụng cho 2 project hiện có:
v-nexus-backendv-nexus-frontend
mobile: chưa có trong phạm vi hiện tại.
Trạng thái hiện tại
| Project | Trạng thái CI | Trạng thái CD | Ghi chú |
|---|---|---|---|
Backend (v-nexus-backend) | Có GitHub Actions (ci-pr.yml, ci-test.yml) | Có auto deploy môi trường test | Deploy qua GHCR + Cloudflare Tunnel SSH + Docker Compose |
Frontend (v-nexus-frontend) | Chưa có workflow GitHub Actions trong repo | Chưa có pipeline tự động trong repo | Có command Cloudflare để build/deploy thủ công (build:cf, deploy:cf) |
Sơ đồ tổng thể 2 project
flowchart LR
DEV[Developer] -->|01. Push/PR| GH[GitHub]
subgraph BE[Backend CI/CD - Active]
GH -->|02. PR -> test| BEPR[ci-pr.yml]
BEPR -->|03. Merge -> test| BEPUSH[ci-test.yml]
BEPUSH -->|04. Build/Push| GHCR[(GHCR)]
BEPUSH -->|05. Deploy| CFSSH[Cloudflare Access + Tunnel SSH]
CFSSH -->|06. Remote docker compose| SERVER[Test Server]
SERVER -->|07. Smoke| SMOKE[API smoke tests]
SMOKE -->|08. Notify| TG[Telegram]
end
subgraph FE[Frontend CI/CD - Current]
GH -->|09. Manual release by dev| FEMANUAL[npm run build:cf\nnpm run deploy:cf]
FEMANUAL -->|10. Deploy| CFW[Cloudflare Workers]
endWorkflow files
.github/workflows/ci-pr.yml.github/workflows/ci-test.yml
CI for PR (ci-pr.yml)
| Mục | Cấu hình |
|---|---|
| Trigger | pull_request vào branch test |
| Job 1 | Unit & Integration Tests (./mvnw -B verify, publish JUnit, upload Jacoco artifact, comment coverage PR) |
| Job 2 | Verify Docker Build (docker/build-push-action, build image không push) |
| Gate | Job 2 phụ thuộc Job 1 (needs) |
| Concurrency | pr-<pull_request.number>, cancel run cũ |
CI/CD on push test (ci-test.yml)
| Stage | Nội dung thực thi |
|---|---|
| Test | Checkout, setup Java 21, ./mvnw -B verify, publish JUnit |
| Build & Push | Đọc pom.xml version, setup buildx, login GHCR, tạo tags (test-latest, test-<sha>, test-<version>-<run_number>), build/push image |
| Deploy | Cài cloudflared, setup ssh-agent, inject known_hosts, SSH qua Cloudflare Access Service Token, docker compose pull, docker compose up -d --remove-orphans, đợi healthcheck container |
| Cleanup | docker image prune -af --filter 'until=168h' trên server |
| Smoke Test | GET /api/actuator/health, GET /api/v3/api-docs, auth negative test POST /api/auth/login (expect 4xx) |
| Notify | Tổng hợp trạng thái jobs và gửi Telegram message HTML |
Sơ đồ chi tiết Backend pipeline (thứ tự trên từng mũi tên)
flowchart TD A[PR opened to test branch] -->|01. Trigger PR workflow| B[Workflow ci-pr.yml] B -->|02. Run job unit-tests| C[Job unit-tests\n- setup-java 21\n- mvnw verify\n- junit report\n- jacoco artifact/comment] C -->|03. If pass -> run build-verify| D[Job build-verify\n- docker buildx\n- build image no push] D -->|04. PR checks pass -> allow merge| E[Merge commit to test] E -->|05. Trigger push workflow| F[Workflow ci-test.yml] F -->|06. Run job test| G[Job test\n- mvnw verify\n- junit report] G -->|07. needs=test| H[Job build-and-push\n- read pom version\n- login GHCR\n- build/push tags] H -->|08. Push image tags| I[(GHCR Package)] H -->|09. needs=build-and-push| J[Job deploy] J -->|10. Install cloudflared + ssh agent + known_hosts| K[Cloudflare Access Auth] K -->|11. Proxy SSH via tunnel| L[Test server shell] L -->|12. docker compose pull/up + health wait| M[Backend container healthy] M -->|13. needs=deploy| N[Job smoke-test\n- health\n- openapi\n- auth negative] N -->|14. needs all previous jobs| O[Job notify-telegram\n- aggregate status\n- send message]
Sơ đồ sequence deploy path backend
sequenceDiagram participant GH as GitHub Actions Runner participant CF as Cloudflare Access/Tunnel participant SV as Test Server participant GR as GHCR GH->>GR: (1) Pull image metadata / push image tags GH->>CF: (2) cloudflared access ssh with Service Token CF->>SV: (3) Forward SSH over Tunnel -> localhost:22 GH->>SV: (4) docker compose pull SV->>GR: (5) Pull test-latest/test-sha image GH->>SV: (6) docker compose up -d --remove-orphans GH->>SV: (7) Poll container health GH->>SV: (8) Run smoke endpoints via public URL
Secrets/Environment bắt buộc cho backend test deploy
| Secret | Mô tả |
|---|---|
SSH_HOST, SSH_USER, SSH_PRIVATE_KEY, SSH_KNOWN_HOSTS | SSH qua Cloudflare Tunnel |
DEPLOY_PATH | Thư mục deploy trên server |
SMOKE_TEST_URL | Base URL để smoke test |
CF_ACCESS_CLIENT_ID, CF_ACCESS_CLIENT_SECRET | Cloudflare Access Service Token |
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID | Thông báo Telegram |
Hiện trạng frontend
- Repo
v-nexus-frontendhiện không có.github/workflows/*. - Chưa có pipeline CI/CD tự động trong repo.
Khả năng deploy hiện có (manual)
| Script | Mục đích |
|---|---|
npm run build:cf | Build output cho Cloudflare (@opennextjs/cloudflare) |
npm run preview:cf | Preview bản Cloudflare build |
npm run deploy:cf | Deploy lên Cloudflare Workers |
Runtime/deploy target
wrangler.jsonc: worker entry.open-next/worker.js,nodejs_compat, assets.open-next/assets.open-next.config.ts: dùngdefineCloudflareConfigdefault.
Định nghĩa hệ thống (frontend)
- Trong giai đoạn hiện tại, frontend được xem là manual CD.
- Khi bổ sung GitHub Actions cho frontend, tài liệu này phải cập nhật thành auto CI/CD.
Mục tiêu tab
- Hướng dẫn đầy đủ one-time setup + vận hành deploy test cho backend.
- Nguồn chuẩn:
cicd-test-setup-checklist.md.
0. Điều kiện đầu vào
| Điều kiện | Yêu cầu |
|---|---|
| Branch triển khai | test |
| Workflow đã có trong repo | ci-pr.yml, ci-test.yml |
| Hạ tầng ngoài code | Telegram, Cloudflare Zero Trust/Tunnel, test server Docker, GitHub Environment secrets |
1. Telegram bot + group (one-time)
- Tạo bot bằng
@BotFather(/newbot) và lấy bot token. - Tạo group deploy notifications, add bot vào group.
- Lấy
chat_idgroup (số âm) quagetUpdates. - Verify gửi tin nhắn bằng curl.
TELEGRAM_BOT_TOKEN="<paste-token>"TELEGRAM_CHAT_ID="<paste-chat-id>"
curl -sS -X POST \ "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -H "Content-Type: application/json" \ -d "{\"chat_id\":\"${TELEGRAM_CHAT_ID}\",\"text\":\"<b>Setup smoke test</b> ✅\",\"parse_mode\":\"HTML\"}" \ | jq '.ok'2. Cloudflare Tunnel SSH + Access (one-time)
- Trong Zero Trust -> Tunnels: add public hostname SSH (
ssh.test.<domain>->localhost:22). - Tạo Access Application kiểu Self-hosted cho
ssh.test.<domain>. - Tạo policy
Allowtheo selectorService Auth. - Tạo Service Token
vnexus-ci-test, lấy:CF_ACCESS_CLIENT_IDCF_ACCESS_CLIENT_SECRET
- Gắn Service Token vào policy Access app.
- Verify SSH qua cloudflared với explicit token flags.
ssh -o ProxyCommand="cloudflared access ssh \ --hostname ssh.your-domain.com \ --service-token-id <Client-ID> \ --service-token-secret <Client-Secret>" \ <ssh-user>@ssh.your-domain.com \ 'echo "tunnel + access OK"'Rule quan trọng: dùng explicit flags --service-token-id và --service-token-secret, không phụ thuộc env vars cho cloudflared access ssh.
3. Test server setup (one-time)
3.1 Cài Docker/Compose
docker --versiondocker compose version3.2 Tạo thư mục deploy
mkdir -p ~/vnexus-deploy-test/logscd ~/vnexus-deploy-test3.3 Tạo SSH key cho GitHub Actions
ssh-keygen -t ed25519 -C "github-actions-vnexus-test" -f ~/vnexus-ci-key -N ""cat ~/vnexus-ci-key.pub >> ~/.ssh/authorized_keyschmod 600 ~/.ssh/authorized_keyscat ~/vnexus-ci-key- Lưu private key vào GitHub Secret
SSH_PRIVATE_KEY. - Sau khi lưu xong, xóa key file:
shred -u ~/vnexus-ci-key3.4 Lấy SSH_KNOWN_HOSTS
ssh -o ProxyCommand="cloudflared access ssh \ --hostname ssh.your-domain.com \ --service-token-id <Client-ID> \ --service-token-secret <Client-Secret>" \ <ssh-user>@ssh.your-domain.com 'echo OK'
grep "ssh.your-domain.com" ~/.ssh/known_hosts3.5 Tạo .env trên server (~/vnexus-deploy-test/.env)
- Điền đủ secrets test environment: DB, Redis, JWT, R2, Sepay, CORS.
- Quyền file:
chmod 600 .env3.6 Tạo compose.yaml trên server
- Copy từ
docs/deploy/compose.test.example.yamlcủa backend repo. - Replace
<GHCR-NAMESPACE>đúng theo namespace thực tế của GHCR image.
3.7 Login GHCR trên server
- Tạo GitHub PAT có
read:packages(vàread:orgnếu org package). - Nếu org bật SSO: authorize token với org.
echo "<PAT>" | docker login ghcr.io -u <github-username> --password-stdin3.8 Verify DB + Redis từ server
# Postgresdocker run --rm --env-file .env eclipse-temurin:21-jre sh -c ' apt-get update -qq && apt-get install -y -qq postgresql-client > /dev/null && \ psql "$DB_URL" -c "SELECT 1"'
# Redisdocker run --rm --env-file .env redis:7-alpine \ redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASSWORD" ping4. GitHub repo setup (one-time)
4.1 Workflow permissions
- Repo Settings -> Actions -> General:
Read and write permissionsAllow GitHub Actions to create and approve pull requests
4.2 Environment test
- Repo Settings -> Environments -> New environment:
test. - Branch rule: chỉ branch
test.
4.3 Add environment secrets
| Secret name |
|---|
SSH_HOST |
SSH_USER |
SSH_PRIVATE_KEY |
SSH_KNOWN_HOSTS |
DEPLOY_PATH |
SMOKE_TEST_URL |
CF_ACCESS_CLIENT_ID |
CF_ACCESS_CLIENT_SECRET |
TELEGRAM_BOT_TOKEN |
TELEGRAM_CHAT_ID |
4.4 DNS app traffic
- Tạo
api-test.<domain>(qua Cloudflare DNS hoặc Tunnel public hostname HTTP ->localhost:8080).
4.5 Branch protection cho test
- Require PR before merge.
- Require approvals: 1.
- Require status checks pass.
- Required checks:
Unit & Integration TestsVerify Docker Build
- Require branch up-to-date.
- Require conversation resolution.
- Require linear history.
- Disallow force push / delete.
5. First deploy verification flow
- Tạo PR vào
test, đợici-pr.ymlxanh. - Merge vào
test, đợici-test.ymlchạy đủ 5 jobs. - Verify hậu deploy:
curl https://api-test.your-domain.com/api/actuator/health
# verify image revision trên serverssh -o ProxyCommand="cloudflared access ssh --hostname <ssh-host> --service-token-id <id> --service-token-secret <secret>" \ <user>@<ssh-host> \ "docker inspect vnexus-backend-test --format '{{ index .Config.Labels \"org.opencontainers.image.revision\" }}'"- Verify GHCR tags và Telegram message.
6. Common failure modes (debug nhanh)
| Symptom | Likely cause | Hướng xử lý |
|---|---|---|
websocket: bad handshake | cloudflared token env không được đọc | Dùng explicit --service-token-id/secret |
Forbidden từ cloudflared | Token chưa gắn policy hoặc sai secret | Kiểm tra Access app/policy/token |
connection refused qua tunnel | Route SSH chưa đúng hoặc daemon cloudflared down | Kiểm tra tunnel hostname + service |
permission denied (publickey) | SSH_PRIVATE_KEY sai/thiếu, chưa add pubkey vào server | Paste lại key đầy đủ + kiểm tra authorized_keys |
host key verification failed | SSH_KNOWN_HOSTS sai/trống | Lấy lại từ local known_hosts |
docker compose pull 401 | PAT GHCR sai scope/hết hạn | Login lại GHCR, thêm read:org nếu cần |
| Healthcheck timeout | App lỗi startup hoặc .env thiếu | Xem docker compose logs backend |
| Smoke test fail | DNS/smoke URL sai, route app lỗi | Check DNS + SMOKE_TEST_URL + path /api/... |
| Telegram 401/chat not found | Bot token/chat id sai, bot rời group | Kiểm tra token, add lại bot, lấy lại chat id |
7. Decommission
- Disable workflows trong GitHub Actions nếu cần dừng pipeline.
- Dừng service trên server:
ssh -o ProxyCommand="cloudflared access ssh --hostname ssh.your-domain.com --service-token-id <id> --service-token-secret <secret>" \ <user>@ssh.your-domain.com \ "cd ~/vnexus-deploy-test && docker compose down"- Revoke Cloudflare Service Token để vô hiệu hóa secrets cũ.
8. Vận hành thường ngày cho dev backend
- Luôn mở PR vào
testthay vì push trực tiếp. - Chỉ merge khi
ci-pr.ymlpass đủ checks. - Sau merge, theo dõi
ci-test.ymlđến jobnotify-telegram. - Nếu deploy fail, xử lý theo bảng lỗi mục 6 rồi re-run workflow.
Mục tiêu tab
- Hướng dẫn dev vận hành frontend khi chưa có auto CI/CD trong repo.
1. Quy trình local trước deploy
- Đồng bộ env:
cp .env.example .env.local- Validate code:
npm installnpm run lintnpm run type-checknpm run build2. Quy trình deploy Cloudflare thủ công
- Build adapter output:
npm run build:cf- Preview nhanh bản build:
npm run preview:cf- Deploy production/test target theo wrangler config:
npm run deploy:cf3. Verify sau deploy frontend
- Trang public load được.
- Luồng auth và gọi API backend test hoạt động bình thường.
- Không có lỗi runtime lớn ở browser console.
4. Trạng thái hệ thống frontend
- Frontend hiện ở chế độ
manual CD. - Khi bổ sung
.github/workflows/*cho frontend, cập nhật tab này thành runbook auto CI/CD.