😶‍🌫️

IaCのステート管理は奥が深い

2024/12/21に公開

はじめに

ecschedule という ECS Scheduled Task をコード管理するツールがあります。
今回はこのツールの削除機能(prune)を既存リソースに対して使うための移行方法をまとめました。

https://github.com/Songmu/ecschedule

IaCでリソースを削除するには

コード管理されているリソースを削除したい場合、定義を削除するだけで実現できるツールとできないツールがあります。その違いは何なのでしょうか?

  • Ansible: ステートを持たないため、削除定義(absent)を行う必要がある
  • Terraform: ステートをS3で管理することで実現可能✅
  • CloudFormation: ステートがCloudFormation内部で自動管理しているため実現可能✅
  • Kubernetes: ステートがk8s内部(etcd)で管理されているため実現可能✅
  • ecschedule: ステートを持たないため実現不可※2023年8月19日まで

違いは、コードでどのリソースを管理しているかという状態(ステート)の情報を持つか否かで決まります。

ecscheduleが削除に対応🎉

2023年8月20日、ecscheduleに待望の削除機能(prune)が実装されました。
https://x.com/waday_x/status/1868680468193034331?s=46

使い方は -prune オプションを出すだけ。非常に簡単に導入できそうです。

% ecschedule -conf ecschedule.yaml apply -all -prune

ちょっとまって

先ほどステートの話をしましたが、README.mdには全くステートの話が出てきません。
このオプションをコマンドに出すだけで既存のリソースもすべて削除対象にできるのでしょうか?

Issueからはじめよう

こちらのIssueを読んでみると、ecscheduleに削除対処として認識させるためにはEventBridge RulesのタグにECSクラスタ名を設定する必要があることがわかりました。

https://github.com/Songmu/ecschedule/pull/71

Terraformであればimportブロックやstateコマンドでstateファイルに取り込む操作を行いますが、ecscheduleはstateの代わりにタグを使っているということですね。

ということで、既存のリソースに対してpruneオプションに対応するためには、各ルールにタグを打ち回る必要があることがわかりました。

移行前チェックリスト

タグを打つ以外にも注意点は色々あるため、チェックリストという形で共有いたします。

  • EventBridge Rule にタグが設定されていること

  • 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]
    

    https://github.com/Songmu/ecschedule/issues/26

移行スクリプトの実行

  • 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 だと仮定した実装です。適宜修正してください。
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