ROSAでスポットインスタンスを使う

こんにちは、Red Hatでソリューションアーキテクトをしている石川です。 今回はROSAで使用することができるスポットインスタンスについて紹介していきたいと思います。

そもそもROSAとは何かについて知りたいという方はこちらの過去記事をご参照下さい。

rheb.hatenablog.com

スポットインスタンスの概要とROSAクラスタへの追加

スポットインスタンスはAWSが提供するEC2インスタンスの提供形態の一つであり、休止中のEC2キャパシティーを使用することで、オンデマンド価格より低料金で利用することが特徴のサービスです。オンデマンド価格と比較し、最大で90%ものコスト削減が可能となるそうです。
一方でスポットインスタンスは稼働中に中断(Terminate)される可能性があり、使用する場合は適切なワークロードを選択する必要があります。 実行が長時間に亘るジョブや、ステートフルなアプリケーションなどに対しては利用を避けるのが良いでしょう。 スポットインスタンス自体の詳細な説明についてはAWSのサイトをご確認下さい。

さて、このスポットインスタンスですがROSAでも使用することができます。 ROSAではOCMコンソールから自身が構築したクラスタに対しての設定やアドオンの追加などができるようになっており、その中の一つにWorker Nodeの追加や削除を行うMachine poolsというタブがあります。

クラスタを構築した段階ではDefaultのMachine poolが存在するだけですが、この画面から新たにMachine poolを追加することができ、その際にスポットインスタンスを選ぶことが可能です。

使用するインスタンスタイプや台数などを設定した後、Use Amazon EC2 Spot Instancesにチェックを入れると、追加するインスタンスをスポットインスタンスにすることができます。

スポットインスタンスの設定として、Use On-Demand instance priceか、Set maximum priceの二つからどちらかを選択します。 Use On-Demand instance priceは文言だけを見るとオンデマンドインスタンスの価格がスポットインスタンスに適用されるように見えますが、実際はスポットインスタンスの利用価格が変動し高くなった場合に、オンデマンドインスタンスの価格と同じになることを許容するという設定です。

一方のSet maximum priceは自分で1時間あたりのスポットインスタンスの価格の最大値を設定することができます。設定する場合、スポットインスタンスの料金履歴などを参考にすると良いでしょう。

ただ後者の場合、設定した価格を超えてしまうとインスタンスが起動できなくなるため、基本的にはUse On-Demand instance priceを利用することをおすすめします。

あとはAdd machine poolするだけでROSAクラスタにスポットインスタンスを追加できます。非常に簡単ですね。
一点気を付けたいポイントとして、一度スポットインスタンスとしてMachine poolを作成すると後からその設定を変更することはできないため注意しましょう。

中断に対しての対処

スポットインスタンスを使う場合に検討する必要があるのがインスタンスの中断に対してどのように対処するかです。

以下のAWSのドキュメントを見ると、中断の通知は実際にスポットインスタンスが停止、または終了が行われる2分前に行われるとあります。 docs.aws.amazon.com

通知は、Amazon EventBridgeを使用してイベント情報として受け取るか、あるいはインスタンスメタデータから確認することができます。

もしこの通知を無視した場合、該当のインスタンスがいきなりシャットダウンするような形となり、その上で稼働するコンテナが正常に処理を終了できず、アプリのダウンタイムが発生する等、様々な問題が起こる可能性があります。

そのため、ROSAではこの通知を自動で確認するための仕組みとして、machine-api-termination-handlerというDaemonSetが、スポットインスタンス上で稼働します。 (起動したスポットインスタンスにはmachine.openshift.io/interruptible-instance: ''というラベルが付与されており、このラベルを見て対象のノードを判断しています。)

各ノードにデプロイされたmachine-api-termination-handlerのPodは、5秒に一回インスタンスメタデータにアクセスし、自ノードが中断の対象となっているかを確認します。

# machine-api-termination-handler Podのログ(中断対象ではないことが出力されている)
I0908 14:49:28.434019 1 handler.go:119] "msg"="Instance not marked for termination" "namespace"="openshift-machine-api" "node"="ip-10-0-196-218.us-east-2.compute.internal"
I0908 14:49:33.433734 1 handler.go:119] "msg"="Instance not marked for termination" "namespace"="openshift-machine-api" "node"="ip-10-0-196-218.us-east-2.compute.internal"
I0908 14:49:38.433317 1 handler.go:119] "msg"="Instance not marked for termination" "namespace"="openshift-machine-api" "node"="ip-10-0-196-218.us-east-2.compute.internal"
...

注意点したいのが、スポットインスタンスにTaintを付与するとmachine-api-termination-handlerPodが起動できなくなってしまいます。そのためスポットインスタンスで特定のPodのみ動かしたい場合はNodeAffinity等を利用するのが良いでしょう。

インスタンスメタデータを確認した結果、中断対象であると分かった場合、machine-api-termination-handlerはNodeの.status.conditionsにTerminatingであるという情報を追加します。

    - type: Terminating
      status: 'True'
      lastHeartbeatTime: '2022-09-07T09:02:32Z'
      lastTransitionTime: '2022-09-07T09:02:32Z'
      reason: TerminationRequested
      message: The cloud provider has marked this instance for termination

上記が追加されると、machine-healthcheck-controllerが対象のノードをUnhealthyと判断し、Drain処理が行われます。これにより対象のノードはスケジュール対象外となり、このノードで稼働していたPodに対して正常な終了処理が行われます。

スポットインスタンスに限った話ではないですが、稼働しているPodを適切に終了するためには、PodのpreStopやterminationGracePeriodSecondsなどを適切に設定しましょう。

スポットインスタンスの中断を実験する

ここまでROSAではどのようにスポットインスタンスの中断に対処しているのかについてご紹介しました。
基本的に、1)スポットインスタンスで動かすワークロードを適切に選択する、2)それらに対して適切なPodの終了処理が実施できるよう設定を行う、という二点が守れていれば、急にインスタンスが中断された場合も問題ないと考えられますが、そうは言っても実際に正しく設定ができているかについて不安を感じられる方は多いかと思います。

そういう時に役立つのがAWSが提供するAWS Fault Injection Simulatorというサービスです。 このサービスを使うと意図的にスポットインスタンスの中断を発生させることができるので、その際にROSAがどういった挙動を取るのかといったことを確認することができます。

以下のドキュメントでは、チュートリアルとしてAWS FISを使用してスポットインスタンスを中断する方法が紹介されています。 docs.aws.amazon.com

スポットインスタンスを利用する際はこちらについても利用を検討してみてはいかがでしょうか。

まとめ

今回はROSAへのスポットインスタンスへの追加についてご紹介しました。 ROSAではスポットインスタンスを利用する際に起こりうる中断に対して、デフォルトで適切に対応するための仕組みを提供しています。 スポットインスタンスは上手く利用することでコストを大きく抑えることもできますので、今回の内容を参考に是非お試しください。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。