Thuta Learning
CI/CD with GitHub Actions
ProjectsDevOps & Toolsbeginner

Project: Staging ကနေ Production သို့ Approval နဲ့

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Project: Staging ကနေ Production သို့ Approval နဲ့ concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး pipeline ထဲမှာ job တွေ ဘယ်လိုစီးဆင်းပြီး ဘာက gate လုပ်သလဲ ခြေရာခံနိုင်ရန်
  • Workflow file ကို ဖတ်ပြီး ဘယ် job တွေ ဘယ်အစဉ်လိုက် run မလဲ ကြိုတင်ပြောနိုင်ရန်

နားလည်ထားရမယ့် အချက်

တစ်ကြိမ်တည်း build လုပ်၊ နေရာတိုင်းကို အဲဒါပဲ deploy လုပ် — ဒီ pipeline တည်ရှိရတဲ့ တစ်ခုတည်းသော အကြောင်းရင်းက ဒီအယူအဆပါ။ စည်းမျဉ်းအဖြစ် အတိအလင်း ပြောရရင် production ကို ရောက်တဲ့ byte တွေဟာ staging မှာ စစ်ပြီးသား byte တွေနဲ့ တစ်လုံးမကျန် တူရပါမယ်။ ဆွဲဆောင်မှုရှိတဲ့ အခြားနည်းလမ်း — deploy job တစ်ခုစီထဲမှာ build step ထည့်လိုက်တာ — က ဘာမှ ဖြစ်မှာမဟုတ်ဘူးလို့ ထင်ရပေမယ့် မဟုတ်ပါဘူး။ Commit တစ်ခုတည်းကနေ build နှစ်ကြိမ် လုပ်လည်း ရလဒ် ကွဲနိုင်ပါတယ် — transitive dependency တစ်ခုက patch version အသစ် ထုတ်လိုက်တာ၊ base image ရွေ့သွားတာ၊ timestamp တစ်ခု ဝင်လာတာ၊ lockfile မပါတဲ့ install က တစ်မျိုးတည်း မဖြေရှင်းတာ။ အဲဒါ ဖြစ်နိုင်တာနဲ့ staging ဟာ သက်သေအဖြစ် ရပ်တည်လို့ မရတော့ပါဘူး။ သင် စမ်းလိုက်တာ တစ်ခု၊ ပို့လိုက်တာ တစ်ခု ဖြစ်နေပါပြီ။

နည်းစနစ်ပိုင်းက အရင်က တွေ့ခဲ့ပြီးသား အပိုင်းတွေချည်းပါ။ build job က တစ်ကြိမ်တည်း compile လုပ်ပြီး dist ကို artifact အဖြစ် တင်ကာ၊ artifact အမည်နဲ့ version string တိုတိုကို job outputs ကနေ ထုတ်ပေးပါတယ်။ ဘာကြောင့် output သုံးရလဲဆိုတော့၊ အောက်ဆင့် job တစ်ခုဟာ အခြား job ရဲ့ environment variable တွေကို မမြင်နိုင်ဘဲ၊ ကြေညာထားတဲ့ outputs ကိုသာ မြင်နိုင်လို့ပါ။ Deploy job နှစ်ခုလုံးက needs နဲ့ အဲဒီ output တွေကို ဖတ်ပြီး၊ ကိုယ့်အလှည့်ကိုလည်း needs နဲ့ပဲ စီစဉ်ပါတယ်။ နှစ်ခုလုံးက build step နဲ့ မစဘဲ download-artifact နဲ့ စပါတယ်။ checkout ပါသေးတာက deploy script တွေ ယူဖို့ပါ — application ရဲ့ byte တွေက artifact ကနေ လာတာဖြစ်ပြီး၊ ဘယ်တော့မှ ပြန် build လုပ်တာ မဟုတ်ပါဘူး။

အဲဒီနှစ်ခုကြားမှာ smoke check ရှိပါတယ်။ သူ့ရဲ့ တစ်ခုတည်းသော ရည်ရွယ်ချက်က ကျရှုံးပြဖို့ပါ။ Health endpoint ကို ခေါ်ပြီး၊ သူ ပြန်ပြောတဲ့ version ဟာ ခုနက deploy လုပ်လိုက်တဲ့ version နဲ့ ကိုက်ရဲ့လား စစ်ပါတယ် — အောင်မြင်တယ်လို့ ပြောပေမယ့် တကယ်တော့ ဘာမှ အစားမထိုးလိုက်တဲ့ deploy မျိုးကို ဖမ်းတာပါ။

