- この章では、以下のことを解説する
- 開発と運用がどのように変わったか
- コンテナがどのように isolation と環境の違いを減らしたか
- Docker とはなにか、k8s でどのように使われているか
- k8s がなにをして開発と運用をどのように助けるか
- 昔のアプリは巨大なモノリスだった
- 遅いリリースライフサイクル
- 最近のアプリはマイクロサービス
- 高速に更新できる
- しかし、デプロイするコンポーネントの数が増えてデータセンターが大きくなると全体の設定や管理の難しさが増す
- resource utilization やハードウェアコストの最適化
- 手動では回らない
- k8s が自動スケジューリング、設定、supervision、failure-handling をやってくれる
- k8s が開発者が自力でデプロイすることを可能にする
- k8s が運用者に定期的なモニタリングやハードウェア故障時の自動リスケなどを助けてくれる
- k8s がハードウェアインフラを抽象化し、データセンター全体を一つの膨大な計算資源として扱えるようにする
- k8s の詳細に入る前にアプリの開発とデプロイが近年どのように変化したか見てみる
- マイクロサービス化とアプリが運用されるインフラの変化の影響が大きい
- モノリシックからマイクロサービスモノリシック
- すべてのコンポーネントが密結合しており、一つの OS 上のプロセスとして動作する
- 一部を変更すると全体を再デプロイすることになる
- パーツ間の明確な境界線がないので、複雑になり品質が落ちる
- モノリシックなアプリをスケールさせるには垂直にサーバーの CPU やメモリを強化する
- スケールアップ
- アプリに変更が必要ないけど上限がある
- もしくは並列に複数のサーバーで分離したアプリのインスタンスを運用する
- スケールアウト
- アプリ側の変更が必要なので不可能な場合もある(e.g. RDB)
- 一部でもスケールできないアプリがあるとアプリ全体がスケールできなくなる
- どうにかして分割しないかぎり
- これらのような問題に対応するために、より小さい独立してデプロイできるマイクロサービスに分割することを強いられる
- Figure 1.1
- それぞれのマイクロサービスは独立したプロセスとして動作して API でやりとりする
- 個別に開発してデプロイ可能で、API の後方互換性が保たれていれば他のサービスの再デプロイを必要としない
- サービス間のやりとりは REST API のような同期的なプロトコルか AMQP(Advanced Message Queueing Protocol) のような非同期的なプロトコルで行う
- これらのプロトコルは言語関係なく使えるので、サービスごとに言語選択は自由になる
- マイクロサービスをスケールさせるのは個別のサービス単位となる
- 他のサービスはもとのスケールを保ったままにできる
- Figure 1.2
- スケールアウトできないサービスだけスケールアップさせて、他のサービスはスケールアウトさせるようなこともできる
- マイクロサービスにも短所はある
- デプロイするコンポーネントの数が増えると複雑になる
- 単純なコンポーネント数だけでなく内部の依存関係も増えるため
- マイクロサービスを運営するチーム間のコミュニケーションが増える
- 退屈かつエラーを起こしやすい
- マイクロサービス間で同じライブラリの異なるバージョンを要求することがある
- Figure 1.3
- これらすべてのライブラリを同じホストに入れたくない
- 環境の違いは開発にとっても運用にとっても大きな問題
- 開発環境と本番環境の違い、本番マシン間の違い、マシン変更など
- この差異はハードウェア、OS、ライブラリなど多岐にわたる
- 本番環境で起こった問題を手元で再現したいが、その環境は一時的なものにしたい
- 近年では開発プロセスや本番環境でのアプリの扱われ方が変化してきている
- 過去は開発チームはアプリを作って運用チームに渡すだけだった
- 今は開発と運用を 1 つのチームで行う
- QA や運用チームも全体のプロセスに関わる
- DevOps
- 開発者が運用に関わるとユーザーの要求や問題、運用が直面する問題への理解が深まる
- より頻繁にアプリをリリースできるようにするにはデプロイプロセスのストリームラインが必要
- 理想的には開発者自信がデプロイできるようになってほしい
- しかし、デプロイにはインフラやハードウェアの理解が必要だけど、開発は通常詳細な知識を持たないし、そもそも知りたいわけでもない
- 開発と運用が一緒に働いても、それぞれに異なるゴールとモチベーションがある
- 開発者は新しい機能を作りたいし、OS のセキュリティパッチを当てたりしたくはない
- 運用はハードウェアインフラ全体やセキュリティ、utilization などに責任を持つ
- インフラの変更がアプリに与える影響とか考慮したくない
- 理想的には開発者はハードウェアインフラについて知らなくてもデプロイできるようになってほしい
- 運用にはアプリケーションの特性をしらなくてもハードウェアインフラを管理できるようになってほしい
- k8s はこれらのことを可能にする
- k8s はいわゆる Linux コンテナ技術を使う
- コンポーネントの数が少ないうちはコンポーネント 1 つにつき 1 VM のような構成がとれる
- しかし、コンポーネントの数が増えるとそうはいかない
- ハードウェアリソースのコスト最適化や、VM の管理コスト削減のため
- 環境を隔離するために VM ではなくてコンテナ技術を使うようになりつつある
- VM よりオーバーヘッドを少なく、異なる隔離された環境を 1 ホスト内に用意できる
- コンテナ内のプロセスにとっては VM で実行されているのと変わらない
- コンテナのほうが VM より軽量
- VM はアプリだけではなくシステムプロセスも稼働するため
- コンテナは VM と違って異なる OS のアプリを 1 ホストで運用できる
- Figure 1.4
- すべてのコンテナは同じカーネルを通じて CPU などのリソースを利用する
- Figure 1.5
- VM の主な利点は、カーネルレベルでも完全に隔離されるのでよりセキュリティ的に安全
- プロセス同士を隔離するためのメカニズムが 2 つある
- Linux Namespaces
- それぞれのプロセスが自信のシステムのみを見られる
- Linux Control Groups (cgroups)
- プロセスが利用できるリソース(CPU、メモリ、ネットワーク帯域など)を制限できる
- デフォルトでは Linux システムは 1 つのネームスペースを持つ
- ファイルシステムやプロセス ID、ユーザー ID、ネットワークインターフェースなどのシステムリソースは 1 つのネームスペースに属する
- 新たなネームスペースを追加できる
- ネームスペースの詳細については Chapter 3
- 他のプロセスのリソースを食わない
- コンテナ技術は長いこと存在するけど、Docker プラットフォームによって広く周知されるようになった
- Docker はマシン間で簡単にコンテナを持ち運べるようにした最初のコンテナシステム
- アプリだけでなく依存するライブラリや OS ファイルシステムもパッケージ化する
- パッケージしたのと完全に同じファイルシステム上でアプリを動作させる
- アプリは完全にホスト OS ではなくコンテナの OS の上で動いていると判断する
- Docker は、アプリのパッケージ化、配布、運用のためのプラットフォーム
- パッケージを Docker のリポジトリに送って、それをマシンから利用できる
- 用語
- Images: アプリとその環境をパッケージしたもの
- ファイルシステムとメタデータ(イメージ起動時に実行するパスなど)を含む
- Registries: Docker イメージを保存して共有できるようにするリポジトリ
- Containers: 実行中の Docker イメージ
- コンテナはホストや他のプロセスからは隔離されたプロセス
- 割り当てられたリソースしか使えない
- Images: アプリとその環境をパッケージしたもの
- Figure 1.6
- Figure 1.7
- アプリ A とアプリ B は同じバイナリとライブラリにアクセスしているが、隔離されたファイルシステムでどのように共有しているのか?
- Docker イメージはレイヤーからなる
- 異なるイメージが完全に同じレイヤーを含むことがある
- Docker イメージは異なるイメージの上に作られるので
- これはネットワークを通したイメージ配布速度を上げる
- ストレージの使用量も下げる
- コンテナが共有レイヤのファイルを書き換えても、他のコンテナはそれを見られない
- Docker イメージレイヤーは readonly
- コンテナが実行されるとき、すべてのレイヤーの上に書込み可能なレイヤーが追加される
- 理論的には Docker イメージはあらゆる Linux マシン上で動作するが、注意点がある
- マシン間で Linux カーネルが異なると同じように動く保証はない
- アプリにカーネルに関連した要求があるときは注意
- VM にはこのような問題はない
- 特定のハードウェアアーキテクチャを想定したアプリにも同じことが言える
- Docker の成功後、Open Container Initiative (OCI) がコンテナフォーマットとランタイムの標準化のために誕生した
- Docker と同様に、rkt も Linux コンテナエンジン
- rkt はセキュリティと composability、オープンスタンダードへの追従に重点を置いている
- OCI コンテナイメージフォーマットを使用しており、Docker のコンテナイメージも実行できる
- この本では Docker にフォーカスする
- k8s が最初にサポートした唯一のコンテナランタイムだから
- k8s は最近 rkt や他のサポートも始めた
- rkt の解説をしたのは、k8s が Docker 専用のツールだと思われないようにするため
- おそらく Google がソフトウェアコンポーネントとインフラのデプロイと管理によりよい方法が必要と最初に気づいた会社だろう
- 長い間、Google は Borg (その後、Omega) という内部システムを開発した
- アプリ開発者と運用が数千のアプリとサービスを管理するのを助ける
- インフラの utilization も助けた
- 数十万のマシンを運用すると、少しの稼働率の改善が数百万ドルのコスト削減につながる
- 長い間 Borg は秘密になっていたけど、2014 年に Google がそれらや他の社内システムの経験を元にした k8s を紹介した
- k8s を通したデプロイはクラスタのサイズに関わらずいつも同じである
- k8s のシステムはマスターノードと複数のワーカーノードからなる
- Figure 1.8
- マスターにアプリを submit すると、k8s はワーカーノードのクラスタにデプロイする
- どのノードにどのコンポーネントがデプロイされるかは問題にならないし、問題になってはいけない
- 開発者は同じワーカーノードで運用されないといけないアプリを指定できる
- それぞれのアプリがどこにデプロイされようとも、他のアプリを見つけてやりとりすることが簡単にできる
- k8s はクラスタのための OS と考えられる
- service discovery やスケーリング、ロードバランシング、self-healing、leader election といったことから開発者を解放する
- 開発者はアプリの機能開発に集中でき、どのようにインフラと結合するかに時間を取られなくなる
- k8s 上ではアプリを自由に再配置できるので、リソース稼働率がよりよくなる
- k8s クラスタは 2 種類のノードに分けられる
- k8s Control Plane (master node)
- k8s 全体のコントロールと管理を行う
- worker nodes
- 実際のアプリをデプロイする
- k8s Control Plane (master node)
- Control Plane は複数のコンポネントからなっていて、これは 1 つのノードでも複数のノードでも実行できる
- 高可用性を保証できる
- API Server
- k8s クラスタとやりとりして操作する
- Scheduler
- アプリのスケジューリングを担当する(アプリコンポーネントをワーカーにアサインする)
- Control Manager
- クラスタレベルの機能を実行する(コンポーネントのレプリケーション、ワーカーノードの追跡など)
- etcd
- 全体のクラスタ設定を永続的に保存する分散データストア
- Control Plane 上ではアプリは実行しない
- Docker, rkt や他のコンテナランタイムでコンテナを実行する
- Kubelet でマスターとノード上のコントロールコンテナとやりとりをする
- Kube-Proxy でアプリコンポーネント間のネットワークトラフィックをプロキシしたりロードバランシングしたりする
- ノード間のネットワークはノード内のコンテナが実際のネットワークに関わらずフラットなネットワークの一部になれるようにセットアップされる
- アプリディスクリプタの形式で API サーバーにアプリの説明を送ることでアプリを実行する
- 説明はアプリコンポーネントのコンテナイメージや、それらのコンポーネントが相互にどのように関係するかといった情報が含まれる
- サービスが内部サービスなのか外部サービスなのか、他のコンポーネントから静的 IP で参照されるのかなど
- API サーバーがアプリの説明を処理すると、スケジューラが指定されたコンテナグループを必要とするリソースを元に利用可能なワーカーノードにスケジュールする
- ワーカーノード上の Kubelet が Docker イメージを pull してコンテナを実行する
- Figure 1.9
- アプリが起動すると、k8s は継続的に description を元にアプリの状態を確認する
- 例えば、Web サーバーのインスタンスを 5 つ稼働するように指定したら、インスタンスが 1 つでも止まったら自動的に再起動する
- ワーカーノードごと死んだ場合も、別のワーカーノードに割り当てる
- コピーの増減もリアルタイムのメトリクス(CPU やメモリ使用量など)を元に自動でやってくれる
- クライアントが簡単にコンテナを探せるように、k8s は単一の静的 IP で公開する
- Kube-proxy がサービスへのリクエストやロードバランシングをいい感じにしてくれるし、コンテナがクラスタ内を移動しても一貫性を保ってくれる
- すべてのサービスが k8s 上に乗ると、Ops チームがデプロイを行うことはなくなる
- 必要なことは k8s をすべてのクラスタノードにインストールしてネットワークを適切に設定するだけ
- 開発者は運用の手助けなくデプロイとアプリの実行ができる
- 一部のアプリは HDD ではなく SSD を使うみたいな場合はハードウェアのことを考えることもあるが、それでも簡単
- コンテナを使うことによってアプリが特定のノードに縛られなくなったので、アプリはクラスタ内をいつでも自由に移動できるようになる
- これによりリソースの最適化が k8s によって自動で行われる
- サーバーが落ちたときも k8s が自動で回復してくれる
- 負荷が高まったときも k8s がモニターして自動でスケールしてくれる
- クラウドプロバイダならクラスタ自体のサイズアップやダウンも自動で行う
- サービスディスカバリのような機能をアプリ側で開発する必要がなくなる
- 最悪でも k8s API サーバーに情報を問い合わせるぐらい
- k8s は最新バージョンがスラッシングを開始したら自動でロールアウトを止めたりする
- モノリシックアプリはデプロイしやすいがメンテが辛いしスケールしない場合がある
- マイクロサービスはそれぞれのコンポーネントを開発しやすくするがデプロイが難しく 1 つのシステムとして設定管理するのが辛い
- Linux コンテナは VM のメリットに加えて、より軽量でハードウェア最適化できる
- Docker は既存のコンテナ技術をより簡単かつ高速に OS 環境ごと供給できるようにした
- k8s はデータセンター全体をアプリのための単一のコンピューターリソースとする
- 開発者は k8s があれば運用管理者なしでアプリをデプロイできる
- k8s はノードの失敗にも自動で対応してくれるので運用管理者はよく眠れる