「Dockerは気に入っているけど、信用していない」人が多い理由
Sadogursky氏は冒頭、聴衆に対して「Dockerを知っているか?」と問いかけ、CI/CD環境の構築にあたり「Docker」が極めてポピュラーかつ標準的なツールとなっていることを改めて強調した。
「今、多くの方が手を挙げたことからも分かるとおり、今やDockerはCI/CDにおいて、なくてはならない技術のひとつになっている。しかし『Dockerについて知っている』人ほど、同時に『Dockerをあまり信用していない』と言う傾向がある。それはなぜなのだろうか」(Sadogursky氏)
Dockerには「docker buildという、とてもパワフルなコマンドがある」とSadogursky氏は言う。このコマンドを利用することで、DockerはDockerfileを参照し、イメージをビルドする。そのイメージが必要な要件を満たせば次のステップへ移り、そのステップで再度docker buildにより新しいイメージをビルドする、というようなCI/CDのパイプラインを構築しているケースも多いのではないだろうか。
しかし、Sadogursky氏は「docker buildはパワフルで簡単であるが故に、手を抜くと面倒な状況が発生する」と指摘した。例えば、DockerfileにおいてOSやミドルウェア、言語環境などのバージョンを明示的に指定していなければ、Dockerは常に「最新」のバージョンを利用してイメージを作成しようとする。Dockerfileの内容は一緒であっても、テスト環境と本番環境とで内容の違うイメージがビルドされてしまう可能性が生まれてしまうわけだ。原則としては一度テストをパスしたら、その時と完全に同じイメージをベースにプロモートを進めたい。そうしなければバージョン依存による予想外の不具合などが発生した場合の検証に膨大な手間がかかるためだ。
この問題を避けるための方法はいくつか考えられる。まずはDockerfileで各コンポーネントのバージョン番号を明示的に指定するというやり方だ。しかし、この方法では十分ではないとSadogursky氏は言う。OSやミドルウェアなどには日々、セキュリティパッチが当てられており、表記上のメジャーバージョン、マイナーバージョンが同じでも実際の内容が異なっているケースが多くあるためだ。
次に考えられるのは利用するソフトウェアのチェックサムを利用するという方法だ。チェックサムは符号化された文字列による誤り検出手法として広く用いられており、チェックサムが同じであれば内容も基本的に同一であると考えられる。しかし、Sadogursky氏は「この方法はバージョン番号を指定するよりもかなりいいが、それでも完璧ではない」とする。まず、長いランダムな文字列として表現されるチェックサムでのバージョン指定はユーザーにとって非常に分かりにくいという点。合わせて、OSのように手元のDockerfile内でチェックサムを明示してインストールできるコンポーネントであればいいが、シェルコマンドからインストールするような言語やミドルウェアについては、そこからさらに依存関係のある多数のモジュールが枝分かれしているため「まったく状態が同じバージョン」を指定することが現実的にはできないという問題がある。Dockerfile内で外部のシェルコマンドファイルなどを呼び出してビルドに利用している場合にも、そのすべてをトレースし、イメージの同一性を維持していくのは極めて手間の掛かる作業になる。
「Dockerが外部のさまざまなテクノロジーをコンテナ化する仕組みである以上、マイクロサービスを迅速に開発、展開するにあたってはイメージ内部の依存関係をしっかりと可視化し、コントロールしながらプロモートしていく必要がある。Dockerfileだけに頼った仕組みを不用意に使っていると、そこに問題が発生しやすくなる。Dockerを好んで使っている人でも『Dockerは信用できない』と感じてしまう理由はそこにある」(Sadogursky氏)
「鉄板のパイプライン」を作るために考えるべきこと
品質保証の観点で言えば、テストをパスしてステージングに進み、そして最終的に本番環境へとデプロイされるイメージは完全に同一であってほしい。それを実現するためにはプロモートのパイプラインにおいて、イメージをフィルタリングする「ゲート」を設け、各ステップをパスしていないイメージは次のステップに進ませない仕組みを用意しておく必要がある。
その手段としてはDockerfile内での「LABEL」コマンドを用いたイメージへのタグ付けや「Docker Hub Repositories」の利用などが考えられるという。LABELによるタグ付けは運用さえ確実に行われれば、かなり有効な方法と言える。しかし、LABELによる属性はあくまで単なるテキストであり、それによるチェックの仕組みだけで十分かどうかは、また別の問題になる。
一方でDocker Hub Repositoriesを利用した場合はパーミッションベースでイメージをコントロールでき、より信頼性は向上する。しかしここでも、ひとつの問題が発生する。Docker Hub Repositoriesでは、Gitのプロジェクトに対応する形でレジストリを管理している。つまり、そのままの状態では「開発」「テスト」「ステージング」「プロダクション」といった品質管理のステップに対応したイメージコントロールはできないのだ。ステップごとに別にプロジェクトにする方法は考えられるが、その場合、パイプライン全体を見渡したイメージ管理が煩雑になるほか、プロモート自体にも手間が掛かってしまう。
では、同じホスト上で各ステップに対応した複数のレジストリを同時に管理するためにはどうしたらよいのだろう。Sadogursky氏は「少しチート的なやり方だが」と前置きした上で「Virtual Hosts/Ports」の機能を使い、アクセスするポートによって、各ステップを振り分けるというやり方を紹介した。docker tagにおいてはリポジトリのプッシュにあたって、プッシュ先のホストをポート込みで指定することができる。このポート名をステップごとに変えることで同一ホスト上に複数のレジストリを持たせ、仮想的な論理構造を実現するというわけだ。
この方法は複数のレジストリを1つのホスト上で管理し、ステップごとに利用可能なイメージをコントロールするための最もスマートなやり方になり得るが、実際にやろうとすると「ステップごとにDockerのレジストリをプルして、タグを変更してプッシュする」という作業が必要になるため、やはり手間は膨大になる。つまり、その運用を自動化するための仕組みを同時に取り入れる必要がある。
Sadogursky氏が所属する「JFrog」ではCI/CDの現場において、この手法を使ったパイプラインを容易に実現するための「JFrog Container Registry」と呼ばれるツールを提供している。このツールは、オンプレミスでの利用は無料。クラウド版も、制限内(2GB/月以下のストレージと5GB/月以下のデータ転送量)であれば、登録から1年間は無料で利用できるという。
このツールを使うとエンドユーザーはツールが用意した仮想的なプロキシを経由してDocker Hub RepositoriesやGitHubなどにアクセスする形になる。同一ホスト上に複数設置したレジストリへのアクセスはJFrog Container Registryによって振り分けられるため、ユーザー側からは特に意識する必要がなくなる。「Virtual Hosts/Ports」を活用したパイプラインの構築にあたって、必須の機能を提供するという。
JFrog Container Registryでは同時にコンテナイメージ内部の依存関係の管理も可能になる。パイプライン上のDockerイメージにカスタムメタデータを付加し、そのイメージがどの時点の環境に基づいているかを可視化することができる。自分たちが作る成果物に関連して各モジュールにどのような依存関係があるのかをしっかり把握、管理した上でコンテナを扱うことができるようになるという。
「各モジュールのアップデートには脆弱性対応など、相応の理由がある。しかし、開発者としては自分が必要なタイミングで、それを適用した環境に移りたいと考えるはずだ。JFrog Container Registryを利用すると意図しない最新版によるイメージのビルドを避け、その適用を自らコントロールできるようになる」(Sadogursky氏)
Sadogursky氏は「鉄板のパイプライン」を構築するためのポイントとして「環境ごとにレジストリを分ける」「すべてのイメージに容易にアクセスできる環境にしておく」「プロモーションを迅速に行えるようにしておく」「『最新版』のメリットを、その内容を理解した上で享受できる環境を作る」という4項目を挙げ、セッションを締めくくった。
お問い合わせ
JFrog Japan