[GitOps] 白嫖神器Github Actions,構建、推送Docker映象一路暢通無阻

黯影行者發表於2023-11-24

DimTechStudio.Com

[GitOps] 白嫖神器Github Actions,構建、推送Docker映象一路暢通無阻

引言

  當你沒找到合適的基礎的Docker映象時,是否會一時衝動,想去自己構建。然而因為網路問題,各種軟體官方源、映象源(包括但不限於apt、pip、maven,npm),不但編譯奇慢無比,還時常出錯白白浪費幾十個G的網路流量;硬體問題,愛用Windows但裝WSL開了Hyper-V玩不了模擬器,用虛擬機器又覺得麻煩佔磁碟空間,還沒錢續期雲伺服器……
  我的朋友,如果你還在為以上種種問題而苦惱,那麼我很榮幸為你推薦Nuget GitOps,Github Actions!
  GitOps,作為一種現代化的運維理念,強調透過版本控制系統來管理基礎設施和應用配置,提高可維護性和可靠性。在這個背景下,Github Actions作為一項強大的CI/CD工具,為我們提供了優雅的解決方案。本文將深入探討如何充分利用Github Actions服務,白嫖其強大的構建能力,實現Docker映象的自動構建與推送,確保網路暢通無阻。
【不得不說同樣是官方源,Nuget就極少像前面幾個源出問題,.net yyds!】

Github Actions簡介

  Github Actions是Github提供的一項持續整合和持續交付服務,與倉庫無縫整合,可透過簡單的YAML配置檔案定義工作流程。這使得我們能夠輕鬆地在程式碼倉庫中管理和執行CI/CD任務,提高開發和部署的效率。藉助Github Actions,我們可以構建、測試和部署專案,將整個開發週期變得更加流暢。
  GitHub可以提供 Linux、Windows 和 macOS 虛擬機器來執行你的工作流程,在使用Github Actions之前,你需要了解以下前置知識:

  • Yaml基礎語法
  • Linux(或Windows或macOS)指令碼相關知識
  • Git及Github的相關知識

