接続出来なくなったEC2をTerraformで作成したレスキューインスタンスを使って復旧する
お疲れさまです。とーちです。
先日、EC2インスタンスにSSMセッションマネージャーでログインできないという事象が発生しました。経験則から「おそらくディスクフルになっているのでは?」と予測はついていたのですが、こういう時にどのように復旧するのが一番簡単かを調べていました。
AWSが提供するトラブルシューティングツール
調べてみると、AWSのSystems Manager Automationに「TroubleshootSSH」というランブックがあることがわかりました。これを使うと自動的に復旧してくれるようなのですが、今回の環境が特殊ということもあり使用できませんでした。
AWSが提供するTroubleshootSSHについては、以下の資料が参考になります
- AWSSupport-TroubleshootSSH - AWS Systems Manager Automation runbook reference
- AWSSupport-TroubleshootSSH - AWS Systems Manager オートメーションランブックリファレンス
- AWSSupport-TroubleshootSSH オートメーションワークフローを使用して EC2 SSH 接続エラーを解決する | AWS re:Post
レスキューインスタンスによる復旧アプローチ
そこで今回は、昔ながらの「レスキューインスタンス」というアプローチを採用しました。これは別のEC2インスタンスを立て、そこに障害発生したEBSボリュームをアタッチして問題を解決する方法です。
レスキューインスタンスを手で立てても良かったのですが、せっかくなのでTerraformでコード化してみました。
復旧手順の流れ
今回のTerraformコードを使った復旧手順は以下の通りです
- 障害が発生したEC2インスタンスを停止する
- EBSボリュームをデタッチする(その前に必要な情報を収集)
- EBSボリュームのスナップショットを取得する(万が一のため)
- 問題のEBSボリュームIDを取得し、Terraformの設定ファイルに指定する
- Terraformを実行してレスキューインスタンスを作成する
- レスキューインスタンスで調査・復旧作業を行う
- EBSボリュームを元のインスタンスに再アタッチする
- EC2インスタンスを起動し、動作確認する
- バックアップを取得する
Terraformコードの解説
Terraformコードはシンプルな構成にしています。使い回しができるように、変数部分はterraform.tfvars
から取得するようにしました。
main.tf
このコードは、シンプルにEC2インスタンスを立て、aws_volume_attachment
リソースで障害が発生したインスタンスのEBSボリュームをアタッチするようにしています。
terraform destroy
を実行したときに復旧対象のEBSボリュームが消えないか心配な方もいるかもしれませんが、今回はaws_volume_attachment
でアタッチしているだけなので、復旧対象のEBSボリュームは消えませんのでご安心ください。
なお、OSはUbuntu 24.04を使用しています。これはAWS SSMパラメータストアから最新のAMI IDを取得しています。
variables.tf
variables.tf
では実行環境によって変動がある値を変数化しています。特にsubnet_id
は障害発生インスタンスと同じAZかつ、NAT接続があるサブネットにしないといけない点に注意が必要です。
特にAZが重要で、EBSボリュームは作成されたアベイラビリティゾーン(AZ)に紐づけられているため、AZが異なるところにレスキューインスタンスが作成されると、対象のEBSがアタッチできません。
また今回のコードではパブリックIPをつけない形でEC2を作成しているのでNAT等インターネットへのアウトバウンド接続があるサブネットに置かないと、ユーザーデータ実行のパッケージインストール等の部分で失敗すると思います。そのためNAT接続があるサブネットを指定するようにしてください。このあたりはお好みでパブリックIPをつけるようにコードを変更するのも可です。
user_data.sh.tftpl
上記のTerraformコードを使い、一つのEBSボリュームをマウントするという状況に限れば、ある程度マウントすべきデバイス名も絞れるかと思ったので、マウントするところまでユーザーデータで自動化しています。
このスクリプトでは、ルートボリューム以外のディスクを特定し、そのパーティションを検出してマウントするという処理を行います
find /mnt/rescue -type f -size +100M -exec du -h {} \; | sort -hr | head -20 > /tmp/large_files.txt
の箇所では、容量の大きなファイル(100MB以上)を検索して/tmp/large_files.txt
にリストアップしてます。手でやってもいいんですが、せっかくなので入れておきました。
実際の復旧手順
それではさっそくTerraformでEC2の復旧を行ってみましょう。
1. 障害発生インスタンスの停止とEBSボリュームのデタッチ
まず障害が発生しているEC2インスタンスを停止します。続いて障害発生インスタンスにアタッチされているEBSをデタッチします。
デタッチする前にマネージメントコンソールのEC2の画面から、以下のような情報は画面キャプチャなどで取っておくといいでしょう。(特にルートデバイス名のところとか)
上記画面のボリュームIDをクリックしてEBSの画面に移動し、ボリュームのデタッチを行います。ボリュームIDをコピーしておいてください。この後使います。
2. EBSボリュームのスナップショット取得
念の為、EBSボリュームのスナップショットも取っておきましょう。転ばぬ先の杖です。
3. Terraformの設定と実行
terraform.tfvars
ファイルを用意します。値は環境に応じて変更してください。
vpc_id = "vpc-12345678"
subnet_id = "subnet-12345678" # 問題のインスタンスと同じAZかつ、NATがあるプライベートサブネット
key_pair_name = "your-key-pair"
problem_volume_id = "vol-12345678" # 問題のあるボリュームID
あとは普通にTerraformを実行するだけです。
# 初期化
terraform init
# 計画の確認
terraform plan -var-file=terraform.tfvars
# リソースの作成
terraform apply -var-file=terraform.tfvars
4. レスキューインスタンスでの調査・復旧
Terraformを実行すると対象のボリュームがアタッチされ、かつマウントされている状態でEC2インスタンスが立ち上がってきます。SSMセッションマネージャーでログインしましょう。
ログインしたらrootユーザーになります
sudo su -
/tmp/large_files.txt
に容量の大きなファイルが検出されているので、こちらを確認します
cat /tmp/large_files.txt
対象の障害ボリュームは /mnt/rescue
にマウントされていますので、このリストを参考に、不要なファイルを削除したり、ログローテーションの設定を修正したりして、ディスクスペースを確保します。
5. ボリュームの再アタッチと動作確認
問題を解決したら、ボリュームを再度元のインスタンスにアタッチし直します。この際、事前に画面キャプチャしておいた情報と同じデバイス名を指定することが重要です。
ボリュームを再アタッチしたら、元のEC2インスタンスを起動し、正常に動作するか確認します。SSMセッションマネージャーでログインできれば成功です!
EC2Rescueについての補足
ちなみに今回立てたレスキューインスタンス上で、EC2Rescueのスクリプトも試してみたのですが、今回レスキューインスタンスとして使用しているUbuntu 24.04では、動作確認されていないようで、Missing Python module 'requests'
というエラーメッセージが出て動きませんでした。
venvで仮想環境を作ってpip install requests
したり、apt-get install -y python3-requests
で入れてみたりしましたが、同様のエラーで動かなかったので、スクリプト自体に手を加える必要がありそうで諦めました。ドキュメントを見る限りAmazon Linux 2023もサポートされていないようなので、もうメンテナンスされていないのかもしれませんね。
まとめ
今回は、EC2インスタンスのディスクフル障害をTerraformで作成したレスキューインスタンスで復旧する方法を紹介しました。汎用的に使い回せるように作ったはずなので、EC2に接続出来ないということがあれば試してみて頂ければと思います。
以上、とーちでした。