Container Registryからタグの無いイメージを自動で削除しよう

# はじめに
IBM Cloud でコンテナリポジトリのサービスであるContainer Registryが、2022年2月より タグの無いイメージ についても容量課金することになりました。本来クラウドリソースを使用して保管しているので、タグが有ろうと無かろうと容量課金されてもおかしくないように思いましたが、2022年の2月から容量課金が発生します。
Container RegistryにコンテナイメージをPushする際は、タグを明示的に指定しなければlatestタグが自動で付与されるので、タグの無いイメージを意図的にPushすることはできません。
つまり、タグが無いイメージはContainer Registryの中で既に存在するタグと同一のものがPushされた時に生成されるか、意図的にコンテナイメージに付与されたタグを削除するかでしか発生しません。そういう観点では、基本的にContainer Registryに保管されているタグの無いイメージはゴミ情報であることがほとんどであるため、IBM CloudのCLIでは、タグの無いイメージを一括削除する[コマンド](https://cloud.ibm.com/docs/Registry?topic=container-registry-cli-plugin-containerregcli&locale=en#ic_cr_image_prune_untagged)が提供されています。

# 課題
とは言え、先ほど紹介したタグの無いイメージを一括削除するコマンドを定期実行するのが良いかと言うと、そもそもその運用は面倒ですし、意図せず同じタグのイメージをPushしてしまって、タグが無い状態になってしまったものが、いきなり削除されても困る、と思うかもしれません。
(Container Registryで削除したイメージは30日間はゴミ箱に移動されて、その期間内であればいつでも戻せるので、本来はそこまで気にする問題ではない気もします)

また、Container Registryでは、Retention Policy(自動保管ルール)を定義することができ、保管する世代数とタグの無いイメージをどのように扱うかをオプションで指定できますが、このオプションが現在イケてない状態です。
こちらの[ドキュメント](https://cloud.ibm.com/docs/Registry?topic=Registry-registry_retention#retention_images)に記載されていますが、コマンドのオプションで指定可能な `--retain-untagged` これが曲者です。

オプションの名前だけ見ると、タグの無いイメージを保持するか？となりますが、こちらのオプションの有無による動作の違いは以下になります。
- オプション有 : タグの無いイメージは **全て保管** され、自動削除の対象とならない
- オプション無 : タグの有るイメージと **まったく同じ扱い** でイメージ保管ルールが適用される

オプション無は、何を言っているかというと下図のイメージで、5世代保管と指定した場合、タグの有無に関係なく最新のコンテナイメージが5つ保存されることになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/937d3368-6eef-0bbb-b4b5-9cfb3cfe7f29.png)

結果、タグの有るイメージだけ世代管理して、タグの無いイメージを一定のルールで削除する、ということが出来ないことが難点です。

今回は、タグ無イメージを世代数を指定して削除するツールを作成しました。

# Container Registry タグ無イメージ世代管理ツール
[Docker hub](https://hub.docker.com/repository/docker/hiroapps/cr-auto-delete-untagged-image)で公開しています。

実行には、下記環境変数が必要です。
```
APIKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ACCOUNT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DOMAIN=jp.icr.io
NAMESPACE=xxxxxx
REPOSITORY=xxxxxx
GENERATION_NUM=10
```
**APIKEY**にはContainer Registryの管理者権限を与えてください。
**ACCOUNT_ID**は下図のようにポータル上部の**管理**から**アカウント設定**を選択して、IDを確認してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/34b906db-cfa1-d146-c296-32cb9ade120b.png)
**DOMAIN**は、[ドキュメント](https://cloud.ibm.com/docs/Registry?topic=Registry-registry_overview&locale=ja#registry_regions_local)記載のドメイン・ネームから利用しているドメインを選択してください
**NAMESPACE**はContainer Registryで利用しているNamespace名
**REPOSITORY**は、タグの無いイメージのイメージ名を指定してください
**GRNERATION_NUM**は、保管しておきたい世代数を指定ください

対象のイメージ毎にしか動作させられないですが、逆にイメージ毎に世代管理設定ができるので、更新頻度に合わせて設定出来ればと思います。

# Code Engineで稼働設定
定期実行させることを前提としていますが、お金かけたくないですよね。
コンテナ化しているので、既にKubernetesといった環境をお持ちであれば、Cronjobで動かすことも出来ると思いますが、そちらの環境のリソースを使いたくない、Manifestの管理もしたくないところもあるでしょう。
無償枠があるCode Engineを使ってみましょう。

ポータルからCode Engineを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/15bc1d64-1139-ce3d-4564-9d53bbbc2ce3.png)

新規であればプロジェクトをまず作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/cfdd34f4-d770-1a75-034c-adc1ec64eda2.png)

