はじめに
前回の記事では、個人開発しているオートロックシステム「Autolock」を例に、コンテナ化するメリットとKubernetesの基本を紹介しました。
コンテナを機能ごとに分割すると、機能や権限を分離できます。一方で、複数のコンテナで使用する設定情報を管理し、変更を反映する仕組みが必要になりました。
そこで今回は、Kubernetes Operatorを自作し、Autolockの設定管理を自動化することにしました。
この記事では、Operatorの基本的な仕組みをAutolock Operatorの開発を題材として紹介します。
前回の記事はこちら
1. なぜOperatorが必要だったのか
Autolockは、メインの制御プログラムやWebアプリ、Webhookなど、複数のコンポーネントで構成されています。
Webアプリから設定を変更した場合、その内容を必要なコンポーネントに反映しなければなりません。
Kubernetesでは、ConfigMapに設定情報を保存し、各Podから参照できます。しかし、ConfigMapを更新しただけでは、アプリケーションが設定を再読み込みするとは限りません。起動時にしか設定を読み込まないプログラムでは、Podの再起動が必要です。
また、設定項目によって影響を受けるコンポーネントは異なります。すべてのDeploymentを毎回更新するのも効率的ではありません。
そこで、次の処理を自動化したいと考えました。
- Webアプリから設定を変更する
- 設定をConfigMapに反映する
- 影響を受けるDeploymentを更新する
- KubernetesにPodの入れ替えを任せる
これらの処理をまとめて管理するために、Kubernetes Operatorを採用しました。
2. Kubernetes Operatorとは
2.1 Operatorの役割
Kubernetesには、DeploymentやServiceなどのリソースが用意されています。
例えば、DeploymentでPodのレプリカ数を2に指定すると、Kubernetesのコントローラーが2つのPodを維持しようとします。
しかし、Deployment自体が削除された場合、KubernetesがそのDeploymentを自動的に再作成してくれるわけではありません。
そこでOperatorを利用すると、CRに定義された理想状態をもとに、必要なDeploymentやConfigMapを作成・更新する処理を実装できます。
Autolockでは、以下の図のようにDeploymentだけでなく、ConfigMapなども含めた複数のリソースを連携させる必要があります。
※この他にもmainロジックとMQTTの処理の構成リソースなど管理しなければいけないリソースが数多くあります。
例えば、設定変更に応じてConfigMapを更新したり、必要なDeploymentを再作成したりする処理です。
こうしたアプリケーション固有の管理処理を自動化する仕組みがOperatorです。
2.2 CRDとCR
Operatorでは、Autolock全体の望ましい状態を表すために、カスタムリソース(Custom Resource、CR)を利用します。
例えば、Autolockの回転方向やセンサーの有効・無効、Deploymentのレプリカ数やコンテナイメージなどをCRにまとめて定義します。
これにより、ConfigMapやDeploymentを個別に変更するのではなく、CRを変更すれば、Operatorが関連するリソースに設定を反映するという構成を実現できます。
この独自リソースの種類や設定項目の型定義などするものが、CRD(CustomResourceDefinition)です。
CRDとCRの違いは、次のとおりです。
- CRD: 独自リソースの種類や設定項目を定義するもの
- CR: 定義されたリソースに実際の設定値を記述したもの
個人的には、C言語に例えると理解しやすいと感じました。
CRDは構造体の型定義、CRはその構造体に値を設定したものに近いイメージです。
実際のAutolockのCRDとCRの一部を抜粋してみていくとイメージがつかみやすくなると思います。
AutolockのCRD(※抜粋)
- CRに記述できる要素(rotate_direction)の型は
string型で、取れる値はleftとrightの2値であるというように定義しています。 - まさに、変数定義でヘッダーファイル
~.hでstd::string rotate_direction;と変数の宣言をしている感じです。
rotate_direction:
description: RotateDirection specifies the thumb-turn
rotation direction.
enum:
- right
- left
type: string
AutolockのCR(※抜粋)
- 実際にrotate_directionに設定する値は
leftという指定をCRで行っています。 - ここはヘッダーで定義した変数に値を代入しているイメージですね。
spec:
rotate_direction: "left"
そしてController(Operatorの処理の実体)がCRの内容を読み取り、ConfigMapやDeploymentなどに設定を反映します。C言語でいうと、変数の値を読み取って処理を実行するプログラムに近いイメージです。
もちろん完全に同じではありませんが、CRで望ましい状態を定義し、Controllerがその状態に近づけるという仕組みを理解するうえで役立つ例えだと思います。
なお、Deploymentなどの標準リソースも、同じように望ましい状態を宣言して利用します。違いは、リソースの種類がKubernetesにあらかじめ用意されているか、自分で定義するかという点です。
2.3 CRとConfigMapの違い
CRとConfigMapは、どちらも設定値を扱いますが、役割が異なります。
- CR: Autolock全体として実現したい設定や構成を定義する
- ConfigMap: 各コンポーネントが実際に利用する設定データを保持する
例えば、CRに回転方向や動作時間を定義し、Controllerがその内容を各コンポーネント向けのConfigMapに反映します。
こうすることで、CRを設定の情報源として一元管理しながら、各Podには必要な設定だけを渡せます。
ただし、CRを作成しただけでConfigMapが自動的に生成されるわけではありません。CRを読み取り、関連するリソースを作成・更新する処理をControllerに実装する必要があります。この辺は割と泥臭い実装が必要なります。
結局Operatorとは、一般的なLinuxの仕組みで例えると、systemdのdaemonに近いイメージです。
例えば、systemdは設定ファイルなどをもとにサービスを起動・管理します。一方、KubernetesのOperatorは、CRに定義された理想状態をもとに、Controllerがクラスタ内のリソースを作成・更新し、その状態を維持します。
Operatorは、CRを定義するためのCRDと、それを監視して理想状態を実現するControllerなどを組み合わせたアプリケーションということです。
3. Autolock Operatorの設計
今回の設計では、Webアプリ・Operator・Kubernetesの役割を分けました。
それぞれの役割は次のとおりです。
- Webアプリ: ユーザーが設定を編集する
- CR: Autolock全体の望ましい状態を保持する
- Operator: CRの内容をConfigMapやDeploymentに反映する
- Kubernetes: Podの起動や更新を管理する
Webアプリは、Kubernetes APIを通じてCRを更新するところまでを担当します。その後の設定反映はOperatorに任せます。OperatorはCRの変更を検知すると、それに合わせて処理を実行します。
また、WebアプリにはCRを操作するために必要な権限だけを与え、ConfigMapやDeploymentの管理権限はOperator側に持たせる構成にできます。ServiceAccountやRoleといったリソースなどを利用すれば、操作できるリソースや操作の種類を制限できます。
なお、通知用トークンやデータベースの認証情報などの機密情報はSecretで管理し、データベースの永続データはPVなどを利用して別途保持します。すべての情報をCRにまとめるのではなく、情報の性質に応じて管理方法を分けることも重要です。
4. 設定変更を反映する仕組み
では、設定が変更されたとき、Operatorはどのように動作するのでしょうか。
4.1 ConfigMapの更新
WebアプリからCRが更新されると、Controllerはその内容を確認し、必要なConfigMapを更新します。
ConfigMapを更新すると、ボリュームとしてマウントされたファイルには、通常、変更内容が反映されます。ただし、アプリケーションが設定を自動的に再読み込みするとは限りません。
Autolockでは、設定変更を確実に反映するため、必要に応じて対象のPodを入れ替える仕組みも用意します。
4.2 ハッシュ値を利用したPodの更新
設定が変更されるたびに、すべてのDeploymentを更新する必要はありません。
そこで、ControllerはConfigMapを更新する際に、その内容からハッシュ値を計算しておきます。
ハッシュ値は、データから計算される固定長の値です。設定内容が変われば、通常はハッシュ値も変わります。
この値を対応するDeploymentのPodテンプレートにアノテーションとして記録します。KubernetesはPodテンプレートの変更を検知すると、新しい設定を使用するPodへの入れ替えを開始します。
この仕組みを利用することで、設定変更の影響を受けるDeploymentだけを更新できます。ハッシュ値の計算とDeploymentへの反映はOperatorが担当し、Podの入れ替えはKubernetesに任せます。
このPodの入れ替え(図の③の)動作はAutolock Operatorに実装したわけではなく、Kubernetes自体の動作です。
例えば、メイン制御用の設定だけが変更された場合は、その設定に対応するハッシュ値を更新し、メイン制御用Deploymentだけを更新します。
このように、設定の変更内容に応じて対象を絞ることで、関係のないコンポーネントの再起動を避けられます。
5. Operatorを作って気づいたこと
今回の開発を通して感じたのは、OperatorはKubernetesを使うために最初から必要なものではなく、運用上の課題を解決するための選択肢だということです。
Operatorを開発するまでは、Kubernetesは冗長化や可用性を高めるためだけの技術だと思っていました。しかし、今回の開発で、Dockerだけでは管理しきれない設定変更やリソースの更新も自動化でき、開発の観点でも活用できる技術だとわかりました。
Autolockでは、機能の追加に伴ってコンテナを分割した結果、設定の共有や変更の反映を管理する必要が出てきました。
そこで、Kubernetesの仕組みを活用し、さらにOperatorを自作することで、Autolock固有の設定管理を自動化しました。
今回の構成を整理すると、次のようになります。
- コンテナ化: 機能ごとに責務や実行環境を分離する
- Kubernetes: コンテナの起動や更新、状態の維持を管理する
- Operator: Autolock固有の設定管理を自動化する
すべてをOperatorで実装する必要はありません。設定変更に伴うリソースの更新はOperatorが担当し、その変更に応じたPodの入れ替えはKubernetesに任せられます。既存の仕組みを活用することで、独自に実装する範囲を必要最小限に抑えられます。
もちろん、すべてのアプリケーションにKubernetesやOperatorが必要なわけではありません。小規模な構成であれば、Docker Composeなどで十分な場合もあります。
しかし、Dockerでの運用に課題を感じたときには、Kubernetesを単なるコンテナ管理基盤としてだけでなく、開発や運用を自動化するための選択肢として検討してみるのもよいと思います。
最後まで読んでいただき、ありがとうございました。
今回開発したOperator↓↓




