参考訳:Docker ネットワーク設計哲学

  • 435
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Docker 社の Blog にネットワーク機能や新しい Compose 1.6 に関する投稿がありました。翻訳しましたので、以下参考程度にどうぞ。

対象となるのは、2016年2月にリリースされた

  • Docker Engine 1.10
  • Docker Swarm 1.1
  • Docker Compose 1.6

です。

Docker Networking Design Philosophy | Docker Blog
https://blog.docker.com/2016/03/docker-networking-design-philosophy/

Docker ネットワーク設計哲学

Docker 1.7 で実験的にネットワーキングが導入されてから Docker 1.9 の初期リリースに至るまで、コミュニティから素晴らしい反響がありました! 何よりもまず、議論、評価、プルリクエスト、山積みの課題、これら全てにかかわる皆さんに感謝を申し上げます。ネットワーク機能(Networking)はリリースごとに改良されています。いずれ設計の背景にある行動指針を紹介しようと思っていました。

利用者が第一(Users First)

Docker の哲学とは、インフラを横断する繫ぎ目のない(シームレスな)アプリケーションのポータビリティと、素晴らしいユーザ経験を備えているツールを構築することです。新しい機能は絶え間ない洗練を繰り返し、最終的なプロダクトでは可能な限りベストなユーザ経験をもたらします。

ネットワーク機能の追加にあたっては、2種類のユーザを想定しました。

  • Docker プラットフォーム上で分散アプリケーションのスタックを作成・デプロイしたいアプリケーション開発者
  • インフラの設定や管理を行うネットワークIT(管理・運用)チーム

私たちは両方の利用者に対して適切なツールを提供したいと考えています。そして、Docker コミュニティの @arungupta@allingeek@Yoanis_gill らが経験したように、簡単にゴールを達成できる機能を提供したいのです。

Docker は利用者に対して一番焦点をあてています。利用者とはアプリケーション・チームや運用担当です。また、ユーザ経験と繫ぎ目のないアプリケーションのポータビリティという、アーキテクチャに対する同じ目標をサポートするエコシステム・パートナーの支援も意味します。この考えを念頭におき、私たちの信念とは、全ての API と UI がエンドユーザに対して公開されており、誰もがその真なる価値に歩み寄れるようにします。エコシステムをサポートする誰でも、また Docker でさえもユーザ経験とポータビリティの維持を順守する必要があります。そうでなければ、Docker はシンプルではありません。

利用者:アプリケーション開発者

アプリケーション開発者であれば、アプリケーション間で通信するために、大部分を理解したくないか、どのように実現しているかの詳細に関与したくないでしょう。実際、開発者はアプリケーションに対して割り当てる IP アドレスですら知りたくはないでしょう。アプリケーション開発者の一般的な関心は、アプリケーションが密接に結び付くレイヤ4のサービス・レイヤ止まりです。

アプリケーション開発者に対して、ネットワーク詳細を調べなくてはいけない悩みを軽減すること。これが Docker ネットワークを設計するにあたっての、導入方針の1つでした。私たちの信念とは、ネットワーク疎通がどうなっているかという、血みどろの詳細を隠し、簡単な API や UI の背後でサービス・ディスカバリ(訳者注:コンテナ間で認識・通信できること)を実現することです。これらを通して、私たちはアプリケーション開発者が分散アプリケーション・スタックをさらに自由に開発できるようにします。私たちは接続性(connectivity)や発見可能性(discoverability)に関する悩みとポータビリティに関する懸念を撲滅するでしょう。ここでの懸念とは、アプリケーション開発者がモノリシックのアプリケーションをマイクロサービス群に変えるのを妨げるものを指します。私たちは開発者の皆さんに対して、簡単な「network creation」や「network connection」などのコマンドで扱える分散アプリケーションを、皆さんのマイクロサービスに結び付けて欲しいと思ってます。

別の導入方針としては、Docker コンテナの移動しやすさという経験(portable experience)を、ネットワークに対しても同様に拡張することです。Docker コンテナの作成にはイメージを使いますが、同じイメージを使う限り、どこで実行しても同じコンテナを動かすことが可能です。同様に、アプリケーション開発者が分散アプリケーションのアプリケーション・スタックを定義する時、どんなインフラを使っても同じように動かしたいと思うでしょう。重要なのは、アプリケーション開発者に対して、何を抽象化して見せるのかです。さらに重要なのは、アプリケーション開発者に対して、何を抽象化して見せるべきではないかです。

