Skip to content

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:

yaml
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สิ่งที่รันผลลัพธ์
cicdbuild-testทดสอบเท่านั้น
feature/*build-testทดสอบเท่านั้น
hotfix/*build-testทดสอบเท่านั้น
developbuild-test → build-docker-image → push-image-to-registryimage push ไป GCR
uatbuild-test → build-docker-image → push-image-to-registryimage push ไป GCR
prodbuild-test → build-docker-image → push-image-to-registryimage push ไป GCR

vtrc-api — build-test step

vtrc-api/bitbucket-pipelines.yml:10-17:

yaml
- step: &build-test
    name: Build and Test
    caches:
      - node
    script:
      - cd ./api
      - npm install
      - npm test

จุดสังเกต:

  1. image: node:12 (pipelines.yml:6) — CI runtime เป็น Node 12 เช่นเดียวกับ production สอดคล้องกับ Dockerfile
  2. npm test — รัน test แต่ไม่มีไฟล์ test จริงใน api/test/ ที่มี substance — ทดสอบจริงจัง ผลลัพธ์คือ step นี้เสมอผ่าน
  3. caches: node — cache node_modules ระหว่างการรันเพื่อเร่งความเร็ว

vtrc-api — build-docker-image step

vtrc-api/bitbucket-pipelines.yml:26-38:

yaml
- 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

จุดสังเกต:

  1. DOCKER_BUILDKIT=1 — เปิด BuildKit เพื่อ build ที่เร็วกว่า
  2. docker save --output ../output-image.tar — save image เป็น tarball เพื่อส่งต่อไป step ถัดไป (push)
  3. artifacts: output-image.tar — กำหนดให้ tarball เป็น artifact ที่ Bitbucket เก็บระหว่าง step

vtrc-api — push-image-to-registry step

vtrc-api/bitbucket-pipelines.yml:39-89:

yaml
- 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}
      ...

จุดสังเกตที่สำคัญ:

  1. image: google/cloud-sdk:alpine — step นี้รันใน image ของ GCP SDK ไม่ใช่ node

  2. GCP_KEY_FILE เป็น base64 secret — secret ที่เก็บใน Bitbucket repository variables (เข้าถึงได้ผ่าน setting ของ repo)

  3. Auth ผ่าน service-account keygcloud auth activate-service-account --key-file gcloud-api-key.json

  4. ชื่อ image ระบุ branch$GCP_IMAGE_NAME--$BITBUCKET_BRANCH ทำให้แต่ละ branch มี namespace image ของตัวเอง (เช่น vtrc-api--prod, vtrc-api--uat)

  5. Tag ที่ push มี 2 แบบ:

    • $BITBUCKET_COMMIT_SHORT — commit hash 7 หลัก
    • latest — tag ล่าสุดเสมอ
  6. ตรวจสอบซ้ำด้วย if — ถ้า image ที่ tag ด้วย commit id มีอยู่แล้ว จะไม่ push ซ้ำ (idempotent) เพื่อประหยัดเวลา

  7. รองรับ BITBUCKET_TAG — ถ้า pipeline ถูก trigger ด้วย git tag (เช่น v1.0.0) จะ push image ที่มี tag นั้นด้วย

vtrc-api — Terraform step (ที่ไม่ได้ใช้)

vtrc-api/bitbucket-pipelines.yml:90-98:

yaml
- 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-approve

step Terraform นี้กำหนดไว้แต่ ไม่ถูกเรียกใช้ใน branch ใดเลย — บ่งชี้ว่ามีแผนจะใช้ Terraform จัดการ infrastructure แต่ยังไม่เสร็จ น่าสนใจเพราะบอกว่าอาจมี .tf files ซ่อนอยู่ที่ไหนสักแห่ง

vtrc-web pipeline — คล้ายกันแต่ต่างที่ branch

vtrc-web/bitbucket-pipelines.yml:105-122:

yaml
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:

  1. ไม่มี parallel — build-test ถูก comment ไว้ (pipeline นี้ไม่มี test step ที่ใช้งานจริง)
  2. feature/cicd แทน cicd ทั่วไป — บ่งชี้ว่ามี branch เฉพาะสำหรับทดสอบ CI/CD เอง
  3. build-front-end-production ใช้ npm install --only=production และ CI=false npm run buildCI=false ปิดการแจ้ง error ของ Webpack ที่ treat warning เป็น error

vtrc-web — firebase-deploy step (ที่ไม่ได้ใช้จริง)

vtrc-web/bitbucket-pipelines.yml:36-42:

yaml
- step: &deploy-front-end
    name: Deploy Front-end
    script:
      - pipe: atlassian/firebase-deploy:0.2.1
        variables:
          FIREBASE_TOKEN: $FIREBASE_TOKEN
          PROJECT_ID: $FIREBASE_PROJECT

step นี้กำหนดไว้แต่ไม่ถูกเรียกใช้ — บ่งชี้ว่ามีแผนจะ 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 สำหรับ prodprod branch deploy อัตโนมัติเมื่อ push — ไม่มีการรออนุมัติ
branch protectionไม่มีในไฟล์ pipeline — ต้องตั้งที่ Bitbucket repo settings

กระบวนการ deploy จริง — หลัง push image

หลังจาก image push ไป GCR แล้ว กระบวนการ deploy ไปยัง server ทำด้วยมือ:

  1. SSH เข้า server
  2. docker pull gcr.io/vtrc-nonprod/vtrc-api--prod:latest
  3. docker-compose up -d vtrc-api (หรือ restart replica ทีละตัว)
  4. ตรวจสอบ log ว่า service start สำเร็จ

ไม่มี script ที่ automate ขั้นตอนนี้ — ทุก deploy ต้องทำด้วยมือทุกครั้ง ทำให้เกิดความเสี่ยง:

  • ลืม pull image ใหม่บาง replica
  • ใช้ tag ผิด (prod image ไป uat หรือกลับกัน)
  • ไม่สามารถ rollback อย่างรวดเร็ว

ข้อแนะนำเพื่อปรับปรุง

  1. เพิ่ม deploy step — ใช้ pipe: atlassian/ssh-run หรือ trigger webhook ที่ server สั่ง docker pull อัตโนมัติ
  2. ใช้ Watchtower หรือเครื่องมือ auto-update ของ Docker — ทำให้ container ใหม่ถูก pull และ restart อัตโนมัติเมื่อ image ใหม่
  3. เพิ่ม lint + test ที่จริงจัง — เปิด lint step ที่ถูก comment ไว้ และเขียน test ที่ครอบคลุม
  4. เพิ่ม approval gate สำหรับ prod branch ใน Bitbucket Pipelines
  5. สร้าง pipeline สำหรับ cu-central-api, centralize-api, และ vtrc-rc-backoffice — ทำให้ทุก repo deploy ด้วยกระบวนการเดียวกัน
  6. เพิ่ม npm audit --production เป็น step บังคับ — บล็อก deploy ถ้ามี critical vulnerability
  7. ใช้ tag ที่กำหนดเวอร์ชัน เป็น default สำหรับ prod — latest ใช้สำหรับ dev/uat เท่านั้น
  8. deploy แบบ rolling — restart replica ทีละตัว ไม่ใช่ทั้งหมดพร้อมกัน เพื่อรักษา uptime