🔨

GitHub Actions を使って Rust のシングルバイナリを作る

2022/01/01に公開

はじめに

シングルバイナリをビルドできれば、 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 の設定ファイルはこのようになります:

rust-musl-builder-gh-actions-public/.github/workflows/build.yml
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 プロジェクトをビルドする

このセクションで作成するプロジェクトのリポジトリはこちらです:

準備

Rust プロジェクトが Private Repository の crate に依存している場合、 GitHub Actions 内で Private Repository にアクセスする必要があるので工夫が必要です。

まずは適当な Private Repository に crate を作りましょう。

$ cargo new --lib rust-musl-builder-gh-actions-private-lib
rust-musl-builder-gh-actions-private-lib/src/lib.rs
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 に書き...

rust-musl-builder-gh-actions-private/Cargo.toml
[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" }

適当に利用しておきます。

rust-musl-builder-gh-actions-private/src/main.rs
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 の設定ファイルはこのようになります:

rust-musl-builder-gh-actions-private/.github/workflows/build.yml
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-libGitHub 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/

脚注
  1. https://github.com/emk/rust-musl-builder/blob/main/Dockerfile#L196 ↩︎ ↩︎

Discussion