以上が、どのようにして Docker 「ネットワーク」の抽象化(CNM)が生まれたかの背景です。この機能は、アプリケーション開発者がアプリケーション・サービスの接続性と発見可能性を検討するにあたり、適切なリファレンスを提供するものです。いかに正確に実現できるかが重要であり、複雑さのために気を散らす必要はありません。また、ある意味、ネットワーク」抽象化は宣言型(declarative)です。これは、トポロジを物理的に「どのように」(how)構築するかではなく、アプリケーションが必要とするトポロジは「何」(what)なのかを、利用者が伝える必要があるからです。

docker-network01.png

例えば、伝統的な三層のウェブ・アプリケーション・スタックでは、ウェブ・サーバとアプリケーション・サーバが1つのネットワーク上に存在します。そして、このアプリケーション・サーバは別のデータベース・サーバのネットワークとも接続します。アプリケーションの開発者は物理ネットワークやファイアウォール等をどのように実装するか考えるべきではないでしょう。アプリケーションをインフラから切り離せば、分散アプリケーションのポータビリティを著しく高めます。これはまた、アプリケーションのトポロジをどれだけ厳密に定義するのかを、開発者が自由に決められることも意味します。

利用者:ネットワーク管理・運用チーム(ネットワーク IT)

アプリケーション開発者が自由にインフラを定義し、アプリケーションをポータブルにデプロイしたくても、ネットワーク担当チームはアプリケーションの SLA やビジネス・ルールに基づき、インフラ上の全てのアプリケーションをスムーズかつ確実にデプロイしようとするでしょう。つまり、アプリケーションの機能性や意図とは切り離された状態で、ネットワーク設定が調整されることを意味します。アジャイル(Agile)が意味する対象は、開発者のスピードだけでなく、様々な種類のネットワーク担当も含みます。なお、ここでのネットワークとは、必要に応じて迅速に変更できる場所であり、かつ、何も壊さずに調整可能なものを指します。

「どのように」(how)実現するかの一部を、「ドライバ」(driver)の抽象化で達成します。ネットワーク・トポロジを抽象化して定義することは、使用するドライバが具体的にどのような用途を目指しているかの検討です。Docker ネットワーキング・プラグイン API の定義では、各ドライバとは簡単に適用できるものであり、あるドライバを別のドライバに置き換えたとしても、あらゆるインフラ上でアプリケーション主体の同じネットワーク・トポロジを簡単にデプロイできるものです。

プラグイン API は、以下のケースにおけるフック(hook)をドライバに提供します:

  • ネットワークの作成時
  • コンテナがネットワークに接続時
  • コンテナが IP アドレスを必要とする時

あらゆるアプリケーション・ネットワークのトポロジでネットワークの接続性を達成するためには、これらが最も重要なフックになります。使用するドライバに関係なく、Docker はアプリケーションに対してネットワーク接続性を保証します。同時に、ネットワーク担当チームは自分たちのインフラでアプリケーション・トポロジを容易にするよう、自由にドライバを選べます。

「プラグイン」と呼ばれる特別な種類のドライバがあります。全てのプラグインはドライバです。しかし、プラグインは Docker Engine のバイナリに入っていません。これらは独立した外部のプログラム(ほとんどが docker コンテナとして提供されている)ですが、組み込み型ドライバと同じドライバ API を使えます。つまり、組み込み型ドライバを外部プラグインに置き換えるだけで、あらゆるネットワーク・トポロジに対応できるのが重要なのです。改めて、Docker の哲学とは「交換可能な内蔵電池」(Batteries included but swappable)です。プラグインはポータビリティをサポートするために重要であり、これがネットワーク担当における選択肢となるのです。

