Amazon EC2 スポットインスタンスを使う

# 目的
開発やステージング環境の Web や API 用途（*1）の EC2 の オンデマンドインスタンス を スポットインスタンス へ変更して、EC2 のコストを減らしたい。

（*1）不確定な日時にある程度の時間の中断（ダウンタイム）が許容でき、ステートレスなサーバ





# オンデマンドインスタンスと比較したスポットインスタンスの特徴
* 作成方法
 * EC2作成前にスポットインスタンスリクエストを発行する

* 中断が発生する可能性がある
 * そんなに頻繁に中断はされない（年に１回あるかないか程度）

* 費用
 * EC2コストが最大90%オフ
 * オンデマンドと同様、スポット価格で従量課金（単位時間）
 * スポット価格は緩やかに変動する
 * [Amazon EC2 スポットインスタンスの料金](https://aws.amazon.com/jp/ec2/spot/pricing/)

上記以外は、ほぼほぼオンデマンドインスタンスと同じような感じで扱うことができる。
中断の動作を「停止」に設定することで、中断が発生してもスポットインスタンスが削除されることなく、停止させることができる。停止しているスポットインスタンスを起動すると、停止前と同じデータ内容で、同じEIPが付加される。


# スポットインスタンス の使用を開始する前に理解しておく概念

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/25037797-d0c9-382a-1c90-8262339cc43d.png)|
|:--|
引用元：[AWS > ドキュメント > Amazon EC2 > Linux インスタンス用ユーザーガイド](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-spot-instances.html#spot-features)

### スポットインスタンス
AWS側がEC2をユーザーに提供するために用意してあるが、まだ誰にも使われておらず待機しているEC2を安価で利用できるサービス

* リージョン、アベイラビリティゾーン、インスタンスタイプごとに独立したスポットキャパシティプール（使用されておらず遊んでいるEC2群）がある。
* 利用時は、どのリージョンの、どのAZの、どのインスタンスタイプをどれくらいの料金で利用するのか指定する。
* 指定したスポットインスタンスのキャパシティプールに空き（遊んでいるEC2）があり、指定した料金がスポット料金を上回っていれば、スポットインスタンスを利用（起動/稼働）することができる。
* 指定したスポットインスタンスのキャパシティプールに空きがない場合は、スポットインスタンスは起動できない。
* 指定したスポットインスタンスのキャパシティプールの空きが不足してくると、スポットインスタンスの稼働が中断（削除/停止）される。
* [スポットインスタンスアドバイザー](https://aws.amazon.com/jp/ec2/spot/instance-advisor/)を使うと、中断される頻度が低いインスタンスタイプを調べることができる。

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/c31279b6-1606-d131-bf07-aa2f7cfd3f5a.png)|
|:--|
引用元：[Amazon EC2 だけじゃない︕ 最⾼のコスト効率を⼿に⼊れるための スポットインスタンス使いこなし術](https://pages.awscloud.com/rs/112-TZM-766/images/AWS-27_AWS_Summit_Online_2020_CMP01.pdf)


### スポット料金
* スポットインスタンスのの時間料金（単位時間あたり）
* 入札制ではない（昔は入札制だった）
* 価格は長期的な需給に基づいてAWS側で調整される（大きな価格変動はない。緩やかに価格変化する）
* スポットインスタンス作成時に、ユーザーが支払い可能な料金を指定する。
* スポット料金がユーザーが指定した料金を下回っている間は、スポットインスタンスを利用（起動/稼働）できる。
* スポット料金がユーザーが指定した料金を上回ったとき、スポットインスタンスは中断（削除/停止）される。
* [スポットインスタンスの料金履歴を確認する](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-spot-instances-history.html)


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/d59c7980-0a83-b1d9-aeb6-fe657c396ab5.png)|
|:--|
引用元：[Amazon EC2 だけじゃない︕ 最⾼のコスト効率を⼿に⼊れるための スポットインスタンス使いこなし術](https://pages.awscloud.com/rs/112-TZM-766/images/AWS-27_AWS_Summit_Online_2020_CMP01.pdf)

# 中断対策
* 中断発生時の動作を「停止」に設定する（デフォルトは削除）
* 支払うことができる上限価格をそのインスタンスのオンデマンドと同じ価格に設定する（上限料金を指定しない場合、デフォルトの上限料金はオンデマンド価格となる）
* 中断（スポットインスタンスが停止）されたら、別のAZやインスタンスタイプで起動を試みる（手動対応）。
* スポットフリートを利用する（設定したスポットプールに自動でスポットインスタンスリクエストを発行する）。
    * 【注意点】スポットフリートで起動したスポットインスタンスは、AWSやスポットフリートでしか中断させることができない。


# 手動（AWSマネージドコンソール）からスポットインスタンスを作成
参照：[スポットインスタンスリクエスト
](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/spot-requests.html)
（１）オンデマンドからスポットインスタンスへ変更したいEC2のAMIを作成する

（２）作成したAMIを選択して、アクションからスポットリクエストを選択

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/fe14d948-f6f6-4fd8-0b16-1ddbfb5bcee0.png)|
|:--|

（３）インスタンスタイプを選択

（４）スポットリクエストの設定

|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/46d68914-c341-8234-edfa-94733a998c02.png)|
|:--|