什麼是Yaml

  當建立Github Actions時,會在程式碼庫.github/workflows目錄下,建立一個.yml 檔案,每個yml對應一個工作流。
  YAML(YAML Ain't Markup Language或YAML是一個人類可讀的資料序列化格式)是一種簡潔且易讀的資料標記語言。它常被用於配置檔案和資料交換格式,以人類可讀的方式表示資料結構,有以下幾個特點:

  • 大小寫敏感。
  • 使用縮排表示層級關係。
  • 縮排只能使用空格,不能用 TAB 字元。
  • 縮排的空格數量不重要,只要層級相同的元素左對齊即可。
  • 表示註釋。

Github Actions的Yaml結構

  • name:workflow名稱
  • on:觸發器
  • env:自定義的環境變數
  • job:一個workflow可以有多個job,每個job包含多個step
  • runs-on:任務容器,如ubuntu-latest,windows-latest,macos-latest
  • step:任務步驟
  • action:每個步驟可以執行多個命令

Github Actions的使用限制

  Github Actions可以免費使用,也可以付費使用,其中免費使用者有以下限制:

  • 使用時長
    可以每月使用2000分鐘,儲存500MB(應該是指倉庫大小),其中不同容器時間係數是不同的,Linux是1,Windows是2(1000分鐘),MacOS是10(200分鐘);
  • 併發作業數20
  • 作業執行時間最長6小時
  • 工作流執行時間每次工作流執行時間限制為 35 天
    (筆者沒看懂這專案)

  更詳細內容可以在官網中找到usage-limits-billing-and-administration

Docker映象的構建

  在Github Actions中配置Docker映象構建的過程非常簡單。透過定義workflow,我們可以指定觸發條件、構建步驟和依賴關係。配置一個構建步驟,執行Docker映象的構建,確保在每次程式碼推送時觸發自動構建流程。這種自動化的構建流程不僅提高了效率,還減少了人為出錯的可能性。

新建workflow

  首先開啟你的Github 程式碼庫,點選Actions

  點選New workflow按鈕

  搜尋Docker,會有很多workflow模板,其中Docker Image是非常簡潔的模板,適合筆者我這種簡約主義者,以下將使用它作為教程示例。
順帶一提,隔壁那個模板很複雜……英文好的人可以研究研究
  點選Configure,進入編寫yml,編寫任務

  官方預設模板內容如下,功能是當push或者pull master分支時,觸發構建流程

name: Docker Image CI
on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Build the Docker image
      run: docker build . --file Dockerfile --tag my-image-name:$(date +%s)

  而run標籤,就是linux下的shell指令碼,既然是shell,那麼就可以做很多事情了。
  比如說你原本已有一套構建指令碼(build.sh),那麼可能稍作修改,就能用著Github Actions中,run改為sh build.sh即可。
  yml可以線上編輯,也可以儲存後pull到原生程式碼庫。

示例程式碼庫

  回到前言所說的內容,筆者痛點是希望找一個人工智慧的執行環境映象,但沒有整合的,人工智慧的映象一般又很大,各環境單獨一個映象下載慢又佔空間,在whl包不衝突的情況下,裝在一起能省很多控制元件,於是乎就誕生了本專案:Wlkr.DockerBuild
注意,本專案目的是編譯基礎環境的映象,沒有什麼程式碼,可能常見的專案開發有所出入,請自行甄選。

  觀察Wlkr.AiRuntime專案,會見到有多個 Dockfile,此舉是為了避免編譯失敗,導致漫長的構建過程又要重新編譯。
  比如Ubuntu映象預設沒有python,除了python還有其他一些深度學習所需的基礎元件也沒有,當你編譯透過python後,安裝pytorch報錯缺失元件,規劃不好又要從python那個步驟開發編譯,非常浪費時間。
  在分而治之的思想指導下, 最終映象構建分為了7個Dockerfile。

觸發器

  修改工作流的yml,改為監聽這7個Dockerfile和一個python檔案

name: Docker Image CI
on:
  push:
    # 監聽的分支
    branches:
      - master
    # 監聽的檔案
    paths:
      - 'Wlkr.AiRuntime/Dockerfile*'
      - 'Wlkr.AiRuntime/model_init.py'

  如果你在自己的linux伺服器下編譯過docker映象,Dockerfile中編譯成功的步驟會有一個快取layer,減少編譯的所需時間。
  但是在Github Actions中,每次執行工作流,均沒有快取。
同時筆者所編譯的映象是環環相扣的,上一層映象有改動時,下層的所有映象也應該重新編譯。

最佳化構建流程

  有沒有最佳化的可能?答案是有的!
  先定義7個flag變數

env:
  flag_python: 0
  flag_pytorch: 0
  flag_modelscope: 0
  flag_modelscope_cv: 0
  flag_mscv_pd: 0
  flag_mscv_pdocr: 0
  flag_mscv_pdocr_mdl: 0
  flag_done: 0

  修改拉取程式碼的action,預設會帶上引數--dept=1,無法滿足後續的操作

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Code
      uses: actions/checkout@v3
      with:
        fetch-depth: 2 #加上這個引數

  在上面監聽檔案中,我們監聽了所有Dockerfile,顯然沒有變動的Dockerfile,是不需要重新編譯重新構建的。
  那麼怎麼知道哪些檔案有變動,哪個映象需要重新編譯?答案就是commit後產生的sha id。

  利用git diff命令,檢查上一次commit與本次commit的檔案是否不同,如果不容,則修改flag,標記為需要編譯。

    - name: Check Modify
      run: |
        echo "previous_sha=${{ github.event.before }}"

        cd Wlkr.AiRuntime
        # python
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.python)" ]; then
          echo "flag_python=1" >> $GITHUB_ENV
        fi
        # 其他flag省略

  在構建的action中,加上新增if: env.flag_python == 1,來確定是否要執行編譯,如果要則將下一步驟的flag也改為1。

    - name: Build python
      if: env.flag_python == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.python -t dimwalker/wlkr.python .
        echo "flag_pytorch=1" >> $GITHUB_ENV

  至此,編譯的最佳化已完成。
  需要注意,每個action都是獨立的,也就是說工作目錄cd Wlkr.AiRuntime要在每個step中先執行一次。

