AMIは登録解除されているがSnapshotだけ残っている場合の一覧を取得・削除（Bash）

AMIを取得するとその裏では自動で紐づくSnapshotが取得されますが、
AMIを登録解除する際は、紐づいているSnapshotは自動で削除されません。

このため、AMIの登録解除だけしてSnapshotの削除が漏れてしまっているケースがあったりします。

本記事ではこのようなSnapshotだけ残っている一覧を取得して一括で削除する処理を紹介します。（Bash)

## Snapshotだけ残っている場合の一覧を取得してみる

今回の実装では、AMI側から紐づくSnapshotの一覧 と Snapshot側からAMIから作成されたSnapshotの一覧　を取得し、これをぶつけて　AMI側がなくて、Snapshot側のみある一覧を作成しています。

この時に、Snapshot側ではAMIから作成されたSnapshotだとどの情報で判断できるかというと。

AMI登録時に作成されたSnapshotを特定するには、Descriptionが下記になっているSnapshotを取得すればよさそうです。

- Created by CreateImage(i-xxxxxxxxxxxxxxxxx) for ami-xxxxxxxxxxxxxxxxx from vol-xxxxxxxx
- Created by CreateImage(i-xxxxxxxxxxxxxxxxx) for ami-xxxxxxxxxxxxxxxxx
- Copied for DestinationAmi ami-xxxxxxxxxxxxxxxxx from SourceAmi ami-xxxxxxxxxxxxxxxxx for SourceSnapshot snap-xxxxxxxxxxxxxxxxx. Task created on xxxxxxx.

```bash:AMIだけ登録解除された一覧を取得
# IFS 区切り文字を改行のみに設定
# aws-cliのoutput text形式の出力は<tab>区切り
# 今回は単一項目だけファイルから読み込み処理しているので関係ないが一応設定
IFS=$'\n'

# 対象とするリージョンを指定
export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_REGION=ap-northeast-1

# AMIから紐づくSnapshotIdを取得してファイルに書き込み
aws ec2 describe-images --owners self --query 'Images[*].[BlockDeviceMappings[*].Ebs[].[SnapshotId]]' --output text | sort > snapshot_ids_from_ami

# AMI取得により作成されたSnapshotを取得
# DescriptionがCreated by CreateImage(i-xxxxxxxxxxxxxxxxx) for ami-xxxxxxxxxxxxxxxxx もしくは
# Created by CreateImage(i-xxxxxxxxxxxxxxxxx) for ami-xxxxxxxxxxxxxxxxx from vol-xxxxxxxx
aws ec2 describe-snapshots --owner-ids self --filters Name=description,Values='Created by CreateImage(i-*) for ami-*' --query 'Snapshots[*].[SnapshotId]' --output text | sort > snapshot_ids_from_snapshot

# 既存AMIをコピーして作成されたSnapshotを取得
# Descriptionが Copied for DestinationAmi ami-xxxxxxxxxxxxxxxxx from SourceAmi ami-xxxxxxxxxxxxxxxxx for SourceSnapshot snap-xxxxxxxxxxxxxxxxx. Task created on xxxxxxx.
aws ec2 describe-snapshots --owner-ids self --filters Name=description,Values='Copied for DestinationAmi ami-* from SourceAmi ami-* for SourceSnapshot snap-*. Task created on *.' --query 'Snapshots[*].[SnapshotId]' --output text | sort >> snapshot_ids_from_snapshot

# uniqコマンドでユニークなラインを取得
# snapshot_ids_from_snapshotにしか存在しないSnapshotId　は AMIが登録解除されたSnapshotと想定
cat snapshot_ids_from_ami snapshot_ids_from_snapshot | sort | uniq -u > snapshot_ids_from_deregisterd_ami

# 削除対象のSnapshotがある場合に詳細な情報を取得
if [ -s snapshot_ids_from_deregisterd_ami ]; then
    # AMIが登録解除されてsnapshotだけ残っているsnapshotidから対象となるSnapshotの詳細を取得
    aws ec2 describe-snapshots --owner-ids self --snapshot-ids $(cat snapshot_ids_from_deregisterd_ami)
else
    # 削除対象のSnapshotなし
    echo "snapshot_ids_from_deregisterd_ami is empty"
fi

```

上記のコマンドで`snapshot_ids_from_deregisterd_ami`にAMIが登録解除されてSnapshotだけ残っているSnapshotIdの一覧を出力します。

この一覧を元にSnapshotIdを削除する場合は下記のコマンドを実行してください

```bash:delete-snapshot
# 紐づけがなくなったSnapshotを読み込みループしながら削除
delete_snapshot_list=$(cat snapshot_ids_from_deregisterd_ami)

for snapshotId in ${delete_snapshot_list}
do
    aws ec2 delete-snapshot --snapshot-id ${snapshotId} || {
        # 削除失敗
        echo "delete-snapshot is failed:${snapshotId}"
        continue
    }
    # 削除成功
    echo "delete-snapshot is success:${snapshotId}"
done
```

下記は削除処理をCloudShellで実行してみたイメージ。
ループしながら一件ずつ削除するので時間はかかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/6272cef7-26e0-8dd9-5fba-c30e59816e7b.png)

## 注意事項

SnapshotのDescriptionの文字列で判断するように実装していますが。

- Created by CreateImage(i-xxxxxxxxxxxxxxxxx) for ami-xxxxxxxxxxxxxxxxx from vol-xxxxxxxx
- Created by CreateImage(i-xxxxxxxxxxxxxxxxx) for ami-xxxxxxxxxxxxxxxxx
- Copied for DestinationAmi ami-xxxxxxxxxxxxxxxxx from SourceAmi ami-xxxxxxxxxxxxxxxxx for SourceSnapshot snap-xxxxxxxxxxxxxxxxx. Task created on xxxxxxx.

上記はAWSドキュメントでこの文言について明示的に記載されている所はみあたらず、実際の動作を確認した結果でこの文言だと判断しているため、仕様が変わったりすると意図しない動作をする事も考えられるの注意が必要です。

このため削除処理実行前には、かならず出力された`snapshot_ids_from_deregisterd_ami`を確認したのちに削除実行してください。

## 総評

AMIの登録解除をした後に、Snapshotの削除処理が漏れてしまいSnapshotが残り続けるといった事はありがちかと思います。

また最初はqueryオプションの`contains(array|string $subject, any $search)`をつかってJMESPathでなんとか実装できないか検討しましたが。

```bash
# 下記のようにSnapshotIdをべた書きにすれば取得した一覧からcontainsで指定したSnapshotIdを除外して取得できる
aws ec2 describe-snapshots --owner-ids self --query 'Snapshots[? !contains(`snap-xxxxx snap-xxxx`,SnapshotId)].SnapshotId'
```

queryの引数をシングルクオートで囲みつつ、`? !contsins`を利用してべた書きSnapshotidを指定している部分を変数で渡すといった方法が思いつかなかったので、一度ファイルに出力して、そのファイルをuniqコマンドで処理して、ユニークな行を取得するといった実装にしています。