GitHub Actions 自托管 Runner 優(yōu)化——和運營商斗智斗勇

在之前的文章 在 Kubernetes 上運行 GitHub Actions Self-hosted Runner[1] 和 關(guān)于從 GitHub Actions Self-Hosted Runner 中偷 Secrets/Credentials 的一些安全研究[2] 中,我們知道已經(jīng)可以通過各種手段讓 Self Hosted Runner 在我們內(nèi)部設(shè)施上跑起來,加上一個設(shè)計合理的專線+路由表,基本已經(jīng)可以流暢地接受+處理 GitHub 上使用了 self-hosted 的 CI 任務(wù)了,由于語法和 GitHub Actions 官方語法一致,基本使用者都會有類似「太順滑了,幾乎沒有任何的體感差異」,「有效減少了高峰用官方 Runner 排隊的問題」,「成功獲得了 ARM64 環(huán)境」,「性能和 RAM 直接翻倍」等等好評。
但是使用一個非海外的基礎(chǔ)設(shè)施我們很快就會看到一些地理位置上的缺陷,比如…
為什么
actions/setup-go@v2可以跑這么久?
那..這..用的國內(nèi)三大運營商,這不是很正常么?(雖然這個包本身并不大,才 120M 左右)

GOPROXY 優(yōu)化
為了優(yōu)化 Golang 做 go mod tidy 等操作,在 Runner 的鏡像中已經(jīng)顯式地指定好了 GOPROXY ,Dockerfile 類似:
ENV?GOPROXY?"http://goproxy.nova.moe,https://proxy.golang.org,direct"
這樣在用戶使用 Golang 程序的時候就可以直接走內(nèi)部 GOPROXY 來加速了,但是這樣依然不夠,因為要給 Runner 安裝 Go ,還需要使用 actions/setup-go@v2 來安裝。
這個時候,有些小機靈鬼就會說了:「那你把 Go 打在 Image 里面不就好了么?」

確實可以,但是這樣對于多版本管理是很不利的,難道你像下圖一樣維護一堆類似 n0vad3v/github-runner:go1130,n0vad3v/github-runner:go1160 的鏡像,然后手動控制這些鏡像的 Container 數(shù)量和 Tag,然后讓用戶去用類 Jenkins 的語法,去手動指定 runs-on: [self-hosted,X64,go1130]?

