Docker Hub で Vim の最新版をタグ付きで自動ビルドするようにした話

今まで Docker Hub 上で最新の Vim のビルドが行われるように設定していたが、ふと思い立って過去のバージョンについても一通りタグに残ってると良さそうだと思って設定した際の記録。

ちなみにこの作業を開始したのは 2018 年 1 月です。どんだけかけとるんだ。

作業前の状態

Docker には Build Trigger 機能があり、URL を叩くことで任意のタイミングでビルドを走らせることができる。

この機能を使い、IFTTT の RSS Feed サービスで Vim のコミット Feed を監視して、新しいコミットがあった際にトリガーを実行し、Webhooks サービスを使って Docker の Build Trigger の URL を叩くことで、latest が常に最新の Vim のバージョンになるようにしていた。

これだと常に最新版の Vim のイメージは作られるが、過去のバージョンについては参照できない。

目標

  • Vim の公式リポジトリに新しいコミットがあったら、自動で Docker Hub 向けにビルドを行う
    • バージョンでタグを付け、過去のビルドも参照できるようにする
  • Vim 7.4 以降の過去のバージョンについてもビルドを行い、参照できるようにする。
  • Docker Hub 側で自動ビルドを行う (自前のマシンでビルドして push はしない)
  • トリガー部分も無料でがんばる
    • 個人的には自宅サーバを使う手もあるが、これは使わないでやる

Dockerfile

まずは指定のバージョンの Vim をビルドできるように ARG として VIM_VERSION を追加した

今までは常に最新の Vimソースコードを取得してビルドしていたが、引数によって特定の Vim のバージョン(Git リポジトリ上の reference)のビルドができるようになった。

また、せっかくなので各種外部インターフェースの有効無効も環境変数で設定できるようにしてみた。デフォルトは全てオフで、VIM_ENABLE_ALL を空以外にすると外部インターフェースが有効になる。

Docker Hub 側の準備

次に Docker Hub で前述のタグを利用したビルドを行えるようにする。

Docker Cloud には実際のビルド処理をスクリプトで置き換える機能があり、実はこれは Docker Hub でも使える1

autobuild のリポジトリ内の Dockerfile と同じディレクトリに hooks/ ディレクトリを作成し、その中にbuild という名前の実行可能なスクリプトを置いておけばよい。今回の場合は以下のようなスクリプトを用意した。

#!/bin/bash

# 実際にはもっと色々している
# VIM_VERSION に指定している ${SOURCE_BRANCH} については後述
docker build --build-arg "VIM_VERSION=${SOURCE_BRANCH}" --build-arg "VIM_ENABLE_ALL=" -t "${IMAGE_NAME}" .
docker build --build-arg "VIM_VERSION=${SOURCE_BRANCH}" --build-arg "VIM_ENABLE_ALL=yes" -t "${IMAGE_NAME}-full" .

通常ビルド(外部インターフェースなし)と、フルビルドをそれぞれ用意することにした。追加のイメージができるので、push 時に追加で push する必要がある。このために、動作を置き換えるのではなく追加の処理をするために post_push スクリプトを用意する。通常の push のあとに実行される。

中身は v8.0.xxxx-full のような full 付きのタグを push しているのと、現在のビルドが最新版だった場合は同じイメージを latest としても push している。

この結果、以下のようなファイル配置になった。

|- Dockerfile
`- hooks/
   |- build
   `- post_push

ビルドのトリガー方法

Vim のコミット Feed を参照して、新しいコミットがあったらビルドする点は変わらない。ただし、今までは常に最新版をビルドしていたが、今回からは指定したバージョンをビルドする必要がある。

ビルドトリガーから任意のパラメータを環境に渡す方法は見付けられなかった。そこで一般的なフローと同様に、タグが push された際にそのタグをビルドするようにした。これは先ほどのスクリプト内で参照している ${SOURCE_BRANCH} で取れる。

厄介なのは、Autobuild で連携しているリポジトリを取得する部分は変更できなさそうだということだ。どうも Docker Hub の Autobuild は Dockerfile があるリポジトリとプロダクトのリポジトリが同一である想定であるらしく、連携しているリポジトリにタグが push されるとビルドがトリガーされてそこから対象タグを clone し、Dockerfile を参照してビルドする。

