【AWS】TerraformでSpotインスタンスを構築する方法

[以前の記事](https://qiita.com/hidekko/items/f921d46fb448c434f793)ではTerraformを用いてAzureに料金の割安なSpotインスタンスを構築する方法を掲載しましたが、今回はAWS版です。
**ちゃんと覚えていました！**
**<font color="Red">えらい！</font>**
**<font color="Red">今回もインドのムンバイリージョンで作業しています。</font>**

:::note info
本記事は2024年7月6日(日本時間)時点のAWSのムンバイリージョンのEC2の料金に関して言及しています。
Spotインスタンスの基本的な概念をわかりやすくするために具体的な金額を出しておりますが、実際の皆様の課金金額は本記事記載の金額に合致しない場合がありますのでご注意ください。
:::

# AWSのSpotインスタンスのコード
コードは簡単ですが、考え方がAzureよりも少し難しいです。
Linux(Amazon Linux 2023)とWindows Server 2022の2台のEC2をSpotインスタンスで構築するコードです。

## 変数設定
```varriable.tf
# ---------------------------
# Variables - 変数設定
# ---------------------------

# region
# ap-northeast-1 東京リージョン
# ap-south-1 ムンバイリージョン
variable "region" {
  default = "ap-south-1"
}

# 環境種別(本番:prd,ステージング:stg,開発:dev)
variable "env_type" {
  default = "dev"
}

# システム名
variable "sys_name" {
  default = "test_lb_dns"
}

# availability_zone
variable "availability_zone" {
  type = object({
    a = string
    b = string #ムンバイリージョンで利用
    c = string
  })
  default = {
    a = "ap-south-1a" # ムンバイ(インド)のアベイラビリティゾーン
    b = "ap-south-1b" # ムンバイ(インド)のアベイラビリティゾーン
    c = "ap-south-1c" # ムンバイ(インド)のアベイラビリティゾーン
  }
}

# ------------------------------
# Web Server と Application Load Balancer用VPC作成
# ------------------------------
# vpc address
variable "vpc_address_pre" {
  default = "10.0."
}

variable "vpc_address_suf" {
  default = "0.0/23"
}

# private subnet suffix address01
variable "private_address_suf01" {
  default = "1.0/26"
}

# private subnet suffix address02
variable "private_address_suf02" {
  default = "1.64/26"
}

# private subnet suffix address03
variable "private_address_suf03" {
  default = "1.128/26"
}

# public subnet suffix address01
variable "public_address_suf01" {
  default = "0.0/24"
}

# EC2のインスタンスタイプ
variable "ec2_instance_type" {
    default = "t2.micro"
}

# VPC Endpoint
variable "vpc_endpoints" {
  type    = list(any)
  default = ["ssm", "ssmmessages", "ec2messages"]
}
```
IPがどうのこう、SSMがどうのこうの書いてますが無視してください。
この```varriable.tf```で大事なのは最後から2ブロック目の```EC2のインスタンスタイプ```とコメントしてある部分で指定している```t2.micro```というインスタンスタイプです。
このインスタンプタイプで以下の```ec2.tf```でLinuxのEC2を構築しています。

## EC2.tf

```ec2.tf
# ---------------------------
# EC2作成
# ---------------------------

# Amazon Linux 2023 の最新版AMIを取得
data "aws_ssm_parameter" "al2023-ami-kernel-default-x86_64" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
}

# ---------------------------
# 必要台数のEC2を作成
# ---------------------------

# Linux
 resource "aws_spot_instance_request" "ec2_app01" { #spot_priceを設定する
   spot_price                  = "0.0047"
   ami                         = data.aws_ssm_parameter.al2023-ami-kernel-default-x86_64.value
   instance_type               = var.ec2_instance_type
   availability_zone           = var.availability_zone.a
   vpc_security_group_ids      = [aws_security_group.sg_ec2_app01.id]
   subnet_id                   = aws_subnet.sn_public_1a.id
   associate_public_ip_address = "true"
   iam_instance_profile        = aws_iam_instance_profile.instance_prof.name
   root_block_device {
     volume_size = 30   # GB
     volume_type = "gp3" # 汎用SSD
     encrypted   = false
     tags        = {
       Snapshot = "false"      
     }
   }
   tags = {
     Name = "${var.env_type}-${var.sys_name}-ec2-app01"
   }
   # NAT Gateway構築後にEC2を構築するパラメーター→Route Table設定後に変更、App01はNAT Gateway構築後でもコケないが、App02と03がコケるため構成変更。(EC2構築前にNAT Gatewayが無いとヒアドキュメント内のdnfコマンドでこける)
   depends_on = [
    aws_route_table_association.associate_rt_sn_public_1a
   ]
 }

# クライアントPC相当のWindows Serverマシンを構築
# ---------------------------
# Windowsマシン用のKey pair作成
# ---------------------------
variable "key_name" {
  default = "kp-ec2-01"
}
variable "key_path" {
  default = "適当なディレクトリ"
}

# 秘密鍵のアルゴリズム設定
resource "tls_private_key" "kp-ec2-01" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

# クライアントPCにKey pair（秘密鍵と公開鍵）を作成
# - Windowsの場合はフォルダを"\\"で区切る（エスケープする必要がある）
# - [terraform apply] 実行後はクライアントPCの公開鍵は自動削除される
locals {
  public_key_file  = "${var.key_path}\\${var.key_name}.id_rsa.pub"
  private_key_file = "${var.key_path}\\${var.key_name}.id_rsa"
}

resource "local_file" "kp_ec2_01_pem" {
  filename = "${local.private_key_file}"
  content  = "${tls_private_key.kp-ec2-01.private_key_pem}"
}

# 上記で作成した公開鍵をAWSのKey pairにインポート
resource "aws_key_pair" "kp_ec2_01" {
  key_name   = "${var.key_name}"
  public_key = "${tls_private_key.kp-ec2-01.public_key_openssh}"
}

# ---------------------------
# クライアント相当のEC2を作成
# ---------------------------

#20240523時点のAMI
#Microsoft Windows Server 2022 Base:ami-0f346136f3b372267
#Microsoft Windows Server 2019 Base:ami-01bd28d73d0053a15
#Microsoft Windows Server 2016 Base:ami-063d3d00f8a97a6d1

 resource "aws_spot_instance_request" "ec2_client01" { #spot_priceを設定する
   spot_price                  = "0.14"
   ami                         = "ami-0f346136f3b372267" #Microsoft Windows Server 2022 Base
   instance_type               = "m4.large"
   availability_zone           = var.availability_zone.a
   vpc_security_group_ids      = [aws_security_group.sg_ec2_client01.id]
   subnet_id                   = aws_subnet.sn_public_1a.id
   associate_public_ip_address = "true"
   key_name                    = "${var.key_name}"
   iam_instance_profile        = aws_iam_instance_profile.instance_prof.name
   root_block_device {
     volume_size = 100   # GB
     volume_type = "gp3" # 汎用SSD
     encrypted   = false
     tags        = {
       Snapshot = "false"      
     }
   }
   tags = {
     Name = "${var.env_type}-${var.sys_name}-ec2-client01"
   }
 }
```

以上です。
勿論ほかにもVPC.tfやsecurity_group.tfがありますが、今回は割愛します。
この```ec2.tf```で2台のEC2を構築しており、上から順にLinux、2台目がWindowsです。
EC2の指定を普通のEC2だと```aws_instance```となるところが```aws_spot_instance_request```となります。
・・・
・・・そうなんですよ・・・
あくまで```request```なんですよ・・・

# requestって何に対するrequestなの？
はい。
AWSの基盤に対して、
**「今このインスタンスタイプで、このリージョンで<font color="Red">この条件でSpotインスタンス使って良いかい？</font>」**
というリクエスト(要望)です。
で、ここで言っている<font color="Red">**条件**</font>が```aws_spot_instance_request```を使うことによって有効になるオプションで指定します。

# 有効になるオプションで何を指定するの？
はい。
コードを見てお気づきだと思いますが、わざとわかりやすく```aws_spot_instance_request```の直下に記載しています。
```spot_price ```です。
詳細を説明します。

## spot_priceについて
<font color="Red">**お金の話**</font>なので、具体的な数字(金額)を用いて説明した方がわかりやすいので、金額を出します。
まずは1台目のLinuxから標準料金を見てみましょう。
AWS Pricing Calcuratorで算出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/359457b7-570c-3dbc-f3fa-76c9ce7e2911.png)
画像が大きくなっちゃいますが必要な情報を一目でわかるようにするため、このようなサイズになってしまいました。
必要な情報は<font color="Red">赤枠</font>で囲っていますが、まとめると以下の通りです。