所以為了解決這個問題,我們還是得讓用戶自己去用一個 Step 來安裝 Go,畢竟環(huán)境的模塊化組裝(以及 Matrix 的使用)是 GitHub Actions 的一大優(yōu)勢,不然一堆 if-else 和 Jenkins 有啥區(qū)別,更何況現(xiàn)在 Runner 安裝了一次 Go 之后就會緩存下來(除非你啟動的時候指定了 --ephemeral),在下一次遇到同版本的時候會直接使用緩存。
actions/setup-go 優(yōu)化
在 Runner 上安裝 Golang,大家一般會使用 actions/setup-go@v2,用法也很簡單,如下:
-?uses:?actions/setup-go@v2
??with:
????go-version:?'1.16'
為了了解這個 Action 是如何工作的,在不看代碼,只看代碼結(jié)構(gòu)的角度,我們從 https://github.com/actions/setup-go/blob/main/__tests__/data/versions-manifest.json 文件中可以發(fā)現(xiàn)它 ”背后的數(shù)據(jù)地址“ 類似:
https://github.com/actions/go-versions/releases/download/1.12.17-20200616.21/go-1.12.17-darwin-x64.tar.gz
反推得到實際的數(shù)據(jù)倉庫為:https://github.com/actions/go-versions/ 的 https://github.com/actions/go-versions/blob/main/versions-manifest.json , 數(shù)據(jù)格式類似如下:
[
??{
????"version":?"1.17.5",
????"stable":?true,
????"release_url":?"https://github.com/actions/go-versions/releases/tag/1.17.5-1559554870",
????"files":?[
??????{
????????"filename":?"go-1.17.5-darwin-x64.tar.gz",
????????"arch":?"x64",
????????"platform":?"darwin",
????????"download_url":?"https://github.com/actions/go-versions/releases/download/1.17.5-1559554870/go-1.17.5-darwin-x64.tar.gz"
??????},
??????{
????????"filename":?"go-1.17.5-linux-x64.tar.gz",
????????"arch":?"x64",
????????"platform":?"linux",
????????"download_url":?"https://github.com/actions/go-versions/releases/download/1.17.5-1559554870/go-1.17.5-linux-x64.tar.gz"
??????},
??????{
????????"filename":?"go-1.17.5-win32-x64.zip",
????????"arch":?"x64",
????????"platform":?"win32",
????????"download_url":?"https://github.com/actions/go-versions/releases/download/1.17.5-1559554870/go-1.17.5-win32-x64.zip"
??????}
????]
??},
]
后來發(fā)現(xiàn)其實 README 中有寫:It will first check the local cache for a version match. If version is not found locally, It will pull it from
mainbranch of go-versions[3]
在確認(rèn)了實際下載的包的地址之后我們就可以反推 setup-go 中是如何使用這個地址的了,通過一波 rg,我們在 src/installer.ts 的 143 行發(fā)現(xiàn):
??const?releases?=?await?tc.getManifestFromRepo(
????'actions',
????'go-versions',
????auth,
????'main'
??);
所以現(xiàn)在緩存的思路就很清晰了:
下載 https://github.com/actions/go-versions/blob/main/versions-manifest.json 中的包到內(nèi)網(wǎng) Fork + 修改一份 https://github.com/actions/go-versions 倉庫,把 download_url 中的內(nèi)容換為內(nèi)網(wǎng)地址 Fork + 修改一份 setup-go,把它獲取 Manifest 的地址指向 Fork 后的 go-versions 倉庫 在 Runner 中調(diào)用 Fork 后的 setup-go
下載包到內(nèi)網(wǎng)
非常容易,Python 可以這么寫,只要指定一下 HOST_URL 為內(nèi)網(wǎng)下載地址,STORE_PATH 為實際存儲地址,GOLANG_VERSION_LIST 中填上想要緩存的 Golang 版本即可,保存為一個 download.py ,運行后等著就好:
import?requests
from?urllib.parse?import?urlparse
import?os
import?json
##?ENV
HOST_URL?=?"http://download.nova.moe/download/github-actions/golang/"
STORE_PATH?=?"/path/to/download/github-actions/golang/"
GOLANG_VERSION_LIST?=?['go-1.16','go-1.17','go-1.13']
##?END?ENV
def?process_each_package(package_filename,?package_url):
????package_path?=?STORE_PATH?+?package_filename
????if?not?os.path.isfile(package_path):
????????print("Downloading:?"?+?package_url)
????????r?=?requests.get(package_url)
????????with?open(package_path,?'wb')?as?f:
????????????f.write(r.content)
????return?package_path
if?__name__?==?'__main__':
????go_versions_url?=?'https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json'
????r?=?requests.get(go_versions_url).json()
????return_list?=?[]
????golang_package_list?=?[]
????for?item?in?r:
????????package_url?=?item['files'][1]['download_url']
????????for?version?in?GOLANG_VERSION_LIST:
????????????if?version?in?package_url:
????????????????golang_package_list.append(package_url)
????????????????a?=?urlparse(package_url)
????????????????package_filename?=?os.path.basename(a.path)
????????????????process_each_package(package_filename,?package_url)
????????????????item['files'][1]['download_url']?=?HOST_URL?+?package_filename
????????????????return_list.append(item)
????with?open('versions-manifest.json',?'w')?as?f:
????????f.write(json.dumps(return_list,?indent=2))
運行結(jié)束后所有的 tar.gz 包都會保存到 STORE_PATH 中,同時運行目錄下會生成一個下載地址已經(jīng)替換為內(nèi)網(wǎng)地址的 versions-manifest.json。
修改 go-versions 和 setup-go
Fork 這兩個倉庫后,將 Fork 后的 go-versions 倉庫下的 versions-manifest.json 替換為剛剛已經(jīng)生成好的版本(這個操作過于簡單建議直接用網(wǎng)頁修改,避免浪費拉倉庫使用的本地帶寬)。
由于 setup-go 需要編譯,為了省事考慮(反正我們只修改兩個變量),直接將 Fork 的 setup-go 中 dist/index.js 的 5037 行
const?releases?=?yield?tc.getManifestFromRepo('actions',?'go-versions',?auth,?"main");
改為 fork 后的地址,比如:
const?releases?=?yield?tc.getManifestFromRepo('n0vad3v',?'go-versions-forked',?auth,?"master");
修改 Runner
在上面的操作完成之后,我們只需要使用 fork 后的 setup-go ,即可使用到內(nèi)網(wǎng)的下載速度了,用法類似:
-?uses:?n0vad3v/setup-go-forked@master
??with:
????go-version:?'1.16'
看看效果?
快到模糊!

小結(jié)
由于緩存 Golang 的包的操作看上去是一個 One shot 的操作,基本沒有短時間內(nèi)持續(xù)更新的需求,暫時也就沒有考慮自動化之類的事情,在有了內(nèi)網(wǎng)緩存之后,整體的 Runner 運行效率一下子就提升了起來,使用體驗又愉快了不少。
這是關(guān)于 GitHub Actions Self-hosted Runner 優(yōu)化的第一篇文章,后續(xù)可能還會有一些相關(guān)的有趣的分享,同時我也在考慮把相關(guān)的組件(比如 關(guān)于從 GitHub Actions Self-Hosted Runner 中偷 Secrets/Credentials 的一些安全研究[4] 中提到的那個假 KMS,以及可用的 Runner 的 Dockerfile)開源出來,不過這些都還沒想好,有興趣的同學(xué)可以期待一下~
引用鏈接
在 Kubernetes 上運行 GitHub Actions Self-hosted Runner: https://nova.moe/run-self-hosted-github-action-runners-on-kubernetes/
[2]關(guān)于從 GitHub Actions Self-Hosted Runner 中偷 Secrets/Credentials 的一些安全研究: https://nova.moe/steal-credentials-from-ci-agents/
[3]go-versions: https://github.com/actions/go-versions/blob/main/versions-manifest.json
[4]關(guān)于從 GitHub Actions Self-Hosted Runner 中偷 Secrets/Credentials 的一些安全研究: https://nova.moe/steal-credentials-from-ci-agents/
原文鏈接:https://nova.moe/self-hosted-runner-golang-cache/


你可能還喜歡
點擊下方圖片即可閱讀

云原生是一種信仰???
關(guān)注公眾號
后臺回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!


點擊?"閱讀原文"?獲取更好的閱讀體驗!
發(fā)現(xiàn)朋友圈變“安靜”了嗎?



