Docker とは何か?
2023-05-16 - 森本 哲也
バックエンドに Go 言語を用いて製品開発をしています。Go 言語で開発されたアプリケーションはコンテナイメージとしてパッケージングされ、システムは複数のコンテナを組み合わせて稼働します。当初は docker cli を用いたスクリプトで運用していました。製品開発を進めていくうちにコンテナイメージの取得やコンテナの制御に、自分たちの要件にあわせた運用ツールがあるとよいように思えてきました。
Docker は Go 言語で開発されていると聞いたことがある人も多いでしょう。github.com/docker にあるリポジトリなどを参照してみるとわかります。私たちは Go 言語でアプリケーションを開発しているので運用ツールも Go 言語で開発することは都合がよいように思いました。実際にそうだと開発してみた後でも思います。しかし、コンテナの運用ツールを開発するにあたり、自分たちが必要とする機能を提供するコンポーネントはどれなのか?何を/どこを調べればよいのか?といった質問に答えるには、いくらか Docker やコンテナ標準化の歴史的経緯や背景を理解しておく必要がありました。
本稿では、Docker のコンポーネントを使ってツールを作ろうとしたときに調査した内容を踏まえて、現時点における Docker とは何なのか?を考えてみたいと思います。
Docker Engine のリリースノート
Docker Engine 23.0 release notes から読んでいきます。2023-02-01 に 23.0.0 というメジャーバージョンがリリースされました。それまでは 20.10.x というバージョン体系だったので大きく変わったことが伺えます。私が関心のある内容を1つ取り上げると Buildx とBuildKit がデフォルトのビルダーになったそうです。RPM で docker-ce をインストールするときに docker-buildx-plugin も要求されるようになりました。Buildx を使うと複数アーキテクチャ向けのビルドができます。
バージョン体系の変更について次のように書いてあります。
Starting with the 23.0.0 release, Docker Engine moves away from using CalVer versioning, and starts using the SemVer versioning format. Changing the version format is a stepping-stone towards Go module compatibility, but the repository doesn’t yet use Go modules, and still requires using a “+incompatible” version. Work continues towards Go module compatibility in a future release.
CalVer のバージョン体系からセマンティックバージョニングに移行したとあります。2020年に 20.10.0 がリリースされて以降 20.10.24 が 2023-04-04 にリリースされており、事実上のセマンティックバージョニングによるバージョン管理だったものを明示的に移行したようにみえます。docker/cli のマイルストーン をみると、すでに 24.0.0 や 25.0.0 のマイルストーンも作られており、今後はメジャーバージョンももう少し早く上げていくようなリリース体制になるのではないかと推測します。
23.0.0 以降のリリース日は次になります。利用者の規模を考慮すると Rapid Release な開発体制と言えるでしょう。安心して使えます。
- 23.0.5 2023-04-26
- 23.0.4 2023-04-17
- 23.0.3 2023-04-04
- 23.0.2 2023-03-28
- 23.0.1 2023-02-09
- 23.0.0 2023-02-01
さらに Go 開発者にとって嬉しいことに Go Modules に対応していくこともリリースノートに明記されています。2018年8月にリリースされた Go 1.11 に Go Modules が追加され、それから5年近く経とうとしていますが Docker のコンポーネントは未だに Go Modules に対応していません。後述する Moby プロジェクトへの名前変更も含め、Docker のコンポーネントを使おうと思ったときにビルド環境を構築するときに混乱や余分な手間などが発生する懸念があります。状況によっては依存関係の解決がうまくできずビルドに失敗することもあるかもしれません。人気のある OSS でビルド設定を変更することがいかに大変かということも伺えます。
Moby プロジェクトの近況
DockerCon 2017 で Docker 創業者の Solomon Hykes 氏から Docker のコアをコンポーネント化して、コンテナを基盤としたシステムのためのライブラリやフレームワークのためのオープンソースプロジェクトとして Moby project が発表されました。github.com/moby という組織にリポジトリがホスティングされています。このときに github.com/docker/docker は github.com/moby/moby にリダイレクトされるようになっています。Go Modules はこういったリポジトリ変更もうまく扱ってくれるので既存のソースコードを変更する必要はないのですが、過去の経緯を知らない新規ユーザーは組織が異なるとびっくりしてしまいます。これで依存関係の解決に失敗してビルドできなくなったときは泣きそうになります。
Moby プロジェクトについて2017年当事の記事はたくさんみつかります。しかし、2023年のいま、Moby プロジェクトはどのような状況なのでしょうか?少し調べてみましょう。Moby Blog をみると2018年4月以降、新しい記事がないのでやや不安になってきます。次に moby リポジトリの insights を引用します。2017年からコミット数は減っています。しかし、ずっと開発は継続していることがわかります。コンポーネント化の影響で機能が分散されてコミット数が減っているだけかもしれません。2022年からはまたコミット数が増えてきているので最近のセマンティックバージョニングへの移行や Rapid Release な開発体制の背景になっていることも伺えます。
今後のマイルストーンなどを調べているときに moby リポジトリの issue を検索していて、2019年11月に名前を元に戻そう (moby → docker) という提案もみつけました。2019年の停滞や混乱から出た提案だとは思います。最近のリリースの雰囲気をみていると停滞や混乱も収まってまたよいサイクルに戻ってきたのではないかと思います。次の issue はいまも open された状態ではありますが、おそらく名前を戻すということはしないのではないかと思います。
この issue に対して Docker 創業者の Solomon Hykes 氏がコメントしている内容 から Moby プロジェクトが必要だった背景や2019年当事の雰囲気を伺うことができます。こちら記事にこのコメントの日本語訳 があります。このコメントから伺えることはたくさんあります。後述するコンテナの標準化について調べた後に読み返して私はより深く理解できました。
同氏は開発ツールとインフラストラクチャを分離することの重要性を示唆しています。次のように役割分担をしたとあります。
- Docker は開発ツール
- Moby はインフラストラクチャ
補足として、このコメントでいう インフラストラクチャ とはコンテナを用いたシステムを構築するためコンポーネントやフレームワークの基盤となる機能を指しています。フレームワークやライブラリという言葉と使い分けるなら、それらの基盤もしくは低レイヤーの機能などを指すこともあるのではないかと思います。そして、同氏は開発ツールに関心がある人たちとインフラストラクチャに関心がある人たちは、異なる目標と優先順位をもつために1つのプロジェクト内でこの2つのグループに属する人たちとうまくコミュニケーションを取ることはできなかったと言っています。
It created too much conflict, too many misunderstandings, and ultimately it burned people out (myself included).
この1文からも議論の過程で同氏も含めて関係者たちが疲弊した雰囲気が伝わってきます。
Unsurprisingly, this created confusion and sometimes anger for infrastructure people, because it forced them to change their definition of Docker.
a lot of the angry Kubernetes people went away,
所々にインフラストラクチャの人たちが怒っていたという修飾があるため、白熱した議論であったことも伺えます。
Ultimately the separation of communities did take place, and it did help increase productivity and decrease drama. But in retrospect, the biggest factor was not the Moby split, but the containerd split.
最終的には、この意図的なプロジェクト (グループ) の分断により、(行き過ぎの) 白熱した議論が減って生産性は向上したとあります。ここでおもしろいのが、Moby による分離ではなく、containerd による分離だったのではないかと同氏はふりかえっています。このふりかえりのコメントは後述するコンテナの標準化で見返すので覚えておいてください。
コンテナと標準化
The differences between Docker, containerd, CRI-O and runc の記事がとても分かりやすかったです。この記事の内容を踏まえてコンテナの標準化をみていきます。コンテナの標準化を理解する上で次の2つの仕様があります。
- Container Runtime Interface (CRI): Kubernetes で異なるコンテナランタイムを使えるようにするためのインターフェース
- 次の2つで構成される
- protocol buffers
- gRPC
- 次の2つで構成される
- Open Container Initiative (OCI): コンテナに関する標準仕様
- 次の3つの仕様で構成される
- イメージフォーマット
- ランタイム
- ディストリビューション
- 次の3つの仕様で構成される
高レベルの仕様からみていくと Kubernetes で利用されている CRI というプラグインインターフェースがあります。プラグインインターフェースという用語を私は初めて知りました。CRI のドキュメントの説明によると、クラスターのコンポーネントを再コンパイルすることなく kubelet が多種多様なコンテナライタイムを使えるようにするための仕組みを指しています。そして、コンテナランタイムが実装する仕様は OCI として標準化されています。OCI ではコンテナイメージのフォーマットであったり、コンテナを作成・実行する仕様を定めています。
docker cli を使ってコンテナを制御するとき、次のようなソフトウェアスタックになります。Docker Engine をインストールすると、dockerd と containerd の2つの daemon プロセスが起動していることに気付いた人もいるでしょう。なぜ2つ必要なんだろう?と疑問に思っていた答えがここにあります。
containerd とは Docker と独立したコンテナの標準仕様を実装したコンテナランタイムの1つです。さらに containerd と runc の2つがあります。これらの棲み分けとして、containerd は OCI の高レベルの機能 (イメージを取得・解凍したりネットワークやストレージを管理したり) を、runc は低レベルの機能 (コンテナを作成したり実行したり) を実装したツールのようです。runc の README には次のように直接このツールを使うことを推奨しておらず、containerd のような高レベルのコンテナソフトウェアから使うことを想定していると明確に書かれています。
Please note that runc is a low level tool not designed with an end user in mind. It is mostly employed by other higher level container software.
Therefore, unless there is some specific use case that prevents the use of tools like Docker or Podman, it is not recommended to use runc directly.
https://github.com/opencontainers/runc#using-runc
コンテナランタイムが高レベルと低レベルの2つのカテゴリに分かれ、そのことが一時期のコンテナランタイムの混乱を招いていたことが Container Runtimes Part 1: An Introduction to Container Runtimes の記事から伺えます。この記事によると、Docker 社はコンテナランタイムとは runc のようなコンテナを実行する部分のみの低レベルなものだと考えて OCI に runc のみを寄贈しました。しかし、OCI で標準化しようとしていたコンテナランタイムとは Docker がサポートしていたすべての機能を含むと考えていました。この誤解によってコンテナランタイムが高レベルと低レベルの2つに分かれてしまうという混乱が生じたとあります。
現在では dockerd とは containerd の wrapper に過ぎません。CRI と OCI という2つの標準化により、コンテナランタイムと直接やり取りすることでコンテナアプリケーションを扱う上で必ずしも dockerd (moby リポジトリで管理) を必要としなくなっているようです。moby リポジトリの issue にあった Solomon Hykes 氏のコメントを思い出してください。Moby プロジェクトを始めたときに想定していたインフラストラクチャとしての役割を担ったのは Moby ではなく containerd ではないかと同氏はふりかえっていました。
2020年12月に Kubernetes 1.20からDockerが非推奨になる理由 という記事が注目を集めました。私も当時読みました (もうあれから2年以上経ったのか) 。まるで「Docker オワコン」のように受け取って誤解をした人たちもいたようです。このソフトウェアスタックを理解していれば Kubernetes が Docker を使わなくなったというだけのニュースでしかないことを理解できます。初期の Kubernetes は Docker を使ってコンテナの制御をしていました。そして CRI というインターフェースを定義したものの、Docker は CRI を実装していないため、Docker と CRI との不足分を埋めるための dockershim と呼ばれるコンポーネントが含まれていました。CRI を実装したコンテナランタイム (例えば containerd) が台頭してきたので 1.20 で dockershim を非推奨にして 1.24 で完全に削除しました。従って、いまの Kubernetes は完全に Docker を必要としないプラットフォームになったと言えます。これはコンテナプラットフォームに Docker を必要としないと言い換えられるかもしれません。
順序が逆になってしまいましたが、containerd の概要についても触れておきます。containerd はもともと Docker (Moby) プロジェクトの一部であったものが、CNCF に寄贈されて 成熟したとみなされて卒業したようです。そのため、おそらく Docker 社の開発者も多く関わっているのではないかと推測します。いまのところ、dockerd は containerd/runc を使う実装になっており、おそらく Docker 社にとってコンテナランタイムを置き換える必要性はないようにみえます。つまり Docker Engine をインストールすると containerd/runc も付いてくると考えてよいです。そして containerd は CRI を実装しているので Kubernetes のコンテナランタイムとしても利用できます。
docker cli を使うエンドユーザー (開発者) からみると、Docker Engine をインストールすればコンテナを制御できることに違いはありません。しかし、その中身はモノリシックなアプリケーションではなく、標準化されたコンポーネントが協調して動いていることを知っておくとよいでしょう。
標準化の後に Docker 社に残ったもの
本稿を書くに至った動機づけは、Docker のコンポーネントを使って運用ツールを作りたいと考えたときに Docker 社の GitHub リポジトリをみても実装がよくわからなかったことがきっかけです。前節では Docker Engine のソフトウェアスタックを確認しましたが、それらのリポジトリが次になります。
コンポーネント | リポジトリ |
---|---|
docker cli | https://github.com/docker/cli |
dockerd | https://github.com/moby/moby |
containerd | https://github.com/containerd/containerd |
runc | https://github.com/opencontainers/runc |
ぱっと見て4つのリポジトリにおいて GitHub 組織が違っていることがわかります。つまり Docker 社の組織配下のリポジトリにあるソースコードを読んでもコンテナアプリケーションがどうやって動いているかは理解できないということです。運用ツールを作る上で自分たちの要件と、標準化されたコンテナランタイムのどのコンポーネントを調査しないといけないかを把握しておく必要があります。Moby プロジェクトは、もしかしたら事実上は Docker 社が主導権をもって開発しているのかもしれませんが、このように俯瞰すると Docker 社が管理しているのは docker cli のみでコンテナランタイムの他の機能はすべて外部のオープンソースコミュニティや標準化組織へ移管されていることが伺えます。
これは Solomon Hykes 氏が2017年に期待したものかどうかはわかりませんが、たしかに Docker は開発ツール (CLI のみ) になったと言えます。
Docker とは何か?
前置きがとても長くなりました。
私たちは Docker のコンポーネントを使って運用ツールを作りたいだけでした。コンテナランタイムをやり取りするには次の2つの方法があります。
- dockerd と通信するなら moby リポジトリの client パッケージ を使う
- containerd と通信するなら containerd の Client を使う
dockerd は containerd の wrapper に過ぎないことが分かりました。Docker 独自のメタデータや機能を必要としない場合はコンテナランタイムである containerd とやり取りして機能を実装できます。もしかしたらなんらかの理由で dockerd を使えないとしても、containerd もしくは他の CRI に準拠したコンテナランタイムを経由してコンテナを制御できるかもしれません。
エンドユーザーからみれば Docker Engine に含まれているものはすべて Docker のようにみえるでしょう。大雑把なグルーピングでは Docker とはコンテナランタイムの1つですと言ってしまってもよいかもしれません。しかし、厳密な考え方では dockerd を提供する Moby を使って運用ツールを作ると言い直した方が適切かもしれません。そして、コンテナアプリケーションを制御するだけであれば、containerd のようなコンテナランタイムと直接通信して目的を達成できる可能性もあります。
Docker とは何ですか?と聞かれたとき、その答えに窮するなにか、これだけの前置きが必要になる分かりにくさが現状の Docker やコンテナランタイムを使った開発にあることを私は理解しました。
また次回 moby client を使ったツールの作り方などを紹介したいと思います。