私たちが Docker ネットワークを拡張するにあたり、プラグインとは第一級オブジェクト(first-class object)であるべきと、当初から明確に考えました。アプリケーションが必要とする接続性と発見可能性は、様々なインフラに応じて幅広い変化に富みます。新しい Docker ネットワーク機能の初期リリースに向けて私たちが決めたのは、ユーザが「電池交換」(swap the battery)可能にすることでした。この考えが、最終的に Docker 1.9 における Docker ネットワーク構築機能(networking)のリリースに至ったのです。

プラグイン API 設計

アプリケーションのネットワーク・トポロジとネットワーク抽象化の関係は、アプリケーション開発者に対して最も関心を高めます。また、IT 管理者であればドライバやプラグインの設定に対して関心があるでしょう。ネットワーク担当であればアプリケーションをデプロイしようとするインフラと、関連するサービスレベルに関心があるでしょう。誰もがアプリケーションの接続性を確実なものに保証したいのです。

保証したいと願うのは、以下の項目です:

  • ネットワーク経路を構築するための、適切な解決策
  • ネットワーク・リソースを管理するための、適切な解決策
  • アプリケーション・サービスを発見するための、適切な解決策
  • 以上の全てを個々に独立して選択できること

ベストな運用経験(operations experience)をもたらすネットワーク設定をするには、ネットワーク担当者が様々な要素から様々な解決策を柔軟に選べるようにします。

組み込まれている全てのプラグイン API 、あるいは拡張ポイント(extension-point)に代わり、プラグイン API を適切な論理設定グループに応じて分割しました。

  • ネットワーク・ドライバ拡張ポイント(network driver extension point)は、ネットワークの接続性の実現するために必要な設定を提供します。
  • IPAM 拡張ポイント(IPAM extension point)は設定、発見、IP アドレスの範囲を管理します。

この設計の着想は Go 言語のインターフェース哲学から得ました。これは「インターフェース」ごとにコンポーザビリティ(組み合わせできる性質)を促すように定義する、と提唱しています。これはネットワーク担当にとって、異なったニーズごとに異なったソリューションを組み合わせ可能なため、幅広い融通性をもたらします。

プラグイン API 設計には、別の側面もあります。異なったプラグインを使う複数のネットワークにコンテナを接続しても、Docker ネットワーク機能が衝突を確実に回避するようにしました。例えば、2つの異なったドライバがあり、固定された同じ経路で静的にルーティングしたいとします。しかし、次にホップする IP が異なる場合があります。このような状況の時は、ユーザ経験を犠牲にせず、単純に各ドライバが独立して経路を選ぶことができません。そのため、プラグイン API の一部である libnetwork は、ドライバがコンテナのネットワーク名前空間に対するアクセスを提供していません。なぜならば、ドライバ自身が衝突を回避できる能力を持たせるために、適切な手法がなかったからです。これが組み込みドライバとプラグインの現実です。自身のドライバが名前空間にアクセスできる CNI のような他のプラグイン・フレームワークは、それ故に、コンテナの名前空間の中を歩き回るよう処理する必要があるかもしれません。そうなれば、ユーザ経験とポータビリティの問題に苦労します。

プラグイン設計にあたっての別の問題は、様々なレイヤ(IPアドレス管理、サービス・ディスカバリ、負荷分散、等)に対し、いかにきめ細かなネットワークの切り替え能力(pluggability)提供するかです。つまり、網羅的なかつ独断的なネットワーク・プラグインに依存するのではなく、ユーザが要件とする機能を満たすためにドライバを選べるようにしています。例として次のようなシナリオを考えてみましょう。ネットワーク運用担当者は(Infoblox などの)何らかの IPAM (IPアドレス管理)ソリューションと異なったネットワーク・プラグイン(Cisco の contiv など)を組み合わせて使いたいとします。libnetwork はコンテナのネットワーク名前空間を管理しますので、私たちは Docker UX に必要な実装が可能となり、異なったプラグイン間の連携も保証できるようになりました。ただし、これはネットワーク運用担当者がネットワーク設計を管理している場合に限り保証されるものです。

Docker API と利用者の相互作用

Docker のネットワーク機能は、2つの異なる利用者が懸念する問題を分離します。そのために、Docker UI で2つの異なる命令を扱えるように設計しました。UI と API の設計にあたっては、ネットワーク担当が最低限の設定でインフラを設定できるようにしました。あるいは、アプリケーション開発者も可能であれば設定できるようにです。これはアプリケーション開発者とネットワーク担当チーム間で、無限ループのワークフローが発生しないようにします。

