Spotインスタンスをもっと活用してみよう

こんにちは。NetAppの大野です。NetAppアドベントカレンダー4日目の記事になります。

アドベントカレンダーのお題には、「データ管理について」とありますが、
ちょっと方向を変えて AWSの Spotインスタンスの活用について書きたいと思います。

個人で リザーブドインスタンス／セービングプランとかは金額的にも、ちょっと無理ですが[^RI]、
Spotインスタンスは、再起動がたまに入るのが気にならないサーバ用途で 個人的に使っていたりします。

ここで「でも、SpotインスタンスだとInterrupt入ったときにサーバ上のデータ消えちゃうでしょ？」と思った方には、是非読んでもらえればと思います。

[^RI]: こういう猛者もいるのでしょうか……？

## はじめに

記事の内容は以下となっており、2022/12/01時点の情報で記載しています。

- AWS EC2 Spotインタンスについて
- EC2の OnDemandインスタンスの代わりに、Spotインスタンスを使う事の検討
- 実際に[Stateful Nodes]を使ってみる

ちょうど re:Invent等もあり、AWSが提供するサービス変更の影響を受ける記事のため、
出来るだけ正確にと気を付けてはいるのですが、最新の情報についてはドキュメント等も適宜ご参照いただければと思います。

## AWS EC2 Spotインタンスについて

### Spot インスタンスって何だろう

