ECRのライフサイクルの設定漏れで障害になりました・・・汗

どもども、ogimanです。

[Lancers（ランサーズ） Advent Calendar 2024](https://qiita.com/advent-calendar/2024/lancers) のトリを務めさせていただきまっす。

今回は実務でハマった障害について語っていこうかなと思います。

おっーーーーと！大事なことを言い忘れてました！「メリークリスマス:santa_tone2:」

さて、この記事が皆様へのクリスマスプレゼントになるでしょうか？
早速、いってみましょう！

今回はストーリー調にしてみたので、ポイントだけ知りたい方は「結論」「事象」「原因」「解決」だけ見れば良し！

# 結論

ECRのライフサイクル設定を行っておらず、イメージの数が上限に達した。
結果、ECRへPushする際に`403 Forbidden`が発生してデプロイが出来なくなる。


# 誰向け
- AWSを使う方
- GithubActionsを使う方

# 事象（障害）

ある日の11時頃の出来事

Aさん「以下のエラーが出て本番デプロイ失敗する状況ですが、どのように対応すれば良いかわかります？」

```
Error: buildx failed with: ERROR: failed to solve: failed to push 8xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: failed commit on ref "manifest-sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": unexpected status from PUT request to https://xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/v2/xxxxx/manifests/sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: 403 Forbidden
```

Bさん「いったん、GithubActionsのワークフローをre-runしてみましょう！」

そして、ワークフローをre-runしてみるも、状況は変わらず・・・
SREチームに声が掛かる！？

# 問題

ECRへPushする際に`403 Forbidden`が発生してデプロイが出来なくなる。
そして、すべてのリリースが出来なくなった・・・

```
Error: buildx failed with: ERROR: failed to solve: failed to push 8xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: failed commit on ref "manifest-sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": unexpected status from PUT request to https://xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/v2/xxxxx/manifests/sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: 403 Forbidden
```

# 調査

さて我らがSREチームの出番だ！
解決に向けて動く！

まず初めにogimanには一つ気になることがあった。
実は昨日にGithubActionsで使っているGitHub Packagesを大幅にアップデートしていた。
理由はEOLを迎えるGitHub Packagesが多かったため。

翌日にリリース周りでなにかがあれば、アップデートが影響していると完全にバイアスが掛かっていた。

従って、Aさんから声を掛けられた時点で、アップデートの切り戻しを想定して行動した。
そして、実際にPRをリバートして切り戻した。

が・・・・・・・・・・

状況は何も変わらない。なぜだ？？？

改めて調査を進めていく

まず、いつからこの事象が発生したのか？
昨晩にGitHub Packagesをリリースしているので、現時点までの間でリリースはしていないか？
- リリースしている場合はGitHub Packagesが原因ではない
- リリースしていない場合はGitHub Packagesの可能性は完全には拭えない

リリースの履歴を確認する。

![スクリーンショット 2024-12-25 20.08.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144316/447fcd62-6aad-0794-c299-b695b609ad70.png)


おっ！事象が発生したのは11時頃、それより前の10時頃ではリリースがうまくいっている。
これでGitHub Packagesが原因ではないことが確定した！

では何が起きているのか？

`403 Forbidden` と出ているので、権限周りを疑う。

GithubActionsからAWSへアクセスするのにOIDCを利用している。
以前にOIDCのFingerprintが変わり、デプロイに影響したことがあった。
従って、次はOIDCを疑ってみた。

OIDC周りに関してもGitHub Packagesをアップデートしているので記載に問題がないか確認
→問題なし

意図しないところでws_iam_policyが変わっていないか確認
→問題なし

OIDCのFingerprintが変わっていないか確認
→問題なし

なるほど、完全に手詰まってきた:skull:
原因がさっぱりわからない。

（ちなみにここまでSREチームはモブプロ的に障害対応を行っています。一つの画面を共有して、あーでもない、こーでもないと意見を出し合っている感じです。）

チームは改めてイメージを作成してPushする流れをコードおよびAWSのコンソールを確認しながら追っていく。

さらに追っていくこと10分！ベテランSREチームメンバーが気づく！

「あれ？なんかイメージの数がキリが良すぎません？？」

![スクリーンショット 2024-12-25 20.08.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144316/96a27078-b4e8-7f8d-8f6f-89ed59e354f9.png)


ということで調べてみると・・・

ガビーン＆キターーー
`リポジトリあたりのイメージの最大数：10,000`

![スクリーンショット 2024-12-25 20.45.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144316/9b624912-e840-e636-3b01-7b57c1eb0310.png)

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/service-quotas.html


ここでSREチームは完全に勝利を確信！
手動でいくつか古いイメージを削除して、GithubActionsのワークフローを再実行。
リリースできるではありませんか！

3時間の戦いの末、我々は解決したのでありました！
めでたしめでたし:relaxed:



# 原因
と、終わりたくなるところですが、きちんと原因を追っていきましょう。

なぜ、今回このようなことが起きたのか？
それはECRのリポジトリのライフサイクルを正しく設定されていなかったからです。

AWSのリソースはすべてterraformで管理しています。

以下のようにリポジトリをvariableで指定して`aws_ecr_lifecycle_policy`を割り当てていました。
一見、variableに追加しているのであれば問題なさそうに見えますが、違うところで問題が起きていました。

```
variable "ecr-repos" {
  type = list(string)
  default = [
    "xxxxxx",
    "xxxxxx",
    "xxxxxx",
    "xxxxxx",
    "xxxxxx"
  ]
}

## 前述のecr-reposで設定したvariableを配列として渡し、繰り返し処理
resource "aws_ecr_lifecycle_policy" "common_ecr_lifecycle_policy" {
  count      = length(var.ecr-repos)
  repository = element(var.ecr-repos, count.index)

  policy = <<EOF
{
    "rules": [
        {
            "rulePriority": 1,
            "description": "Keep last 30 images",
            "selection": {
                "tagStatus": "any",
                "countType": "imageCountMoreThan",
                "countNumber": 30
            },
            "action": {
                "type": "expire"
            }
        }
    ]
}
EOF
}


```

なぜか`aws_ecr_repository`をリポジトリを指定する際に`variable`を利用せずに、それぞれのリソースをそのまま記載していたのです。


```
resource "aws_ecr_repository" "xxxx" {
  name = "xxxxx"
}

resource "aws_ecr_repository" "xxxx" {
  name = "xxxxx"
}

```

つまり、新しくリポジトリを追加した際に`aws_ecr_repository`だけ追加して、`aws_ecr_lifecycle_policy`を追加し忘れていたということです。

これによりリリースする度にイメージが作成され、`10,000`という上限に達してしまいました。
残念すぎる・・・・

これによる影響は以下です。
- リリースが3時間止まる
- ECRに`10,000`というイメージが残っているので無駄にコストが掛かっていた（毎月$170程度ドブに捨てていた）

みなさん。terraformの設計は重複管理が出来ないように仕組み化しておきましょうね（猛省）

※小話
本来、terraformで繰り返し処理させる場合、count より for_each を使ったほうがベターだと思います。詳細は以下の記事参照ください。
https://tellme.tokyo/post/2022/06/12/terraform-count-for-each/

# 解決策

暫定：
- 上限に達したリポジトリからイメージを削除する

恒久：
- `aws_ecr_lifecycle_policy`を設定していないリポジトリに `aws_ecr_lifecycle_policy`を設定する
- 重複管理しないようにterraformの設計を見直す

# 最後に

この記事で言いたかったことは以下です。

- `403 Forbidden` と`ECRの上限数`は関係ないやろーーーーーー！！
- ECRのライフサイクルはしっかり設定しましょう
- terraformでんるべく重複管理しないような設計にしましょう

記録を残すことで同じ事象にハマった人の役に立ちますように・・・:clap:

この記事が少しでも参考になったら、 :heart: お願いします！
励みになります♪

以上です！

それでは良い年末年始をお過ごしください:robot: