在 Linux 上測試軟體時,我使用各種架構的伺服器,例如 Intel、AMD、Arm 等。當我 分配了一台滿足我的測試需求的 Linux 機器 [1],我仍然需要執行許多步驟:
- 下載並安裝必備軟體
- 驗證構建伺服器上是否有新的測試軟體包
- 獲取並設置依賴軟體包所需的 yum 倉庫
- 下載並安裝新的測試軟體包(基於步驟 2)
- 獲取並設置必需的 SSL 證書
- 設置測試環境,獲取所需的 Git 倉庫,更改配置,重新啟動守護進程等
- 做其他需要做的事情
用腳本自動化#
這些步驟非常常規,以至於有必要對其進行自動化並將腳本保存到中央位置(例如檔案伺服器),在需要時可以在此處下載腳本。為此,我編寫了 100-120 行的 Bash shell 腳本,它為我完成了所有配置(包括錯誤檢查)。這個腳本通過以下方式簡化了我的工作流程:
- 配置新的 Linux 系統(支持測試的架構)
- 登錄系統並從中央位置下載自動化 shell 腳本
- 運行它來配置系統
- 開始測試
學習 Go 語言#
我想學習 Go 語言 有一段時間了,將我心愛的 Shell 腳本轉換為 Go 程式似乎是一個很好的專案,可以幫助我入門。它的語法看起來很簡單,在嘗試了一些測試程式後,我開始著手提高自己的知識並熟悉 Go 標準庫。
我花了一個星期的時間在筆記本電腦上編寫 Go 程式。我經常在我的 x86 伺服器上測試程式,清除錯誤並使程式健壯起來,一切都很順利。
直到完全轉換到 Go 程式前,我繼續依賴自己的 shell 腳本。然後,我將二進制檔案推送到中央檔案伺服器上,以便每次配置新伺服器時,我要做的就是獲取二進制檔案,將可執行標誌打開,然後運行二進制檔案。我對早期的結果很滿意:
$ wget http://file.example.com/<myuser>/bins/prepnode
$ chmod +x ./prepnode
$ ./prepnode
然後,出現了一個問題
第二週,我從資源池中分配了一台新的伺服器,像往常一樣,我下載了二進制檔案,設置了可執行標誌,然後運行二進制檔案。但這次它出錯了,是一個奇怪的錯誤:
$ ./prepnode
bash: ./prepnode: cannot execute binary file: Exec format error
$
起初,我以為可能沒有成功設置可執行標誌。但是,它已按預期設置:
$ ls -l prepnode
-rwxr-xr-x. 1 root root 2640529 Dec 16 05:43 prepnode
發生了什麼事?我沒有對源代碼進行任何更改,編譯沒有引發任何錯誤或警告,而且上次運行時效果很好,因此我仔細查看了錯誤消息 format error。
我檢查了二進制檔案的格式,一切看起來都沒問題:
$ file prepnode
prepnode: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
我迅速運行了以下命令,識別所配置的測試伺服器的架構以及二進制試圖運行的平台。它是 Arm64 架構,但是我編譯的二進制檔案(在我的 x86 筆記本電腦上)生成的是 x86-64 格式的二進制檔案:
$ uname -m
aarch64
腳本編寫者的編譯第一課#
在那之前,我從未考慮過這種情況(儘管我知道這一點)。我主要研究腳本語言(通常是 Python)以及 Shell 腳本。在任何架構的大多數 Linux 伺服器上都可以使用 Bash Shell 和 Python 解釋器。總之,之前一切都很順利。
但是,現在我正在處理 Go 這種編譯語言,它生成可執行的二進制檔案。編譯後的二進制檔案由特定架構的指令碼或匯編指令組成,這就是為什麼我收到格式錯誤的原因。由於 Arm64 CPU(運行二進制檔案的地方)無法解釋二進制檔案的 x86-64 指令,因此它拋出錯誤。以前,shell 和 Python 解釋器為我處理了底層指令碼或特定架構的指令。
Go 的交叉編譯#
我檢查了 Golang 的文檔,發現要生成 Arm64 二進制檔案,我要做的就是在運行 go build 命令編譯 Go 程式之前設置兩個環境變數。
GOOS 指的是操作系統,例如 Linux、Windows、BSD 等,而 GOARCH 指的是要在哪種架構上構建程式。
$ env GOOS=linux GOARCH=arm64 go build -o prepnode_arm64
構建程式後,我重新運行 file 命令,這一次它顯示的是 ARM AArch64,而不是之前顯示的 x86。因此,我在我的筆記本上能為不同的架構構建二進制檔案。
$ file prepnode_arm64
prepnode_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped
我將二進制檔案從筆記本電腦複製到 ARM 伺服器上。現在運行二進制檔案(將可執行標誌打開)不會產生任何錯誤:
$ ./prepnode_arm64 -h
Usage of ./prepnode_arm64:
-c Clean existing installation
-n Do not start test run (default true)
-s Use stage environment, default is qa
-v Enable verbose output
其他架構呢?#
x86 和 Arm 是我測試軟體所支持的 5 種架構中的兩種,我擔心 Go 可能不會支持其它架構,但事實並非如此。你可以查看 Go 支持的架構:
$ go tool dist list
Go 支持多種平台和操作系統,包括:
AIX
Android
Darwin
Dragonfly
FreeBSD
Illumos
ios
Js/wasm
JavaScript
Linux
NetBSD
OpenBSD
Plan 9
Solaris
Windows
要查找其支持的特定 Linux 架構,運行:
$ go tool dist list | grep linux
如下面的輸出所示,Go 支持我使用的所有體系結構。儘管 x86_64 不在列表中,但 AMD64 兼容 x86-64,所以你可以生成 AMD64 二進制檔案,它可以在 x86 架構上正常運行:
$ go tool dist list | grep linux
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
處理所有架構#
為我測試的所有體系結構生成二進制檔案,就像從我的 x86 筆記本電腦編寫一個微小的 shell 腳本一樣簡單:
#!/usr/bin/bash
archs=(amd64 arm64 ppc64le ppc64 s390x)
for arch in ${archs[@]}
do
env GOOS=linux GOARCH=${arch} go build -o prepnode_${arch}
done
$ file prepnode_*
prepnode_amd64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=y03MzCXoZERH-0EwAAYI/p909FDnk7xEUo2LdHIyo/V2ABa7X_rLkPNHaFqUQ6/5p_q8MZiR2WYkA5CzJiF, not stripped
prepnode_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=q-H-CCtLv__jVOcdcOpA/CywRwDz9LN2Wk_fWeJHt/K4-3P5tU2mzlWJa0noGN/SEev9TJFyvHdKZnPaZgb, not stripped
prepnode_ppc64: ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, Go BuildID=DMWfc1QwOGIq2hxEzL_u/UE-9CIvkIMeNC_ocW4ry/r-7NcMATXatoXJQz3yUO/xzfiDIBuUxbuiyaw5Goq, not stripped
prepnode_ppc64le: ELF 64-bit LSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, Go BuildID=C6qCjxwO9s63FJKDrv3f/xCJa4E6LPVpEZqmbF6B4/Mu6T_OR-dx-vLavn1Gyq/AWR1pK1cLz9YzLSFt5eU, not stripped
prepnode_s390x: ELF 64-bit MSB executable, IBM S/390, version 1 (SYSV), statically linked, Go BuildID=faC_HDe1_iVq2XhpPD3d/7TIv0rulE4RZybgJVmPz/o_SZW_0iS0EkJJZHANxx/zuZgo79Je7zAs3v6Lxuz, not stripped
現在,每當配置一台新機器時,我就運行以下 wget 命令下載特定體系結構的二進制檔案,將可執行標誌打開,然後運行:
$ wget http://file.domain.com/<myuser>/bins/prepnode_<arch>
$ chmod +x ./prepnode_<arch>
$ ./prepnode_<arch>
為什麼?#
你可能想知道,為什麼我沒有堅持使用 shell 腳本或將程式移植到 Python 而不是編譯語言上來避免這些麻煩。所以有舍有得,那樣的話我不會了解 Go 的交叉編譯功能,以及程式在 CPU 上執行時的底層工作原理。在計算機中,總要考慮取捨,但絕不要讓它們阻礙你的學習。
參考#
Cross-compiling made easy with Golang [1]
使用 Golang 的交叉編譯 [2]