2022/12/02の時点で[AWS EC2 スポットインスタンス](https://aws.amazon.com/jp/ec2/spot/)のページには以下の様に書かれています。

1. Amazon EC2 スポットインスタンスを使うと、AWS クラウド内の使用されていない EC2 キャパシティーを活用できます。
2. スポットインスタンスは、オンデマンド料金に比べ最大 90% の割引料金でご利用いただけます。
3. スポットインスタンスは、ステートレス、耐障害性、または柔軟性を備えたさまざまなアプリケーションでご利用いただけます。

1,2 から 「AWSの中で余ってるリソースを、割引価格で使える」 となっていて、気軽に個人でも使えますし結構魅力的に思います。
ただ、3を見ると、その上で走らせるのは何でも良いというわけではなく、何やら条件が並んでいます。

- **ステートレス**－ステートを持たないアプリケーション（Stateless）
- **耐障害性を備えた**－突然落ちても大丈夫なアプリケーション（Fault-Tolerant）
- **柔軟性を備えた**－？

柔軟性が具体的に何をさすかはさておき、これには以下の様なワークロードが含まれるとあります。

- ビッグデータ
- コンテナ化されたワークロード
- CI/CD
- ウェブサービス
- ハイパフォーマンスコンピューティング (HPC)
- テストおよび開発

まとめると、「**インスタンスが中断されても、再実行・再開可能なワークロード**」が Spot インスタンスの対象となる、かと思います。

### なぜ中断が前提に？

Spot インスタンスは先に示した「AWS クラウド内の**使用されていない EC2 キャパシティー**を活用」とある通りで、AWSクラウド内の余ったリソースで提供されています。

このため、AWSクラウド側でリソースが不足してきた時等[^Interruption]は、AWSからの要求により、Spotインスタンスを止めてリソースを返却する必要があります。

この返却はいつ起こるかわかりません[^推奨シグナル]。このため、「中断されても やり直しが出来る」事がワークロードに求められる事になります。

[^Interruption]: 中断の理由 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/interruption-reasons.html

[^推奨シグナル]: いつおこるかは分かりませんが、中断リスクが高まった場合の[推奨シグナル](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/rebalance-recommendations.html)と、中断2分前の通知があります。このため、いきなりバッツンと落ちてしまうのではなく、ある程度対処する事が可能です。ただし、推奨シグナルについては、いつ来るのについて、決まった答えが無いようです。

### Spotの中断=Interruptionと、Spot インスタンスの返し方

突然「返して」と言われたときの[返し方](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/interruption-behavior.html)は、三つから選べます。

1. 終了（Terminate/再開不可-デフォルト）
1. 停止（Stop/再開可能）
1. 休止（Hibernate/再開可能）

"終了"ではなく、"停止" か "休止" を選べば 一時的に止まりはするものの、再開可能な形となります[^PersistentRequest]。

ただ「いつ止まるかわからない」に加えて、Spotインスタンスを再び起動できるキャパシティや価格が、いつ回復するか分かりません。業務等になるべく影響を与えない様にするには何等かの工夫が必要になりそうです。

[^PersistentRequest]: これは 永続的リクエストの条件でもあります。

## EC2の OnDemandインスタンスの代わりに、Spotインスタンスを使う事の検討

価格的なメリットが大きいSpotインスタンスですが、ここまでのお話では、単体では少し使いにくい様に思います。

このため AWS EC2 では EC2 Fleet/ EC2 Spot Fleetを使って、インスタンスを 複数のプールから取り出してグループとしてまとめて利用する事が出来るようになっています。

ただ、Fleetでは 一般的に ステートレスであったり、フォールトトレラントなアプリケーションが対象になるかと思います。これは データの入った EBSボリューム等を含めた、永続的なリソースの管理がやりにくい（出来ればやりたくない）事等が理由としてあげられるかと思います[^DataPersistent]。

この点を改善するために、[Spot by NetApp] には [Stateful Nodes] という機能が提供されています。  
これは Spotインスタンスを使っているのですが、以下の様な管理機能を実現しています。

- 通常のオンデマンドインスタンスを Spotインスタンスに変換する
- Spotインスタンスが中断された時の自動的な再リクエストによる再起動
- 希望する Spotインスタンスが利用できない場合に OnDemandインスタンスへの切り替え
- OnDemandインスタンスからSpotインスタンスへの切り戻し
- Private IP/Public IP(Elastic IP)、EBS ルート／データボリュームの永続化

[^DataPersistent]: このために外部のRDBやRedisといった物に、ステートを全て逃がしてやる事も重要です。

### [Stateful Nodes]が永続化するリソース

ステートフルアプリケーションを Spot インスタンスで実行した場合、中断の前後で変わって欲しくない、永続的に利用したいリソースは以下かと思います。

- ルートボリューム
- データボリューム(EBS)
- Private/Public IP(EIP)アドレス

普通に考えるとブートボリュームを EBSにして Spotインスタンスの終了時の状態で残しておけばよさそうです。

ただ、Spot インスタンスの設定で回収時に "終了" とした場合、
ルートボリュームを残しておくことができず、終了タイミングで削除されてしまいます。

休止や停止の場合、インスタンスタイプ等を変えなければ継続利用できない場合に、支障が出てしまいそうです。

[Stateful Nodes] では、これらの永続化したいリソースを自動的に管理して、Spotインスタンスの中断前後での接続性、データの維持を行ってくれます。

### [Stateful Nodes]によるSpotインスタンス中断後の回復

AWSにより Spotインスタンスを終了されてしまったという事は、AWS側のリソースが足りない、
もしくは望む金額では Spotインスタンスを利用できない事になったのかと思います。

同じ条件で Spotインスタンスを使おうとして、リクエストを出しても多分使えません。
となると、回復させるためには

先ほどの永続化条件＝同じルート／データボリューム／IPアドレスを使いながら

- 条件の違う Spot インスタンスをリクエストする
- OnDemand インスタンスを使う

といった事を実現する必要があります。  

どちらを行ったにせよ、価格が上昇してしまいますので、どこかで

- リソースが空いたら、また安いSpotインスタンスに戻す

も必要な機能になります。

こういった切り戻しまで含めて [Stateful Nodes] では 自動管理する事が可能です。

## 実際に[Stateful Nodes]を使ってみる

こういった特徴がありますので、ここでは、実際に OnDemandインスタンスで動いている
WebサーバをSpot インスタンスに変更してみたいと思います。

### 準備1. Webサーバの準備をする

まずは 移行対象のサーバをAWS上にたてておきます。

<img width="400px" alt="2022-12-04 00_44_12-My First Post __ My Web Site.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/7690cda2-88c2-7261-5742-af15d109a5db.png" />

Public IPはインスタンス間で固定的に付け直す必要があるため、Elastic IPにしておく必要があります。
EBSは必要ならつけておきましょう。

### 準備2. [Spot.io]のアカウントを作る

まずは [Spot.io] に行ってアカウントを作成します。

右上のSign Inボタン（見当たらなければバーガーボタンの中にあります）をポチっとした後

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/bfbaa52d-aa83-e520-d5d2-7a744ff4ff5b.png)

左下、または中央の下にある "Don't have an account yet? **Sign Up**" のリンクをクリック

![2022-12-04 00_51_34-Window.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/29174348-1a79-5084-4365-1a5f80cb90b2.png)

