hitode909の日記

趣味はマリンスポーツですの日記です

Dockerイメージのlatestタグのダイジェストをリポジトリに置いておくグッズをGitHub Actionsで作った

社内システムをAWS ECS上で運用しているのだけど、ECSってやつでデプロイできるらしい…くらいの知識からスタートしていたので、運用するイメージの指定にlatestタグのまま運用していた。
次のバージョンでは起動する前にデータベースのマイグレーションをしてほしい、という案内がきて、ということは、何かの拍子に最新に上がってしまうとアプリケーションが正しく起動できなくなることを意味している。どのタイミングでイメージが更新されるかあまり考えていなかったけど、オートスケールしてタスクが増減したり、EC2ごと壊れたらECSが立て直してくれるので、そういう任意のタイミングでlatestタグがpullされることになる。
latestタグのまま運用するのはよくあるアンチパターンなようで、ちょうどこの人と同じミスをしていた。

latestを使うのはやめましょう、ということで、取り急ぎダイジェストを使うようにしたけど、次の課題は、イメージの更新があったことをどうやって知るのか?ということ。プライベートなイメージなので、ログインしないと新着を確認することができない。毎日DockerHubにログインしてイメージのダイジェストを確認するのは面倒そう。
自分たちで開発しているならイメージをいつビルドしたかはGitHubでも見ればわかるけど、他社が作ったイメージを共有してもらっている形なので、いつ更新されるかわからない。

GitHub Actionsから取ってくる

人間がやるのは面倒なのでCIからやると良いにちがいない。以下のことをやってくれるのをGitHub Actionsで作れば自動化できそう。

  1. Secretsから認証情報を取ってきて、DockerHubにログインする
  2. 該当イメージのtag情報を見て、latestであるもののdigestを調べる
  3. digestをファイルに書き出す
  4. 変更をPull Requestする

これを1日1回くらい実行しておけば毎日の最新イメージをPull Requestで届けてもらえる。
定期実行してファイルを更新するのは普段仕事でメンテナンスしているプロダクトでもやっていて、協力会社の方がGoogle Spreadsheetを更新してくれるので、機械的に取り込んでPull Requestし、人間が目視でチェックしてからマージするとデータの更新が完了する、というのをやっている。

練習でaws-cliのダイジェストを記録するリポジトリを作ってみたらあっさりできた。

実装はこういう素朴なシェルスクリプトだけで、curlしてjqしてファイルに書き出して終わり。

curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/amazon/aws-cli/tags \
 | jq -r '.results[] | select(.name=="latest").images[] | [select(.architecture=="amd64")][0].digest' \
 > digest.txt

ワークフローのYAMLのほうが大きい。何時でもいいから00:00にしているけどUTCなので注意。

生成されたPull Requestの様子


うまくいったので社内システム向けにも同じ変更を用意した。実行するイメージのダイジェストはTypeScriptのコードに直接書かず、ファイルに切り出して読む形にした。
snapshot testがあったので、ファイルを更新したあとsnapshotを更新してからPull Requestするようにしたところ、人間はstaging環境とかで動作確認して、大丈夫そうだったらマージしてproductionにもデプロイする、というワークフローで暮らせそうになってきた。

要検討:アーキテクチャについて

Dockerイメージにはアーキテクチャという概念があって、対象のCPUとかを選べる。とりあえずamd64のものを探す、くらいにしてみている。
実行環境に合わせたアーキテクチャ選びはDockerコマンドが内部的にやってくれているはずなので、API呼び出しを手で書かなくても、docker loginしてdocker pullして、降ってきたイメージを調べるというアプローチも考えられそう。しかし実行したくないのにイメージ本体をダウンロードしてしまうのがいまいちで、dry-runして解決されたイメージ情報だけもらえるとよかったけど、dry-run機能はなさそう。
GitHub ActionsではUbuntuで実行するけど本番環境のアーキテクチャは別です、みたいなときには更新環境と本番環境が違うので、platformは明示的に渡すしかない?
nginxとかだとアーキテクチャがたくさんあって練習で触って見るには複雑だったので、アーキテクチャが1つだけ置いてあって簡単そうなamazon/aws-cliで練習した。

要検討(2): Renovateでできる説

RenovateはDockerイメージの更新に対応していて、また、Regex Managerを使うと、このファイルのここをこの言語として更新してくれ、という指定ができるので、Renovateだけでもできるという説がある。id:ikesyoさんに教えていただいた。無理にRenovateでやらなくても、snapshotの更新もやるなら今回のように手書きしてもよさそうですね、と話していた。