つまりどういうことかと言うと、今回のようにプロダクトのリポジトリDockerfile を管理しているリポジトリが別になっている場合、Dockerfile しかないリポジトリに対して v8.0.0000 のようなバージョンのタグを無駄に push することになる。 仕方ないので今回はこの方法で行くことにした。つまり、Dockerfile を管理しているリポジトリに対して Vim のバージョンのタグを push すると、Docker Hub 上でそのバージョンの Vim のビルドが走る仕組みになる。

ビルドをトリガーする

Web hook を叩く方法から Git リポジトリにタグを作る方法になったので、GitHub API でタグを作ることにする。

これまでと同じように IFTTT でやりたいところだが、GitHub API は User-Agent の指定を必須としているところ、IFTTT は User-Agent を送らず、また HTTP リクエストヘッダを自由にカスタマイズすることができない。つまり IFTTT は今回は使えない。

そこで今回採用したのが Google Apps Script だ。採用した理由は、無料だから。

で、今回のために作ったのが以下。

https://github.com/thinca/gas-docker-vim-updater

せっかくの機会なので TypeScript に入門してみた。ので、余計に時間がかかった…。

RSS フィードを読む機能が必要だったため、今回のためにそのためのライブラリを自作した。過去に読んだフィードは Google スプレッドシートに記録し、新着を抽出する。

Google Apps Script には、定期的に関数を実行する機能があるため、これを使って RSS フィードをチェックし、更新があれば GitHub API を叩いてタグを作成するようにした。

不要になったタグを削除する

タグはビルドのトリガーにするために必要なもので、ビルドが終わったタグについては不要になる。これは消したい。

Docker Hub には、ビルドが終わって push された際にそのイベントを WebHooks で通知する機能がある。そして Google Apps Script は Web アプリとしてエントリーポイントを持つことができる。これを利用して、ビルドが終わって Docker イメージが push されたらそのイベントを Google Apps Script に通知し、そこから GitHub API を叩いてタグを消すようにした。

過去のバージョンをビルドする

最新版はビルドされるようになった。しかしこれだと過去のバージョンはビルドされない。

そこで少しずつタグを作っていく Ruby スクリプトを書いて、これを走らせた。実際には運用しながら少しずつスクリプトを修正、改善していった。

一気にタグを作らなかったのは、そうすると最新版が来たときにキューが詰まっていてなかなかビルドされなくなるからである。実際に全てビルドし終えるのに1ヶ月以上かかった。また、キューを大量に積むとなんとなくまずそうな気がしたというのもある。

中にはそもそもビルドが通らないバージョンが存在したり、Docker Hub が謎のエラーでビルドを失敗させたりしてかなりカオスだった。

課題 - 失敗したビルドに対応する

過去のバージョンをビルドしていてわかったことだが、前項で少し触れたように、ビルドできるはずのバージョンがビルドに失敗することは割とある。例えば apk でパッケージの更新に失敗すると言ったわかりやすいネットワーク由来のものから、コンテナの構築になぜか失敗するというよくわからないものまで様々な理由で失敗する場合がある。これらの失敗は多くの場合、再ビルドを実行すれば問題なくビルドが通る。

さて、これに対応したいところなのだが、難しいことに、Vim はビルドできないバージョンがリリースされることがありえる。これは今でも Bram さんの手による暖かみのあるパッチリリースが行われている賜物である。

そう、つまりこれだと、Docker Hub 由来でたまたま失敗したのか、そもそもビルドが通らないのかがわからない。従って、失敗していたら再実行するというソリューションは使うのが難しい。やるとしても回数制限してやる感じになりそう。

というわけでつらいので今のところやってない。せめて Vim のリリースが全てビルドが通ることが保証されてればなー2

やってみての感想

結果的にかなりあれこれすることになって大仕事になった割にどれだけ活用できるか不明だけど、あれこれ触れたのはよかった。しかし、時間をかけすぎた感がやばい…。

CI とかバグ調査等に使える可能性がなきにしもあらずなので使いたい方はどんどん使ってください。


  1. Docker Cloud と Docker Hub の違いをよくわかってない…。

  2. とは言えコンパイラのバージョンとか外部インターフェースのバージョンの依存で落ちる場合までは保証できないだろうしどちらにしてもつらそう。