Docker映象的推送

  Docker Hub作為一個廣泛使用的Docker映象倉庫,為開發者提供了便捷的映象儲存和分享平臺。透過Github Actions,我們可以配置自動將構建好的Docker映象推送到Docker Hub。這一步驟使得我們的應用在構建完成後,能夠迅速被部署和共享,為團隊協作和持續整合提供了更多可能性。
  在構建和推送Docker映象的過程中,網路通訊可能會成為一個潛在的問題。為了確保暢通無阻,我們可以採取一系列措施,如配置合適的網路代理、最佳化映象構建步驟、以及合理選擇構建和推送的時機。這些措施將有助於提高構建成功率,確保整個流程的順暢進行。
  但在大區域網中,依然寸步難行。而你只需將構建步驟移到Github Actions中,一切問題都能迎刃而解,真香!

有一點需要注入,雖然Github和Docker Hub很香,但是Docker Hub的映象時開源的!有商用、涉密等使用要求的人,請謹慎使用。

登入Docker Hub

  現在先回到Github Actions的workflow編輯頁面,它的右邊也是有很多action模板的!

  把Docker login的程式碼複製進你的workflow yml中,放在steps靠前的位置

  保留示例中的三個引數即可
  其中在env節點加上變臉REGISTRY,留空是將映象推送到docker hub中,如果是其他庫則填相應的地址

    - name: Docker Login
      uses: docker/login-action@v3.0.0
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ secrets.DOCKERACC }}
        password: ${{ secrets.DOCKERPWD }}

DOCKERACCDOCKERPWD則需要在程式碼庫中的settings裡設定。
  按照docker官網說法,這的PWD也可以是你設定的一個token,多人協作就不怕密碼洩露的風險

推送Docker映象

  很簡單,稍微修改構建的步驟,增加依據push命令即可

    - name: Build & Push python
      if: env.flag_python == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.python -t dimwalker/wlkr.python .
        docker push dimwalker/wlkr.python:latest
        echo "flag_pytorch=1" >> $GITHUB_ENV

完整的yml

  你沒猜錯,這段就是用來水字數的。

name: Docker Image CI

on:
  push:
    # 監聽的分支
    branches:
      - master
    # 監聽的檔案
    paths:
      - 'Wlkr.AiRuntime/Dockerfile*'
      - 'Wlkr.AiRuntime/model_init.py'
