はじめに
第1章 CRDとController
第2章 client-goと知っておくべき周辺知識
第3章 Sample Controller解説
第4章 controller-runtimeとcontroller-tools
第5章 KubebuilderでSample Controllerを実装しよう
第6章 OperatorSDKでSample Controllerを実装しよう
第7章 Custom Resourceの応用機能を実装しよう
本書を手に取っていただき、ありがとうございます。本書は、進化が早く日本語のドキュメントの少ないKubernetes1の拡張機能の入門について紹介したい、という気持ちから生まれました。
Kubernetesはコンテナオーケストレーターのデファクトスタンダードとしてその地位を固めており、Kubernetesを活用する事例の発表が増えてきていることを感じています。Kubernetesは標準でさまざまなResourceを実装しており、その標準Resourceを組み合わせるだけで、信頼性の高いサービス基盤を構築することが可能です。
しかし、Kubernetesクラスタのプロビジョニングやソフトウェアのオペレーションの自動化など、Kubernetesの標準機能だけでは解決できない問題も顕著になってきています。クラウドネイティブ2時代において、オペレーションの自動化を進めて、よりスケーラブルな仕組みを構築することは今後も求め続けられることでしょう。
そのKubernetes上でのオペレーションの自動化を進めるために現在注目を浴びているのが、Kubernetesの拡張機能を利用して作られるControllerとOperatorです。
Kubernetes上でService Meshを実現するためのIstio3やサーバーレスワークロードのプラットフォームを構築するためのKnative4も独自のControllerによって、その機能を実現しています。
またKubernetes上でMySQL Clusterを構築・運用するためのMySQL Operator5やPrometheusを構築・運用するためのPrometheus Operator6などが、ステートフルなソフトウェアのオペレーションを自動化するためのOperatorとして提供されています。
タイトルにあるとおり、本書ではKubernetesの拡張機能であるCustom Resource Definition(CRD)とCustom Controllerを始めるための内容を紹介し、後半ではサンプルのControllerを実装していきます。
本書の位置付けは、Custom Controllerの入門です。そのため、Production Readyかつ実践的なControllerを書くために必要充分な内容が載っているわけではありません。しかし、本書がただでさえ難しいController実践のための道標となれば幸いです。
Controller実装の詳細をより詳しく知りたい方や、よりProduction Readyに向けた高度な内容を参照したい方は、O'Reilly社出版『Programming Kubernetes』7をご参照ください。
・KubernetesまたはKubernetesの標準Resourceについて、ある程度理解している
・Go言語の文法や基礎を理解している
・KubernetesでControllerがどのように動いているか理解したい
・KubernetesでControllerを自分でも実装したい
本書は、次の流れでOperatorを自作するまでのフローを経ていきます。
・第1章: Controllerとはなにか? CRDとはなにか? について学びます
・第2章: Controllerを実装する上で知っておくべき知識を学びます
・第3章: Sample Controllerの実装を通して、Kubernetes標準Controllerの実装を学びます
・第4章: Operator作成に必要なライブラリーであるcontroller-runtime, controller-toolsについて学びます
・第5章: SDKであるKubebuilderを使って、Sample Controllerを実装します
・第6章: Operator SDKを使って、Sample Controllerを実装します
・第7章: 応用機能(Webhook, API Version互換)を学び、実装します
本書での次のソフトウェアをそれぞれ次のバージョンで検証しています。OSは、macOSを想定しています。
これと異なる環境では、説明の内容と動作結果などが異なる場合があります。GitHubのアカウントを持っているものと想定して記載しています。本書ではローカル環境でのKubernetesクラスタとして、Minikubeで記載をしていますが、Microk8sやKindなどの他のツールで作成したものでも構いません。
・Go v1.13以上
・Minikube v1.5.2以上
・Kubernetes v1.16以上
・Kubebuilder v2.2.0
・OperatorSDK v0.15.1
・cert-manager v0.11.0
サンプルコードは、次のURLからダウンロードできます。
・https://github.com/govargo/kubecontorller-book-sample-snippet
・https://github.com/govargo/foo-controller-kubebuilder
・https://github.com/govargo/foo-controller-operatorsdk
本文中のソースコードやマニフェストファイルなどの行末に、「⇨」を入れている場合があります。これは紙幅の関係上、本来は一行で表示すべき箇所を改行していることを示しています。実際に試す際は、改行せず一行内におさえて記載してください。
本書に記載された内容は、情報の提供のみを目的としています。したがって、本書を用いた開発、製作、運用は、必ずご自身の責任と判断によって行ってください。これらの情報による開発、製作、運用の結果について、著者はいかなる責任も負いません。
本書に記載されている会社名、製品名などは、一般に各社の登録商標または商標、商品名です。会社名、製品名については、本文中では©、®、™マークなどは表示していません。
本書籍は、技術系同人誌即売会「技術書典」で頒布されたものを底本としています。
本章では、Kubernetesの拡張機能であるCRDとCustom Controllerについて触れていきます。CRDとControllerがどういったものか知った後に、実際に自分でCRDを作りつつ理解を深めていきましょう。
CRDについて見て行く前に、Kubernetesのアーキテクチャについて確認していきます。Kubernetesはハイアーキテクチャレベルでは、Master NodeとWorker Nodeに構成が分かれています。
Master Nodeが認証・認可やResourceの管理、コンテナのスケジューリングなどの全体管理を行い、Worker Nodeがコンテナの実行部分を担います。
コンポーネントレベルでみると、Masterにはetcd、kube-apiserver、kube-scheduler、etcd、kube-controller-manager、cloud-controller-manager、kube-dnsが動いています。Workerにはkubelet、kube-proxy、Container Runtimeが動いています1。
図1.2がコンポーネントレベルのアーキテクチャを表しています。
次節では、この中のkube-apiserver(api-server)とkube-controller-manager(controller-manager)に焦点を当てて説明していきます。
api-serverは、kubectlからのリクエストを受けた際にDeploymentやServiceなどのObjectの作成・更新・削除などを行います。Resourceの作成・更新・削除などを行う際は、api-serverがリクエストを受け付けて、そのObjectの情報をetcdに永続化します。schedulerやcontroller-managerがResourceを操作しようとする時も、api-serverを経由します。
データストアであるetcdにアクセスできるのは、api-serverだけに限られています。
controller-managerは、DeploymentやServiceなどのKubernetes標準ResourceのObjectの管理を行います。controller-managerの中には、Deployment ControllerやService Controllerなどの複数のControllerがひとつのバイナリにまとまっています2。
Controllerでは、Control Loopと呼ばれる処理が実行されています。Control Loopとは、あるべき姿と実際の状態を比較して、実際の状態をあるべき理想の状態へと近づけるためのロジックです。Reconciliation Loopと呼ばれることもあります。
Control Loopでは、大きく分けて次のような流れの処理を行います。
1.現在のResourceのStatus(状態)を読み込む
2.Desired State(望んだ状態)に合わせて、Actual State(現在の状態)を変更する
3.現在のResourceのStatus(状態)を更新する
Controllerは、1~3をひとつのセットとして、プロセス内で繰り返し処理を続けます。
例として、ReplicaSet ControllerからPodが作成されるケースを考えてみます。Podを稼働させるために、ReplicaSet Controllerだけでなく、Controller以外の他のMasterコンポーネントも協調して動作します。
ReplicaSetをApplyして、デリバリーされるまでの動きをまとめると、次のような流れになります。
1.ReplicaSet Controllerは、ReplicaSetが作成されたことを検知し、メインロジックの中でspec.nodeNameが空のPodを作成します3
2.Schedulerがspec.nodeNameが空であることを検知し、Scheduler queueに追加します4
3.kubeletが新しいPodの存在を検知しますが、spec.nodeNameが空なのでコンテナ起動をスキップします5
4.SchedulerがPodをScheduler queueから取り出して、スケジューリング可能かつSystem Resourceが充分なNodeにアサインします6。アサイン時に、spec.nodeNameをapi-server経由で更新します
5.kubeletがPodの更新Eventを検知し、spec.nodeNameが自身のNodeと合致するかを比較します。合致していたらコンテナを起動し7、api-server経由でstatusを更新します8
6.Podが(何らかの理由で)Terminatingしたとします
7.kubeletがapi-server経由で、Podのstatus.conditionを"terminated"に更新します9
8.ReplicaSet ControllerはPodのTerminateを検知し、api-server経由でPodを削除し10、新しくPodを生成します11
9.以降、ループを繰り返す...
Controllerと他のMasterコンポーネントが協調して動くと書きましたが、実際にはそれぞれのコンポーネントはapi-serverを除いて、命令を送るような明示的な連携を行なっていません。それにもかかわらず、全体的な整合性が取れています。
ここで、Masterのそれぞれのコンポーネントを、それぞれひとつのControllerだと考えてみましょう。Controllerたちは、それぞれのControl Loopを回すことだけに集中します。Eventが発生すると、それを検知し、実際の状態を望んだ状態に近づけるための動作を起こします。
コンポーネント間で手続き的な連携を取らずとも、それぞれのControllerがそのControl Loopを回すことによって、不思議と全体の整合性が取れていることが分かります。
KubernetesのCo-FounderであるJoe Beda氏の「Kubernetes is more jazz improv than orchestration.(Kubernetesはオーケストレーションというよりは、むしろジャズの即興だ)12」という説明は非常に核心を突いているといえるでしょう。
api-serverが指揮者となってすべてを命じるのではなく、各コンポーネントがプレイヤーとなって演奏をし、それらが重なることで不思議とひとつの即興の楽曲として成立しているのです。
KubernetesはなぜこのようなReconcile(調整)処理を行うのでしょうか。
それは、Kubernetesが分散コンポーネントで構築されていることに端を発しています。
前述のReplicaSet Controllerの例で見たとおり、KubernetesではEventがキーとなっています。Eventの発火に応じてControllerは動作しますが、前提として、複数のControllerが非同期かつ並行して動作しています。
ここでControllerが宣言的な挙動(Reconcile)ではなく、手続き的な挙動(Procedure)をすると仮定しましょう。もし、ReplicaSet Controllerが手続き的な挙動をする場合は、図1.4の、Eventが発生したタイミングに応じてPodの数の増減を行います。
しかしここで、Controllerが動いている際に、ネットワーク障害やバグなどによって情報が失われてしまうケースを考えてみましょう。ControllerはEventをトリガーとして動作しますが、障害でEventが失われてしまうと、当然期待どおりの動作をしなくなってしまいます。
その状態を解消できる方法が、再実行する時間(resync interval)を決めてReconciliationのループを繰り返すことです。
望んだ状態に近づけるReconcileをresync intervalの間隔ごとに繰り返すことで、たとえEventが失われたとしても、理想の状態(宣言的な状態)に近づけることができます。
こうしたedge-triggerとlevel-triggerの違いとKubernetesにおけるReconcileの詳細について、詳しく知りたい方は脚注の記事も参考にしてください13。
本節で、ControllerがControl Loopを実行すること、なぜControl Loopを実行するのかについてご理解いただけたでしょうか。
Control Loopは、Kubernetesの基本骨子ともいえます。Custom ControllerであってもControl Loopを実装していくことに変わりはありません。
本書では、Controller開発ツールの整理・必要なコンポーネントの理解・サンプルの実装を通して、Controllerの実装に踏み出すためのサポートをしていきます。
Kubernetesは、それ自体に標準のResourceを多く揃えています。たとえば、ひとつ以上のコンテナを動かすPodや複数のPodを制御するReplicaSet、ReplicaSetを管理してデプロイ戦略を実現するDeploymentなどのResourceを備えています。Kubernetesではそれらの標準Resourceに加えて、ユーザーが独自のResourceを定義し、そのResourceをクラスタ上で利用できます。それがCustom Resource Definition(以降、CRD)です。
Kubernetesが提供しているAPI機能を拡張するための方法として、CRDも含めた次の方法が主に提供されています。
1.Admission Webhook: APIリクエストを変更・検証するためのWebhookを追加する方法
2.CRD: 独自のResourceを定義するAPI拡張方法
3.API Aggregation: 追加のAPIを実装し、Aggregation Layerに登録することで拡張する方法
1のAdmission Webhookは、プラグイン型のWebhookです。Admission Webhookには、Mutating Admission Webhookと Validating Admission Webhookの2種類があります。Mutating WebhookとValidating Webhookは、それぞれ次の目的で利用されます。
・Mutating: APIリクエストの変更
・Validating: APIリクエストの検証
Mutatingは、APIリクエストの変更に使われています。
たとえばPodのObjectを作る際に、pod.spec.imagePullPolicyを記載しなくても、作成後のObjectのspecにはpod.spec.imagePullPolicy: Alwaysがデフォルトで設定されます。これはAlwaysPullImages14というAdmission Controllerによる効果です。
同じように、APIリクエストにデフォルト値を設定したり値を変更したりするような独自実装をしたい場合は、Mutating Webhookで実現できます。
Validatingは、APIリクエストの検証を行います。Validatingでは、labelやspecなどのフィールドのチェックを行い、チェックエラーの場合はエラーを返却します。
標準で備わっているAdmission Webhookと同様に、独自にAPIリクエストの変更やバリデーションチェックをカスタマイズして行いたい場合は、プラグイン用のAPIを実装し、Admission Webhookとしてkube-api-serverに追加で登録して利用します。
3のAPI Aggregationは、kube-api-server以外にcustom api-serverを実装し、拡張するための方法です。
Cloud Providerが提供しているKubernetesクラスタ外部のResource(マネージドサービス)を扱うためのService Catalog15やMetrics ServerなどのAPIを実現するために利用されています。
CRDはAPI Serverの処理が終わった後に非同期でメインロジックであるReconcilation Loopを実行しますが、API AggregationはAPI Serverの処理途中でHookするため、いくつかの制限があるCRDよりも自由度が高い処理を行えます。
しかし、API Aggregationは実装が難しく仕組みも煩雑なため、昨今ではCRDによる拡張が主流になっています。
それぞれの拡張方法の自由度を比較すると、次のようになります。
(低) Admission Webhook < CRD < API Aggregation (高)
本書では、CRDとControllerによるKubernetes拡張に焦点を当てて説明していきます。次節からCRDとCustom Resourceの詳細を確認していきましょう。
Kubernetesの拡張機能であるCustom Resource(CR)を利用するには、その定義であるCustom Resource Definition(CRD)をKubernetesクラスタに登録する必要があります。
CRDはKubernetes v1.15まではbeta機能でしたが、v1.16からはGAとなり、標準機能になっています16。
リスト1.1は、apiVersionがv1beta1のCRDのサンプルです17。
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: samples.stable.example.com
spec:
group: stable.example.com
versions:
- name: v1alpha
served: true
storage: true
scope: Namespaced
names:
kind: Sample
plural: samples
singular: sample
shortNames: ["sp"]
Kubernetes v1.15までCRDはapiVersion: apiextensions.k8s.io/v1beta1でしたが、v1.16からGAになり、v1に昇格しました。CRDを定義する時は、apiVersion: apiextensions.k8s.io/v1とkind: CustomResourceDefinitionを宣言します。しかし、v1で標準になったValidation機能については後述するため、リスト1.1ではあえてapiVersion: apiextensions.k8s.io/v1beta1を指定しています。また、CRDを作成するためには、specの定義が必要です。これはCRのspecとは別物で、あくまでCRDのspecですので、ご注意ください。
specのうち、group、names、scope、versionsが必須項目になっています。
kubectl explainはAPI Resourceのフィールドを調べることができるコマンドです。kubectl explain <resource名>やkubectl explain <resource名>.specなどで各フィールドの説明や必須項目の有無を確認できます。CRDもKubernetesのAPI Resourceであるため、kubectl explain crdでフィールドの詳細を確認できます。
CRDに慣れないうちは、kubectl explain crd.specでフィールドを調べると、理解の助けになるでしょう。
specのうち、重要な項目について簡単に説明します。
spec.group
作成するCRが所属するAPI Groupを指定する
spec.versions.served
REST API経由を介して提供されるかどうかのフラグ
spec.versions.storage
storage versionとして登録するかのフラグ(storage versionはetcdに保存される)。複数バージョン(v1alpha, v1beta, v1など)が存在する場合、ひとつのバージョンだけ選択されている必要がある
spec.scope
作成するResourceが、Cluster全体に適応されるかNamespace内にだけ適応されるか、というscopeを指定する
spec.names.kind
作成するResourceのkindを指定する。キャメルケース(各単語や要素語の先頭の文字を大文字で表記)で記載する
spec.names.plural
作成するResourceの名前の複数形を指定する
spec.names.singular
作成するResourceの名前の単数形を指定する。通常はkindと同じものを記載する(ただしkindはキャメルケースだが、singularはすべて小文字)
spec.names.shortNames
作成するResourceの名前の短縮形を指定する
特に実際の開発で重要になるのが、spec.versionsとspec.scopeです。言うまでもなく、バージョン管理とスコープが重要になるのはどのソフトウェアであっても同様です。
本章では、あくまでサンプルのCRDを作成するため、バージョンはv1alphaのみ・スコープがNamespacedのCRDサンプルで話を進めていきます。
Custom Resource(CR)はCRDで定義した後に、通常のResourceと同じくYAMLでspecを定義します。specにどのようなフィールドを設定するかは、作成者に委ねられます。自分が利用したいフィールドを独自に定義・利用できるため、比較的自由度が高いことがCRの特徴です。
リスト1.1を定義した後に宣言する、CRのサンプルが リスト1.218です。
apiVersion: "stable.example.com/v1alpha"
kind: Sample
metadata:
name: my-cr-sample-object
spec:
image: my-cr-image
message: "Hello Custom Resource!"
apiVersionとkindには、リスト1.1で定義したAPI Group(stable.example.com/v1alpha)とkind(Sample)を指定します。
specには前述のとおり、独自のフィールドを定義しました。spec.imageとspec.messageというフィールドを設けていますが、あくまでサンプルですので、この項目自体には特に意味はありません。
CRDがbetaの時点では、CRのspecは自由に設定可能でした。GAを迎えたCRD v1からは、「1.5 CRDの応用機能」で後述するOpenAPI Schemeベースとして、事前にCRのspecフィールドをCRDに定義することが必須になっています。この詳細は以降の節で後述します。