如何使用 Github Workflow 完成 CI/CD

Github Workflow 介紹

前言

為了利用 Github Workflow 完成 CI/CD,分成幾個部分來寫,分別是

  1. Github Workflow 的基本設定
  2. Github Workflow 中進行 CI
  3. 登入登出 AWS ECR 與 github container registry
  4. build & upload image
  5. 更新 helm value 中的 image tag,後續讓 argocd 得以偵測到 helm values 的變化後,進行自動化部屬

基本設定

  • action 為 push 的時候,所有 branch 都會進行 github workflow
  • action 為 pull_request 的時候,只有 main branch 會進行 github workflow
  • types
  • paths
on:
  push:
    branches: ["*"]
  pull_request:
    branches: ["main"]
    types:
      - opened
    paths:
      - "**.js"

env 設定

  • 設定方式如下,可以依照自身需求進行設定
env:
  AWS_DEFAULT_REGION: ap-southeast-1
  GIT_USER_NAME: jennyc

permissions 設定

  • 設定方式如下,可以依照自身需求進行設定
permissions:
  id-token: write # This is required for requesting the JWT
  contents: read # This is required for actions/checkout

自動化測試 CI

checkout repository

  • 使用 actions/checkout@v3 進行

    - name: Checkout repository
      uses: actions/checkout@v3
    

執行 npm install

- name: Install dependencies
  run: npm install

執行 npm test

- name: Run tests
  run: npm run test

Build multi-platform images 並上傳至 AWS ECR

前言

這部分需要先設定 AWS Credential 才能使用 AWS ECR,
接著 build multi-platform image 並且上傳至 AWS ECR。

1. 設定 AWS Credential

  • 設定 AWS Credential

    • 使用 GitHub’s OIDC provider 方式取得 short-lived credentials

    • workflow 要怎麼寫?

      • env.AWS_DEFAULT_REGION 需要特別設定,與 AWS ECR 相同的 region
      • role-to-assume 填入 AWS role
      • role-session-name 預設是 GitHubActions,可以自行調整名稱
        - name: Configure AWS credentials
          uses: aws-actions/configure-aws-credentials@v1
          with:
              role-to-assume: arn:aws:iam::1234567890:role/example-role
              role-session-name: GitHubActionsWithAwsEcrUsingOIDCSession 
              aws-region: ${{env.AWS_DEFAULT_REGION}}
      

2. 登入 AWS private ECR

  • 有兩種方式
    • Using access key id and secret access key to login

        - name: Configure AWS credentials
          uses: aws-actions/configure-aws-credentials@v1
          with:
            aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
            aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
            aws-region: ap-southeast-1
      
    • Using OpenID Connect (OIDC) to login

      • 官方推薦使用此方式
        - name: Login to Amazon ECR
          id: login-ecr
          uses: aws-actions/amazon-ecr-login@v1
      

3. 登入 AWS public ECR

4. 登出 AWS ECR

- name: Logout of Amazon ECR
  if: always()
  run: docker logout ${{ steps.login-ecr.outputs.registry }}

5. 設定 short sha 為 image tag

  • 設定以 7 digits 長度的 short sha 作為 image tag,原因可以參考:
  • 將 output 出去的參數命名為 sha_short
      - name: Set short sha outputs
        id: vars
        run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

6. Build multi-platform images 並上傳 images 到 AWS ECR

  • 建立多平台 docker images 的官方文件
  • IMAGE_TAG 會從步驟 5 取得 output 的變數 sha_short
  • --platform 可接上需要的 platform 參考,例如 linux/amd64, linux/arm64
  - name: Build, tag, and push docker image to Amazon ECR
    env:
        REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        REPOSITORY: pet-app
        IMAGE_TAG: ${{ steps.vars.outputs.sha_short }}
    run: |
        docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
        ( 官方建議: docker run --privileged --rm tonistiigi/binfmt --install all )
        docker buildx create --name mybuilder --driver docker-container --bootstrap
        docker buildx use mybuilder
        docker buildx inspect
        docker buildx build --platform linux/amd64,linux/arm64 -t $REGISTRY/$REPOSITORY:$IMAGE_TAG --push .
  • 為什麼需要 docker run --rm --privileged multiarch/qemu-user-static --reset -p yes ?

    • 因為遇到 Error: while loading /usr/local/sbin/node: No such file or directory 問題
      • 先使用方式一 : 連接方式,結果還是有相同問題
          ln -s /usr/bin/node /usr/local/sbin/node
        
      • 後來使用方式二
        • 使用 docker buildx 指令前先執行以下命令

            docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
          
        • 詳細原因的 Stack Overflow 原始文章在這邊,以下是節錄部分

          ` When you ask the Linux kernel to run some executable file, it needs to know, how to load this specific file, and whether this file is compatible with current machine, or not. By default, the ELF binary compiled for, say, arm64v8 is rejected by the kernel, running on amd64 hardware.

          However, the binfmt_misc feature of the kernel allows you to tell it, how to handle the executables it cannot usually handle on its own - this includes the cases when the kernel does not know the binary format or considers it incompatible with current machine.
          `

        • github issue

  • 官方建議使用以下命令解決 QEMU binaries 問題,官方文件

      docker run --privileged --rm tonistiigi/binfmt --install all
    