env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ''
  flag_python: 0
  flag_pytorch: 0
  flag_modelscope: 0
  flag_modelscope_cv: 0
  flag_mscv_pd: 0
  flag_mscv_pdocr: 0
  flag_mscv_pdocr_mdl: 0
  flag_done: 0

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Code
      uses: actions/checkout@v3
      with:
        fetch-depth: 2

    - name: Docker Login
      uses: docker/login-action@v3.0.0
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ secrets.DOCKERACC }}
        password: ${{ secrets.DOCKERPWD }}

    - name: Check Modify
      run: |
        echo "previous_sha=${{ github.event.before }}"

        cd Wlkr.AiRuntime
        # python
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.python)" ]; then
          echo "flag_python=1" >> $GITHUB_ENV
        fi
        # pytorch
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.pytorch)" ]; then
          echo "flag_pytorch=1" >> $GITHUB_ENV
        fi
        # modelscope
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.modelscope)" ]; then
          echo "flag_modelscope=1" >> $GITHUB_ENV
        fi
        #
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.modelscope_cv)" ]; then
          echo "flag_modelscope_cv=1" >> $GITHUB_ENV
        fi
        # mscv_pd
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.mscv_pd)" ]; then
          echo "flag_mscv_pd=1" >> $GITHUB_ENV
        fi
        # mscv_pdocr
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.mscv_pdocr)" ]; then
          echo "flag_mscv_pdocr=1" >> $GITHUB_ENV
        fi
        # mscv_pdocr_mdl
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- Dockerfile.mscv_pdocr_mdl)" ]; then
          echo "flag_mscv_pdocr_mdl=1" >> $GITHUB_ENV
        fi
        if [ -n "$(git diff --name-only ${{ github.event.before }} HEAD -- model_init.py)" ]; then
          echo "flag_mscv_pdocr_mdl=1" >> $GITHUB_ENV
        fi

    - name: Print Env
      run: |
        echo "flag_python: ${{ env.flag_python }}"
        echo "flag_pytorch: ${{ env.flag_pytorch }}"
        echo "flag_modelscope: ${{ env.flag_modelscope }}"
        echo "flag_modelscope_cv: ${{ env.flag_modelscope_cv }}"
        echo "flag_mscv_pd: ${{ env.flag_mscv_pd }}"
        echo "flag_mscv_pdocr: ${{ env.flag_mscv_pdocr }}"
        echo "flag_mscv_pdocr_mdl: ${{ env.flag_mscv_pdocr_mdl }}"

    - name: Build & Push python
      if: env.flag_python == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.python -t dimwalker/wlkr.python .
        docker push dimwalker/wlkr.python:latest
        echo "flag_pytorch=1" >> $GITHUB_ENV

    - name: Build & Push pytorch
      if: env.flag_pytorch == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.pytorch -t dimwalker/wlkr.pytorch .
        docker push dimwalker/wlkr.pytorch:latest
        echo "flag_modelscope=1" >> $GITHUB_ENV

    - name: Build & Push modelscope
      if: env.flag_modelscope == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.modelscope -t dimwalker/wlkr.modelscope .
        docker push dimwalker/wlkr.modelscope:latest
        echo "flag_modelscope_cv=1" >> $GITHUB_ENV

    - name: Build & Push modelscope_cv
      if: env.flag_modelscope_cv == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.modelscope_cv -t dimwalker/wlkr.modelscope_cv .
        docker push dimwalker/wlkr.modelscope_cv:latest
        echo "flag_mscv_pd=1" >> $GITHUB_ENV

    - name: Build & Push mscv_pd
      if: env.flag_mscv_pd == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.mscv_pd -t dimwalker/wlkr.mscv_pd .
        docker push dimwalker/wlkr.mscv_pd:latest
        echo "flag_mscv_pdocr=1" >> $GITHUB_ENV

    - name: Build & Push mscv_pdocr
      if: env.flag_mscv_pdocr == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.mscv_pdocr -t dimwalker/wlkr.mscv_pdocr .
        docker push dimwalker/wlkr.mscv_pdocr:latest
        echo "flag_mscv_pdocr_mdl=1" >> $GITHUB_ENV

    - name: Build & Push mscv_pdocr_mdl
      if: env.flag_mscv_pdocr_mdl == 1
      run: |
        cd Wlkr.AiRuntime
        # 基礎
        docker build -f Dockerfile.mscv_pdocr_mdl -t dimwalker/wlkr.mscv_pdocr_mdl .
        docker push dimwalker/wlkr.mscv_pdocr_mdl:latest
        echo "flag_done=1" >> $GITHUB_ENV

    - name: Print Env End
      run: |
        echo "flag_python: ${{ env.flag_python }}"
        echo "flag_pytorch: ${{ env.flag_pytorch }}"
        echo "flag_modelscope: ${{ env.flag_modelscope }}"
        echo "flag_modelscope_cv: ${{ env.flag_modelscope_cv }}"
        echo "flag_mscv_pd: ${{ env.flag_mscv_pd }}"
        echo "flag_mscv_pdocr: ${{ env.flag_mscv_pdocr }}"
        echo "flag_mscv_pdocr_mdl: ${{ env.flag_mscv_pdocr_mdl }}"
        echo "flag_done: ${{ env.flag_done }}"

  Well Done!

總結

  在本文中,我們深入探討了如何充分發揮 GitHub Actions 在 GitOps 中的作用,特別是在構建和推送 Docker 映象方面。透過 GitHub Actions,開發者能夠充分利用雲端資源,輕鬆實現 CI/CD 流程。
  透過配置 GitHub Actions 的工作流程,我們可以確保構建和推送 Docker 映象的流程暢通無阻。這不僅提高了開發團隊的效率,還降低了出錯的可能性,使整個開發過程更加可靠和可預測。
  總的來說,GitHub Actions作為一個整合於GitHub平臺的CI/CD工具,為開發者提供了強大而靈活的工具,支援他們構建出高質量的軟體。尤其是對於 Docker 映象的構建和推送,GitHub Actions 提供了簡單易用的方式,使開發者能夠專注於程式碼本身而不必過多關心底層的部署細節。
  除了Github Actions,筆者以往還使用過Webhooks+Jenkins的方式,在自己的雲端伺服器編譯映象。未來,我們可以期待 GitOps 在持續整合、持續部署領域的進一步演進。隨著雲原生技術的不斷髮展,這些工具將更加貼近開發者的需求,提供更多創新的功能,幫助開發團隊更好地應對快速變化的軟體交付需求。