Gate ကတော့ environment key ပါ။ environment: production လို့ အမည်တပ်လိုက်ရင်၊ အဲဒီ environment မှာ required reviewers သတ်မှတ်ထားတဲ့အခါ job က စောင့်နေပြီး၊ လူတစ်ယောက် GitHub UI မှာ approve လုပ်မှသာ ဆက်သွားပါတယ်။ Environment တွေက secret ကိုပါ ကန့်သတ်ပေးလို့၊ production deploy credential ကို approval ဖြတ်ပြီးတဲ့ job ကသာ ဖတ်နိုင်ပါတယ်။ Approval နဲ့ credential ဟာ တစ်နေရာတည်းမှာ ရှိနေတာကြောင့် workflow file ကို ပြင်ရုံနဲ့ ဒီ gate ကို ကျော်လို့ မရတာပါ။

text
BUILD ONCE, PROMOTE THROUGH ENVIRONMENTS
----------------------------------------
             +--------------------------+
             |          build           |
             |  npm ci && npm run build |
             |  outputs:                |
             |    artifact-name         |
             |    version               |
             +--------------------------+
                          |
                          |  upload-artifact: app-<sha12>
                          |  (built exactly once, here, forever)
                          v
             +--------------------------+
             |     deploy-staging       |  environment: staging
             |  download-artifact       |  no reviewers -> automatic
             +--------------------------+
                          |  needs
                          v
             +--------------------------+
             |     smoke-staging        |  GET /healthz
             |  version must match      |  red here stops the line
             +--------------------------+
                          |  needs
                          v
  =========================================================
  = APPROVAL GATE - environment: production                =
  = the run pauses; a required reviewer clicks Approve     =
  = environment-scoped secrets unlock only after that      =
  =========================================================
                          |
                          v
             +--------------------------+
             |    deploy-production     |  download-artifact
             |  the SAME app-<sha12>    |  no rebuild anywhere
             +--------------------------+

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

ဒီ workflow ကို run ဖို့ repository setting ထဲမှာ environment နှစ်ခု လိုပါတယ် — staging နဲ့ production။ staging မှာ protection rule မထားပါဘူး။ production မှာတော့ required reviewers ထည့်ပြီး၊ DEPLOY_TOKEN ကို အဲဒီ environment ရဲ့ secret အဖြစ် သီးသန့် ထားပါတယ်။ ဒါက အရေးကြီးပါတယ် — production credential ဟာ repository secret မဟုတ်ဘဲ environment secret ဖြစ်နေမှသာ၊ gate ကို မဖြတ်ရသေးတဲ့ job တစ်ခုက အဲဒါကို လုံးဝ မဖတ်နိုင်ပါဘူး။

build job က version ကို commit SHA ရဲ့ ရှေ့ဆုံး ၁၂ လုံးကနေ တွက်ပြီး၊ artifact အမည်ကို app-<sha12> အဖြစ် သတ်မှတ်ကာ နှစ်ခုလုံးကို output အဖြစ် ထုတ်ပေးပါတယ်။ ဒီအမည်ကို job သုံးခုက ကိုးကားပါတယ်။ String ကို နေရာသုံးနေရာမှာ လက်နဲ့ ထပ်ရေးမယ့်အစား output တစ်ခုတည်းကို မျှသုံးလိုက်တာက၊ pipeline တစ်ခုလုံး တကယ်ပဲ artifact တစ်ခုတည်းကို ကိုင်နေတယ်ဆိုတာကို structural အဖြစ် အာမခံလိုက်တာပါ။

smoke-staging job မှာ URL နဲ့ version ကို run line ထဲ တိုက်ရိုက် interpolate မလုပ်ဘဲ env အဖြစ် ပေးထားတာ သတိပြုပါ။ ${{ }} ကို shell command ထဲ တိုက်ရိုက် ထည့်တာဟာ script injection အန္တရာယ် ရှိလို့၊ env variable ကနေ ဖြတ်သွားတာ အလေ့အထ ကောင်းတစ်ခုပါ။