|項目|設定値|
|---|---|
|リージョン|ムンバイ|
|OS|Linux|
|インスタンス|t2.micro|

他にも手ナンシーやワークロードという項目もありますが、基本的にはこれらの項目で<font color="Red">**EC2の1時間当たりの金額が決定します。**</font>
画像にある通り、この条件だと<font color="Red">**1時間当たり0.0124USD**</font>かかります。

次にSpotインスタンスの料金です。
Spotインスタンスの価格を5分おきに更新してくれるAWSの公式サイトです。
https://aws.amazon.com/jp/ec2/spot/pricing/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/c2a8b9f7-8717-3e98-657b-2aac464c0522.png)
ここでも標準料金と同一スペックである以下の表
|項目|設定値|
|---|---|
|リージョン|ムンバイ|
|OS|Linux|
|インスタンス|t2.micro|

と同じスペックで見積もると、<font color="Red">**1時間当たり0.0047USD**</font>です。
**<font color="Red">約38%の料金で利用することが可能です。</font>**

これをTerraformのコードで表現すると
```ec2(抜粋).tf
 resource "aws_spot_instance_request" "ec2_app01" {
   spot_price                  = "0.0047"
```
になります。
```aws_spot_instance_request```でSpotインスタンスの利用Request(要望)を出し、<font color="Red">ムンバイリージョンでt2.microを利用した場合**1時間当たりのコストを0.0047USDにしてくれ**、という金額の指定</font>を```spot_price```で行う、という理屈です。