ロケーション、プロジェクト名、リソースグループを選択して作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/f2ee7521-ca16-873c-cffc-0339a0401542.png)

作成されたプロジェクトを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/65343c33-dde0-0e1e-206c-b17675826778.png)

シークレットおよびconfigmapを選択し作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/f11b4e89-5d5b-4e14-9ebb-4616e99f14dc.png)

シークレットを選択し、登録するkey-value群の名前を指定、後は登録するkey-valueを一つずつ指定します※CLIでは[.envファイルから生成可能](https://cloud.ibm.com/docs/codeengine?topic=codeengine-cli#cli-secret-create)です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/bfc0e782-fea1-19f2-fea2-218f291ef331.png)

作成を選択すると、作成されたsecretesの情報が表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/6d342265-f003-cba6-07f0-93b0fa089e43.png)

プロジェクトからジョブを選んで作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/db91728c-3064-8b79-4321-17eb4005643f.png)

まず、ジョブの名前を入れて、イメージ参照に以下のパスを入れる。イメージ構成のボタンから、Container Registryの中のイメージを指定することも可能です
`docker.io/hiroapps/cr-auto-delete-untagged-image:v1`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/6ce5e2aa-9bce-944e-1dc8-cda8c74f2e3a.png)

画面下部の**ランタイム設定**は、一番スペックの低い **0.125個のvCPU/0.25GB** を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/b95ec169-6607-0a57-da87-0f27dc4ac96b.png)

環境変数のオプションを開いて追加を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/8794d498-8428-6b23-452c-b2f8255d38d7.png)

画面右側で、シークレット全体の参照を選択し、先ほど作成したシークレットを選び完了を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/c4a4fed8-5f27-03af-6533-5530de48740d.png)

選択したシークレットが反映されていることを確認して、作成を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/df4a493f-5744-8ff4-cf1a-c69c584f188a.png)

これで、ジョブが作成されたので、ジョブの実行依頼を選択し、手動で実行します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/57d1a5fa-9bbc-a593-802b-f61b98237754.png)

画面右に表示されるので、ランタイム条件に変更が無ければ、ジョブの実行依頼を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/2ab2af12-9f07-a9c3-aff4-cd9920eb198a.png)

処理内容に問題無ければ、完了のステータスに遷移します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/b9213b29-680d-bcc6-87b5-b0883a008f05.png)

ログを参照したい場合は、LogDNAのサービスと連携していたら、以下のように参照可能です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/3230adc5-f828-0b38-8f7e-11c5c32b0bf2.png)
※これだと2つの古いタグ無しイメージを削除しています


ジョブが動くことが確認できたので、定期実行の設定を行います。プロジェクトからイベント・サブスクリプション、作成を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/8c2a41fd-e232-9942-7eb6-d4c31dfa2ec4.png)

周期タイマー、名前を指定して次へを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/0fbb236c-9c52-ea04-3e12-6a26368ea225.png)

crontab式で記述できるので、とりあえず1日1回を指定して次へを選択
※UTCでの時刻設定となるので、JSTでは +9:00 される点に注意
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/b9b8c34e-fe88-2e0d-6ed2-7e1dd8503f97.png)

カスタム・イベント・データは特に指定せず次へを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/b3fe9b3e-a2de-4820-b786-4c9418631bf7.png)

コンポーネント・タイプにジョブを選択、名前は今回作成したジョブの名前を選択し、次へを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/7d6fabe2-7a31-5701-4ebe-658604499ec5.png)

要約を確認して、問題なければ作成を選択
※ Crontab式では `0 0 * * *` で指定しているが、その下部に `9:00 JST` となっている点に注意
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/13889aea-d8e9-6480-ed28-62e8113a9ad6.png)

これで無事に定期実行のスケジュールが完了しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/087ca424-440f-04f9-3d09-9f736812dae2.png)

# 本当に無償枠に収まるのか？
Code Engineは[リンク先](https://cloud.ibm.com/codeengine/overview)記載の通り、下記が毎月の無償枠として提供されています
- 100,000 vCPU秒
- 200,000 GB秒
- 100,000 http要求

今回のランタイムは `0.125個のvCPU/0.25GB` となっています。
1回の処理が100秒で完了すると仮定すると、以下になるので、無償枠のはるか下で収まります。
- 0.125vCPU × 100秒 × 31日 = 387.5 vCPU秒
- 0.25GB × 100秒 × 31日 = 775 GB秒

# さいごに
もちろんContainer Registryで設定できるRetention Policyで、タグ無イメージの世代管理も出来るようになれば、このツールは不要になるかもしれませんが、イメージ単位で細かく設定できるので、必要に応じて使って頂ければと思います。