Production အရေးပါမှုက deploy-production ရဲ့ needs: [build, smoke-staging] မှာပါ။ build ကို ထည့်ထားတာ artifact အမည် output ကို ဖတ်ဖို့၊ smoke-staging ကို ထည့်ထားတာ staging က တကယ် ကျန်းမာနေမှသာ ဆက်သွားဖို့ပါ။ Smoke check နီသွားရင် production job က approval တောင် မတောင်းဘဲ skip ဖြစ်သွားလို့၊ ပျက်နေတဲ့ build တစ်ခုကို approve လုပ်ဖို့ လူတစ်ယောက်ကို ခေါ်စရာ မလိုတော့ပါဘူး။

အတူတူ စမ်းရေးကြည့်မယ်

yaml
name: Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: false

jobs:
  build:
    name: Build once
    runs-on: ubuntu-latest
    outputs:
      artifact-name: ${{ steps.meta.outputs.artifact-name }}
      version: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: npm
      - run: npm ci
      - run: npm run build
      - name: Name this build
        id: meta
        run: |
          version="${GITHUB_SHA:0:12}"
          echo "version=$version" >> "$GITHUB_OUTPUT"
          echo "artifact-name=app-$version" >> "$GITHUB_OUTPUT"
      - name: Upload the one and only build
        uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.meta.outputs.artifact-name }}
          path: dist/
          retention-days: 14
          if-no-files-found: error

  deploy-staging:
    name: Deploy to staging
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - name: Check out deploy scripts only
        uses: actions/checkout@v4
      - name: Download the built artifact
        uses: actions/download-artifact@v4
        with:
          name: ${{ needs.build.outputs.artifact-name }}
          path: dist
      - name: Deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
          DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}
        run: ./scripts/deploy.sh dist "$DEPLOY_HOST"

  smoke-staging:
    name: Smoke check staging
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Assert staging is serving this build
        env:
          TARGET_URL: https://staging.example.com
          EXPECTED_VERSION: ${{ needs.build.outputs.version }}
        run: ./scripts/smoke.sh "$TARGET_URL" "$EXPECTED_VERSION"

  deploy-production:
    name: Deploy to production
    needs: [build, smoke-staging]
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - name: Check out deploy scripts only
        uses: actions/checkout@v4
      - name: Download the same artifact staging ran
        uses: actions/download-artifact@v4
        with:
          name: ${{ needs.build.outputs.artifact-name }}
          path: dist
      - name: Deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
          DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}
        run: ./scripts/deploy.sh dist "$DEPLOY_HOST"
      - name: Assert production is serving this build
        env:
          TARGET_URL: https://app.example.com
          EXPECTED_VERSION: ${{ needs.build.outputs.version }}
        run: ./scripts/smoke.sh "$TARGET_URL" "$EXPECTED_VERSION"
You should see
main ကို push လိုက်တာနဲ့ build job တစ်ခုတည်း စပါတယ်။ သူက dependency install လုပ်၊ project ကို build လုပ်၊ commit SHA ကနေ version တွက်ပြီး dist ကို app-<sha12> အမည်နဲ့ artifact တစ်ခုအဖြစ် တင်ကာ artifact-name နဲ့ version ကို output အဖြစ် ထုတ်ပေးပါတယ်။ dist ထဲ ဘာမှ မရှိရင် if-no-files-found: error ကြောင့် အဲဒီနေရာမှာတင် ရပ်သွားပါတယ်။

ပြီးရင် deploy-staging က အလိုအလျောက် စပါတယ် — staging environment မှာ reviewer မလိုလို့ပါ။ သူက ပြန် build မလုပ်ဘဲ အဲဒီ artifact ကိုပဲ download လုပ်ပြီး deploy script ကို run ပါတယ်။ ပြီးမှ smoke-staging က health endpoint ကို ခေါ်ပြီး၊ ပြန်လာတဲ့ version ဟာ ခုနက deploy လုပ်လိုက်တဲ့ version နဲ့ ကိုက်မကိုက် စစ်ပါတယ်။ မကိုက်ရင် job နီသွားပြီး pipeline က အဲဒီမှာ ရပ်သွားပါတယ်။