いかがでしょうか？
少しわかりづらいですね。
こちらのオプションはあくまでオプション、必須ではないのですが、これを指定しないと標準料金でEC2を利用することになるため実質指定しないとSpotインスタンスの旨味はありません。

# spot_priceをめっちゃ安く指定するとどうなるの？
試してみました。
Requestが蹴られるので、EC2の構築ができません。
Terraform上は特にエラーメッセージも出ませんが、AWSコンソールにログインしてみると安い価格を要望したEC2は影も形も見当たりません。

いかがでしょうか？
金額をしっかりと指定できることは強みですが、毎回時価であるSpotインスタンスの価格を調べて指定するのは少し面倒だなと感じました。
この辺はお金が絡んでいるのでAzureと比較して甲乙つけがたいですが、**<font color="Orange">しっかりと自分で金額管理、いくらの単価でEC2が動作しているのか管理したい場合はAWSのTerraformのコードが向いていると思いますし、</font><font color="Blue">とりあえず(いくらかはわからないが標準料金よりは安く)VMを使いたいんだ、簡単に安く使いたいんだ、という方はAzureの金額指定の無い自動選択のコードが向いていると思います。</font>**

# 確認
はい。
すみません。
確認が漏れておりましたね。
どうぞご覧ください。
Linux
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/60a2935f-dcf1-1d7f-a85e-999b97eb05c9.png)
Windows
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/de9297f4-b753-d5df-94f0-9312999cba9c.png)

どちらも<font color="Red">赤枠内</font>の通りSpotインスタンスで構築されています。

<font color="Red">**これでAWSでもお財布にやさしくガンガン検証できますね！**</font>

本日はここまで。