**最高価格**：**空入力** （選択したインスタンスタイプのオンデマンドと同じ価格に設定される）
**永続的リクエスト**：**チェックする**（スポットインスタンスが中断された場合に、スポットインスタンスリクエストを再送信させる） 
**中断動作**：**停止**（永続リクエストを選択している場合は、中断されたスポットインスタンスをスポットサービスが停止するか休止するかを指定できる。また、選択したインスタンスタイプによっては、休止動作を対応していないものがある。）
**リクエスト有効期間の終了**：**任意の時刻**（スポットインスタンスリクエストの有効期限は指定しない）

# Terraformからスポットインスタンスを作成

参照：[Resource: aws_spot_instance_request](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/spot_instance_request)

```terraform
     1	resource "aws_spot_instance_request" "spot-ec2-test" {
     2	  #spot instance request
     3	  wait_for_fulfillment            = "true"
     4	  spot_type                       = "persistent"
     5	  instance_interruption_behaviour = "stop"
     6
     7	  tags = {
     8	    Name      = "spot-ec2-test"
     9	    ManagedBy = "Terraform"
    10	  }
    11
    12	  lifecycle {
    13	    ignore_changes = ["id"]
    14	  }
    15
    16	  # ec2 instance
    17	  ami                         = "ami-**********"
    18	  availability_zone           = "ap-northeast-1c"
    19	  #disable_api_termination     = true
    20	  ebs_optimized               = false
    21	  instance_type               = "t2.micro"
    22	  key_name                    = "**********"
    23	  vpc_security_group_ids      = ["sg-**********", "sg-**********"]
    24	  subnet_id                   = "subnet-**********"
    25	  associate_public_ip_address = true
    26	  iam_instance_profile        = "**********"
    27	}
```

2行目〜14行目：
スポットインスタンスリクエスト用

7行目〜10行目：
このtagsで設定したものは、起動したEC2のタグには設定されない。
スポットリクエストのタグに設定される。

12行目〜14行目：
運用していくとリクエストIDは変更されるので、Terraformでidの変更を無視するようにしておく（forces new resource されないようにしておく）

17行目〜26行目：
EC2インスタンス用設定（[aws_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance)）

19行目：
スポットインスタンでは、終了保護設定は使えない



#スポットリクエストの確認
AWSマネジメントコンソール ⇒ EC2 ⇒ スポットリクエスト
から現在のスポットリクエストのステータスを確認することができる。

|![スクリーンショット 2021-10-06 17.55.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/89fdfad2-d648-c79a-c890-aa067e9042e5.png)|
|:--|

**状態**
スポットリクエストが受理され、関連付けられたスポットインスタンス（容量の項目にあるEC2インスタンスID）が存在していると、状態は「active」となる。

|![スクリーンショット 2021-10-06 18.28.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/5828f8d6-e2e3-fab0-2946-bdeb9939fe67.png)|
|:--|

引用元：[スポットインスタンスリクエスト](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/spot-requests.html)

**容量**
スポットリクエストに関連付けられたスポットインスタンス（EC2インスタンスのID）

