Thuta Learning
CI/CD with GitHub Actions
ProjectsDevOps & Toolsbeginner

Project: Docker Image တစ်ခု Build ပြီး Push လုပ်ခြင်း

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

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

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

Image တစ်ခု push လုပ်တဲ့အခါ workflow ဟာ စစ်ဆေးသူ ဘဝကနေ ထုတ်လုပ်သူ ဘဝကို ကူးပြောင်းသွားပါပြီ။ ပြီးတော့ တစ်ခုခုကို ထုတ်လုပ်တဲ့နေရာဟာ permission အရေးအပါဆုံး နေရာလည်း ဖြစ်ပါတယ်။ GitHub Container Registry က built-in GITHUB_TOKEN ကို လက်ခံလို့၊ personal access token ဖန်တီးစရာ၊ လှည့်ပြောင်းစရာ၊ ပေါက်ကြားစရာ မလိုတော့ပါဘူး — ဒါပေမယ့် job က မှန်ကန်တဲ့ scope ကို တောင်းမှသာပါ။ job level မှာ contents: read နဲ့ packages: write လို့ ကြေညာလိုက်တာက အလုပ်နှစ်ခု လုပ်ပါတယ်။ Push အတွက် လိုတဲ့ write scope ကို ပေးလိုက်တာ တစ်ခု၊ ပြီးတော့ permission တစ်ခုခုကို နာမည်တပ်ပြီး ကြေညာလိုက်တာနဲ့ ကျန် scope အားလုံး none ပြန်ဖြစ်သွားလို့ ဒီ job မလိုတဲ့ အခွင့်အရေးတိုင်း ခွာချလိုက်တာ တစ်ခုပါ။ ဒီနေရာမှာ least privilege ဆိုတာ ကောင်းရင်ကောင်းမယ့် အရာ မဟုတ်ပါဘူး — issue, release, code အားလုံးကို ရေးခွင့်ရှိတဲ့ token ကိုင်ထားတဲ့ build step တစ်ခု အလုအယက်ခံရတာဟာ၊ package ပဲ push လို့ရတဲ့ token ထက် အများကြီး ဆိုးပါတယ်။

နောက်တစ်ခုက conditional execution ပါ။ ဒီ workflow ဟာ pull request မှာလည်း run ပါတယ် — merge မလုပ်ခင် Dockerfile က build ဖြစ်သေးလားဆိုတာ သိချင်လို့ပါ။ ဒါပေမယ့် push မလုပ်ရပါဘူး။ Fork ကလာတဲ့ pull request မှာ GITHUB_TOKEN ဟာ read-only ဖြစ်နေတာ မှန်ပါတယ်။ ဒါပေမယ့် အဲဒီ ကျရှုံးမှုကို အားကိုးထားတာက ပြောင်းပြန်ပါ — token က ကျယ်နေတဲ့ အခြေအနေမျိုးဆိုရင် လူစိမ်းတစ်ယောက်ရဲ့ branch ကလာတဲ့ code ကို ကိုယ့် registry ပေါ် တင်လိုက်တာ ဖြစ်သွားပါလိမ့်မယ်။ ဒါကြောင့် login step ကို if: github.event_name != 'pull_request' နဲ့ ကာထားပြီး၊ build-push-action ကို push: ${{ github.event_name != 'pull_request' }} ပေးထားပါတယ်။ Job တူ၊ step တူ၊ အဆုံးသတ်ချက် မတူပါ။

metadata-action က git ref တွေကို tag အဖြစ် ပြောင်းပေးလို့ tag တွေကို လက်နဲ့ ရေးစရာ မလိုတော့ပါဘူး။ အရေးအကြီးဆုံးက type=sha ပါ။ main လို branch tag က ရွေ့သွားပါတယ် — မနေ့က ညွှန်ထားတဲ့ image ဟာ ဒီနေ့ ပျောက်နေပါပြီ။ SHA tag ကတော့ မပြောင်းလဲပါဘူး၊ commit တစ်ခုတည်းဆီ ပြန်ခြေရာခံလို့ ရပါတယ်။ Rollback ဆိုတာ rebuild မဟုတ်ဘဲ redeploy ဖြစ်သွားစေတာ ဒါကြောင့်ပါ။

နောက်ဆုံးမှာ cache-from နဲ့ cache-to ကို type=gha ထားလိုက်တာက Docker ရဲ့ layer cache ကို Actions cache ထဲ ထားပေးလို့၊ runner အသစ်ဖြစ်ပေမယ့် မပြောင်းလဲတဲ့ layer တွေကို run တစ်ခုကနေ တစ်ခု ပြန်သုံးနိုင်ပါတယ်။

