CI CD Pipeline - ianchen0119/Introduce-to-5GC GitHub Wiki

在先前的文章有稍微介紹過常見的 DevOps Tools,而本篇文章筆者會分享幫 free5GC 開源專案加入 CI pipeline 的實作!

進入正題

free5GC 專案中的每個 Network Function 都是獨立的專案,最後再由主專案連接這些子專案,透過修改 submodule 的 commit hash 做新版本的釋出。 因為有子專案的特性,對外部貢獻者提交的 Pull Request 進行審查時其實會遇到一個問題:

  1. 如果連 Network Function 的相依套件也被更新,我們必須要手動 pull 下來做一些基本的測試
  2. 沒辦法快速的做整合測試(將整個核心網路運作起來)

也因為這樣,雖然我們也有要將每一次的釋出的快速的交付的客人手上,但有任何新功能加入時都要耗費不少的功伕。 為了解決這個問題,我們就來看看如何用市面上的 CI 工具做出一個可用的解決方案吧!

設計選擇

已知的 CI/CD Tools 大概有:Circle CI、Jenkins、Drone CI、GitLab CI 以及 GitHub Actions,我想,每有哪一個 solution 一定是最好的,只有最合適當的! 而我們的需求有:

  • 能與 GitHub 整合
  • 要有較高的執行權限(因為 UPF 會需要跑 Linux Kernel Module)

原先我們是考慮以自建 Drone CI 去整合目前的專案,但偶然我看到了兩個很有趣的東西:

  • GitHub Actions 提供 Self-hosted runner
  • Act - Run your GitHub Actions locally 🚀

前者幫我解決了 Linux kernel module 沒辦法 inject 到 GitHub-hosted runner 的問題,而後者可以讓我在本地開發與測試 workflow,這樣可以提高 CI pipeline 的開發效率。

專案介紹

Run your GitHub Actions locally! Why would you want to do this? Two reasons:

  • Fast Feedback - Rather than having to commit/push every time you want to test out the changes you are making to your .github/workflows/ files (or for any changes to embedded GitHub actions), you can use act to run the actions locally. The environment variables and filesystem are all configured to match what GitHub provides.
  • Local Task Runner - I love make. However, I also hate repeating myself. With act, you can use the GitHub Actions defined in your .github/workflows/ to replace your Makefile!

上面這段描述取自 Act 專案的 README,如果沒有一個這樣的工具,要把基本的功能測試加入 CI pipeline 其實需要反覆的測試,而且加上我們有安裝 Linux kernel module 的需要,能夠在本地先驗證 workflow 毫無疑問是最有效的開發方式。

Act 的使用方式其實相當簡單:

