AWS：コマンドでリザーブドインスタンスの購入

最近AWSのリザーブドインスタンス・ノードを購入する機会があったので、個人的メモとして残します。
リザーブドインスタンスの購入は操作ミス時のリスクが大きいため、できればこうした作業はGUIではなくコマンドで実行したいですね。
この記事ではEC2、RDS、Elasticacheについて触れていますが、リザーブド○○の対象は他にもあります。（以下参考）
[Qiita:【AWS】 リザーブドインスタンスの対象サービスを全部言えますか？](https://qiita.com/uechi-shingo/items/54cd342a9aee1f7ec3f8)


# 参考にしたサイト
https://dev.classmethod.jp/articles/aws-cli-ri/

https://dev.classmethod.jp/articles/ec2-reserved-instance-on-future-date/

# EC2
EC2のリザーブドインスタンスは予約購入が可能。
予約購入しておくと契約更新がコントロールしやすくなる。

### ① OfferingIdを確認
購入の条件（例）
- 提供クラス：スタンダード
- テナンシー：デフォルト
- 前払い：なし
- 期間（秒）：1年（＝31536000秒）
- インスタンスタイプ：c5a.large
- OS：Linux
- スコープ：リージョン
```sh:コマンド
aws ec2 describe-reserved-instances-offerings --region ap-northeast-1 \
  --offering-class standard \
  --instance-tenancy default \
  --offering-type "No Upfront" \
  --filters \
    Name=duration,Values=31536000 \
    Name=instance-type,Values="c5a.large" \
    Name=product-description,Values="Linux/UNIX" \
    Name=scope,Values=Region
```
ポイント：オプションを指定してできるだけ絞り込みましょう。出力までの時間が短縮されます。

出力の内容が問題ないことを確認したら、**ReservedInstancesOfferingId**をメモする。
(以下の"**c55cb717-68b3-4178-afd8-8bf1ec25d727**")

```json:出力
{
    "ReservedInstancesOfferings": [
        {
            "CurrencyCode": "USD",
            "InstanceTenancy": "default",
            "Marketplace": false,
            "OfferingClass": "standard",
            "OfferingType": "No Upfront",
            "PricingDetails": [],
            "RecurringCharges": [
                {
                    "Amount": 0.06,
                    "Frequency": "Hourly"
                }
            ],
            "Scope": "Region",
            "ReservedInstancesOfferingId": "c55cb717-68b3-4178-afd8-8bf1ec25d727",
            "InstanceType": "c5a.large",
            "Duration": 31536000,
            "UsagePrice": 0.0,
            "FixedPrice": 0.0,
            "ProductDescription": "Linux/UNIX"
        }
    ]
}

```

### ② OfferingIdを指定して予約購入
オプション「--purchase-time」で予約購入時刻を指定する。
※日本時間(JST)で指定する場合は時刻の末尾に"**+09:00**"をつける
```sh:コマンド
aws ec2 purchase-reserved-instances-offering --region ap-northeast-1 \
  --reserved-instances-offering-id c55cb717-68b3-4178-afd8-8bf1ec25d727 \
  --instance-count 2 \
  --purchase-time "2025-2-10T12:00:00+09:00"
```

### ③ 予約購入を確認する場合
```sh:コマンド
aws ec2 describe-reserved-instances --region ap-northeast-1
```

### ④ 予約購入をキャンセルする場合
```sh:コマンド
aws ec2 delete-queued-reserved-instances --region ap-northeast-1 \
  --reserved-instances-ids <③予約購入確認で表示されたReservedInstancesId>
```



# RDS
RDSのリザーブドインスタンスは**予約購入ができない**。
購入のコマンドを実行した時点で購入が確定されるため注意。

### ① OfferingIdを確認
購入の条件（例）
- インスタンスタイプ：db.r5.2xlarge
- DBエンジンタイプ：Aurora MySQL
- 前払い：なし
- 期間（秒）：1年（＝31536000秒）
```sh:コマンド
aws rds describe-reserved-db-instances-offerings --region ap-northeast-1 \
  --db-instance-class "db.r5.2xlarge" \
  --product-description "aurora-mysql" \
  --offering-type "No Upfront" \
  --duration 31536000
```

出力の内容が問題ないことを確認したら、**ReservedDBInstancesOfferingId**をメモする。
(以下の"**7f0f3516-8b87-4d1e-9c48-d9510472756a**")

```json:出力
{
    "ReservedDBInstancesOfferings": [
        {
            "ReservedDBInstancesOfferingId": "7f0f3516-8b87-4d1e-9c48-d9510472756a",
            "DBInstanceClass": "db.r5.2xlarge",
            "Duration": 31536000,
            "FixedPrice": 0.0,
            "UsagePrice": 0.0,
            "CurrencyCode": "USD",
            "ProductDescription": "aurora-mysql",
            "OfferingType": "No Upfront",
            "MultiAZ": false,
            "RecurringCharges": [
                {
                    "RecurringChargeAmount": 0.93,
                    "RecurringChargeFrequency": "Hourly"
                }
            ]
        }
    ]
}
```
### ② OfferingIdを指定して購入
①でメモしたReservedDBInstancesOfferingIdを入力する。

```sh:コマンド
aws rds purchase-reserved-db-instances-offering --region ap-northeast-1 \
  --reserved-db-instances-offering-id "7f0f3516-8b87-4d1e-9c48-d9510472756a" \
  --db-instance-count 1 \
  --reserved-db-instance-id "rds-01"
```

- 注意1：実行前に購入台数をよく確認すること。
- 注意2：オプション「--reserved-db-instance-id」は一意である必要があるため、再実行した際はエラーで購入できない（はず…怖くて2回実施していない）。
コマンド履歴から誤って再実行してしまうリスク回避のために指定することを推奨。


# Elasticache
Elasticacheのリザーブドノードは**予約購入ができない**。
購入のコマンドを実行した時点で購入が確定されるため注意。

### ① OfferingIdを確認
購入の条件（例）
- エンジンの種類：Memcached
- ノードタイプ：cache.m6g.large
- 前払い：なし
- 期間（秒）：1年（＝31536000秒）
```sh:コマンド
aws elasticache describe-reserved-cache-nodes-offerings --region ap-northeast-1 \
  --product-description "memcached" \
  --cache-node-type "cache.m6g.large" \
  --offering-type "No Upfront" \
  --duration 31536000
```

出力の内容が問題ないことを確認したら、**ReservedCacheNodesOfferingId**をメモする。
(以下の"**172fe6c8-59a4-4a36-8982-e15e149fe6c4**")

```json:出力
{
    "ReservedCacheNodesOfferings": [
        {
            "ReservedCacheNodesOfferingId": "172fe6c8-59a4-4a36-8982-e15e149fe6c4",
            "CacheNodeType": "cache.m6g.large",
            "Duration": 31536000,
            "FixedPrice": 0.0,
            "UsagePrice": 0.0,
            "ProductDescription": "memcached",
            "OfferingType": "No Upfront",
            "RecurringCharges": [
                {
                    "RecurringChargeAmount": 0.131,
                    "RecurringChargeFrequency": "Hourly"
                }
            ]
        }
    ]
}
```

### ② OfferingIdを指定して購入
①でメモしたReservedCacheNodesOfferingIdを入力する。

```sh:コマンド
aws elasticache purchase-reserved-cache-nodes-offering --region ap-northeast-1 \
  --reserved-cache-nodes-offering-id "172fe6c8-59a4-4a36-8982-e15e149fe6c4" \
  --cache-node-count 1 \
  --reserved-cache-node-id "elasticache-01"
```

- 注意1：実行前に購入台数をよく確認すること。
- 注意2：オプション「--reserved-cache-node-id」は一意である必要があるため、再実行した際はエラーで購入できない（はず…怖くて2回実施していない）。
コマンド履歴から誤って再実行してしまうリスク回避のために指定することを推奨。


# 注意・コツ
OfferingIDは変わることがあるので、以前使用していたものを使いまわすのではなく都度確認しましょう。
オプションで指定する具体的な値が不明な場合は、そのオプション抜きで実行してみて、出力にある値を参考にすると良いでしょう。

# おわりに
リザーブドインスタンスの購入は経験する機会が少ない割にミスした時のリスクが大きいので、しっかり準備しておきましょう。
コマンドにdry-runのオプションがあると事前に試せて良いんだけどなぁ…。