text
IMAGE PIPELINE - PUSH VERSUS PULL REQUEST
-----------------------------------------
  push to main  or  tag v*            pull_request (incl. forks)
            |                                     |
            v                                     v
     +-------------+                       +-------------+
     |    test     |                       |    test     |
     +-------------+                       +-------------+
            |  needs: test                        |  needs: test
            v                                     v
  +-----------------------------+     +-----------------------------+
  | image                       |     | image                       |
  | permissions:                |     | permissions:                |
  |   contents: read            |     |   contents: read            |
  |   packages: write           |     |   packages: write           |
  |                             |     |                             |
  | login-action    -> ghcr.io  |     | login-action    -> SKIPPED  |
  | metadata-action -> tags     |     | metadata-action -> tags     |
  | build-push      -> push:yes |     | build-push      -> push:no  |
  | cache-from/to   -> type=gha |     | cache-from/to   -> type=gha |
  +-----------------------------+     +-----------------------------+
            |                                     |
            v                                     v
   ghcr.io/OWNER/REPO:main              nothing published; the build
   ghcr.io/OWNER/REPO:sha-<full sha>    result is only a green check

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

Workflow က test job နဲ့ စပါတယ်။ image job မှာ needs: test ပါလို့၊ test မအောင်ရင် image ကို build တောင် မလုပ်ပါဘူး — ပျက်နေတဲ့ image ကို push မလုပ်မိအောင် အစောဆုံး ကာကွယ်ချက်ပါ။

image job ရဲ့ step အစီအစဉ်က အရေးကြီးပါတယ်။ setup-buildx-action က BuildKit builder ကို တပ်ဆင်ပေးပါတယ် — cache-from နဲ့ cache-to က BuildKit မပါဘဲ အလုပ်မလုပ်ပါဘူး။ ပြီးမှ login၊ ပြီးမှ metadata၊ နောက်ဆုံးမှ build-push ပါ။ metadata-action က တွက်ထားတဲ့ tag နဲ့ label စာရင်းကို steps.meta.outputs.tags နဲ့ steps.meta.outputs.labels အဖြစ် ထွက်ပေးလို့၊ build-push-action က အဲဒါကို တိုက်ရိုက် စားလိုက်ပါတယ်။ ဒါကြောင့် branch, tag, pr, sha အားလုံးအတွက် tag စည်းမျဉ်းကို တစ်နေရာတည်းမှာပဲ ထိန်းရပါတယ်။

Production ရှုထောင့်ကနေ ကြည့်ရင် ဒီ workflow ရဲ့ တန်ဖိုးက tag နှစ်မျိုးကို တစ်ပြိုင်တည်း ထုတ်ပေးတာပါ။ main tag က နောက်ဆုံး version ကို လိုချင်တဲ့ လူတွေအတွက်၊ sha-<full sha> tag ကတော့ deploy record အတွက်ပါ။ Deployment system တွေဟာ main ကို မကိုးကားသင့်ပါဘူး — SHA tag ကို ကိုးကားရပါမယ်။ ညနေ ၆ နာရီမှာ ပြဿနာတက်ရင် မနေ့က SHA tag ကို ပြန်ညွှန်လိုက်ရုံနဲ့ ပြီးပါပြီ။ Git history ကို ပြန်ရှာစရာ၊ ပြန် build လုပ်စရာ မလိုတော့ပါဘူး။

cache-to: type=gha,mode=max က intermediate layer အားလုံးကို သိမ်းလို့ multi-stage Dockerfile တွေမှာ အထူး အကျိုးရှိပါတယ်။ mode=min (default) ကတော့ နောက်ဆုံး stage ရဲ့ layer တွေကိုပဲ သိမ်းလို့၊ dependency install stage ကို အမြဲ ပြန်လုပ်နေရပါလိမ့်မယ်။

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

yaml
name: Build and Push Image

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    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 test

  image:
    name: Build image
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Set up Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Derive tags and labels
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=sha,format=long
          labels: |
            org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
            org.opencontainers.image.revision=${{ github.sha }}

      - name: Build and conditionally push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Record pushed tags in the run summary
        if: github.event_name != 'pull_request'
        run: |
          echo "### Pushed image tags" >> "$GITHUB_STEP_SUMMARY"
          echo "${{ steps.meta.outputs.tags }}" >> "$GITHUB_STEP_SUMMARY"
