詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

Dockerfile をベースイメージの更新に自動で追従させる

前回のエントリで作った 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 がベースイメージの同じバージョンに基づいていること
    • ./DockerfileFROM foo:1.2.3./alpine/DockerfileFROM 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 がこちらです。このエントリを書くために探すまで、バージョンが上がってたことに気づかなかったので成功ですね。

Update google/cloud-sdk Docker tag to v292 by renovate · Pull Request #6 · motemen/docker-datastore-emulator · GitHub

タグを打つ

最後に、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

いかがでしたか? これが全部無料で実現できているだなんて、いったい自分は前世でどんな善行を積んできたんだ……とおののくばかりです。今生でもがんばりたいですね。

Dockerfile を元にコンテナを走らせてローカルにポートを割り当てるところまでを自動化 (boot2docker のラッパ)

……というのを書いてみた。boot2docker によって OSX でもかなり簡単に Docker が使えるようになり、開発に必要なミドルウェアを用意する助けになってくれるけれど、いちいち -p オプションでポート番号を指定するまでもなく、ミドルウェア(コンテナ)によって決まったポートが割り当てられていれば十分、ということもよくある。

そこでこの forward2docker。 以下のようにすると、Dockerfile からイメージを作成し、走らせて、EXPOSE されているポート番号 + 10000 番を割り当てて待ち受けてくれる。

% curl -LO https://raw.github.com/motemen/forward2docker/master/forward2docker
% chmod +x forward2docker
% ./forward2docker path/to/Dockerfile

やっていることは

  1. boot2docker のセットアップ
  2. Dockerfile からイメージの作成
  3. コンテナを起動
  4. boot2docker の VMssh して、Docker コンテナにポートフォワーディング
  5. 終了時にはコンテナを削除

ってな感じ。ポイントは ssh-L オプションを使ってるのと、expect 便利ですねってところです。

動作例

以下のような Dockerfile を用意して:

FROM ubuntu

RUN apt-get update
RUN apt-get -y install python
RUN apt-get clean

EXPOSE 8000

CMD [ "python", "-m", "SimpleHTTPServer" ]

こんな感じに動きます:

% ./forward2docker Dockerfile
[2014-02-12 09:42:38] boot2docker-vm is running.
[forward2docker] boot2docker initialized.
[forward2docker] Building image...
Uploading context 2.048 kB
Uploading context
Step 0 : FROM ubuntu
 ---> 9cd978db300e
Step 1 : RUN apt-get update
 ---> Using cache
 ---> d84a5d48bca3
Step 2 : RUN apt-get -y install python
 ---> Using cache
 ---> ea40812f2a47
Step 3 : RUN apt-get clean
 ---> Running in 333d55cc02c8
 ---> ee7660de7b2e
Step 4 : EXPOSE 8000
 ---> Running in 2f2af35885fc
 ---> 382d08a83e30
Step 5 : CMD [ "python", "-m", "SimpleHTTPServer" ]
 ---> Running in 93368aab402d
 ---> 3e29099a8799
Successfully built 3e29099a8799
[forward2docker] Starting container...
[forward2docker] bbde0dd94e1e25f8d8860366cccf5cb827db7147588eda313bb15b0b3833d1d0: listen on port 18000
[forward2docker] Starting port forwarding...
spawn ./boot2docker ssh -L18000:172.17.0.2:8000 -- echo Success! && docker logs -f bbde0dd94e1e25f8d8860366cccf5cb827db7147588eda313bb15b0b3833d1d0
Warning: Permanently added '[localhost]:2022' (RSA) to the list of known hosts.
docker@localhost's password:
Success!

この時点で、http://localhost:18000/ で Docker 上で動いているウェブサーバにアクセスできます。Ctrl-C で(ssh セッションを)終了させると、最後にコンテナを消しさっておわり。

^CKilled by signal 2.
[forward2docker] Cleaning up container...
% 

はてなで一緒に働きませんか?