Linux 上でソフトウェアをテストする際、私は Intel、AMD、Arm などのさまざまなアーキテクチャのサーバーを使用します。テストニーズを満たす Linux マシン [1] を割り当てた後でも、まだ多くの手順を実行する必要があります:
- 必要なソフトウェアをダウンロードしてインストールする
- ビルドサーバーに新しいテストパッケージがあるか確認する
- 依存ソフトウェアパッケージに必要な yum リポジトリを取得して設定する
- 新しいテストパッケージをダウンロードしてインストールする(ステップ 2 に基づく)
- 必要な SSL 証明書を取得して設定する
- テスト環境を設定し、必要な Git リポジトリを取得し、設定を変更し、デーモンを再起動するなど
- その他必要なことを行う
スクリプトによる自動化#
これらの手順は非常に一般的であるため、自動化してスクリプトを中央の場所(例えばファイルサーバー)に保存し、必要に応じてここからスクリプトをダウンロードできるようにする必要があります。そのために、私は 100〜120 行の Bash シェルスクリプトを作成し、すべての設定を行いました(エラーチェックを含む)。このスクリプトは、以下の方法で私のワークフローを簡素化しました:
- 新しい Linux システムを設定する(テストをサポートするアーキテクチャ)
- システムにログインし、中央の場所から自動化シェルスクリプトをダウンロードする
- それを実行してシステムを設定する
- テストを開始する
Go 言語を学ぶ#
私はしばらくの間 Go 言語を学びたいと思っており、愛用のシェルスクリプトを Go プログラムに変換するのは、入門に役立つ良いプロジェクトのように思えました。その文法は非常にシンプルに見え、いくつかのテストプログラムを試した後、私は自分の知識を深め、Go 標準ライブラリに慣れるために取り組み始めました。
私はノートパソコンで Go プログラムを書くのに 1 週間を費やしました。私は頻繁に x86 サーバーでプログラムをテストし、エラーを修正し、プログラムを堅牢にし、すべてが順調に進みました。
完全に Go プログラムに移行するまで、私は自分のシェルスクリプトに依存し続けました。その後、バイナリファイルを中央ファイルサーバーにプッシュし、新しいサーバーを設定するたびに、私がやるべきことはバイナリファイルを取得し、実行可能フラグをオンにしてからバイナリファイルを実行することだけでした。私は初期の結果に非常に満足していました:
$ wget http://file.example.com/<myuser>/bins/prepnode
$ chmod +x ./prepnode
$ ./prepnode
そして、問題が発生しました
第 2 週目、私はリソースプールから新しいサーバーを割り当て、いつものようにバイナリファイルをダウンロードし、実行可能フラグを設定してからバイナリファイルを実行しました。しかし、今回はエラーが発生しました。奇妙なエラーです:
$ ./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)やシェルスクリプトを研究していました。ほとんどのアーキテクチャの Linux サーバーでは Bash シェルと Python インタープリターが使用できます。要するに、以前はすべてが順調でした。
しかし、今私は Go というコンパイル言語を扱っており、実行可能なバイナリファイルを生成します。コンパイルされたバイナリファイルは特定のアーキテクチャの命令コードまたはアセンブリ命令で構成されており、これが私がフォーマットエラーを受け取った理由です。Arm64 CPU(バイナリファイルが実行される場所)は x86-64 命令を解釈できないため、エラーが発生します。以前は、シェルと Python インタープリターが私のために基盤の命令コードまたは特定のアーキテクチャの命令を処理してくれました。
Go のクロスコンパイル#
私は Golang のドキュメントを確認し、Arm64 バイナリファイルを生成するには、go build コマンドを実行して Go プログラムをコンパイルする前に 2 つの環境変数を設定するだけでよいことを発見しました。
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 既存のインストールをクリーンアップする
-n テスト実行を開始しない(デフォルトはtrue)
-s ステージ環境を使用する、デフォルトはqa
-v 詳細出力を有効にする
他のアーキテクチャは?#
x86 と Arm は、私がテストしているソフトウェアがサポートする 5 つのアーキテクチャのうちの 2 つであり、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 ノートパソコンから小さなシェルスクリプトを書くのと同じくらい簡単です:
#!/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>
なぜ?#
あなたは、なぜ私がシェルスクリプトを使い続けなかったり、プログラムを Python に移植してコンパイル言語を避けなかったのか疑問に思うかもしれません。だから、トレードオフがあるのです。その結果、私は Go のクロスコンパイル機能やプログラムが CPU 上で実行される際の基盤の動作を理解することができました。コンピュータでは、常にトレードオフを考慮する必要がありますが、それが学習を妨げることは決してありません。
参考#
Cross-compiling made easy with Golang [1]
Golang を使用したクロスコンパイル [2]