Smoke check အောင်မြင်ရင် deploy-production ဟာ ချက်ချင်း run မသွားဘဲ approval စောင့်နေတဲ့ အခြေအနေမှာ ရပ်နေပါတယ်။ Required reviewer တစ်ယောက်ယောက် GitHub UI မှာ approve မလုပ်မချင်း job က မစပါဘူး၊ environment-scoped secret တွေကိုလည်း မရပါဘူး။ Approve လုပ်လိုက်မှ job က ဆက်သွားပြီး၊ staging မှာ စစ်ခဲ့တဲ့ artifact အတူတူကိုပဲ download လုပ်ကာ deploy လုပ်ပြီး၊ production ကို smoke check ထပ်လုပ်ပါတယ်။ Reviewer က reject လုပ်ရင် သို့မဟုတ် timeout ရောက်သွားရင် run က အဲဒီအတိုင်း အဆုံးသတ်ပြီး production မှာ ဘာမှ မပြောင်းပါဘူး။

၅ မိနစ် စမ်းကြည့်

Environment နှစ်ခု ဖန်တီးပြီး ဒီ pipeline ကို repository တစ်ခုမှာ တပ်ဆင်ပါ။ production environment မှာ ကိုယ့်ကိုယ်ကိုယ် required reviewer အဖြစ် ထည့်ပါ။ Push လုပ်ပြီး run က ဘယ်နေရာမှာ ရပ်စောင့်နေလဲ ကြည့်ကာ၊ တစ်ကြိမ် reject လုပ်ကြည့်ပါ — deploy-production ရဲ့ အခြေအနေ ဘာဖြစ်သွားလဲ၊ ကျန် job တွေကော ဘာဖြစ်လဲ မှတ်ပါ။ ပြီးရင် smoke.sh ကို အမြဲ ကျရှုံးအောင် ပြင်ပြီး ထပ် push ပါ။ ဒီတစ်ခါ approval တောင် မတောင်းတော့တာကို အတည်ပြုပါ။ နောက်ဆုံးအဆင့်အနေနဲ့ — deploy-production ထဲမှာ npm run build ကို ထပ်ထည့်လိုက်ရင် ဒီ pipeline ရဲ့ အာမခံချက် ဘာတွေ ပျက်သွားလဲ စာနဲ့ ချရေးကြည့်ပါ။

သတိလေးတစ်ချက်

Deploy job တစ်ခုစီထဲမှာ သီးသန့် ပြန် build လုပ်တာ။ Production က staging ဘယ်တော့မှ မစမ်းခဲ့တဲ့ binary ကို run နေတာ ဖြစ်သွားပြီး၊ dependency တစ်ခု ရွေ့သွားရုံနဲ့ staging မှာ ဘယ်တော့မှ မပေါ်ခဲ့တဲ့ bug က production မှာ ပေါ်လာနိုင်ပါတယ်။

Production deploy secret ကို environment secret မဟုတ်ဘဲ repository secret အဖြစ် ထားတာ။ Approval gate က UI မှာ ရှိနေပေမယ့် workflow ထဲက ဘယ် job ကမဆို — approval မဖြတ်ရသေးတဲ့ job အပါအဝင် — အဲဒီ credential ကို ဖတ်နိုင်နေတော့ gate ဟာ လက်တွေ့မှာ အလှဆင်ချက် ဖြစ်သွားပါတယ်။

GitHub Docs - Using environments for deploymentCI/CD with GitHub Actions

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • Deploy job တစ်ခုစီထဲမှာ သီးသန့် ပြန် build လုပ်တာ။ Production က staging ဘယ်တော့မှ မစမ်းခဲ့တဲ့ binary ကို run နေတာ ဖြစ်သွားပြီး၊ dependency တစ်ခု ရွေ့သွားရုံနဲ့ staging မှာ ဘယ်တော့မှ မပေါ်ခဲ့တဲ့ bug က production မှာ ပေါ်လာနိုင်ပါတယ်။
  • Production deploy secret ကို environment secret မဟုတ်ဘဲ repository secret အဖြစ် ထားတာ။ Approval gate က UI မှာ ရှိနေပေမယ့် workflow ထဲက ဘယ် job ကမဆို — approval မဖြတ်ရသေးတဲ့ job အပါအဝင် — အဲဒီ credential ကို ဖတ်နိုင်နေတော့ gate ဟာ လက်တွေ့မှာ အလှဆင်ချက် ဖြစ်သွားပါတယ်။
  • Workflow ကို production branch ပေါ် တိုက်ရိုက်မစမ်းဘဲ branch (သို့) test repository တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