**ステータス**
スポットインスタンスの仕様（キャパシティ、価格）がすべて満たされているとステータスは「fulfilled」となる。
※スポットインスタンスの仕様がすべて満たされると、スポットリクエストが受理される。

**永続性**
スポットインスタンス作成時に「永続的リクエスト」にチェックを入れると、永続性が「persistent」となる。

>永続スポットインスタンスリクエストは、リクエストが受理された後も、リクエストの有効期限が切れるかユーザーによりキャンセルされるまで、アクティブ状態を維持します。
>スポット料金が上限価格を上回った場合、または利用できる容量がなくなった場合には、スポットインスタンスは中断されます。インスタンスが中断された後にスポット料金が上限価格以下になるか、容量が再び利用可能になると、スポットインスタンスが開始 (停止している場合)、あるいは再開 (休止状態の場合)されます。

引用：[スポットインスタンスリクエスト
](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/spot-requests.html)

#現行EC2オンデマンドインスタンスをスポットインスタンスへ切り替える例
1. 現行EC2オンデマンドインスタンスと同等内容のスポットインスタンスを作成する<br/>
<注意>現行オンデマンドインスタンスでcronが設定されている場合は、スポットインスタンス起動時に２重起動しても問題ないものかを確認しておく
2. 現行EC2オンデマンドインスタンスのEIPを、スポットインスタンスへ割り当てる
3. ALB（ターゲットグループ）に登録している場合は、ターゲットインスタンスをオンデマンドインスタンスからオンデマンドインスタンスへ変更する
3. EC2オンデマンドインスタンスを停止する
4. しばらく様子見て問題なければEC2オンデマンドインスタンスを削除する

#スポットインスタンス起動後の操作
**スポットインスタンスのEC2のNameを設定する**

* Terraform の tags で設定した Name は、起動した EC2 の タグの Name には設定されない



**起動しているスポットインスタンスのEC2を停止(stop)したい**

* オンデマンドインスタンスと同じように、AWSマネジメントコンソールのEC2から、起動している対象のスポットインスタンスの EC2 のインスタンスの状態を"停止"する。

**停止しているスポットインスタンスのEC2を開始(running)したい**

* オンデマンドインスタンスと同じように、AWSマネジメントコンソールのEC2から、停止している対象のスポットインスタンスの EC2 のインスタンスの状態を"開始"する。

**スポットインスタンスを削除(terminate)したい**

* [AWSマネジメントコンソールから]EC2のスポットリクエストで、対象のスポットインスタンスに紐付いているスポットリクエストを"キャンセル"する。
* [terraformから] aws_spot_instance_request の terraform テンプレートファイルを全行削除して apply する。

# その他
**IAMロール「AWSServiceRoleForEC2Spot」**

* スポットインスタンスを作成すると、このIAMロールが自動的に作成される（初回スポットインスタンス作成時のみ）。
* EC2スポットサービスに、スポットインスタンスを作成、起動、停止することができる権限を与えるためのIAMロール。
* AWS管理のIAMポリシー「AWSEC2SpotServiceRolePolicy」がアタッチされている。
* ALB ターゲットグループのインスタンス入れ替え

**EC2インスタンスの終了保護を有効**

* スポットインスタンスは、「[終了保護を有効](https://aws.amazon.com/jp/premiumsupport/knowledge-center/accidental-termination/)」にすることができない。
* 有効にしようとすると、下記エラーが出る

```
Modifying 'disableApiTermination' is not supported for spot instances.
```

# 運用後に起きたこと
* 古いインスタンスタイプ（例えば t2.medium）の場合
    * 頻繁（数日おきとか）にスポットリクエストが発行され、その度にEC2が再起動する。
    * ひどいときは、スポットリクエストが 「capacity-not-available」 でとまり、EC2が起動しない。
    * t2.medium から t3.medium へのスペックアップは、ENI周りで下記エラーが出てスペックアップできない。
    * ということで、オンデマンドに戻して、リザーブドインスタンスまたは、平日深夜・休日停止を検討する。

```
Error requesting spot instances: InvalidParameterCombination: Enhanced networking with the Elastic Network Adapter (ENA) is required for the 't3.medium' instance type. Ensure that you are using an AMI that is enabled for ENA.
```