例えば、アプリケーション開発者がネットワーク担当に対して、ある名前のネットワーク作成を依頼したとします。そうすると、ネットワーク担当は独自に作業を進められます。適切なインフラ上で、ネットワークを作成する設定を流し始められるでしょう。同時に、アプリケーション開発者はアプリケーションが必要とするネットワーク・トポロジを達成できるよう、適切な名前をネットワークに付けてアプリケーションを組み立てる(composing)ことが可能なのです。

このような考えを元に、UI と API では以下の点を考慮しました。

  • ネットワーク担当が作成・管理可能であり、どのネットワークドライバと IPAM ドライバを組み合わせられるかを制御できるネットワーク。これはまた、あらゆるドライバの設定を通して、サブネット、ゲートウェイ、 IP の範囲など、様々なネットワーク設定も指定できるようにします。
  • 作成したネットワークに対して、あらゆるコンテナが接続できるような設定。アプリケーション開発者は、主に接続性と発見可能性に対して集中できるようにします。

典型的な手法として、アプリケーション開発者は「Docker Compose」ファイルを使いアプリケーションを組み立てる(compose)ことが可能です。このファイルにアプリケーション・サービスが必要な全てを記述できます。この中には定義したネットワーク・トポロジを参照し、どのようにアプリケーションを接続できるかの定義も含みます。これにより、ネットワーク担当でも事前にネットワークをプロビジョニングできるかもしれません。

つまり、開発者がアプリケーション・トポロジを定義した Docker Compose ファイルを使って、アプリケーションを構築するのです。全く同じ compose ファイルさえ準備しておけば、アプリケーション・トポロジをあらゆるインフラ上にデプロイできます。それだけでなく、インフラ要件に応じて、ネットワーク担当が予め作成しておいた(Compose ファイルで参照可能な)ネットワークにもデプロイできます。これら抽象化の鍵となるのは、異なった環境にデプロイする度に、毎回アプリケーション開発者が Compose ファイルを確認する必要がないことです。

開発者とネットワーク担当のワークフローにおいて、それぞれ懸念事項を分割します。これにより、個々にネットワークをプロビジョニングして、アプリケーションをデプロイするために、必要に応じて異なったプラグインを利用できます。

それでは、以下の Compose v2 アプリケーションを例に考えてみましょう。

$ cat docker-compose.yml
version: "2"
services:
  voting-app:
      image: docker/example-voting-app-voting-app
      ports:
      - "80"
      networks:
      - votenet
  result-app:
      image: docker/example-voting-app-result-app
      ports:
      - "80"
      networks:
      - votenet
  worker:
      image: docker/example-voting-app-worker
      networks:
      - votenet
  redis:
      image: redis
      networks:
      - votenet
  db:
      image: postgres:9.4
      volumes:
      - "db-data:/var/lib/postgresql/data"
      networks:
      - votenet
volumes:
  db-data:
networks:
  votenet:

Compose v2 のデフォルトでは、デフォルトのドライバを使ってアプリケーション用の docker ネットワークが作成可能です。Docker Engine に対して実行すると、ブリッジ・ドライバが default(デフォルトという名前のネットワーク・ドライバ)になります。そのため、アプリケーション起動時に、「default driver」を使ってネットワークを作成しているのが確認できます。

$ docker-compose up -d
Creating network "voteapp_votenet" with the default driver
Starting db
Starting redis
Starting voteapp_worker_1
Starting voteapp_voting-app_1
Starting voteapp_result-app_1

アプリケーションが起動します。アプリケーション開発者はネットワーク特有の設定を知らなくても適切に扱えるようになります。

より詳細を確認しましょう。

$ docker network inspect - voteapp_votenet
[
      {
      "Name": "- voteapp_votenet",
      "Id": "7be1879036b217c072c824157e82403081ec60edfc4f34599674444ba01f0c57",
      "Scope": "local",
      "Driver": "bridge",
      "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
            {
                  "Subnet": "172.19.0.0/16",
                  "Gateway": "172.19.0.1/16"
            }
            ]
      },
      ...
      ...
      ...
]