You should see
main ကို push တဲ့အခါ test job အရင် run ပြီး၊ အောင်မြင်မှ image job စပါတယ်။ image job က Buildx ကို တပ်ဆင်၊ GITHUB_TOKEN နဲ့ ghcr.io ကို login ဝင်၊ metadata-action က branch နာမည်နဲ့ commit SHA ကနေ tag စာရင်း ထုတ်ပြီး၊ Buildx က Actions cache ထဲက layer တွေကို ပြန်သုံးကာ image ကို build လုပ်ပါတယ်။ ပြီးရင် tag နှစ်မျိုးလုံး — branch tag နဲ့ sha tag — ကို registry ဆီ push လုပ်ပြီး၊ ပြောင်းလဲသွားတဲ့ layer တွေကို cache ထဲ ပြန်သိမ်းပါတယ်။ Run summary မှာ push လိုက်တဲ့ tag စာရင်း ပေါ်လာပါတယ်။ Version tag (v1.2.3 လိုမျိုး) push တဲ့အခါဆိုရင် semver pattern က version tag ကိုပါ ထပ်ထုတ်ပေးပါတယ်။

Pull request တစ်ခုမှာဆိုရင် အစီအစဉ် တူပေမယ့် အဆုံးသတ် မတူပါဘူး။ login step က if condition ကြောင့် skip ဖြစ်ပြီး၊ metadata-action ကတော့ pr tag ကို ဆက်ထုတ်ပေးပါတယ်။ build-push-action က push: false ရလို့ image ကို build ပဲ လုပ်ပြီး ဘယ်နေရာမှ မတင်ပါဘူး။ ဒါကြောင့် Dockerfile ပျက်နေရင် pull request မှာ ချက်ချင်း အနီ ပေါ်ပေမယ့်၊ registry ကတော့ မထိခိုက်ဘဲ ရှိနေပါတယ်။ Fork ကလာတဲ့ pull request တွေမှာ secret မရနိုင်တာကို ဒီ ဖွဲ့စည်းပုံက ပဋိပက္ခ မဖြစ်စေဘဲ ကျော်လွှားပါတယ်။

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

ဒီ workflow ကို Dockerfile ရှိတဲ့ repository တစ်ခုမှာ ထည့်ပြီး သုံးဆင့် လုပ်ကြည့်ပါ။ ပထမ — permissions block ထဲက packages: write ကို ဖျက်ပြီး main ကို push ပါ။ Push step ဘယ်လို ကျရှုံးလဲ၊ error message က scope အကြောင်း ဘာပြောလဲ ကြည့်ပါ။ ပြီးရင် ပြန်ထည့်ပါ။ ဒုတိယ — pull request တစ်ခု ဖွင့်ပြီး၊ image job က အောင်မြင်ပေမယ့် Packages စာမျက်နှာမှာ ဘာမှ အသစ် မပေါ်တာကို အတည်ပြုပါ။ တတိယ — cache-to line ကို ဖြုတ်ပြီး ဆက်တိုက် နှစ်ကြိမ် run ကြည့်ကာ၊ layer cache မရှိတဲ့အခါ build အချိန် ဘယ်လောက် ကွာသွားလဲ နှိုင်းယှဉ်ပါ။ ပြီးရင် platforms မှာ linux/arm64 ထပ်ထည့်ရင် ဘာဖြစ်လာမလဲ စမ်းကြည့်ပါ။

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

Pull request မှာလည်း push: true ထားလိုက်တာ။ Fork ကလာတဲ့ pull request တိုင်းမှာ GITHUB_TOKEN က read-only ဖြစ်လို့ job တွေ အမြဲနီနေပြီး၊ ပိုဆိုးတာက အဲဒါကို ဖြေရှင်းဖို့ ဆိုပြီး pull_request_target သို့မဟုတ် ကျယ်ပြန့်တဲ့ PAT တစ်ခု ထည့်လိုက်ရင် စစ်မှုမခံရသေးတဲ့ code ကို registry ပေါ် တင်ခွင့် ပေးလိုက်တာ ဖြစ်သွားပါတယ်။

Deployment တွေမှာ image ကို :latest သို့မဟုတ် :main နဲ့ ကိုးကားတာ။ Tag က ရွေ့သွားလို့ ဒီနေ့ deploy လုပ်တဲ့ image ဟာ မနေ့က စမ်းခဲ့တဲ့ image မဟုတ်တော့ဘဲ၊ rollback လုပ်ဖို့ ကိုင်စရာ တည်ငြိမ်တဲ့ အမည် တစ်ခုမှ မကျန်တော့ပါဘူး။

