LLVMのビルドなどCPUパワーとメモリを大量とするソフトウェアのビルドについて、自分のプライベートPCでは到底歯が立たないので何とかしたい。EC2インスタンスを使って問題を解決したい。
Threadripperなどを買って並列ビルドすればよいのだろうが、そもそもの初期コストの面でもんだ出し、それだったらEC2を使って必要な場合のみ強力なマシンを立ち上げる方法を構築したい。
ただし、LLVMをビルドするためにいきなり強力なインスタンスを立ち上げてしまうと、それだけでお金がもったいない。そこで以下のような方針を取る。
- 普段は小さなインスタンス(=ホストインスタンス)を使って作業する(t2.microのような)。コードの編集などもホストインスタンス上で作業する(フロントエンド)
- ビルド時に強力なインスタンス(=ビルドインスタンス)(m5a.4xlarge)を立ち上げてビルドを実行する。ビルド終了するとインスタンスはすぐに殺す。
- ビルド実行結果を回収する。テストプログラムの実行などは小さなインスタンスを使って作業する。
ここで問題になるのは、小さなインスタンスと強力なインスタンスでどのようにデータを共有するかという問題がある。 通常、この手のデータのやり取りにはS3を使うものと思われるが、S3を経由してデータを転送するのは時間がかかるしコストも必要だ。なるべく高速にビルドを実行してすぐに結果を回収したいので、もう少し方式を考え直したい。
そこで思いついたのが、小さなインスタンスと強力なインスタンスでディスクを共有する方法だ。 ディスクの共有を行うことができれば、LLVMの作業ファイルをすぐにビルドインスタンスに渡すことができる。この方法で行こう。
しかしこの方法にも問題が発生した。EBSのディスクは複数のインスタンス間で共有ができない。つまり、直接ディスク経由でデータを渡すことができない。
そこでこれをさらに解決する方法を模索した。 つまり、作業中はホストインスタンスに作業用のディスクをアタッチしておき、ビルドを開始するとディスクをデタッチ、そしてディスクをビルドインスタンスにアタッチする。 これで上手く行くはずだ。
ディスクのアタッチ・デタッチ
さて、この方式の実現のためにホストインスタンス上ではboto3を使ってすべてのインスタンスの操作をPythonで実装している。 boto3の資料を読んでいると、インスタンスに対するアタッチ・デタッチは以下のようにして実現可能だ。
- ディスクのアタッチ
device_id = '/dev/xvdb'
response = instance.attach_volume(
Device = device_id,
InstanceId = instance_id,
VolumeId = volume_id,
)
- ディスクのデタッチ
response = instance.detach_volume( Device = device_id, InstanceId = instance_id, VolumeId = volume_id, )
処理の終了条件をどのように判定するか
これらのコマンドを発行しても、すぐにディスクがくっついたり切り離されるわけではない。処理が完了するまで待ち合わせる必要がある。 これをどのようにして実現するか。
- アタッチの完了待ち合わせ
while True: info = ec2_client.describe_volumes(VolumeIds=[volume_id]) time.sleep(1.0) print("Attaching ...") print("AttachVolume : {}".format(info['Volumes'][0]['Attachments'][0]['State'])) if info['Volumes'][0]['Attachments'][0]['State'] == 'attached': break
- デタッチの完了待ち合わせ
while True: time.sleep(1.0) info = ec2_client.describe_volumes(VolumeIds=[volume_id]) print("Detaching ...") if info['Volumes'][0]['Attachments'] == []: break