13.8 CI/CD pipeline (Bitbucket Pipelines)
บทนี้สำรวจกระบวนการ build และ deploy ผ่าน Bitbucket Pipelines — เครื่องมือ CI/CD เพียงตัวเดียวที่พบในระบบ
ภาพรวม — ใครมี pipeline บ้าง
| Repo | มี bitbucket-pipelines.yml | ใช้งานจริง |
|---|---|---|
| vtrc-api | ✅ | ✅ |
| vtrc-web | ✅ | ✅ |
| cu-central-api | ❌ | — |
| centralize-api | ❌ | — |
| vtrc-rc-backoffice | ❌ | — |
จากการค้นหาด้วย find . -name "bitbucket-pipelines.yml" พบเพียง 2 repos — หมายความว่าการ deploy ของ cu-central-api, centralize-api, และ vtrc-rc-backoffice ต้องกระทำด้วยมือ (manual process) ผ่าน SSH เข้า server แล้วสั่ง docker pull + docker-compose up -d
vtrc-api pipeline — ขั้นตอนหลัก
vtrc-api/bitbucket-pipelines.yml:99-132:
pipelines:
branches:
"cicd":
- parallel:
- step: *build-test
"feature/*":
- parallel:
- step: *build-test
"hotfix/*":
- parallel:
- step: *build-test
"develop":
- parallel:
- step: *build-test
- step: *build-docker-image
- step: *push-image-to-registry
"uat":
- parallel:
- step: *build-test
- step: *build-docker-image
- step: *push-image-to-registry
"prod":
- parallel:
- step: *build-test
- step: *build-docker-image
- step: *push-image-to-registryกลยุทธ์ branch:
| Branch | สิ่งที่รัน | ผลลัพธ์ |
|---|---|---|
cicd | build-test | ทดสอบเท่านั้น |
feature/* | build-test | ทดสอบเท่านั้น |
hotfix/* | build-test | ทดสอบเท่านั้น |
develop | build-test → build-docker-image → push-image-to-registry | image push ไป GCR |
uat | build-test → build-docker-image → push-image-to-registry | image push ไป GCR |
prod | build-test → build-docker-image → push-image-to-registry | image push ไป GCR |
vtrc-api — build-test step
vtrc-api/bitbucket-pipelines.yml:10-17:
- step: &build-test
name: Build and Test
caches:
- node
script:
- cd ./api
- npm install
- npm testจุดสังเกต:
- image: node:12 (
pipelines.yml:6) — CI runtime เป็น Node 12 เช่นเดียวกับ production สอดคล้องกับ Dockerfile npm test— รัน test แต่ไม่มีไฟล์ test จริงในapi/test/ที่มี substance — ทดสอบจริงจัง ผลลัพธ์คือ step นี้เสมอผ่านcaches: node— cachenode_modulesระหว่างการรันเพื่อเร่งความเร็ว
vtrc-api — build-docker-image step
vtrc-api/bitbucket-pipelines.yml:26-38:
- step: &build-docker-image
name: Build Docker Image
script:
- cd ./api
- export DOCKER_BUILDKIT=1
- export IMAGE_NAME=$GCP_IMAGE_NAME
- docker build -t $IMAGE_NAME .
- docker save --output ../output-image.tar $IMAGE_NAME
services:
- docker
artifacts:
- output-image.tarจุดสังเกต:
- DOCKER_BUILDKIT=1 — เปิด BuildKit เพื่อ build ที่เร็วกว่า
docker save --output ../output-image.tar— save image เป็น tarball เพื่อส่งต่อไป step ถัดไป (push)artifacts: output-image.tar— กำหนดให้ tarball เป็น artifact ที่ Bitbucket เก็บระหว่าง step
vtrc-api — push-image-to-registry step
vtrc-api/bitbucket-pipelines.yml:39-89:
- step: &push-image-to-registry
name: Push Image to Google Cloud Registry
image: google/cloud-sdk:alpine
script:
- docker load --input output-image.tar
- cd ./api
# Authenticating with the service account key file
- echo $GCP_KEY_FILE | base64 -d > ./gcloud-api-key.json
- gcloud auth activate-service-account --key-file gcloud-api-key.json
- gcloud config set project $GCP_PROJECT_ID
# Login to google docker hub
- cat ./gcloud-api-key.json | docker login -u _json_key --password-stdin https://$GCP_REGISTRY_HOSTNAME
# Prepare image name and shorten commit id
- export IMAGE_NAME=$GCP_REGISTRY_HOSTNAME/$GCP_PROJECT_ID/$GCP_IMAGE_NAME--$BITBUCKET_BRANCH
- export BITBUCKET_COMMIT_SHORT="${BITBUCKET_COMMIT::7}"
# If any image does not exist with commit id, then
- if [ "$(gcloud container images list-tags ${IMAGE_NAME} | grep ${BITBUCKET_COMMIT_SHORT})" == '' ]; then
# Tag docker image with commit id and push
- docker tag $GCP_IMAGE_NAME $IMAGE_NAME:$BITBUCKET_COMMIT_SHORT
- docker push $IMAGE_NAME:$BITBUCKET_COMMIT_SHORT
# Tag docker image with :latest tag and push
- docker tag $GCP_IMAGE_NAME ${IMAGE_NAME}
- docker push ${IMAGE_NAME}
...จุดสังเกตที่สำคัญ:
image: google/cloud-sdk:alpine — step นี้รันใน image ของ GCP SDK ไม่ใช่ node
GCP_KEY_FILEเป็น base64 secret — secret ที่เก็บใน Bitbucket repository variables (เข้าถึงได้ผ่าน setting ของ repo)Auth ผ่าน service-account key —
gcloud auth activate-service-account --key-file gcloud-api-key.jsonชื่อ image ระบุ branch —
$GCP_IMAGE_NAME--$BITBUCKET_BRANCHทำให้แต่ละ branch มี namespace image ของตัวเอง (เช่นvtrc-api--prod,vtrc-api--uat)Tag ที่ push มี 2 แบบ:
$BITBUCKET_COMMIT_SHORT— commit hash 7 หลักlatest— tag ล่าสุดเสมอ
ตรวจสอบซ้ำด้วย
if— ถ้า image ที่ tag ด้วย commit id มีอยู่แล้ว จะไม่ push ซ้ำ (idempotent) เพื่อประหยัดเวลารองรับ
BITBUCKET_TAG— ถ้า pipeline ถูก trigger ด้วย git tag (เช่น v1.0.0) จะ push image ที่มี tag นั้นด้วย
vtrc-api — Terraform step (ที่ไม่ได้ใช้)
vtrc-api/bitbucket-pipelines.yml:90-98:
- step: &init-infra-terraform
name: Initiate infrastructure using Terraform
image: hashicorp/terraform:full
script:
- terraform init
- terraform validate
- terraform refresh
- terraform plan
- terraform apply -input=false -auto-approvestep Terraform นี้กำหนดไว้แต่ ไม่ถูกเรียกใช้ใน branch ใดเลย — บ่งชี้ว่ามีแผนจะใช้ Terraform จัดการ infrastructure แต่ยังไม่เสร็จ น่าสนใจเพราะบอกว่าอาจมี .tf files ซ่อนอยู่ที่ไหนสักแห่ง
vtrc-web pipeline — คล้ายกันแต่ต่างที่ branch
vtrc-web/bitbucket-pipelines.yml:105-122:
pipelines:
branches:
"feature/cicd":
- step: *build-front-end-production
- step: *build-docker
- step: *push-image-to-registry
"develop":
- step: *build-front-end-production
- step: *build-docker
- step: *push-image-to-registry
"uat":
- step: *build-front-end-production
- step: *build-docker
- step: *push-image-to-registry
"prod":
- step: *build-front-end-production
- step: *build-docker
- step: *push-image-to-registryความแตกต่างจาก vtrc-api:
- ไม่มี
parallel— build-test ถูก comment ไว้ (pipeline นี้ไม่มี test step ที่ใช้งานจริง) feature/cicdแทนcicdทั่วไป — บ่งชี้ว่ามี branch เฉพาะสำหรับทดสอบ CI/CD เองbuild-front-end-productionใช้npm install --only=productionและCI=false npm run build—CI=falseปิดการแจ้ง error ของ Webpack ที่ treat warning เป็น error
vtrc-web — firebase-deploy step (ที่ไม่ได้ใช้จริง)
vtrc-web/bitbucket-pipelines.yml:36-42:
- step: &deploy-front-end
name: Deploy Front-end
script:
- pipe: atlassian/firebase-deploy:0.2.1
variables:
FIREBASE_TOKEN: $FIREBASE_TOKEN
PROJECT_ID: $FIREBASE_PROJECTstep นี้กำหนดไว้แต่ไม่ถูกเรียกใช้ — บ่งชี้ว่ามีแผนจะ deploy frontend ไป Firebase Hosting แต่ย้ายไปใช้ nginx + Docker แทน
สิ่งที่ขาดจาก pipeline
| สิ่งที่ขาด | ผลกระทบ |
|---|---|
| test step ที่จริงจัง | npm test ไม่มี test file จริง → build-test เสมอผ่าน |
| lint step | ถูก comment ไว้ทั้งสอง repo (# - step: *linting) → code quality ไม่ถูกตรวจใน CI |
| security scan | ไม่มี npm audit หรือ SAST tool |
| deploy step (หลัง push) | pipeline push image ไป GCR จบ — ไม่มีขั้นตอนสั่ง server ให้ docker pull |
| rollback mechanism | ไม่มี — ถ้า deploy พังต้อง revert commit แล้ว push ใหม่ |
| approval gate สำหรับ prod | prod branch deploy อัตโนมัติเมื่อ push — ไม่มีการรออนุมัติ |
| branch protection | ไม่มีในไฟล์ pipeline — ต้องตั้งที่ Bitbucket repo settings |
กระบวนการ deploy จริง — หลัง push image
หลังจาก image push ไป GCR แล้ว กระบวนการ deploy ไปยัง server ทำด้วยมือ:
- SSH เข้า server
docker pull gcr.io/vtrc-nonprod/vtrc-api--prod:latestdocker-compose up -d vtrc-api(หรือ restart replica ทีละตัว)- ตรวจสอบ log ว่า service start สำเร็จ
ไม่มี script ที่ automate ขั้นตอนนี้ — ทุก deploy ต้องทำด้วยมือทุกครั้ง ทำให้เกิดความเสี่ยง:
- ลืม pull image ใหม่บาง replica
- ใช้ tag ผิด (prod image ไป uat หรือกลับกัน)
- ไม่สามารถ rollback อย่างรวดเร็ว
ข้อแนะนำเพื่อปรับปรุง
- เพิ่ม deploy step — ใช้
pipe: atlassian/ssh-runหรือ trigger webhook ที่ server สั่งdocker pullอัตโนมัติ - ใช้ Watchtower หรือเครื่องมือ auto-update ของ Docker — ทำให้ container ใหม่ถูก pull และ restart อัตโนมัติเมื่อ image ใหม่
- เพิ่ม lint + test ที่จริงจัง — เปิด lint step ที่ถูก comment ไว้ และเขียน test ที่ครอบคลุม
- เพิ่ม approval gate สำหรับ prod branch ใน Bitbucket Pipelines
- สร้าง pipeline สำหรับ cu-central-api, centralize-api, และ vtrc-rc-backoffice — ทำให้ทุก repo deploy ด้วยกระบวนการเดียวกัน
- เพิ่ม
npm audit --productionเป็น step บังคับ — บล็อก deploy ถ้ามี critical vulnerability - ใช้ tag ที่กำหนดเวอร์ชัน เป็น default สำหรับ prod —
latestใช้สำหรับ dev/uat เท่านั้น - deploy แบบ rolling — restart replica ทีละตัว ไม่ใช่ทั้งหมดพร้อมกัน เพื่อรักษา uptime