IaCのステート管理は奥が深い
はじめに
ecschedule
という ECS Scheduled Task
をコード管理するツールがあります。
今回はこのツールの削除機能(prune)を既存リソースに対して使うための移行方法をまとめました。
IaCでリソースを削除するには
コード管理されているリソースを削除したい場合、定義を削除するだけで実現できるツールとできないツールがあります。その違いは何なのでしょうか?
- Ansible: ステートを持たないため、削除定義(absent)を行う必要がある
- Terraform: ステートをS3で管理することで実現可能✅
- CloudFormation: ステートがCloudFormation内部で自動管理しているため実現可能✅
- Kubernetes: ステートがk8s内部(etcd)で管理されているため実現可能✅
- ecschedule: ステートを持たないため実現不可※2023年8月19日まで
違いは、コードでどのリソースを管理しているかという状態(ステート)の情報を持つか否かで決まります。
ecscheduleが削除に対応🎉
2023年8月20日、ecscheduleに待望の削除機能(prune)が実装されました。
使い方は -prune
オプションを出すだけ。非常に簡単に導入できそうです。
% ecschedule -conf ecschedule.yaml apply -all -prune
ちょっとまって
先ほどステートの話をしましたが、README.mdには全くステートの話が出てきません。
このオプションをコマンドに出すだけで既存のリソースもすべて削除対象にできるのでしょうか?
Issueからはじめよう
こちらのIssueを読んでみると、ecscheduleに削除対処として認識させるためにはEventBridge RulesのタグにECSクラスタ名を設定する必要があることがわかりました。
Terraformであればimportブロックやstateコマンドでstateファイルに取り込む操作を行いますが、ecscheduleはstateの代わりにタグを使っているということですね。
ということで、既存のリソースに対してpruneオプションに対応するためには、各ルールにタグを打ち回る必要があることがわかりました。
移行前チェックリスト
タグを打つ以外にも注意点は色々あるため、チェックリストという形で共有いたします。
-
EventBridge Rule にタグが設定されていること
- Key: ecschedule:tracking-id
- Vlue: ECSクラスタ名
https://github.com/snaka/ecschedule/blob/cf420a1999df68976744c687545e02f3e9126b34/rule.go#L292-L293
-
EventBridge Rule のターゲットIDがEventBridgeルール名と同じであること
- ルール名が
batch-schedule-1
ならターゲットIDもbatch-schedule-1
に揃えてください
https://github.com/snaka/ecschedule/blob/cf420a1999df68976744c687545e02f3e9126b34/rule.go#L529-L531 - ターゲットIDがEventBridgeルール名と違う場合、ターゲットが削除されずエラーになります
[ecschedule] 💢 operation error CloudWatch Events: DeleteRule, https response error StatusCode: 400, RequestID: xxxx, api error ValidationException: Rule can't be deleted since it has targets.
- ルール名が
-
ecshedule apply -prune
を実行する環境に下記ポリシーが付与されていること"resource-groups:SearchResources", "tag:GetResources", "events:TagResource"
-
クラスタに設定されている全てのルール定義をパラメータとして渡すこと
-
ecschedule.yaml
は1つのタスク定義に紐付くルールのみではなく、ECSクラスタに設定された全てのルールを含める必要があり、含めなかったルールは全て削除されます😱 - 下記Issueに紐付くPRがマージされれば、より多くのユースケースに対応できるようになりそうです
https://github.com/Songmu/ecschedule/issues/92
ecschedule -conf ecschedule.yaml apply -all -prune -dry-run
-
-
(optional)ECSクラスタに設定された全てのルールをdumpする場合、ターゲット設定が削除された中途半端な状態のルールが残っていないこと
- panic になります🤯
$ ecschedule dump --cluster <クラスタ名> --region ap-northeast-1 panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x2 addr=0x8 pc=0x10322adec]
移行スクリプトの実行
- ECS Scheduled Task向けのルール全てにタグを打つスクリプトを実行します
region=$(aws configure get region)
account_id=$(aws sts get-caller-identity --query Account --output text)
rules=$(aws events list-rules --query "Rules[].Name" --output text)
for rule in $rules; do
targets_json=$(aws events list-targets-by-rule --rule "$rule")
ecs_target=$(echo "$targets_json" | jq -r '.Targets[] | select(.EcsParameters) | .Arn' 2> /dev/null)
if [ -n "$ecs_target" ]; then
cluster_name=$(echo "$ecs_target" | sed 's/.*cluster\///')
aws events tag-resource \
--resource-arn "arn:aws:events:$region:$account_id:rule/$rule" \
--tags Key="ecschedule:tracking-id",Value="$cluster_name"
echo "タグを設定しました: ecschedule:tracking-id=$cluster_name"
fi
done
- (optional)ターゲットIDの修正が必要な場合はその処理もスクリプトに含めることを推奨します
- 変更前のターゲットIDが
default
だと仮定した実装です。適宜修正してください。
- 変更前のターゲットIDが
for rule in $rules; do
targets_json=$(aws events list-targets-by-rule --rule "$rule")
# 処理対象をターゲットIDが `default` かつ `EcsParameters` フィールドが存在するものに絞る
new_targets_json=$(echo "$targets_json" | jq --arg new_id "$rule" '
.Targets |= map(
if .Id == "default" and .EcsParameters then
.Id = $new_id
else
.
end
)')
if [[ "$(echo "$new_targets_json" | jq -c .)" != "$(echo "$targets_json" | jq -c .)" ]]; then
# 既存のターゲットを削除
aws events remove-targets --rule "$rule" --ids "default"
# リネーム後のターゲットを追加
aws events put-targets --rule "$rule" --targets "$(echo "$new_targets_json" | jq '.Targets')"
fi
done
おわりに
IaCのステート管理を理解することで、今回のような移行作業も進めやすくなります。
IaCは奥が深くて楽しいですね!
Discussion