続く アカウント作成画面で フルネーム、emailアドレス、パスワードを入れて Sign Upをすると確認用メールが届きます。
メール内の確認リンクをクリックすればアカウント作成完了です。

### 準備3. [Spot.io]にクラウドプロバイダの登録

アカウントが作成出来たら、次に [Spot.io]とクラウドプロバイダを接続してやる必要があります。
これにより [Spot.io]側から、クラウドのリソースを制御する事ができるようになります。

ここではAWSに Cloud Formationを使って追加する手順を簡単に書いておきます。

※ この前にOrganization登録があったように思うのですが、スクリーンショットの取得を忘れてしまいました! すいません🙇

1. ここではAWSを選択  
   <img width="400px" alt="2022-12-03 22_27_13-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/70e07a86-a595-9622-9750-4959f2f1a083.png" />
2. Linked Accountを選択  
   <img width="400px" alt="2022-12-03 22_28_01-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/350a3ed1-10b0-5a84-7c2d-ef864b379cba.png" />
3. CloudFormationを使ってリンクするので Automaticallyを選択  
   <img width="400px" alt="2022-12-03 22_28_45-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/df4902ef-8b47-8d03-b87e-606f43213634.png" />
4. GovCloudか中国のユーザかと聞かれるので、Noで答える
5. Run Templateのボタンを押すとAWS Consoleに飛びます  
   <img width="400px" alt="2022-12-03 22_31_31-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/f90943b2-bf84-fd95-b29f-1316eed34815.png" />
5. Cloud Formationのスタックのクイック作成画面が出てきます。一番下にある、 **機能** の **□ AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。** にチェックを入れてから、［スタックの作成］をクリックします。  
   <img width="400px" alt="2022-12-03 22_33_06-CloudFormation - スタック.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/f9ce956c-95e6-2a7b-4cd4-0684b836ae07.png" />
7. 作成が終わると[Spot.io]側の ダッシュボードに "Your account has been successfully connected!"と表示されたら接続完了です。
   <img width="400px" alt="2022-12-03 22_34_24-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/bfc69f5c-d295-8e4c-6aa3-366d84ce5e0a.png" />

### OnDemandインスタンスを Spotインスタンス＝Stateful Nodeに変換する

準備が出来ましたので実際に変換（Import）してみます。

1. 左のサービス一覧から[Elastigroup]を選択します。  
   <img width="400px" alt="2022-12-03 22_35_24-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/51af6c14-0075-249f-ec3c-b7ddaebb3e3d.png" />
2. 左のメニューから[Stateful Nodes]を選択し、右のペインにある［New Node］のボタンを押します。  
   <img width="400px" alt="2022-12-03 22_35_35-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/43c4ecaa-acd1-68f2-c6c5-9f83d43a95aa.png" />
3. "Create a new stateful node from an existing instance"を選択し、変換するインスタンスの リージョンと 対象のOnDemandeインスタンスのIDを入力します。  
   ここで **Private IP を引き継ごうとした場合、インスタンスが終了(Terminate)されてしまいます** 。  
   念のため、ここではまだ、元からあったインスタンスを終了させたくないので、[Use a new private IP]をチェックして、Private IPの変更を許容しています。  
   設定が終わったら ［Select］ボタンを押してインスタンスの情報をインポートします。
   <img width="400px" alt="2022-12-03 22_37_10-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/dd8859d5-2241-0cf8-346a-dceaaddc3be3.png" />
4. 構成がインポートされるので構成を確認し、［Create］を押します。  
   <img width="400px" alt="2022-12-03 22_37_37-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/787912da-3446-d9f4-45ce-c1b32b7590f0.png" />
5. Stateful Nodeが作成されます。  
   最初は状態が Pausedですが、数分待つと 状態が Importingに変わってインポート処理が走ります。  
   <img width="400px" alt="2022-12-03 22_38_14-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/82472b48-15d2-4012-b734-50daa28db766.png" />
7. ステータスが Importingから Activeになるのを待ちます。
   Activeになっても Health Check Statusが EC2で警告になるはずですが先に進めます。
8. AWS Consoleから 元になった OnDemandインスタンスを停止して、Elastic IPの関連付けを解除します。
9. Elastic IPが自動的に Spot Instance側に関連付けられてサービスが再開となります。  
   特にOnDemandと変わらずアクセスできますので、確認してみてください。
   <img width="400px" alt="2022-12-04 00_44_12-My First Post __ My Web Site.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/7690cda2-88c2-7261-5742-af15d109a5db.png" />

