GitHub Actions を使って Rust のシングルバイナリを作る
はじめに
シングルバイナリをビルドできれば、 Linux 環境ならどこでも実行できて便利です。
シングルバイナリをビルドするときには、共有ライブラリを動的リンクせず、静的リンクさせることが重要です。
ekidd/rust-musl-builder を利用すれば、このあたりをいい感じにラップしてくれるのでシングルバイナリをすっとビルドすることができます。
この記事では、 ekidd/rust-musl-builder を GitHub Actions を使って実行する方法について示します。
Private Repository に依存しない Rust プロジェクトをビルドする
このセクションで作成するプロジェクトのリポジトリはこちらです: https://github.com/Kumassy/rust-musl-builder-gh-actions-public
準備
$ cargo new --bin rust-musl-builder-gh-actions-public
コマンドを実行して適当なプロジェクトを作ります。普通にビルドすると、このようにたくさんの共有ライブラリに依存します
[ec2-user@ip-10-0-0-8 rust-musl-builder-gh-actions-public]$ cargo build --release
Finished release [optimized] target(s) in 0.68s
[ec2-user@ip-10-0-0-8 rust-musl-builder-gh-actions-public]$ ldd target/release/rust-musl-builder-gh-actions-public
linux-vdso.so.1 (0x00007ffd991be000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f855e0c1000)
librt.so.1 => /lib64/librt.so.1 (0x00007f855deb9000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f855dc9b000)
libm.so.6 => /lib64/libm.so.6 (0x00007f855d95b000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f855d757000)
libc.so.6 => /lib64/libc.so.6 (0x00007f855d3ac000)
/lib64/ld-linux-x86-64.so.2 (0x00007f855e522000)
GitHub Actions の設定
GitHub Actions で ekidd/rust-musl-builder を実行して、共有ライブラリを静的リンクさせましょう。 GitHub Actions の設定ファイルはこのようになります:
name: build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build binary
run: docker run --rm -u root -v `pwd`:/home/rust/src ekidd/rust-musl-builder cargo build --release
- name: save artifacts
uses: actions/upload-artifact@v2
with:
name: rust-musl-builder-gh-actions-public
path: ./target/x86_64-unknown-linux-musl/release/rust-musl-builder-gh-actions-public
設定のポイント -- 実行ユーザー
ekidd/rust-musl-builder コンテナの実行ユーザーは rust
です[1]。
このままだと
9c52ccc112e5: Pull complete
30f889750a5f: Pull complete
89cccf25f099: Pull complete
18791a7d13f1: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:efc2d99a30573bac4fa71f050f400e8727bc419d79359674b99be451ee3c4064
Status: Downloaded newer image for ekidd/rust-musl-builder:latest
error: Permission denied (os error 13) at path "/home/rust/src/targetT7NqMz"
Error: Process completed with exit code 101.
のように怒られたので、 -u root
オプションをつけて実行ユーザーを root
にしてこの問題を回避しています。
GitHub Actions の実行結果
Actions が Pass するとこのように成果物を Artifacts としてダウンロードできるようになります
ちゃんと静的リンクできていますね!
[ec2-user@ip-10-0-0-8 work_gh_actions]$ ./rust-musl-builder-gh-actions-public
Hello, world!
[ec2-user@ip-10-0-0-8 work_gh_actions]$ ldd rust-musl-builder-gh-actions-public
not a dynamic executable
Private Repository に依存する Rust プロジェクトをビルドする
このセクションで作成するプロジェクトのリポジトリはこちらです:
- https://github.com/Kumassy/rust-musl-builder-gh-actions-private
- Kumassy/rust-musl-builder-gh-actions-private-lib (Private Repository)
準備
Rust プロジェクトが Private Repository の crate に依存している場合、 GitHub Actions 内で Private Repository にアクセスする必要があるので工夫が必要です。
まずは適当な Private Repository に crate を作りましょう。
$ cargo new --lib rust-musl-builder-gh-actions-private-lib
pub fn add2(v: u32) -> u32 {
v + 2
}
#[cfg(test)]
mod tests {
use crate::add2;
#[test]
fn it_works() {
let result = add2(2);
assert_eq!(result, 4);
}
}
これを Kumassy/rust-musl-builder-gh-actions-private-lib に push しておきました。
次にこの crate に依存した Rust プロジェクトをつくります
$ cargo new --bin rust-musl-builder-gh-actions-private
先ほどの crate を dependencies に書き...
[package]
name = "rust-musl-builder-gh-actions-private"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rust-musl-builder-gh-actions-private-lib = { git = "ssh://git@github.com/Kumassy/rust-musl-builder-gh-actions-private-lib", branch = "main" }
適当に利用しておきます。
use rust_musl_builder_gh_actions_private_lib::add2;
fn main() {
let four = add2(2);
println!("Hello, world!: {}", four);
}
こちらも普通にビルドすると、このようにたくさんの共有ライブラリに依存します
[ec2-user@ip-10-0-0-8 rust-musl-builder-gh-actions-private]$ cargo build --release
Updating git repository `ssh://git@github.com/Kumassy/rust-musl-builder-gh-actions-private-lib`
Compiling rust-musl-builder-gh-actions-private-lib v0.1.0 (ssh://git@github.com/Kumassy/rust-musl-builder-gh-actions-private-lib?branch=main#c90a327d)
Compiling rust-musl-builder-gh-actions-private v0.1.0 (/home/ec2-user/rust-musl-builder-gh-actions-private)
Building [=============> ] 1/2: rust-musl-builder-gh-actions-private(bin) Building [=============> ] 1/2: rust-musl-builder-gh-actions-private(bin)
Finished release [optimized] target(s) in 3.60s
[ec2-user@ip-10-0-0-8 rust-musl-builder-gh-actions-private]$ ldd target/release/rust-musl-builder-gh-actions-private
linux-vdso.so.1 (0x00007ffd40b10000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f4f0a9e6000)
librt.so.1 => /lib64/librt.so.1 (0x00007f4f0a7de000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f4f0a5c0000)
libm.so.6 => /lib64/libm.so.6 (0x00007f4f0a280000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f4f0a07c000)
libc.so.6 => /lib64/libc.so.6 (0x00007f4f09cd1000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4f0ae47000)
Kumassy/rust-musl-builder-gh-actions-public の GitHub Actions をそのまま実行すると、依存 crate rust-musl-builder-gh-actions-private-lib
にアクセスできないよ!と怒られるので、このあたりの設定をしていきましょう
30f889750a5f: Pull complete
89cccf25f099: Pull complete
18791a7d13f1: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:efc2d99a30573bac4fa71f050f400e8727bc419d79359674b99be451ee3c4064
Status: Downloaded newer image for ekidd/rust-musl-builder:latest
Updating git repository `ssh://git@github.com/Kumassy/rust-musl-builder-gh-actions-private-lib`
error: failed to get `rust-musl-builder-gh-actions-private-lib` as a dependency of package `rust-musl-builder-gh-actions-private v0.1.0 (/home/rust/src)`
Caused by:
failed to load source for dependency `rust-musl-builder-gh-actions-private-lib`
Caused by:
Unable to update ssh://git@github.com/Kumassy/rust-musl-builder-gh-actions-private-lib?branch=main#c90a327d
Caused by:
failed to clone into: /root/.cargo/git/db/rust-musl-builder-gh-actions-private-lib-c31c66033672e334
Caused by:
failed to authenticate when downloading repository
* attempted ssh-agent authentication, but no usernames succeeded: `git`
if the git CLI succeeds then `net.git-fetch-with-cli` may help here
https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
Caused by:
error authenticating: no auth sock variable; class=Ssh (23)
Error: Process completed with exit code 101.
Deploy Key の登録
SSH Key を作成します。 PassPhrase は空にしておきます
kumassy@KumachinePro .ssh % ssh-keygen -t ed25519 -C "Deploy Key for rust-musl-builder"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/kumassy/.ssh/id_ed25519): deploy_key_rust_musl_builder
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in deploy_key_rust_musl_builder
Your public key has been saved in deploy_key_rust_musl_builder.pub
The key fingerprint is:
SHA256:tlWDYHXQTK98VE8ktZxhiKsbAE9w/eXV1DRP8/9dNU0 Deploy Key for rust-musl-builder
The key's randomart image is:
+--[ED25519 256]--+
| ...+.o=+ oXE|
| ..o o +o+oB&|
| + o * +=*|
| o = = +|
| S o o . o|
| . = . +|
| . o o|
| . |
| |
+----[SHA256]-----+
公開鍵 deploy_key_rust_musl_builder.pub
を、 Private な依存 crate rust-musl-builder-gh-actions-private-lib
に Deploy Key として登録します。
Repository の Settings -> Deploy Keys から Add deploy key をクリックします
公開鍵 deploy_key_rust_musl_builder.pub
の中身を貼り付けて Add Key します。Title
はわかりやすい名前であればなんでも OK です。
公開鍵が Deploy Key として登録できました
次に、 Kumassy/rust-musl-builder-gh-actions-private-lib
の利用者側の Repository, Kumassy/rust-musl-builder-gh-actions-private
に秘密鍵を登録します。
Repository の Settings -> Secrets から New repository secret をクリックします
秘密鍵 deploy_key_rust_musl_builder
の中身を貼り付けて Add secret します。Name
は後で GitHub Actions から参照するため、 DEPLOY_KEY_SECRET
としておきます
秘密鍵が Secret として登録できました
GitHub Actions の設定
GitHub Actions で先ほど登録した秘密鍵を参照するように変更しましょう。GitHub Actions の設定ファイルはこのようになります:
name: build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.5.3
with:
ssh-private-key: ${{ secrets.DEPLOY_KEY_SECRET }}
- name: Build binary
run: docker run --rm -u root -v ${HOME}/.ssh:/root/.ssh -v $SSH_AUTH_SOCK:/ssh-agent -e SSH_AUTH_SOCK=/ssh-agent -v `pwd`:/home/rust/src ekidd/rust-musl-builder cargo build --release
- name: save artifacts
uses: actions/upload-artifact@v2
with:
name: rust-musl-builder-gh-actions-private
path: ./target/x86_64-unknown-linux-musl/release/rust-musl-builder-gh-actions-private
設定のポイント -- SSH 鍵の読み込み
SSH 鍵を ssh-agent にロードするために、サードパーティ製の Actions webfactory/ssh-agent を利用しています。この Actions は
- SSH Agent の起動
- SSH 鍵の登録
-
known_hosts
の設定
を行ってくれます。この Actions を使用して secrets.DEPLOY_KEY_SECRET
を読み込むことで、 Private Repository Kumassy/rust-musl-builder-gh-actions-private-lib
を GitHub Runner から 参照できる状態になります
設定のポイント -- SSH Agent の Forwarding
webfactory/ssh-agent を利用することで GitHub Runner から は Private Repository を参照できるようになりましたが、 Docker Container から Private Repository を参照できるようにするにはさらに工夫が必要です。
-v $SSH_AUTH_SOCK:/ssh-agent -e SSH_AUTH_SOCK=/ssh-agent
オプションを追加することで、 GitHub Runner で実行している SSH Agent を Docker Container に Forwarding します。こうすることで、 GitHub Runner で実行している Docker Container から Private Repository を参照できるようになります。
設定のポイント -- known_hosts の Forwarding
単に SSH Agent を Forwarding するだけでは、 GitHub.com のホスト認証に失敗して
Host key verification failed.
のようなエラーが出ます。
-v ${HOME}/.ssh:/root/.ssh
として known_hosts
を Docker Container に渡すことで解決できます
設定のポイント -- 実行ユーザー
webfactory/ssh-agent は $SSH_AUTH_SOCK
に Socket を作成しますが、この owner は
srw------- 1 runner docker 0 Dec 31 13:37 /tmp/ssh-zNyADzkqWM1d/agent.1597
です。一方で ekidd/rust-musl-builder コンテナの実行ユーザーは rust
です[1:1]。コンテナの実行ユーザーで SSH Agent の Socket にアクセスできず、
18791a7d13f1: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:efc2d99a30573bac4fa71f050f400e8727bc419d79359674b99be451ee3c4064
Status: Downloaded newer image for ekidd/rust-musl-builder:latest
Error connecting to agent: Permission denied
Error: Process completed with exit code 2.
のようなエラーが出ます。ここでは、-u root
オプションをつけて実行ユーザーを root
にしてこの問題を回避しています。
GitHub Actions の実行結果
Actions が Pass するとこのように成果物を Artifacts としてダウンロードできるようになります
ちゃんと静的リンクできていますね!
[ec2-user@ip-10-0-0-8 work_gh_actions]$ ./rust-musl-builder-gh-actions-private
Hello, world!: 4
[ec2-user@ip-10-0-0-8 work_gh_actions]$ ldd ./rust-musl-builder-gh-actions-private
not a dynamic executable
参考文献
RustでLinux用シングルバイナリを作るまで
https://blog.mamansoft.net/2018/08/20/rust-linux-single-binary/
Discussion