Rustコンパイラをクラウドでビルドする備忘録(改良版)

概要

前回の改良版。 Rustコンパイラをいじっているとビルドの遅さのせいで作業に支障をきたすため、クラウドでビルドするようにしてみる。

EC2を立ち上げる

Ubuntu 16.04 (HVM) の c5.4xlarge を使うことにする(結局、手元とあわせたくなったので18.04に上げた)。使うインスタンスの強さは財布と相談して決めることにする。ディスクは結構必要なので上げておく。

何度も再起動して使いたいのでEIPを割り当てておく。(デフォルトのグローバルIPは再起動のたびに変わるっぽい)

高くて強いインスタンスを使っていくので、使わないときは落とし忘れないように気をつける(停止時はEBSやEIPだけ課金される)。このあたりも必要なら自動でシャットダウンする仕組みを考えるといいかもしれない。

IAMユーザーを作る

当該インスタンスを立ち上げられるだけの専用のユーザーを作る。以下のようなインラインポリシーを作る。 (i- ではじまる部分に当該インスタンスインスタンスIDを入れる)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ec2:StartInstances",
            "Resource": "arn:aws:ec2:*:*:instance/i-0123456789abcdef0"
        }
    ]
}

このユーザーのコンソールへのログインを不許可にしてクレデンシャルを発行する。手元のマシンで以下を実行してクレデンシャルを入れる。 (~/.aws/credentials を直接編集してもOK)

$ aws configure --profile rust-builder

セットアップ

手元から rust-builder という名前でSSHできるようにする。依存関係として以下を入れておく。

$ sudo apt update && sudo apt upgrade && sudo apt install build-essential python-minimal cmake curl git

自動シャットダウン

以下のファイルを /home/qnighy/shutdown-timer.sh として置き、実行権限をつける。 (ユーザー名は適宜置き換えて) (ログイン状態のチェックのために w の出力を雑にgrepしているので、そこの置き換えを忘れないように)

#!/usr/bin/env bash
set -ue

count=0
sleep=600
max_count=6

while true; do
  sleep "$sleep"
  date >&2
  if w | grep -q qnighy; then
    echo "logged in; reset" >&2
    count=0
  else
    count="$((count + 1))"
    echo "not logged in; count=$count" >&2
    if [[ $count -ge $max_count ]]; then
      echo "shutting down..." >&2
      shutdown -h now
    fi
  fi
done

また以下のsystemd設定ファイルを /etc/systemd/system/shutdown-timer.service に置く。

[Unit]
Description = shutdown timer

[Service]
ExecStart = /home/qnighy/shutdown-timer.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

以下で有効化

sudo systemctl enable shutdown-timer
sudo systemctl start shutdown-timer

これにより、10分に1回の頻度でログイン状態をチェックし、60分間非ログインだったらシャットダウンするようになる。

スクリプトの配置

Rustコンパイラを手元にcloneして、 x.py があるのと同じ位置に以下の y.sh を置いてexec permissionをつける。

#!/usr/bin/env bash
set -ue

# Remote side:
# sudo apt update && sudo apt upgrade && sudo apt install build-essential python-minimal cmake curl git gdb

ec2_profile=rust-builder
ec2_region=ap-northeast-1
ec2_instance=i-0123456789abcdef0

builder_host=rust-builder
local_host="$(hostname)"
dirpath="$(realpath --relative-to "$HOME" "$(pwd)")"

while ! aws --profile "$ec2_profile" --region "$ec2_region" ec2 start-instances --instance-ids "$ec2_instance" | grep -q running; do
  echo "waiting for EC2 wakeup..." >&2
  sleep 1
done

git submodule update --init --recursive
rsync -avz --delete --exclude '.git' --exclude '/build' "$HOME/$dirpath/" "$builder_host:$local_host/$dirpath"
ssh -t "$builder_host" "cd $local_host/$dirpath; $@"

やっていることは以下。

  • まずEC2インスタンスを立ち上げる、replyに "start" が含まれるようになるまで待つ。
  • rsyncで送って向こうでビルドを叩く。
  • gitを全部転送したくないので .git は省く。ただしsubmoduleが必要なのでsubmodule updateを自動で叩く。 (x.pyは.gitがあるときはsubmodule updateを自動でやってくれる)
  • build も当然省く。手元のbuildと衝突しうるし、 --delete に巻き込まれないようにする意図もある。ただしbuildというディレクトリがソースツリーの中にあるので、 /build と指定する必要がある。
  • tmuxを立ち上げたりはしない。ビルドが短くなることが期待されているので途中で切れてもよい。むしろ自動アタッチとか考えるのが面倒。

ビルド

./remote.sh ./x.py test --stage 1 のように通常のビルドコマンドに ./remote.sh をつけるとリモートでのビルドになる。

設定

クラウドとは関係ないが、コンパイラを開発するときは config.toml を絶対にいじったほうがよい。

  • compiler-docs = true。ほとんどの関数はdoc-comment (///) を持たないが、シグネチャが一覧できるだけでもありがたい。
  • debug-assertions = true。 これがないとログがほとんど出ない。また、これを有効にすると一部の追加のチェックが行われる。
  • debuginfo = true, debuginfo-lines = true。 これがないとICE(コンパイラ内部エラー)の時のバックトレースがちょっと貧弱で困る。
  • incremental = true。 効果は計測してないけど効率がちょっと上がりそう