EC2インスタンスの削除保護を有効化してもShutdown behaviorによるterminateを防げないのは何故か

#はじめに
本記事は過去に別の場所で投稿したものを再編集したものです。
[EC2のTermination protectionとShutdown behaviorについて](https://www.simpline.co.jp/tech_ty/ec2%E3%81%AEtermination-protection%E3%81%A8shutdown-behavior%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/)

当時と比べればAWSドキュメントやマネジメントコンソール上の表現も分かりやすくなっていますが、初心者の方が混乱しやすい部分である「削除保護」と「シャットダウン動作」について整理します。

#1.EC2インスタンスのライフサイクル
先に前提知識としてインスタンスの状態遷移について確認しておきます。

[インスタンスのライフサイクル]
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-lifecycle.html

公式ドキュメントから画像を拝借します。
![instance_lifecycle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/16a41ac1-daab-3bd2-ac01-535e4f6506e9.png)

状態遷移中のステータスを除いて考えれば、基本的には以下のいずれかのステータスに落ち着きます。

- ruuning（稼働中）
- stopped（停止済み）
- terminated（削除済み）

AWSドキュメントやマネジメントコンソール上での`terminate`の日本語表現が、「削除」であったり「終了」であったりするので、何を表しているかを注意しましょう。

（似たような話で、「インスタンスの開始」という言葉が、AMIからのLaunchを表すのか、stoppedからrunningに遷移させることなのかが文脈によって異なる場合あります。）

インスタンスのライフサイクルにおける`shutting-down`は、EC2のパラメータ「シャットダウン動作」が表すシャットダウンとは観点が異なることにも注意してください。（後述します。）

#2.削除保護(Termination protection)とは

`DisableApiTermination`属性の有効/無効を定義するものです。有効になっている場合、マネジメントコンソール、CLI、APIによるインスタンスの削除を防止することができます。デフォルトでは無効になっています。

削除保護が有効なインスタンスをマネジメントコンソールから削除しようとした時はこのようなエラーが表示されます。

|![削除保護.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/6408d1bb-7d88-0aca-0eb8-9a2d3b85efbe.jpeg)|
|:-:|

CLIによる削除を試みたときは以下のようなエラー。

`aws ec2 terminate-instances --instance-ids i-xxxxxxxx`

```
A client error (OperationNotPermitted) occurred when calling the TerminateInstances operation: The instance 'i-xxxxxxxx' may not be terminated. Modify its 'disableApiTermination' instance attribute and try again.
```

インスタンスの削除を完全に回避できるというものではなく、APIによる削除以外の、以下のようなケースでの削除には対応していません。

- シャットダウン動作による削除
- Auto Scalingグループによる削除

削除保護について詳しくはこちらをどうぞ。

[インスタンスの削除保護の有効化]
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingDisableAPITermination

#3.シャットダウン動作（Shutdown behavior）とは
`InstanceInitiatedShutdownBehavior`属性で定義されるもので、「停止」か「削除」かを選択できます。デフォルトでは「停止」になっています。ドキュメントを見てみます。

[インスタンスによって起動されたシャットダウン動作の変更]
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior

「インスタンスによって起動されたシャットダウン」という日本語がなかなか飲み込みづらいですが、「Instance Initiated Shutdown」の訳です。言い換えるなら、「OSレベルで行われたシャットダウン」になります。OSレベルでシャットダウンが行われたときに、EC2インスタンスとしてのステータスを`stopped`に遷移させるか`terminated`に遷移させるかを定義するのがシャットダウン動作です。

#4.User initiated shutdownとInstance initiated shutdown
前者はAPIによるEC2インスタンスの停止、後者はOSレベルでのシャットダウンによる停止を表します。AWSマネジメントコンソールのEC2インスタンスの詳細画面において、インスタンスが`stopped`であれば、[説明]タブから状態遷移の理由を確認できます。

▼マネジメントコンソールから停止させた場合。
![マネコンからの停止.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/f9d64994-ffec-a9e8-0864-3b859f367be9.png)

▼OSレベルでシャットダウンを行って停止させた場合。
![OSから停止.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/471c4964-51e3-e3ca-5a99-e28daf5bdc8e.png)


| 項目    |       マネコンから停止の場合 |    OSから停止の場合    |
|:-----------------|:------------------:|:------------------:|
| 状態遷移の理由             |     User initiated (併せてイベントが実行された時刻が記載) |        User initiated（時刻の記載無し）     |
| 状態遷移の理由メッセージ      |       Client.UserInitiatedShutdown: User initiated shutdown |       Client.InstanceInitiatedShutdown: Instance initiated shutdown      |
| CloudTrailでのイベント履歴          |  確認できる |       確認できない      |

マネコンからの停止時にはCloudTrailには以下のように記録されます。
（ちなみに以下のイベントから読み取れる、マネしてはいけない部分があります。何でしょうか。[^1]）

```json:CloudTrailイベント
{
    "eventVersion": "1.05",
    "userIdentity": {
        "type": "Root",
        "principalId": "<AWSアカウント番号>",
        "arn": "arn:aws:iam::<AWSアカウント番号>:root",
        "accountId": "<AWSアカウント番号>",
        "accessKeyId": "ASIARVFQHJ6WLG2NXDOX",
        "sessionContext": {
            "attributes": {
                "mfaAuthenticated": "false",
                "creationDate": "2019-05-11T05:52:18Z"
            }
        },
        "invokedBy": "signin.amazonaws.com"
    },
    "eventTime": "2019-05-11T06:18:17Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "StopInstances",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "xx.5x.xx1.xx1",
    "userAgent": "signin.amazonaws.com",
    "requestParameters": {
        "instancesSet": {
            "items": [
                {
                    "instanceId": "i-0xxxxxxxxxxx1ea"
                }
            ]
        },
        "force": false
    },
    "responseElements": {
        "requestId": "d930edfd-2019-41a9-a63c-f3axxxxxeddb",
        "instancesSet": {
            "items": [
                {
                    "instanceId": "i-0xxxxxxxxxxx1ea",
                    "currentState": {
                        "code": 64,
                        "name": "stopping"
                    },
                    "previousState": {
                        "code": 16,
                        "name": "running"
                    }
                }
            ]
        }
    },
    "requestID": "d930edfd-2019-41a9-a63c-f3axxxxxeddb",
    "eventID": "5c3xxx81-54d9-43ed-8c5d-5f5xxxxx4f3a",
    "eventType": "AwsApiCall",
    "recipientAccountId": "<AWSアカウント番号>"
}
```
蛇足ですが、インスタンスのステータスを表すcodeはこういうのが割り当てられているんだ、といったものをAPIリファレンスから深掘りしていくのも面白いですね。[^2]

[InstanceState]
https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceState.html

#5.動作の確認
以下の設定で、マネコンからの停止とOSからの停止をそれぞれ試します。

- 削除保護：有効
- シャットダウン動作：終了

▼マネコンからの停止

|![stopped.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/9957ddd2-982d-a460-9708-fb87febda9ae.jpeg)|
|:-:|

マネコンからの停止は`User initiated shutdown`に該当し、「シャットダウン動作」の設定に影響を受けないため、通常通り`stopped`になりました。

▼OSからの停止

今回はWindowsで試してみました。

|![shutdown.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/a545b454-6ce7-1fec-24a1-240edbffe02a.jpeg)|
|:-:|

`Instance initiated shutdown`が行われたため、「シャットダウン動作」の設定に従いEC2インスタンスを削除する処理が走ります。冒頭のライフサイクルで確認した通り、`terminated`に遷移する前の`shutting-down`というステータスに変わります。[^3]

|![shuttingdown.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/9355ed72-b35f-b4cb-3549-8a6593a39cf8.jpeg)|
|:-:|

削除されました。ちなみに`terminated`になったインスタンスは一定時間（数分～）経過後に、一覧画面に表示されなくなります。

|![teminated.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155963/9f9cfc9c-62ce-f821-4035-f0348b1f0ed1.jpeg)|
|:-:|

#おわりに
「シャットダウン動作」は、（ユーザーからでなく）インスタンス上のOSからのシャットダウンがあった場合に、インスタンスを停止させるか削除させるかが選択できる。

「削除保護」はAPIによる削除から保護する。その保護対象には「シャットダウン動作」による削除が含まれていない。

上記のような整理になります。この記事でも表記がゆれていますが、terminateの訳が「削除」と「終了」で統一されていなかったり、initiateが「開始」と訳されていたりと、日本語だけで理解しようとするととっつきにくい部分があります。原文を読みながら理解するといいでしょう。


##おまけ：CLIで打ってみた時のメモ
（当時の記事から引用したが、インスタンスIDの桁数が少ないことに時代を感じる）

▼Termination protection
・有効にする
`aws ec2 modify-instance-attribute --instance-id i-xxxxxxxx --disable-api-termination`
・無効にする
`aws ec2 modify-instance-attribute --instance-id i-xxxxxxxx --no-disable-api-termination`

▼Shutdown behavior
・削除にする
`aws ec2 modify-instance-attribute --instance-id i-xxxxxxxx  --instance-initiated-shutdown-behavior terminate`
・停止にする
`aws ec2 modify-instance-attribute --instance-id i-xxxxxxxx  --instance-initiated-shutdown-behavior stop`

[^2]: rebootingというステータスはないようだ。ライフサイクルの図はちょっと紛らわしい。
[^1]: rootアカウントで作業している。しかもrootアカウントにMFAを設定していない。
[^3]: stoppedの状態からterminateした時はshutting-downを経由しないので、ここでのshutting-downとはOSのシャットダウンを表しているのだろうか…。stoppedに向けたOSのシャットダウンはstoppingで表されて、terminatedに向けたOSのシャットダウンはshutting-downで表されるとか。ちょっと分からないです。