Environment နှစ်ခု ဖန်တီးပြီး ဒီ pipeline ကို repository တစ်ခုမှာ တပ်ဆင်ပါ။ production environment မှာ ကိုယ့်ကိုယ်ကိုယ် required reviewer အဖြစ် ထည့်ပါ။ Push လုပ်ပြီး run က ဘယ်နေရာမှာ ရပ်စောင့်နေလဲ ကြည့်ကာ၊ တစ်ကြိမ် reject လုပ်ကြည့်ပါ — deploy-production ရဲ့ အခြေအနေ ဘာဖြစ်သွားလဲ၊ ကျန် job တွေကော ဘာဖြစ်လဲ မှတ်ပါ။ ပြီးရင် smoke.sh ကို အမြဲ ကျရှုံးအောင် ပြင်ပြီး ထပ် push ပါ။ ဒီတစ်ခါ approval တောင် မတောင်းတော့တာကို အတည်ပြုပါ။ နောက်ဆုံးအဆင့်အနေနဲ့ — deploy-production ထဲမှာ npm run build ကို ထပ်ထည့်လိုက်ရင် ဒီ pipeline ရဲ့ အာမခံချက် ဘာတွေ ပျက်သွားလဲ စာနဲ့ ချရေးကြည့်ပါ။

You'll know it worked when: main ကို push လိုက်တာနဲ့ build job တစ်ခုတည်း စပါတယ်။ သူက dependency install လုပ်၊ project ကို build လုပ်၊ commit SHA ကနေ version တွက်ပြီး dist ကို app-<sha12> အမည်နဲ့ artifact တစ်ခုအဖြစ် တင်ကာ artifact-name နဲ့ version ကို output အဖြစ် ထုတ်ပေးပါတယ်။ dist ထဲ ဘာမှ မရှိရင် if-no-files-found: error ကြောင့် အဲဒီနေရာမှာတင် ရပ်သွားပါတယ်။ ပြီးရင် deploy-staging က အလိုအလျောက် စပါတယ် — staging environment မှာ reviewer မလိုလို့ပါ။ သူက ပြန် build မလုပ်ဘဲ အဲဒီ artifact ကိုပဲ download လုပ်ပြီး deploy script ကို run ပါတယ်။ ပြီးမှ smoke-staging က health endpoint ကို ခေါ်ပြီး၊ ပြန်လာတဲ့ version ဟာ ခုနက deploy လုပ်လိုက်တဲ့ version နဲ့ ကိုက်မကိုက် စစ်ပါတယ်။ မကိုက်ရင် job နီသွားပြီး pipeline က အဲဒီမှာ ရပ်သွားပါတယ်။ Smoke check အောင်မြင်ရင် deploy-production ဟာ ချက်ချင်း run မသွားဘဲ approval စောင့်နေတဲ့ အခြေအနေမှာ ရပ်နေပါတယ်။ Required reviewer တစ်ယောက်ယောက် GitHub UI မှာ approve မလုပ်မချင်း job က မစပါဘူး၊ environment-scoped secret တွေကိုလည်း မရပါဘူး။ Approve လုပ်လိုက်မှ job က ဆက်သွားပြီး၊ staging မှာ စစ်ခဲ့တဲ့ artifact အတူတူကိုပဲ download လုပ်ကာ deploy လုပ်ပြီး၊ production ကို smoke check ထပ်လုပ်ပါတယ်။ Reviewer က reject လုပ်ရင် သို့မဟုတ် timeout ရောက်သွားရင် run က အဲဒီအတိုင်း အဆုံးသတ်ပြီး production မှာ ဘာမှ မပြောင်းပါဘူး။

Project: Staging ကနေ Production သို့ Approval နဲ့ | Thuta Learning