Mahout: A K8s operator for Mastodon, written in OCaml and model checked by NuSMV
OCaml で Mastodon 用の Kubernetes operator を書きました。Mahout という名前がついています。ついでに、operator のコアのロジック(reconciler の部分)について NuSMV でモデル検査しました。
百聞は一見にしかずということで、Mahout がどんな感じで動くのかお見せしたいと思います。
Mahout には e2e テストがあって、Mahout のデモにはこの環境を利用するのが楽なのでこれを使います。Mahout のレポジトリを clone してきて make create-cluster
を打つと kind を使って Kubernetes クラスタを立ち上げてくれます:
このクラスタでは、Mastodon が動作するために必要な PostgreSQL や Redis が動いています:
また、Mastodon が動作するために必要な環境変数の定義は Secret
として保存されています:
Helm を使って Mahout をセットアップします:
Mahout が立ちました。同時に、この Helm Chart は Mastodon
リソースという CRD も導入しています:
これで準備ができたので、Mastodon サーバを立てましょう。Mahout が導入した Mastodon
リソースを作ります。以下のようなマニフェストを適用します。
すると Mahout が勝手に Mastodon 本体(web)や WebSocket サーバ(streaming)、Sidekiq などを立ち上げてくれて、サービスが開始されます。しばらく待っていると、それぞれの Pod が起動しているのがわかります:
立ち上がった Mastodon に接続してみます。gateway を port-forward して:
/etc/hosts
に server name を追記します:
http://mastodon.test:8000/ にアクセスすると Mastodon の画面が映ります。便利!
Mastodon
リソースを更に作れば更に別の Mastodon サーバを立てることができます。便利!(2 回目)
Mastodon の新しいバージョンが公開されたら、Mastodon
リソースの image
フィールドを書き換えることで、Mastodon のアップデートを行うことができます。
フィールドを書き換えると、Mahout がそれを検知して、勝手に DB マイグレーションを走らせてくれます。しばらくすると、Mastodon のバージョンが 4.2.0 に上がっていることを確認できます:
ちなみに、Mastodon のバージョンによってはマイグレーションを 2 度実行する必要があるのですが(参考:Mastodon v4.2.0)、Mahout はそれを勝手もやってくれます。便利!(3 回目)
Mahout は Kubernetes コントローラです。よくあるコントローラと同様、Mahout ではカスタムリソースである Mastodon リソースを導入します。ユーザが Mastodon リソースを作ったり編集したりすると、Mahout がそれを検知し、必要な Kubernetes リソース(Deployment や Job など)を作ってくれます。
Kubernetes のドキュメントを見ると:
オペレーター(すなわち、コントローラー)はどの言語/ランタイムでも実装でき、Kubernetes APIのクライアントとして機能させることができます。
と書いてありますが、公式のクライアントライブラリ が用意されているのは当然 Go や Java など一部の言語だけです。非公式のライブラリの一覧も同じページにありますが、残念ながら OCaml のクライアントライブラリはありません。というわけで、OCaml で Kubernetes コントローラを書くためには、まず Kubernetes API を叩くライブラリを整備する必要があります。
幸い、Kubernetes API は OpenAPI spec の形で記述されています(swagger.json)。OpenAPI には OCaml 用のコード生成器もあるので、これに食わせればいい……かと思いきや、そう話は簡単ではありません。
OpenAPI の OCaml 対応は正直微妙で、単純に Kubernetes の swagger.json を入力しても動く OCaml コードは出てきてくれません。これはコンパイルが通らないというのもそうですし、コンパイルできても動作が不良であるという点もありました。一例を挙げると、object
型の入力を Yojson.Safe.t
ではなく (string * string) list
だと思って展開してしまう(のでリクエストの JSON を正しくパーズできない)とか、フィールド名を勝手にスネークケースにしてしまう(のでリクエストの JSON を正しくパーズできない)とか。ということでなんやかんや修正しました。ついでに、Lwt の代わりに Eio を使うようにしました。Eio は OCaml 5.0 で導入された effect handler を使った非同期 I/O を提供するナウいライブラリです。Mahout では全体で Eio を使っています。
修正したコードはここで公開しています。Kubernetes 用に特化している部分があるので、OpenAPI 本体への contribution は見合わせています。
続きます。