更新 helm value 的 image tag

前言

應用是以 terraform 方式部屬,並搭配使用 helm charts。而 docker image 的 tag 是寫在 helm values 內。
目標是更新 helm values 的 docker image tag 值,後續讓 argocd 得以偵測到 helm values 的變化後,進行自動化部屬。

需做到兩件事情:

  1. checkout 私有 repository
  2. 設定與更新 image tag

1. 先 checkout 私有存放 helm value 的 repository

需要先設定 credential 才能 fetch private repository,有兩種設定的方式

  • 設定 deploy key

    • 步驟如下,可參考這篇

      1. Create a new SSH key pair for your repository. Do not set a passphrase.
      2. Copy the contents of the public key (.pub file) to a new repository deploy key and check the box to “Allow write access.”
      3. Add a secret to the repository containing the entire contents of the private key.
      4. As shown in the example below, configure actions/checkout to use the deploy key you have created.
    • workflow 要怎麼寫?

      • ssh-key 填入 private key 的 secrets 名稱
      • path 設定 checkout 的 repo 會存在哪一個資料夾內
        - name: Checkout ${{env.TERRAFORM_REPOSITORY_NAME}} repo and push file to ${{env.TERRAFORM_REPOSITORY_NAME}}
          uses: actions/checkout@v3
          with:
            repository: ${{env.TERRAFORM_REPOSITORY_OWNER_NAME}}/${{env.TERRAFORM_REPOSITORY_NAME}}
            ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
            path: ${{env.TERRAFORM_REPOSITORY_NAME}}
      
  • 設定 Personal access token (PAT)

    • 建立 PAT 的方式,官方文件

    • 將建立完成的 PAT 設定到 repository 的 secrets 中,設定的 secrets 名稱為 GH_PAT,官方文件

    • workflow 要怎麼寫?

      • repository 設定為 owner/repository_name
      • token 填入 PAT 的 secrets 名稱,這邊是將 secrets 名稱為 GH_PAT 的內容設定為 PAT
      - name: Checkout ${{env.TERRAFORM_REPOSITORY_NAME}} repo and push file to ${{env.TERRAFORM_REPOSITORY_NAME}}
          uses: actions/checkout@v3
          with:
              repository: ${{env.TERRAFORM_REPOSITORY_OWNER_NAME}}/${{env.TERRAFORM_REPOSITORY_NAME}}
              token: ${{ secrets.GH_PAT }}
      

2. 設定 image tag

  • 設定以 7 digits 長度的 short sha 作為 image tag
      - name: Set short sha outputs
        id: vars
        run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

3. 更新 image tag

  • 以 yq 的 github action 工具修改 yaml 檔內的 image tag 值
  • 進行 git 操作,設定 user name, user email、commit 以及 push
  • tag 取上一個步驟 output 的參數 sha_short
      - name: Update image tag
        uses: mikefarah/yq@master
        with:
          cmd: yq -i '.pet_app_dashboard_site.image.tag = "${{ steps.vars.outputs.sha_short }}"' ./${{env.TERRAFORM_REPOSITORY_NAME}}/${{env.HELM_FILE_NAME}}
      - run: |
          cd ${{env.TERRAFORM_REPOSITORY_NAME}}
          git config user.name ${{env.GIT_USER_NAME}}
          git config user.email ${{env.GIT_USER_EMAIL}}
          git add ${{env.HELM_FILE_NAME}}
          git commit -m "update image tag to ${{ steps.vars.outputs.sha_short }}"
          git push origin main

若要將 docker image 上傳至 github container registry 該怎麼做?

1. 登入與登出 github container registry

  • 需要設定 env.REGISTRY 為 ghcr.io

  • 不需要另外設定 github.actor 與 GITHUB_TOKEN

        - name: Login to GitHub Container Registry
          uses: docker/login-action@v2
          with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
    

2. Extract metadata

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

3. Build and push docker image

  • labels 的值可以從步驟二取出 outputs labels 來使用
      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO }}:${{ steps.vars.outputs.sha_short }}
          labels: ${{ steps.meta.outputs.labels }}

參考資料

最後,若喜歡我的分享,可以免費幫我按讚,是對我最大的鼓勵!