GitHub Docs - Publishing Docker imagesCI/CD with GitHub Actions

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

  • Pull request မှာလည်း push: true ထားလိုက်တာ။ Fork ကလာတဲ့ pull request တိုင်းမှာ GITHUB_TOKEN က read-only ဖြစ်လို့ job တွေ အမြဲနီနေပြီး၊ ပိုဆိုးတာက အဲဒါကို ဖြေရှင်းဖို့ ဆိုပြီး pull_request_target သို့မဟုတ် ကျယ်ပြန့်တဲ့ PAT တစ်ခု ထည့်လိုက်ရင် စစ်မှုမခံရသေးတဲ့ code ကို registry ပေါ် တင်ခွင့် ပေးလိုက်တာ ဖြစ်သွားပါတယ်။
  • Deployment တွေမှာ image ကို :latest သို့မဟုတ် :main နဲ့ ကိုးကားတာ။ Tag က ရွေ့သွားလို့ ဒီနေ့ deploy လုပ်တဲ့ image ဟာ မနေ့က စမ်းခဲ့တဲ့ image မဟုတ်တော့ဘဲ၊ rollback လုပ်ဖို့ ကိုင်စရာ တည်ငြိမ်တဲ့ အမည် တစ်ခုမှ မကျန်တော့ပါဘူး။
  • Workflow ကို production branch ပေါ် တိုက်ရိုက်မစမ်းဘဲ branch (သို့) test repository တွင် အရင်အတည်ပြုပါ။

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

ဒီ workflow ကို Dockerfile ရှိတဲ့ repository တစ်ခုမှာ ထည့်ပြီး သုံးဆင့် လုပ်ကြည့်ပါ။ ပထမ — permissions block ထဲက packages: write ကို ဖျက်ပြီး main ကို push ပါ။ Push step ဘယ်လို ကျရှုံးလဲ၊ error message က scope အကြောင်း ဘာပြောလဲ ကြည့်ပါ။ ပြီးရင် ပြန်ထည့်ပါ။ ဒုတိယ — pull request တစ်ခု ဖွင့်ပြီး၊ image job က အောင်မြင်ပေမယ့် Packages စာမျက်နှာမှာ ဘာမှ အသစ် မပေါ်တာကို အတည်ပြုပါ။ တတိယ — cache-to line ကို ဖြုတ်ပြီး ဆက်တိုက် နှစ်ကြိမ် run ကြည့်ကာ၊ layer cache မရှိတဲ့အခါ build အချိန် ဘယ်လောက် ကွာသွားလဲ နှိုင်းယှဉ်ပါ။ ပြီးရင် platforms မှာ linux/arm64 ထပ်ထည့်ရင် ဘာဖြစ်လာမလဲ စမ်းကြည့်ပါ။

You'll know it worked when: main ကို push တဲ့အခါ test job အရင် run ပြီး၊ အောင်မြင်မှ image job စပါတယ်။ image job က Buildx ကို တပ်ဆင်၊ GITHUB_TOKEN နဲ့ ghcr.io ကို login ဝင်၊ metadata-action က branch နာမည်နဲ့ commit SHA ကနေ tag စာရင်း ထုတ်ပြီး၊ Buildx က Actions cache ထဲက layer တွေကို ပြန်သုံးကာ image ကို build လုပ်ပါတယ်။ ပြီးရင် tag နှစ်မျိုးလုံး — branch tag နဲ့ sha tag — ကို registry ဆီ push လုပ်ပြီး၊ ပြောင်းလဲသွားတဲ့ layer တွေကို cache ထဲ ပြန်သိမ်းပါတယ်။ Run summary မှာ push လိုက်တဲ့ tag စာရင်း ပေါ်လာပါတယ်။ Version tag (v1.2.3 လိုမျိုး) push တဲ့အခါဆိုရင် semver pattern က version tag ကိုပါ ထပ်ထုတ်ပေးပါတယ်။ Pull request တစ်ခုမှာဆိုရင် အစီအစဉ် တူပေမယ့် အဆုံးသတ် မတူပါဘူး။ login step က if condition ကြောင့် skip ဖြစ်ပြီး၊ metadata-action ကတော့ pr tag ကို ဆက်ထုတ်ပေးပါတယ်။ build-push-action က push: false ရလို့ image ကို build ပဲ လုပ်ပြီး ဘယ်နေရာမှ မတင်ပါဘူး။ ဒါကြောင့် Dockerfile ပျက်နေရင် pull request မှာ ချက်ချင်း အနီ ပေါ်ပေမယ့်၊ registry ကတော့ မထိခိုက်ဘဲ ရှိနေပါတယ်။ Fork ကလာတဲ့ pull request တွေမှာ secret မရနိုင်တာကို ဒီ ဖွဲ့စည်းပုံက ပဋိပက္ခ မဖြစ်စေဘဲ ကျော်လွှားပါတယ်။

Project: Docker Image တစ်ခု Build ပြီး Push လုပ်ခြင်း | Thuta Learning