Kubernetes とはどのようなシステムなのか
システムについて考えるとき、まず このシステムはどういった目的のために作られたものなのか を把握する必要がある。
この記事では、k8s の目的についてまとめ、それを実現するためにどのようなコンポーネントが存在するかを記載する。
Kubernetesとは何か? | Kubernetes になぜ kubernetes が必要だったのかが過去の経緯がまとめられている。
- 物理機の時代
- 物理機では、アプリケーションに対するリソースの制限を書ける方法がなく、リソースの調整が難しかった。これにより、キャパシティプランニングが難しく、また物理機のメンテナンスコストがかかっていた
- VM の時代
- 物理機上で VM を動かし、各VMにフレーバーという形でリソースの制限をかけることで、リソース効率を上げることができた。また VM 間での通信に制限を設けることで、他のアプリケーションへアクセスできないようにセキュリティを強化できた
- コンテナの時代
- VM と似ているが、アプリケーション間で OS の機能を共有することにより、VM と比べて起動を速くできた。linux の cgroup や namespace を利用することでリソース、プロセスやファイルシステムなどを分離して、他のコンテナへのアクセスに制限をかけることができる
- 補足:コンテナで行われている分離については、Go言語で自分好みのコンテナを作成する – Think Abstract に記載した。
- VM は OS の起動から異なるが、コンテナの実体はプロセスを分けているだけのため、セキュリティの度合いも変わってくる(VM もホスト OS の脆弱性がありうるが)
- 軽量かつ独立した作りにより、VM と比べてすぐに変更して適用することがしやすいため、devops のプラクティスが VM と比べて実施しやすくなった
さて、ここまでの経緯で開発やリソース効率のためにコンテナでの開発が着目されてきた。 しかし、コンテナを本番環境で稼働させるためには運用面で多くの考慮点があった。例えば、下記のようなものだ。
- コンテナに対して細かくリソースを割り当てられるが、どうやって設定し管理するのか(設定やリソースの管理)
- コンテナがダウンした時、別の VM やマシンにどうやって割り当てるのか(スケジューリング)
- 新しいコンテナのバージョンに変えたときに、どうやって既存のものを置き換えるのか(デプロイ)
- コンテナがホストの情報のアクセスする時、どのように許可するのか(セキュリティ)
こうした課題を解決するために Kubernetes は複数のコンテナを簡単に管理できるようにするシステムとして開発された。
ちなみに、複数コンテナを管理するシステムは Kubernetes だけではなく、docker-compose, Apache mesos など他にも存在する。そもそも Kubernetes も Google 内で開発された borg が元になっている。
k8s のコンポーネントについて
Kubernetesのコンポーネント | Kubernetes に記載されているが、自分の理解のための補足も入れていく(今後、順次追記していく予定)
Kubernetes 自体がマイクロサービスとして動作しており、各コンポーネントが動作する場所に特に指定があるわけではない。ただし、高可用性を保つための推奨構成はあり、例えば etcd やコントローラー系コンポーネントはそれぞれ他のコンポーネントとは異なるノードで稼働させる。
etcd / etcd node
Kubernetes の中で唯一状態 (state) を扱う分散 KVS。Kubernetes の情報をまとめた箇所になるため、etcd で不具合が起こると後述の apiserver や scheduler が機能しなくなり、Kubernetes のコントロールが行えないことになる。
etcd はマイクロサービスにおいて重要な分散データベースとなっており、一貫性も可用性も求められる。 そのために、Raft という consensus アルゴリズムを使って、どれか1つのリーダーを選出し、それがデータの書き込みを管理するような仕組みになっている。
参考:
api に v2, v3 があり、v3 を指定していると v2 の情報が取れないなど、バージョンを意識した使い方を行う必要がある。
etcd docs | Migrate applications from using API v2 to API v3
kube-apiserver / master node
kube-apiserver は Kubernetes のコントロールプレーンのフロントエンドになる。つまり、kubectl コマンドを通じてやり取りする先は kube-apiserver となる。
kube-apiserver は etcd に情報を書き込むため、kube-apiserver 自体はスケールアウトして処理能力を高めることができる(ステートレスな作りになっている)
kube-scheduler / master node
Kubernetes の頭脳というべきコンポーネントで、pod をどのノードで稼働させるかを選択する。 scheduler は様々な変数を考慮して配置を行うことができ、リソース要求、アフィニティなどの設定を加味したスケジューリングを行う。
kube-controller-manager / master node
そもそも controller は何かと説明する前に、Kubernetes を理解する上で最も重要なアイデアの1つに reconciliation loop (調和ループ) というものがある。
これは何かというと、「宣言した状態に、実際の状態を合わせる (reconcile する)」ことだ。Kubernetes に限った話ではないが、設定された値と実際の状態の差分を短い頻度で確認し、差異がある場合はそれを修復しようとする。
reconciliation loop にどのような旨みがあるのかというと、実際のシステムを運用しているとわかるが、実際のシステムの状態は外部からのリクエストや運用上の作業などによって常に変化にさらされる。 これまで、システムの管理者は手動のオペレーションやスクリプトなどで期待した状態に戻す作業を適用的 (adaptive) に実施していた。
reconciliation loop をシステムが持つことで、実際に起きた変化に対して適応的に状態を戻すような動きをさせることができる。 もちろん、そんな簡単に実現されるわけではなく、差分をチェックし続けたり、差分を理解してどのような手順で復旧させるかをシステムは理解していないといけない。
分かりやすい例で、pod が何かの理由でクラッシュした場合、設定では 3 としていたコンテナの数が 2 になったとする。このとき、宣言していた状態は 3 コンテナのため、新しいコンテナを別のノードに作成して数が 3 になることを確認する必要がある。
この処理を定義しているのが、各種 controller だ。 そして、Kubernetes を稼働させるためにはいくつかの controller が動作している
- node controller
- replication controller
- endpoint controller
- service account, token controller
先程述べたコンテナ数の数を一致させるのは replication controller だ。
kube-controller-manager はこれらの controller を実行する元となるプロセスだ。そのため、kube-controller-manager が機能していないと、デプロイしたはずの pod がいつまでも起動しなかったり、node のオートヒールができなかったりする。
cloud-controller-manager / master node
k8s 1.16 から導入されたコンポーネント。
cloud provider への処理を実施する controller。 cloud provider とは、AWS や GCP, Azure といった Kubernetes を動作させる IaaS を提供するシステムであり、これらのシステムは Kubernetes とは異なるチームや仕様により開発が進められる。
そのため、Kubernetes とは独立して開発が行えるようにインタフェースを作り、Kubernetes からは cloud-controller-manager を通じて指示を出すようにする。 どういった指示を出すかというと、
- VM インスタンス情報の取得/書き込み
- ロードバランサー情報の取得/書き込み
- route 情報の取得/書き込み
- zone 情報の取得
などノードやネットワークに関する情報のやり取りだ。
cloud-controller-manager を有効にするときは、kube-controller-manager で --cloud-provider
を external
にする必要がある。
kubelet / worker node
kubelet は worker ノードで実行されるエージェントで、kube-apiserver と情報のやり取りを行う。 これによって、kube-scheduler からの依頼を apiserver を通じて受け取り、コンテナをノード上で稼働させたりする。
重要な運用ポイントとして、worker ノードと master ノードを分離することによって、コントローラー系コンポーネントで異常が起きたとしても worker で稼働するアプリケーションの動作に影響はないようにする点だ。 (kubelet は kube-apiserver と通信が取れない場合でも稼働中のコンテナに対して何か操作を行うわけではない)
Kubernetes のバージョンアップ等ではまず master の更新から始める。その際、誤った設定などで master が正常に動かないということがありうる。そんなケースでも worker で稼働するコンテナには影響が出ないようにする。
kube-proxy / worker node
kube-proxy は Kubernetes クラスタ内の通信ができるようにする。
例えば、App A -> App B へのコンテナ間通信を行う際、App B が動作するノード上の tcp のポートが受け付けられる状態になっている必要がある。 また、App B が複数のノードに存在する場合は、tcp round robin を行い、どれか1つのノードを選択してコネクションを張る。これによって、通信の可用性を実現する。
その他クラスタアドオン
ここまで説明したコンポーネントは、Kubernetes の必須コンポーネントであるが、実際にKubernetes 上で本番のシステムを運用する場合、他にも様々な仕組みが必要になる。
代表的なものでいうと、クラスターDNS のようにクラスタ内通信時に他の pod へアクセスするための service.local ドメインに対する名前解決を実現するなど、ほぼ必須なものも存在する。