TL;DR

セマンティック・バージョニングを Docker のイメージタグへ直輸入すると、将来つらいことになりますよ。

はじめに

Docker や Kubernetes 等コンテナ技術に慣れてくると、自前で docker イメージをビルドする機械が増えてきます。イメージのビルドの回数が少ないうちは気にしなくて構わないのですが、ビルドしたイメージにデグレードがあったとき等の保険として、イメージタグは便利です。

しかしながら、イメージタグは、使い方が誤解されがちです。結果、長期的に思わぬ行き詰まりに遭遇する場合があります。

本稿から数回に分けて、下記のような記事を書こうと思います。

  • イメージタグのアンチパターンを例示します。
  • 例示を踏まえ、安全なイメージタグ運用の提案を試みます。

本題に入る前に、予防線を貼っておきます。 大抵の運用環境、特にステージングやサンドボックスと呼ばれる環境で使うイメージであれば、本稿の内容は overkill かもしれません。また、趣味運用の自宅環境も同様です。

企業グレードの “本番環境” では、少なからず指針策定の参考になるはずです。 「デプロイ後に問題が発覚したが、タグの打ち方に問題があったので、過去の環境に切り戻せない。」という、胃に穴が空くような事態が少しでも減ることを、祈っています。

アンチパターン: セマンティック・バージョニングへの準拠

ソフトウェアのバージョン付けの指針として、セマンティック・バージョニング(semver) は有名です。 依存するコンポーネントに破壊的変更が発生した際の、自らが開発しているソフトウェアへの影響を予期するためのルールとして働きます。策定者は「依存性地獄」と名付けていますが、まさにこの問題は「地獄」であり、semver は、福音です。

そのメリットは認めた上で「Docker イメージタグに semver を適用とする試みは、殆どのケースで失敗」します。主な理由は下記のとおりです。

理由: イメージタグと semver では使える文字種が異なる

semver ではビルドメタデータの識別として + が使えます。イメージタグでは使えません。 これ自身は「使わなければ良い」で済むかもしれませんが、次に示す理由により重みを持ちます。

理由: semver が期待するような依存関係固定が難しい

下記のような Dockerfile で作成したコンテナを mycontainer と命名し、運用することを想定してみます。

FROM alpine:1.1.1

RUN apk add --no-cache curl

なんとなく依存関係が固定できた気がしたとしたら、それこそが「落とし穴」です。

  • alpine:1.1.1 は更新される可能性がある。
  • curl も更新される可能性がある。

日々発見される脆弱性への対応の結果として、DockerHub の公式イメージは、同じタグでありながら中身が更新されています。上記例は alpine としましたが、Debian も Ubuntu も同様に更新されています。

それでは自分のコンテナのタグを固定 (たとえば mycontainer:1.0.0 )し、タグの上書きを禁止することで依存関係を固定できないでしょうか? このような試みは、下記のような理由で失敗します。

  • mycontainer:1.0.0 作成後に発見されたベースイメージの脆弱性に、対応できない。
  • ビルドメタデータを付加して解決したいが、イメージタグには + の使用が許可されていない。

そもそも「ベースイメージの内容が変更になっても、そのイメージタグが不変でありえる」という Dcoker のイメージタグの一般的な運用が、semver とは相容れません。

理由: イメージタグでは、semver と - の運用が異なる。

semver では、そのバージョンが “プレリリース” であることを示すために - を識別子として使います。

一方、 DockerHub の official image では、別の意図を示すために - をイメージタグに含めています。他のイメージでも DockerHub のルールは参考にされています。

具体例を挙げましょう。

Debian Linux の DockerHub official image には -slim という文字列を含むタグがあります。 たとえば 12.012.0-slim といった具合です。 もし semver で解釈するならば、下記のような解釈になります。

  • 12.0-slim は、プレリリース版である。
  • 12.0 は正式版であり、12.0-slim よりも新しい。

しかしながら、この解釈は誤りです。 12.0-slim イメージは、ひと手間を掛けて 12.0 よりイメージのサイズを小さくしたものを指しています。

このように、イメージタグと semver との間には、その解釈が大きく異なる部分があります。「なんとなく表記上は寄せられるから」という理由で援用すると、将来的に胃の痛くなる事態に遭遇するでしょう。

バージョン更新 OSS ではどのように対応しているか

すこし視点を変えてみます。 最近は、バージョンの更新を支援するツールが OSS でも登場しています。有名どころは Dependabot-core と Renovate でしょうか。それぞれにおいてイメージタグの取り扱いを見てみましょう。

Dependabot-core

本稿執筆時点では、イメージタグが Ruby GEM と同様のバージョニングがされていることを期待しています。本稿を読んだ方なら、実用的にかなり厳しい気がしてくるかと思いますが、実際のところ期待通りに動かないとの issue が上がっています。

Renovate

Dependabot-core に比べると後発である Renovate は、Dependabot-core の失敗をよく理解しているようです。

Docker 対応では semver をデフォルトにしていません

それでもなお semver が、イメージタグとして適切な場合はあるか

ここまで、ほぼ全否定の論調でした。 今までイメージタグに semver を適用できると思っていた方々にとっては、戸惑いや不満の感想を抱く内容かもしれません。

ほぼ唯一かもしれませんが、イメージタグに semver を用いても問題がなさそうなユースケースは、あります。 それは scratch イメージ から作るシングルバイナリイメージです。

FROM scratch
COPY hello /
CMD ["/hello"]

イメージに含めるバイナリ (上記例なら hello )のバージョンニングが下記を満たすなら、そのバージョンを示すイメージタグを与えても良いかもしれません。

しかしそれでも、下記の事情から、ビルドメタデータを付加したくなるはずです。

  • プログラミング言語用ライブラリマネージャの殆どが、緩いルールでバージョン管理を行っている。
  • 決定性ビルドを行えるビルドツールは、現時点で一般的とは言い難い。

+ を文字にイメージタグに含められない限り、semver の援用は無理筋と見做して良さそうに思います。

「(semver では使えないがイメージタグでは使える) _ を、ビルドメタデータの識別子として使う」 という手は取り得るのではありますが…その決断をする前に、そこまでして semver に拘る理由を、いま一度検討したほうが良いのではないでしょうか。

今回分のまとめ

Docker のイメージタグに関してはアンチパターンがいくつかあるのですが、今回は、もっとも誤用されがちな semver を重点的に扱いました。

  • semver 自身は重要な指針であり、心得として有用な概念です。
  • ただし、万能ではありません。イメージタグへの援用に関しては、利便性は極めて限定的です。