前回のエントリで作った Docker イメージ motemen/datastore-emulator は、google/cloud-sdk をベースにしているが、このベースイメージがけっこうな頻度で更新される。とうぜん自分はその追従に手を煩わせる気はなくて、全部自動でやってほしい。
やりたかったこと
- google/cloud-sdk:x.y.z がリリースされたら、
- リポジトリ中の ./Dockerfile と ./alpine/Dockerfile の FROM を google/cloud-sdk:x.y.z(-alpine) に更新し、
- x.y.z タグを打って git push することで、
- Docker Hub に x.y.z(-alpine) タグとしてリリースする
これを自動かつ無料で実現したい。
採用しなかった案: 自分でなんか作る
はじめは適当な GitHub Actions をこしらえて、Docker Hub のイメージ更新を検出する、ということをやるつもりだった。
更新フィードを Docker Hub が吐いてくれれば……と考えていたけど、イメージは日付が新しければよいというものではなく、*-slim
みたいなバリエーションもある以上、タグをバージョンとして解釈して適切なものを選択する、としなくてはいけない。やればできそうなんだけど、Dockerflie の公式なパーザがないのはネックだった。それが理由じゃないんだけど、この案は不採用。もっと楽な方法があったからだ。
Renovate を使う
もうこれで説明しきったも同然なんだけど、最近試して気に入っていた Renovate が Dockerfile の更新に対応しているので、これを使う。簡単に言うと、依存関係を更新する Pull Request を作ってくれるやつです。
Whitesource Renovate - Automated Dependency Updates
Dockerfile を置いたリポジトリについて、Renovate を設定すれば勝手に更新を見つけてプルリクしてくれる。
docker:enableMajor
という、Dockerfile でメジャーバージョンの更新を有効にする preset が効いていなかった不具合があったのだけど、つい最近解消されました。
ちなみに Dependabot も Dockerfile の更新に対応しているが、複数の Dockerfile の更新をひとつの PR で処理する、というのができなさそうなので今回はそぐわなかった。他の理由もあって Renovate のほうが好みだというのもある。
自動マージを有効にするためにテスト書く
Renovate には automerge
という設定項目があって、これを使うと(CI が通ったあとに)PR を自動マージするってことができる。CI をうまく構成すればハンズフリーで運用できそうだ。というわけで以下のようなテストを書く。
- 全ての Dockerfile がベースイメージの同じバージョンに基づいていること
./Dockerfile
がFROM foo:1.2.3
で./alpine/Dockerfile
がFROM foo:2.0.0-alpine
だったらこまる
- それぞれの Dockerfile が正しく派生イメージになっていること
./alpine/Dockerfile
は*-alpine
みたいなタグになってること、ということ
それぞれをテストするスクリプトを書いて、GitHub Actions を仕込む。FROM
の解析は以下のような雑なワンライナーでお茶を濁している。
perl -nle 'print "$1\t$2\t$3" if /^FROM (\S+?):([0-9.]+)(?:-([^:-]+))?(?:$| )/' "$@"
うまく動いてマージされた Pull Request がこちらです。このエントリを書くために探すまで、バージョンが上がってたことに気づかなかったので成功ですね。
タグを打つ
最後に、master にマージされたら Dockerfile をベースに git tag を打ち、これをトリガに Docker Hub でビルドさせる。
これも先ほどの雑なワンライナーを使って、こんな感じにする。GitHub Actions の checkout@v2 であれば特に設定なしで git push できちゃうので、便利。
https://github.com/motemen/docker-datastore-emulator/blob/8aed69b/.github/workflows/tag.yml#L17-L23
tag=$(./scripts/parse-dockerfile-from Dockerfile | cut -f2) echo "tag: $tag" if ! git rev-parse --quiet --verify "refs/tags/$tag" > /dev/null; then git tag "$tag" git push --tags fi
いかがでしたか? これが全部無料で実現できているだなんて、いったい自分は前世でどんな善行を積んできたんだ……とおののくばかりです。今生でもがんばりたいですね。