### 実験：Spotインスタンスの中断が発生したらどうなるか試してみる

ここで、中断が発生しても特に問題無くSpotインスタンス間でデータが引き継がれる事を確認してみます。

この引継ぎ処理は EBSのつけかえ等の処理が入るため、それなりに時間がかかります。インスタンスの構成や状況にもよりますが、10分ぐらいはかかると思いますので少しのんびり結果を待っていただければと思います。

1. EBS等のデータがちゃんと引き継がれるのを確認するため、念のためサーバ上のコンテンツに変更を加えます
   <img width="400px" alt="2022-12-03 23_13_30-My Second Post __ My Web Site.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/cf77a4c2-d39c-18f0-9704-3b5a380e86c3.png" />
2. [Spot.io]のコンソールから、[Stateful Nodes]の Recycleを実行します
   <img width="400px" alt="2022-12-03 23_14_26-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/271c422b-e396-c21b-2bb8-a8ef771c6eb2.png" />
3. 確認のため Recycle と入力して ［Recycle］ボタンを押します。
   <img width="400px" alt="2022-12-03 23_14_41-Spot _ Console.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/b45c710b-0c03-bfcd-6116-1bb2c21a7d70.png" />
4. Recycleの終了を待ちます

Spotインスタンスが一度終了し、その後新しいSpotインスタンスが起動してきます。
その後、問題無くサービスが継続されている事が確認してみてください。

なお、Spotのインスタンスが変更のタイミングで、SSHのホストキーが変更になります。このため、EIPめがけてSSH接続をかけると、ホストキーが変更になった事による警告を受ける事になりますので注意してください（ssh-keygen -R 等で一旦削除する必要があります）。

## 終わりに

無事、Spotインスタンスを単体のステートフルアプリケーションでも使える事が確認できたでしょうか？

もちろん単体サーバなので Spotの中断時にダウンタイムがあり、多数のユーザが使う本番系で使うには考慮が必要かと思いますが、ちょっとしたサーバの管理等には利用できるのではないかと思います。

Spotのコンソールで確認すると、OnDemandインスタンスから Spotインスタンスにしたことによるコスト効果が表示されています。気持ち良く割引が効いている事が確認できます（以下は立ち上げたばかりで、金額としては微々たるものですが70%オフ！）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/e5c5c343-1c91-5f3f-7ad8-c3c3760a8656.png)

でも、[Spot.io]を使うのに お金かかるんでしょ？と思われた方がいると思うのですが、実は現在は20インスタンスまで無料だったりしますので、是非体験活用をしてみていただければと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128512/98698e6f-190a-43f8-e7f4-0c8926e22a51.png)

なお、[Spot by NetApp]には 今回紹介した仕組みを利用[^chickenegg]して よりかしこくスケールアウト型のアプリケーションを管理できる [Elastigroup] という製品があり、どちらかと言えばこちらが本命です。

[^chickenegg]: 正確には[Elastigroup]を、Single Node専用にしたのが [Stateful Nodes]なので、[Elastigroup]の仕組みを利用した製品が[Stateful Nodes]、という言い方が正しい。はず。

また、これを Kubernetes Clusterの自動管理に応用した [Ocean]、更に Kubernetesの上で Apache Sparkを動かす[Ocean for Apache Spark]等もラインナップされていて、これらは本番系で使う事を意識したサービスになっていますので、ご興味のある方は是非 [Spot.io]をチェックしてみていただければと思います！

こういった技術が必要とされる背景に、企業が クラウドのコストをどうやってコントロールしていくのか？ クラウドサービスの複雑な課金体系を理解し、削減する努力を、今後どうやって継続していくのか？といった悩みがあるかと思います。ネットアップは、そういったクラウドを使っていく上での悩みに対応するソリューションをSpotブランドで提供しておりますので、困ったときには思い出していただけば幸いに思います。

[Spot.io]: https://spot.io/
[Spot by NetApp]: https://spot.io/
[Stateful Nodes]: https://docs.spot.io/managed-instance/
[Elastigroup]: https://spot.io/products/elastigroup/
[Ocean]: https://spot.io/products/ocean/
[Ocean for Apache Spark]: https://spot.io/products/ocean-apache-spark/