アプリケーションを停止しましょう。

$ docker-compose down

このようにして、運用チームがステージング環境にアプリケーションをデプロイすることも考えられます。ネットワーク担当は予めプロビジョンしておいたネットワークを管理できます。そのためには、事前に docker network コマンドを使ってネットワークを作成します。例:

$ docker network create -d overlay --subnet=70.28.0.0/16 --gateway=70.28.5.254 voteapp_votenet
6d215748f300a0eda3878e76fe99e717c8ef85a87de0779e379c92af5d615b88

あるいは、ネットワーク担当は Docker Compose アプリケーションの拡張機能(詳細はこちらの compose 機能のページ)を使い、別の Compose ファイルを追加してネットワーク設定を管理することも可能です。「docker-compose.override.yml」ファイルは、アプリケーションを一切変更しません。

$ cat docker-compose.override.yml
version : "2"
networks:
  votenet:
      driver: overlay
      ipam:
      config:
      - subnet: 70.28.0.0/16
      gateway: 70.28.5.254

この例では、ステージング環境で「overlay」(オーバレイ)ネットワーク・ドライバを使っています。これは複数のホストに対するネットワーク疎通を提供し、このネットワークに対してネットワーク担当が適切な IPAM 設定(IPアドレス管理)を指定できます。

$ docker-compose up -d
Creating network "- voteapp_votenet" with driver "overlay"
Starting voteapp_worker_1
Starting redis
Starting db
Starting voteapp_voting-app_1
Starting voteapp_result-app_1

同じアプリケーションを実行しますが、今回は「overlay」という名称の異なったドライバでネットワークを作成しました。「overlay」ドライバは複数のホストに対するネットワーク疎通をもたらします。この状態で「docker network connect」コマンドを使い、ネットワークの詳細を掘り進めてみましょう。ネットワーク設定時に指定した IPAM 設定が適用されていることと、全てのコンテナが対象のサブネットの IP アドレスを持っていることがわかります。

$ docker network inspect - voteapp_votenet
[
      {
      "Name": "- voteapp_votenet",
      "Id": "b510c0affb2289548a07af7cc7e3f778987fc43812ac0603c5d01b7acf6c12be",
      "Scope": "global",
      "Driver": "overlay",
      "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
            {
                  "Subnet": "70.28.0.0/16",
                  "Gateway": "70.28.5.254"
            }
            ]
      },
      ...
      ...
      ...
]

Docker Swarm 上で Compose アプリケーションを実行する時には、コンテナは各ホストを横断してスケジュールされます(訳者注:スケジュールとは、どのホスト上にコンテナを実行するか計画し、コンテナを起動すること)。その際、オーバレイ・ドライバを使えばコンテナ間に繫ぎ目のない(シームレスな)接続性を提供します。

このブログ投稿では、以上が Docker ネットワーク機能の設計方針によって実現した内容の説明になります。

依然としてアプリケーションは王様

これまで言及していませんが、全ての運用担当は設定の調整に集中しており、アプリケーションが依然として王様のままです。そのため、私たちが取りかかるにあたり、可能な限り多くのネットワーク構成要素を隠すことに努めました。そのため、最終的には IP アドレスですら必要に応じて隠せるようになりました。IP アドレスはインフラの根本を形成するものですが、これがアプリケーションのポータビリティ(移植性)を低くしています。仮にアプリケーションがコンパイル時に使った名前でお互いに発見可能であれば、もうそれでポータビリティの物語が完成です。これを実現するのが、組み込み型 DNS サーバ(embedded DNS server)による暗黙のコンテナ・ディスカバリです。コンテナは「リンク」(コンテナ間の連結)し「エイリアス」(別名の指定)が可能なため、コンパイル時に使った名前でお互いのコンテナを発見できるようにします。

Docker ネットワーク機能を皆さん自身が使いたい場合は、以下のリソースもご覧ください:

docs.docker.jp の翻訳ドキュメント
* Docker ネットワーク・ユーザガイド ※Docker 1.10 対応
* Docker ネットワーク・プラグイン ※Docker 1.10 対応
* Docker Compose ※1.5 -> 1.6 移行作業中