はじめに
これまでのseriesでは、HDFS上のrawログとApache IcebergをAmazon S3へ移行し、AWS Glue Data Catalog、Spark、Flink、Trino、Grafana、Zeppelinから利用する構成を作成した。
本記事では、実機検証後にAWS環境を残さない場合の削除手順をまとめる。ただし本seriesでは、これまでの記事で作成したS3 Raw backupとS3 Icebergを継続利用するため、これらのbucket、Glue database/table、Athena workgroup、home-rawlog-backup、home-aws、home-aws-adminは削除しない。
本seriesの標準経路ではAmazon S3を継続利用するため、S3/Glue削除手順は実施しない。本記事の対象は、別途作成した検証専用resourceだけである。
AWS環境の削除は復旧できない操作を含む。特にS3のobject versionを完全削除すると、versioningを有効にしていても復元できない。commandをそのまま実行せず、AWS account ID、region、resource名、共有利用の有無を確認し、削除承認を得てから実施する。
今回削除するもの
このseriesで作成した可能性があるresourceは次のとおりである。
| resource | 例 | 削除条件 |
|---|---|---|
| S3 raw bucket | <YOUR_UNIQUE_RAW_BUCKET> |
検証専用で、他jobが読み書きしていない |
| S3 Iceberg bucket | <YOUR_UNIQUE_ICEBERG_BUCKET> |
Iceberg、checkpoint、savepointを含めて不要 |
| Glue Database | logs |
既存databaseではなく検証専用 |
| Glue Table |
syslog_icebergなど |
他systemから参照されていない |
| IAM user / role | Spark、Flink、Trino、Connect用 | 検証専用principalである |
| IAM policy | 対象bucket・Glue用policy | 他principalへattachされていない |
| Access key | 検証用IAM userのkey | 該当user専用である |
| KMS key | 任意で作成したcustomer managed key | 検証専用で、他dataを暗号化していない |
| Athena query result | 検証で生成した結果 | 専用prefixまたは専用bucketである |
次は、このseriesでは新規作成していないため自動的に削除対象にしない。
- AWS account、Organization、VPC
- 既存のCloudTrail、AWS Config、GuardDuty
- 共有のIAM role、IAM policy、KMS key
- 既存のAthena query result bucket
- 他systemと共有しているGlue Database
- backup、監査、法令保持のために必要なS3 object
削除の順序
依存関係を考慮して、次の順で削除する。
1. 削除対象を固定する
2. Kafka Connect / Spark / Flinkなどのwriterを停止する
3. 最終backupとresource一覧を保存する
4. Trino / Grafana / Zeppelinなどのreaderを切り離す
5. Glue TableとDatabaseを削除する
6. S3 multipart uploadを中止する
7. S3の全object versionとdelete markerを削除する
8. 空になったS3 bucketを削除する
9. 検証専用IAM credential、user、role、policyを削除する
10. 検証専用KMS keyがあれば削除をscheduleする
11. local hostのAWS設定を撤去する
12. resourceと料金が残っていないことを確認する
IAM principalは、S3とGlueを削除するための最後のAPI callが終わるまで残す。
1. 対象accountとresourceを固定する
最初に変数を設定する。placeholderを実在するresource名へ置き換え、実行前に表示内容を確認する。
export AWS_REGION='ap-northeast-1'
export AWS_PROFILE='home-aws-admin'
source /etc/rawlog-s3-backup/common.env
export PRESERVE_RAW_BUCKET="${S3_BUCKET_NAME}"
source /etc/iceberg/aws.env
export PRESERVE_ICEBERG_BUCKET="${ICEBERG_BUCKET}"
export AWS_RAW_BUCKET='<DELETE_ONLY_IF_SEPARATE_TEST_BUCKET>'
export AWS_ICEBERG_BUCKET='<DELETE_ONLY_IF_SEPARATE_TEST_BUCKET>'
AWS_RAW_BUCKETまたはAWS_ICEBERG_BUCKETがPRESERVE_RAW_BUCKET、PRESERVE_ICEBERG_BUCKETのいずれかと一致した場合は、以降の削除を中止する。
for delete_bucket in "${AWS_RAW_BUCKET}" "${AWS_ICEBERG_BUCKET}"; do
case "${delete_bucket}" in
"${PRESERVE_RAW_BUCKET}"|"${PRESERVE_ICEBERG_BUCKET}")
printf 'ERROR: backup bucket is protected: %s\n' "${delete_bucket}" >&2
return 1 2>/dev/null || exit 1
;;
esac
done
caller identityを確認する。
aws sts get-caller-identity \
--profile "${AWS_PROFILE}" \
--region "${AWS_REGION}"
表示されたAccountが想定する検証用AWS account IDと一致しない場合は中止する。
対象bucketのregion、versioning、Object Lockを確認する。
for bucket in "${AWS_RAW_BUCKET}" "${AWS_ICEBERG_BUCKET}"; do
echo "===== ${bucket} ====="
aws s3api get-bucket-location \
--bucket "${bucket}" \
--profile "${AWS_PROFILE}"
aws s3api get-bucket-versioning \
--bucket "${bucket}" \
--profile "${AWS_PROFILE}"
aws s3api get-object-lock-configuration \
--bucket "${bucket}" \
--profile "${AWS_PROFILE}" || true
done
Object Lockのretentionまたはlegal holdで保護されたversionは、保持条件を満たすまで完全削除できない。AccessDeniedを回避するために保護を迂回するのではなく、保持期限と組織のdata retention policyを確認して削除を延期する。
Glue DatabaseとTableを確認する。
aws glue get-database \
--name "${GLUE_DATABASE}" \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}"
aws glue get-tables \
--database-name "${GLUE_DATABASE}" \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}" \
--query 'TableList[].{Name:Name,Location:StorageDescriptor.Location}' \
--output table
table locationに今回のbucket以外が含まれる場合、Database全体を削除しない。検証用Tableだけを個別に削除する。
2. AWSへ書き込む処理を停止する
削除中にobjectやGlue metadataが再作成されないよう、writerを先に停止する。
Kafka Connect S3 Sinkを停止する
対象connectorを確認してpauseする。
curl -fsS http://connect1:8083/connectors | jq
curl -fsS -X PUT \
http://connect1:8083/connectors/s3-syslog-sink/pause
curl -fsS -X PUT \
http://connect1:8083/connectors/s3-authlog-sink/pause
Sparkのバッチ処理を停止する
AWS S3へ書き込むcronとsystemd timerを停止する。
systemctl list-timers --all | rg -i 'spark|iceberg|log'
実際のunit名を確認し、対象timerだけを停止する。
Flink
対象job IDを確認し、今後復旧する可能性がある場合はfinal savepointをAWS以外の保持先へ取得してから停止する。
sudo -u flink /opt/flink/current/bin/flink list -a
AWS環境そのものを削除する場合、削除対象bucket内のsavepointはrollbackに利用できない。必要なsavepointは別の保存先へ退避する。
Trino、Athena、Grafana、Zeppelin
readerはdataを作らないが、削除中の誤参照を防ぐため、次を実施する。
- Trinoの
iceberg_awscatalogを利用する定期queryを停止する - GrafanaのAWS版dashboardを無効化する
- Zeppelinの
trino_awsNotebookを停止またはread-onlyにする - Athenaのscheduled queryを停止する
3. 最終backupと証跡を取得する
削除後に必要となる可能性がある情報を保存する。
aws s3 ls "s3://${AWS_RAW_BUCKET}/" \
--recursive --summarize \
--profile "${AWS_PROFILE}"
aws s3 ls "s3://${AWS_ICEBERG_BUCKET}/" \
--recursive --summarize \
--profile "${AWS_PROFILE}"
Glue Table定義をJSONで取得する。
aws glue get-tables \
--database-name "${GLUE_DATABASE}" \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}" \
--output json > glue-tables-before-delete.json
次も削除記録へ残す。
- AWS account IDとregion
- bucket名とobject/version数
- Glue Database/Table名
- IAM principalとpolicy ARN
- KMSキーのARN
- writerを停止した時刻
- backupの保存先と復元確認結果
- 削除承認者と作業日時
backupを同じ削除対象bucketへ置かない。
4. Glue Data Catalogを削除する
GlueのTableを削除しても、S3上のIceberg data fileやmetadata fileは削除されない。先にcatalog metadataを削除し、その後S3を個別に空にする。
検証専用Tableだけを削除する場合
対象を表示してから、1 tableずつ削除する。
aws glue get-tables \
--database-name "${GLUE_DATABASE}" \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}" \
--query 'TableList[].Name' \
--output table
aws glue delete-table \
--database-name "${GLUE_DATABASE}" \
--name '<DELETE_TARGET_TABLE>' \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}"
今回の例では、次のようなTableが候補になる。ただし、存在確認と所有者確認なしに一括削除しない。
syslog_iceberg
authlog_iceberg
syslog_events
authlog_events
syslog_host_1m
authlog_host_1m
Database全体を削除する場合
logsが検証専用で、全Tableを削除してよい場合だけ実行する。
aws glue delete-database \
--name "${GLUE_DATABASE}" \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}"
Glue Databaseを削除すると、所属するTable、Table Version、Partitionなどへアクセスできなくなり、関連metadataは非同期削除される。即時に個別resourceを消す必要がある場合は、AWS公式手順に従ってTable VersionやPartitionを先に削除する。
5. 未完了multipart uploadを削除する
未完了multipart uploadのpartにもstorage料金が発生する。各bucketで一覧を確認する。
for bucket in "${AWS_RAW_BUCKET}" "${AWS_ICEBERG_BUCKET}"; do
aws s3api list-multipart-uploads \
--bucket "${bucket}" \
--profile "${AWS_PROFILE}" \
--query 'Uploads[].{Key:Key,UploadId:UploadId,Initiated:Initiated}' \
--output table
done
削除対象bucketであることを再確認し、各uploadを中止する。
aws s3api abort-multipart-upload \
--bucket '<DELETE_TARGET_BUCKET>' \
--key '<OBJECT_KEY>' \
--upload-id '<UPLOAD_ID>' \
--profile "${AWS_PROFILE}"
一覧が空になるまで確認する。upload中のwriterが残っている場合は、先にwriterを特定する。
6. versioning有効bucketを空にする
本seriesではS3 Versioningを有効にしている。次のcommandだけでは、current objectにdelete markerが追加されるだけで、過去versionは残る。
aws s3 rm "s3://<BUCKET>/" --recursive
bucketを完全に削除するには、すべてのobject versionとdelete markerをversion ID付きで削除する必要がある。
削除前の件数を確認する
for bucket in "${AWS_RAW_BUCKET}" "${AWS_ICEBERG_BUCKET}"; do
echo "===== ${bucket} ====="
aws s3api list-object-versions \
--bucket "${bucket}" \
--profile "${AWS_PROFILE}" \
--query '{Versions:length(Versions),DeleteMarkers:length(DeleteMarkers)}'
done
全versionとdelete markerを削除する
次は、指定した1 bucketのversionとdelete markerを最大1,000件ずつ削除する例である。実行前にDELETE_BUCKETを表示し、AWS account IDと照合する。
export DELETE_BUCKET='<EXACT_BUCKET_NAME_APPROVED_FOR_DELETION>'
printf 'DELETE_BUCKET=%s\n' "${DELETE_BUCKET}"
aws sts get-caller-identity --profile "${AWS_PROFILE}"
aws s3api get-bucket-versioning \
--bucket "${DELETE_BUCKET}" \
--profile "${AWS_PROFILE}"
一覧をlocal fileへ保存し、削除予定件数を確認する。
aws s3api list-object-versions \
--bucket "${DELETE_BUCKET}" \
--profile "${AWS_PROFILE}" \
--output json > "${DELETE_BUCKET}-versions.json"
jq '{
versions: (.Versions // [] | length),
delete_markers: (.DeleteMarkers // [] | length)
}' "${DELETE_BUCKET}-versions.json"
承認後、versionとdelete markerを1,000件単位で削除する。
jq -c '
[(.Versions // [])[], (.DeleteMarkers // [])[]]
| map({Key: .Key, VersionId: .VersionId})
| range(0; length; 1000) as $offset
| {Objects: .[$offset:$offset + 1000], Quiet: true}
' "${DELETE_BUCKET}-versions.json" |
while IFS= read -r delete_request; do
aws s3api delete-objects \
--bucket "${DELETE_BUCKET}" \
--delete "${delete_request}" \
--profile "${AWS_PROFILE}"
done
削除処理中にwriterが残っていた場合や、一覧取得後にobjectが作成された場合はversionが残る。再度一覧を取得し、VersionsとDeleteMarkersの両方が空になるまで確認する。
aws s3api list-object-versions \
--bucket "${DELETE_BUCKET}" \
--profile "${AWS_PROFILE}" \
--query '{Versions:Versions,DeleteMarkers:DeleteMarkers}'
大量のobjectを含む本番規模bucketでは、このshell例をそのまま使わない。S3 Lifecycle、S3 Batch Operations、AWS Supportへの相談を含め、削除時間、API request料金、Object Lock、replicationを考慮した削除計画を作る。
7. S3 bucketを削除する
全version、delete marker、multipart uploadが存在しないことを確認してからbucketを削除する。
aws s3api delete-bucket \
--bucket '<EXACT_EMPTY_BUCKET_NAME>' \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}"
raw bucketとIceberg bucketを別々に確認して実施する。BucketNotEmptyが返った場合は、通常objectだけでなく、過去version、delete marker、multipart uploadを再確認する。
削除確認を行う。
aws s3api head-bucket \
--bucket '<DELETED_BUCKET_NAME>' \
--profile "${AWS_PROFILE}"
削除後は404が想定結果である。403の場合は、bucket名が他accountで再利用された可能性や権限不足を区別できないため、AWS Consoleとresource一覧でも確認する。
8. 検証専用IAM resourceを削除する
IAM resourceはS3とGlueの削除確認後に処理する。共有roleや共有policyは削除しない。
Access keyを無効化してから削除する
対象userとkeyを確認する。
aws iam list-access-keys \
--user-name '<DELETE_TARGET_USER>' \
--profile "${AWS_PROFILE}"
まず無効化し、利用者がいないことを確認する。
aws iam update-access-key \
--user-name '<DELETE_TARGET_USER>' \
--access-key-id '<ACCESS_KEY_ID>' \
--status Inactive \
--profile "${AWS_PROFILE}"
観察後に削除する。
aws iam delete-access-key \
--user-name '<DELETE_TARGET_USER>' \
--access-key-id '<ACCESS_KEY_ID>' \
--profile "${AWS_PROFILE}"
IAM userを削除する
userを削除する前に、attached policy、inline policy、group、login profile、service-specific credentialなどを確認して削除する必要がある。
aws iam list-attached-user-policies \
--user-name '<DELETE_TARGET_USER>' \
--profile "${AWS_PROFILE}"
aws iam list-user-policies \
--user-name '<DELETE_TARGET_USER>' \
--profile "${AWS_PROFILE}"
aws iam list-groups-for-user \
--user-name '<DELETE_TARGET_USER>' \
--profile "${AWS_PROFILE}"
すべての関連resourceを外した後、検証専用userを削除する。
aws iam delete-user \
--user-name '<DELETE_TARGET_USER>' \
--profile "${AWS_PROFILE}"
IAM roleとcustomer managed policy
roleはinstance profile、attached policy、inline policyを確認する。policyは他principalへのattachmentと全default以外のversionを確認する。
aws iam list-entities-for-policy \
--policy-arn '<DELETE_TARGET_POLICY_ARN>' \
--profile "${AWS_PROFILE}"
他principalが表示されたpolicyは削除しない。検証専用であることを確認してからdetach、role/user削除、policy削除の順で処理する。
9. 検証専用KMS keyを削除する
SSE-S3のAES256だけを使用した場合、customer managed KMS keyは作成されていないため、この作業は不要である。
SSE-KMS用に検証専用keyを作成した場合は、他bucket、CloudWatch Logs、Secrets Managerなどが利用していないことを確認する。KMS keyの削除は即時ではなく、待機期間を指定してscheduleする。
aws kms describe-key \
--key-id '<DELETE_TARGET_KMS_KEY_ARN>' \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}"
検証専用であることを承認後、AWSの許容範囲内で待機期間を指定する。
aws kms schedule-key-deletion \
--key-id '<DELETE_TARGET_KMS_KEY_ARN>' \
--pending-window-in-days 30 \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}"
共有keyをschedule deletionすると、他dataを復号できなくなる。key policy、aliasだけで判断せず、利用resourceを確認する。
10. local側のAWS設定を撤去する
AWS resource削除後、各serviceに残るcredentialと設定を撤去する。
| service | 確認するもの |
|---|---|
| Kafka Connect | S3 Sink connector、AWS EnvironmentFile、S3 Sink plugin |
| Spark | AWS profile、Glue catalog設定、hadoop-aws依存 |
| Flink | AWS profile、EnvironmentFile、S3 checkpoint設定、Iceberg AWS bundle |
| Trino |
iceberg_aws.properties、AWS EnvironmentFile |
| Zeppelin |
trino_aws Interpreter、AWS用Notebook |
| rclone | AWS remoteとcredential |
設定fileを削除する前に、他のAWS workloadが同じcredentialやlibraryを使っていないことを確認する。
特に次は自動的に削除しない。
- Sparkが別の
s3a://を利用している場合のhadoop-aws - Flinkが別のS3 checkpointを利用している場合のS3 filesystem plugin
- Trinoが別のAWS catalogを利用している場合のAWS credential
- 共有Kafka Connect workerのS3 Sink plugin
11. 削除結果を確認する
Glue
aws glue get-database \
--name "${GLUE_DATABASE}" \
--region "${AWS_REGION}" \
--profile "${AWS_PROFILE}"
Database全体を削除した場合、EntityNotFoundExceptionが想定結果になる。Tableだけ削除した場合は、get-tablesで対象Tableがないことを確認する。
S3
aws s3api list-buckets \
--profile "${AWS_PROFILE}" \
--query 'Buckets[].Name' \
--output text
削除対象bucket名が表示されないことを確認する。
IAM
aws iam get-user \
--user-name '<DELETED_USER>' \
--profile "${AWS_PROFILE}"
aws iam get-role \
--role-name '<DELETED_ROLE>' \
--profile "${AWS_PROFILE}"
対象を削除した場合、NoSuchEntityが想定結果になる。
料金
Cost Explorer、S3 Storage Lens、Billing画面で次を確認する。
- S3 current/noncurrent versionのstorage
- incomplete multipart uploadのstorage
- S3 APIリクエスト
- Glue Data Catalogの利用量
- KMS key
- データ転送
- CloudWatch Logsなど周辺resource
Cost Explorerなどの反映には時間差があるため、削除直後だけでなく翌日以降も確認する。
削除できない場合の確認表
| 症状 | 主な原因 | 確認 |
|---|---|---|
BucketNotEmpty |
過去version、delete marker、multipart upload |
list-object-versions、list-multipart-uploads
|
AccessDenied |
IAM不足、bucket policy、Object Lock | caller identity、policy、retention、legal hold |
| objectが再作成される | writerが残っている | Connect、Spark timer、Flink job、scheduled query |
| Glue Table削除後もS3容量が残る | Glue削除はdata fileを削除しない | S3 version一覧 |
| IAM userを削除できない | key、policy、group等が残っている | IAMの関連resource一覧 |
| KMS keyを削除できない | 待機期間、共有利用、権限 | key state、key policy、利用resource |
まとめ
AWS検証環境の削除では、S3 bucketだけを削除しようとしても完了しない。本seriesではversioningを有効にしているため、current object、noncurrent version、delete marker、multipart uploadをすべて確認する必要がある。
削除順序は、writer停止、backup、Glue、S3 data、S3 bucket、IAM、KMS、local設定とする。IAM credentialを先に消すと後続の削除ができず、writerを止めずにS3を空にするとobjectが再作成される。
本番移行先としてAWSを利用し続ける場合は、この削除手順を実行しない。検証専用resourceと共有resourceを明確に分け、account ID、region、resource名、Object Lock、backup、承認を確認してから削除する。
参考資料
- Amazon S3: Deleting object versions from a versioning-enabled bucket
- Amazon S3: Aborting a multipart upload
- AWS CLI: abort-multipart-upload
- AWS Glue: DeleteDatabase
関連記事
実機検証について
本記事の削除手順は、執筆時点では実機環境での一連の動作検証をまだ完了していない。掲載commandにはresourceを完全削除する操作が含まれるため、検証用AWS accountまたは隔離された専用resourceで確認する必要がある。
実施する場合は、削除対象一覧、backup、restore手順、Object Lock、共有利用の有無を確認し、複数名で承認した正確なresource名だけを対象にする。実機検証の結果に応じて本記事を更新する予定である。