1. 在專案的 .github/workflows 下放置寫好的 CI 腳本(副檔名為 .yml

2. 如果你的測試教本有監聽 push event,只要輸入 act 就會觸發 workflow 了:

$ act

當然,如果你的測試腳本會在收到 pull request event 時觸發,可以使用 $ act pull_request

撰寫腳本

GitHub Actions 的腳本是以 job 的概念執行的,每個 job 可以執行多個動作,GitHub 上也有很多開發者提供寫好的腳本讓大家參考:

name: Functionality Test
# trigger workflow in local: sudo act --privileged --container-cap-add CAP_SYS_ADMIN --container-cap-add NET_ADMIN pull_request
on:
  branches:
      - main
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Go
      uses: actions/setup-go@v3
      with:
        go-version: 1.17

    - name: Start MongoDB
      uses: supercharge/[email protected]
      with:
        mongodb-version: 4.4

    - name: Install dependencies
      run: |
        sudo apt -y update
        apt -y install netcat psmisc iproute2 git gcc g++ cmake autoconf libtool pkg-config libmnl-dev libyaml-dev
        go get -u github.com/sirupsen/logrus

    - name: Build
      run: make

    - name: Basic Test
      run: |
        ./test_ci.sh TestRegistration
        sleep 1
        ./test_ci.sh TestGUTIRegistration
        sleep 1
        ./test_ci.sh TestServiceRequest
        sleep 1
        ./test_ci.sh TestXnHandover
        sleep 1
        ./test_ci.sh TestDeregistration
        sleep 1
        ./test_ci.sh TestPDUSessionReleaseRequest
        sleep 1
        ./test_ci.sh TestPaging
        sleep 1
        ./test_ci.sh TestN2Handover
        sleep 1
        ./test_ci.sh TestReSynchronization

上面的腳本可以分成兩塊,第一部分描述腳本在什麼狀況下會被觸發,第二部分則是描述了有哪些 job 要被執行。

on:
  branches:
      - main
  pull_request:
  • 監聽 main 分支的 push event
  • 監聽所有分支的 pull request
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Go
      uses: actions/setup-go@v3
      with:
        go-version: 1.17

    - name: Start MongoDB
      uses: supercharge/[email protected]
      with:
        mongodb-version: 4.4

    - name: Install dependencies
      run: |
        sudo apt -y update
        apt -y install netcat psmisc iproute2 git gcc g++ cmake autoconf libtool pkg-config libmnl-dev libyaml-dev
        go get -u github.com/sirupsen/logrus

    - name: Build
      run: make

    - name: Basic Test
      run: |
        ./test_ci.sh TestRegistration
        sleep 1
        ./test_ci.sh TestGUTIRegistration
        sleep 1
        ./test_ci.sh TestServiceRequest
        sleep 1
        ./test_ci.sh TestXnHandover
        sleep 1
        ./test_ci.sh TestDeregistration
        sleep 1
        ./test_ci.sh TestPDUSessionReleaseRequest
        sleep 1
        ./test_ci.sh TestPaging
        sleep 1
        ./test_ci.sh TestN2Handover
        sleep 1
        ./test_ci.sh TestReSynchronization
  • 一開始我們告訴 GitHub Actions 這次的腳本會在什麼樣的環境下執行,以筆者的腳本來看就是運作在 ubuntu runner 上。
  • 接著會使用到其他開發者提供的腳本,讓這次的腳本可以運作在一個安裝好 mongoDB 與 golang 的環境上。
  • 接下來的部分就是安裝一些編譯 free5GC 必備的套件(包含 cmake、gcc、g++ 等工具)。
  • 最後,使用 make 編譯所有的 Network Function,再去執行 free5GC 提供的測試腳本就大功告成啦!

補充:

  • 這個腳本在本地測試之前,必須確保主機已經安裝了 gtp5g。
  • 實際放上 GitHub 上面使用時,需要將 runner 的環境從 ubuntu-latest 替換成我們自架的 Self-hosted runner。

如何自架 GitHub Actions runner

這部分可以參考官方提供的文件,其實 GitHub 已將把這一塊弄的非常完善,要在公有雲或是私有雲上建立一個 runner 不會花費超過 10 分鐘的時間。

總結

本文使用 Act 快速的撰寫了一個 free5GC 能用的 CI workflow,之後有任何的 pull request,我們只要請提交者更新 submodule hash 並且提交一份 PR 到 free5GC main repository,GitHub Actions 就可以幫我們驗證基本的功能性是否正確囉!

但這份腳本其實也還有一些缺點,像是 Non3gpp 的測試項目受限於 container 與 sandbox 的環境無法正常運作。此外,對於商業使用上,我們可能還需要在新版本釋出時自動的將通過測試的程式部署到客戶端。 這邊我們可以參考 Erricson 的實作方法:

簡單來說,Erricson 使用的 solution 多了部署與部署後的測試,新版本 release 會先經過 Acceptance tests,確認沒問題以後,可以使用任何你想得到的方式部署新版本的軟體到客戶端(之前提到的 Infrastructure as Code、Event Notify、GitOps 等方法),部署完成後再對客戶端的核心網路執行 Acceptance test,確保這次的軟體交付是沒有問題的。

目前這份 CI workflow 已經整合到 free5GC 專案內,有興趣的朋友可以參考:

References