Dockerイメージに何も加えてないことを第三者機関で保証したい
なぜ保証したいか?
API上でも{auto: true}みたいな風にリポジトリの情報が取得できる。しかしながら、docker pushで自動でビルドされたものでないものもpushできてしまった。これだと、何らかなの混ぜ物をしてpushされる可能性もあるし、ちゃんとGitHubなどに公開されている情報だけでビルドされた保証が欲しくなった。 混ぜものされる問題は、
他にもマイニングされてしまうなどの計算資源を使われる可能性
などもある。これを解決するのがこのページのテーマ。
以下のGitHubリポジトリでイメージのビルドやイメージの配布を行っている。(執筆時) https://gh-card.dev/repos/nwtgck/docker-repository.svg https://github.com/nwtgck/docker-repository
ビルドと配布の流れ
仕組みは以下の通りで、なぜこういうふうになっているか後述。
コミットメッセージの#[イメージ名]をトリガーにrepos/イメージ名がCIでビルドすべきとわかる
例:コミットメッセージ#[nwtgck/piping-server] Bump up to 0.9.2とするとrepos/nwtgck/piping-serverがビルドされる
ビルドされたイメージたちはdocker saveでtarファイルたちになる
tarファイルたちのSHA256のハッシュがTravis CIで計算されjobのログに出力される
gitのタグはimage/イメージ名/タイムスタンプの形式でTravis CIで作られpushされる
タグはどんどん増えてくスタイル
イメージを使いたい人はtarをダウンロードして、docker loadでイメージを読み込めばいい
以下はリリースの例。
Jobのリンクを開いてログを見ると、tarファイルのSHA256が確認できる。 このハッシュとダウンロードしたtarのハッシュを比較することで、イメージが改竄されていないことを保証できるはず。
https://gyazo.com/fc842bbd8e3f951e351a147210941b7b
リリースのメッセージはイメージを取得したい人の利便性にこだわっていて、
上記のリリースの"commands"に書いてあるコマンドはコピペ可能で使えるようになっている。
その他、コミットのリンクやJobのURLなどもリリースごとに適切に変わるようになっていて、手軽さと利便性を極力追求した。
改竄を検出できる。
ビルドと配布の流れがこうなった理由
Travisを選んだのは、他の選択肢のCircleCIがビルド時間が、per weekで上限があったからTravisになった。Dockerのビルドはものによっては時間がかかることがあるので、ビルド時間を気にしてコミットを避けるのは、避けたかった。
コミットメッセージでトリガーする方式になった理由。他の案としては、Dockerイメージごとにブランチを分けるなどもあった。ブランチごとに分ければ、そのブランチごとでイメージごとに成長する感じになる。そのイメージに関する変更はそのブランチでしか行われないし、そこにタグを付けるかなにかすることで、新しいイメージをトリガーしたりできるはず。
しかし以下のようなデメリットがあり、コミットメッセージトリガーのメリットのほうが勝った判断したので、この案はやめた。
ブランチは表面上で見えずらくわからりづらい
.travis.ymlなどのCI設定ファイルの変更したときに、すべてのブランチでマージするのはかなり面倒
ボットを作ってもいいが、これはこの案になったときの最終手段で、なるべくなら今ある技術に乗っかる形で、新しいものを作って保守作業を増やすべきではないという判断
コミットメッセージでトリガーすると、無駄なコミットで汚くなることが真っ先にデメリットとして上がる。
だが、master以外のブランチにコミットしてもCIは走る。そのため、適当なブランチにcommit --allow-emtpyなどで簡単にトリガーでき、なおかつmasterが汚れる心配もない。
ブランチで分けるとの比べ、すべてのDockerイメージに関する情報がmasterを見れば全部把握できるので、管理もしやすいはず。
プルリクエストをgh-release-triggerみたいなブランチに向けてしてもらうようにすれば、リポジトリ管理者以外でもトリガーに参加できる。
などのメリットがあり、今のところコミットメッセージでのトリガー方式になっている。
上で書いてないが、#[イメージ名]はコミットメッセージ見つかった最初のものだけが使われる。
これに関する理由は、なるべくTravisの1つのビルドの時間を短くしたかったから。ただこれは今後変わる可能性がある。
build.rbというこのプロジェクトのコアのスクリプトも少し変更すれば、複数のDockerビルドに対応できるように設計したつもり。複数イメージビルドされる可能性として考えられるのは、
TravisでDockerを使うときにVMを立ち上げるので、コンテナベースのビルドと比べてスタートに時間がかかる点
が一番真っ先にあがる。細かく分けることでオーバーヘッドの和が大きくなるのが、不便だと感じてきた段階で変更したい。
いましないのは、必要性がまだ感じられないから(YAGNI)。
なぜGitHub Releaseで配布なのか?
CIでビルド後にDocker Hubにpushすればいいんじゃないか?これは最終的にはDocker Hubにpushになりそう。docker imageのハッシュをCIのログに吐くようにすれば保証できるはず。 ただ、今のところGitHub Releaseにしている。以下がDocker Hubプッシュしたくない理由。
現在Docker Hubにトークンがなく、DockerのパスワードをCIに設定しないといけない
もちろん、yamlに書くわけではなく、設定画面で見えないように設定するが、パスワードを書くのは怖い
GitHubのトークンならアクセス権限を絞ったりすることができるし、最悪トークンだけを執行すれば流出の被害をそこで止められる。
だが、パスワードだとDocker Hubにログインしてできるすべての権限を持ってしまう。パスワードを新しく変更することもできてしまう。
つまり、パスワード流出は影響範囲が広すぎる
2GBは割と大きく、そこまで不自由でないと思い、今の所デメリットにはならなかった。
多分一番のデメリットはDocker Registryではないので、docker pullできないところな気がする。
自分が管理していないDockerイメージのビルドと配布できたらいいかもしれないというのも、GitHub Releaseを使った理由かもしれない。
あとスケール、このプロジェクトをフォークして、各自自分のTravisに向かってビルドするようにすれば、フォークされて分だけjob数も増えてくるはず。Dockerビルドしてイメージを公開するプログラムの仕様が安定したら、フォークとかしなくてよくなり、少しやりやすくなりそう。
これも上で触れていないが、build.bashでビルドされる理由。(GitHubリポジトリのREADME.mdで説明がある。)
任意のコマンドが実行できるシェルスクリプトになっているのは、柔軟性のため。いまのところこのプロジェクトは試験的な側面があるので、docker_build.yamlみたいな設定ファイルの仕様などを作るよりは、シェルでなるべく多くできるようにしておこうという判断になった。ただ、セキュリティ的な観点で気にしていて、yamlみたいなものにしたい。
柔軟性というのは、このプロジェクトのイメージのビルドは、イメージ1つではなくて、複数のイメージを想定している。
bashならdocker buildを2回書けばそれが実現可能。あとは、Docker Automated Buildでは設定できないような、ARGとかを注入したりも簡単な対応できる。いろんなビルド方法があると思うので、それに柔軟に対応するには、今の所bashが適当だった。bash内でgit cloneとかdocker以外のコマンドを叩くこともできる。だんだんとビルドが抽象化できそうなら、そのときにyamlの形式を考えたりしたほうが良いという判断。