コンテナをオーケストレーションする

https://github.com/kurazuuuuuu/hackz-megalo

Go

Kubernetes

Redis

GCP

Terraform

コンテナをオーケストレーションしてる感って必要だと思うんですよ。

くらず / kurazu

推しアイデア

1プレイヤー1GKEで破産できるところ。

作った背景

Kubernetesって"コンテナオーケストレーション"って言われるものですが、実際オーケストレーションしてる感はないですよね。これはそれを解決します。

推し技術

Kubernetes, gRPC

プロジェクト詳細

みなさん、Kubernetes使ってますか?

もちろん使ってますよね。

と、思っていたらアピールタイムとか雑談の中でKubernetes知ってる人がほぼ居なかったので軽く説明します。涙。

Kubernetesは"コンテナオーケストレーション"と呼ばれ、マニフェストファイルと呼ばれる定義書のようなもので決められた状態を必ず維持するという性質を持ちます。

AWS, GCPなど様々なクラウドサービスで利用可能で、もちろんローカルのマシンでも利用することができます。

マイクロサービスアーキテクチャなど複数のPOD、サービスを使用するアプリケーションのデプロイには最適なんです。

で、

Kubernetesは"コンテナオーケストレーション"と呼ばれますが、実際あんまりオーケストレーションしてる感はありません。

ところで、オーケストレーションってなんでしょうか? image

そう、指揮者なんです。インフラを触っているあなたは指揮者なんです。

でも、別にKubernetes触ってて「うお〜!指揮者してるわ〜!」とはならないと思います。指揮者とまで言わずとも、Podたちを支配している感覚はありませんよね?少なくとも僕はそう思います。

なのでその問題を解決できる方法を作りました。


概要

  • KubernetesのPodをいじめたり観察して遊ぶやつ
  • Podが死んでもk8sの性質神秘の力で新たな生命(Pod)が生えてくる

Podは非常にか弱い生命です。大切にしてあげてください。 手が触れたり強い風が当たると死んでしまうかもしれません。


遊び方

このアプリを遊ぶには1プレイヤー1つのGoogle Cloudプロジェクトが必要になります!!

動作環境

  • Terraform
  • Cloudflareで取得したドメイン
  • Google Cloud CLI (ログイン済み)
  • WebXRが動作するデバイス (Meta Questシリーズ等)

環境構築

  1. Google Cloudでプロジェクトを作成
  2. Cloudflare Oneでコネクタの設定画面からTOKENを取得・メモ
    • http://nginx-proxy-svc:80をサービスとしてアプリケーションルートに登録
    • Optional: Cloudflare Accessでアプリケーションを作成、URLを保護
  3. Githubリポジトリをクローン
  4. terraform/gcpディレクトリの中のproject, secret_manager, deployの順番でinit, apply
    • *.tfvars.example使ってシークレットをローカルに保存
  5. 色々起動して完了。まあ、遊ぶのにはWebXRに対応したデバイスが必要になるのでハードルが高いです。

技術構成

フロントエンド

  • Three.js
  • Rapier (2D, 3D物理エンジン)
  • WebXR
  • WebSocket

バックエンド

  • Go

インフラ

IaC

  • Terraform

Network

  • Cloudflare
  • Cloudflare Tunnel

Deployment

  • Google Kubernetes Engine (GKE)
  • Google Memorystore for Redis
  • Google Logging
  • Google Error Reporting
  • Google Secret Manager

CI/CD

  • Google Cloud Build
  • Google Cloud Deploy
  • Google Cloud Artifact Registry
  • Google Cloud Storage

開発環境

  • WezTerm (neovim)
  • Terraform
  • Meta Quest Developer Hub
    • Meta Questデバイスのハードウェアレベルのメトリクス取得、ADB関連、ミラーリングなど様々な機能が使えます。
  • OrbStack (macOS)
    • Docker Container
    • Kubernetes
  • Skaffold
  • OpenSpec

IDE

  • Antigravity

フロントエンド開発

  • Vite v8系
    • ナウい。ビルドが鬼のように早い。
  • Vite+
    • めっちゃ最近登場したやつ、ナウい。
    • 超便利なツールチェインでなんでもできる。コレ一つで開発開始

バックエンド開発

  • Air
  • golangci-lint

AIツール

フロント・バックエンド開発
  • Codex CLI (gpt-5.3-codex, gpt-5.4)
インフラ構築
  • Antigravity (gemini-3.1-pro)
  • Gemini CLI (gemini-3.1-pro)

Google Cloud採用のため、Google系のAIツールを使うことで得意なのかな?とか思っていましたが、今のところそんなことは無さそうです。

情報収集
  • Grok
    • Reddit, Twitterから情報を集めるように指定して使ってます。Google検索のようなインターネットでメジャーな情報ではなく、ニッチな情報を集めるのが得意な印象があります。
  • NotebookLM
    • 関係ありそうなURLを適当にぶち込んでたまに質問して使っています。
  • Gemini (DeepResearch)
  • ChatGPT (DeepResearch)

構成図

image

インフラ

Kubernetes, CI/CDのデプロイはGoogle Cloud, ネットワーク関係はCloudflareを採用しています。

いつも通りの技術スタックにはなりますが、今までよりモダンな開発はできたと思います。

Terraform

今回もTerraformでIaCしました。前回はAWSだったのでGCPは初です。

GCPはサービスごとにAPIを有効にする必要がありますが、これもTerraformでApply, Destroyできたのでそうしています。

CI/CD

Cloud Builld --> Artifact Registry --> Cloud Deploy

Cloud Build, Cloud Runの構成は以前使ったことがありますが、Cloud Deployは初使用です。

後述しますが、Cloud DeployはSkaffoldを使用するのですが、開発環境・本番環境で差分を少なく開発ができたため読みやすくて良かったです。

環境変数に関しては、フロントエンドはCloud Buildでビルドをする際にSecret Managerから注入することで解決できますが、GKEは少し工夫をする必要がありました。

GKEにはCloudflare Tunnel用のcloudflaredサービスがいるため、開発環境・本番環境の両方でKubernetes Secretを使う方式を取っており、なるべく差分を減らしていました。

通常のGKEではGoogle Secret Managerからシークレットを格納することはできないのですが、現在プレビュー版として利用可能なシークレット同期機能を使い、Secret ManagerからGKEへ直接シークレットを同期させています。

セキュリティ的な観点で実装済みの別方法を使う方が良いそうですが、今回の用途ではベストマッチだったため採用です。

Kubernetes

Kubernetes EngineのAutopilotクラスタを使用しています。 AutopilotにすることでPOD数、リソースの消費に合わせてインスタンスを調整してくれるため、自分でインスタンス設定をチューニングする必要がありません。同スペックでは比較的コストが高くはなるのですが、短い期間なのでないようなものです()

master-service, controller-service, slave-serviceの3つのサービスに分割し、それに加えRedis, cloudflaredサービスの系5サービスを1つのクラスタにデプロイしています。

各サービス間はすべてGoのgRPCで接続、通信をしています。

例外としてmaster-serviceは外部(Cloudflare Tunnel)と通信するため、HTTP, WebSocketでも通信できるようにしています。

マスターサービス (master-service)

マスターは外部・内部の通信の中継地点サービスです。 Goは単一のプロセスで複数の通信プロトコルを取り扱うことができたため、マスターだけは唯一HTTP, WebSocket, gRPCの3つの通信プロトコルを使用しています。

コントローラーサービス (controller-service)

コントローラーは本アプリで遊ばれてしまう対象のPOD(slave-service)の状態・存在を管理するサービスです。

マスター・コントローラー間では直接通信を行っておらず、中間に存在するRedisを絶対情報として扱い、Redisの状態が正しいことになるため、マスターがPodを死亡としてマークすればコントローラーはそれに従ってプロセスを殺します。

このため、理論上はマスター・コントローラーのレプリケーション数を増やし、ロードバランサを適切に設定すれば理論上はRedisの情報が壊れることはないはずです()

スレーブサービス (slave-service)

遊ばれる対象です。プロセスの生存=Podの命であるため、コントローラーの命令でプロセスが停止されてしまった場合、Podは新たな生命に生まれ変わることを強制されます。

スレーブ自身は状態を一切持たず、コントローラーの停止命令くらいしか処理できません。

これこそまさにオーケストレーションですね()

Cloudflare

実質的に1人プレイになるため、認証とアクセス制限をCloudflare Accessに統合しています。

Cloudflare Access経由でアクセスする専用のアプリケーショントークンが生成され、クライアント側に渡されます。 そのトークンをバックエンドでCloudflare AccessのTeamで用意されている公開鍵でJWTの検証をするこ とで実質的にバックエンドの認証をしています。

Cloudflare | Validate JWTs


外部公開に関して、今回はクラスタにはいわゆるIngressやLBを配置しておらず、外部からの通信はできないようにしています。

そのため、Cloudflare Tunnelを使用して外部公開をしています。 Cloudflare Accessを使う都合上すごく相性が良かったので非常にLove♡

また、TunnelくんはHTTPだけじゃなくてWebSocketまで使えちゃう良い子なのでこれからも愛用していきます♡

実際の動作

まずはデプロイ状態から。 設計していたCI/CDパイプラインは無事に完成・動作しており、すべてのフローが問題なく動作しています。

image

転がってるGopherくんみたいなやつ。潰されるらしい。 WebXRのハンドトラッキングを使用して操作・インタラクションができます。
image


可哀想かもしれない

image

WebXRシーン上でGopherくんPODを潰すと実際にKubernetesにいる紐づいているPODで動作しているサービスに強制停止命令がgRPC経由で送信され、プロセスが死んでしまいます。そして、Kubernetesであるためプロセスの異常停止につきPODを再起動するため、また再度PODが生まれ、コントローラーに自己申告することでまた新たに登録されるようになっています。


Kubernetesの神秘の力

image

Podを潰しすぎて大量のサービスが死んでしまいました...これは大変です... これを復旧するには人の手で作業をしないといけないかも...?

image

さすがKubernetes...ッ!俺たちにできないことを平然とやってのけるッ! そこに痺れる憧れるゥ!!

はい、Kubernetesはマニフェストで決められた運命(さだめ)に従うのでしばらくするときれいに元通りにしてくれます。 Kubernetesの神秘の力ですね。スタンドパワー!!


やってみたこと / こだわってること

設計について

これはいつも通りなのですが、今回もアーキテクチャは自分で設計、実装はエージェントのフローを実施しました。

draw.ioカキカキ楽しい。構成図・Topa`zは一切AI使わずにやりました。手書き最高。

VRも使って設計

ただ、それとは別に新しい手法も取り入れました。 僕自身VRChatというデスクトップ・VRなど複数のデバイスで遊ぶ事ができる、メタバースのようなプラットフォームをよく使うのですが、今回そこを最終の設計仕上げに使ってみました。

ワールドのギミックとして空間に自由に描くことができるペンがよく実装されているので、それを使って無限のホワイトボードとしてたくさん描くことができます。 image

自由におしゃべりができるGemini Liveを片手に、色々自分の考えを確かめつつ、仮想のコワーキングスペースで設計を空間に描く。かわいい、かっこいいアバター、おしゃれだったりチルなワールドなど仮想空間特有の魅力があり、すごく楽しく作業できました。VR環境を持ってる人はぜひお試しください。

Obsidian

最近使い始めたObsidianも使って設計しました。 初期段階の構想レベルのものは基本ここになぐり書きしています。

ただ、Obsidianの魅力であるツリーのようなものではなく、単純にデバイスを問わず使用できるMarkdownメモとして使っています。

多分、プロダクトの設計というのは文字ベースで描くよりも、draw.ioやfigjamのような付箋を貼り付けることができるツールを使用することがメジャーだし推奨されていると思います。紙に描くのも良いらしいですね。

しかし、僕はそういったコトをするよりとりあえず思いついたことをキーボードでガリガリ書く方が早いし合っていると思ったのでこの手法を取っています。それこそObsidianやGoogle Documentのようにデバイスを問わずにいつでもメモが書けるツールがあるとすごく快適に使えて良いです。

自宅鯖にCouchDBをデプロイし、Cloudflare Zero Trust経由でVPN接続しているので、デバイス・場所を問わずにリアルタイム同期できます。

開発環境について

フロントエンド

ちょうどメガロカップの数日前にリリースされたVite v8, Vite+を採用しています。

特にVite+は環境構築をかなり簡略化してくれる上、Vite v8採用のためビルドが非常に高速です。かつvp checkやその他ツールなどリンターを含め様々な開発体験を向上サせてくれるツールが含まれており、Vite+一つで開発を始められたためすごく良かったです。

バックエンド・k8sローカル

OrbStackでk8sクラスタ作成、SkaffoldでGKEと同じようにテストしています。

また、クラスタのモニタリング用にk9sというCLIで利用できるツールを使っています。

Docker Desktopを使うのがメジャーですが、おま環なのか自分のMacbookでまともに動かなくなったのでOrbStackに完全移行しました。めちゃくちゃ快適。

docker composeなど既存のDocker Engine向けコマンドは大体使うことができるので移行も特に大変なこともありませんでした。

Skaffold


Skaffold は、継続的な開発、継続的インテグレーション(CI)、継続的デリバリー(CD)をオーケストレートすることで、デベロッパーの生産性を向上させるコマンドライン ツールです。

> Google Cloudのドキュメントから引用


Skaffoldを使うことでフロントエンド・バックエンド・その他サービスなど、本番環境へデプロイする状態とほぼ同じ環境でテストを行うことが可能で、skaffold devで起動している状態ではファイルの変更を検出し、自動で再ビルドを行いPODのイメージを更新してくれるなど、開発効率がかなり向上しました。

今回インフラにCloud Deploy, Kubernetes Engineが含まれており、Cloud DeployにはSkaffoldが必要だったため、初めて使用しました。

Immersed

[#VRも使って設計] に近い話になりますが、ハッカソン当日の3日間、ImmersedというVRヘッドセット上で複数の仮想モニターを展開可能なアプリを使用し、3画面で常に作業を進めていました。

これ、ガチでいいです。

今回一人チーム(?)だったこともあり、スペースに限りはあったものの、広い作業スペースを確保して効率よく開発できて楽しかったです。^^

Chrome, ターミナル複数窓, Discord, Youtube, Draw.ioみたいな大量ウィンドウを常に開いた状態にできて神。なお、16GBのMacbook Proには荷が重かったようでSWAPが限界突破しておりました。ごめんねMacbook。

WebXRのデバッグ

WebXRをデバッグするにはChromeの拡張を使う、エミュレータを使用するなどの方法はありますが、今回Questで直接デバッグする手法を採用しています。

しかし、Questから開発サーバーにアクセスするのには少し手間がかかってしまいます。

これを解決するために、先述したSkaffoldを使うことで、本番環境と同じようにCloudflare Tunnelで外部に出してしまうことで開発機からCloudflare、CloudflareからQuestのようにインターネットさえ繋がっていれば使えるようにしました。

テザリングでネットワーク構築したり、ハッカソン会場のネットワーク経由でローカルIPから接続するなど色々な手段はあるとおもいますが、本番環境と差分を減らしたかったため開発環境でも初めてTunnelを使っています。

OpenSpec

以前参加したカンファレンスで知ったAIエージェントのスペック駆動開発を支援するツールを導入しました。

AGENTS.mdで明示的に使用を固定化し、基本的にすべての作業をopenspec changesopenspec validateなどを使って自律的に要件を生成、バリデーションをし精査をしてから実装を行わせています。

時間がかかったりコンテキストを比較的圧迫してしまうなどの問題はありますが、それよりも1発でかなり思った動作になることが多く、相対的にコスト削減になっていると思います。

他にもスペックを作成して実装を行うエージェント・ツールは存在しますが、環境依存が少なく、簡単にかつ軽く使えるOpenSpecは非常に重宝しています。

Codexをメインに使っていましたが、High, Extra HIghと合わせて使うとかなりいい感じにしてくれて楽しかったです。

途中で起きた問題たち

Goが早すぎる問題

スレーブサービスも含めすべてのサービスがGoで動いているのですが、スレーブの再起動が早すぎて全然Podが死んでる感がありました。マシンの性能次第というのもあるとは思いますが、基本的にめちゃくちゃ早いと判断するのが良さそうです。

image 画像は開発中のOrbStackのActivity Monitorですが、Goで動いてるサービス達のメモリ消費量が凄まじく少ないことがわかります。gRPCしか使ってないサービスはまだわかりますが、HTTPまでやってるMasterでさえもこのメモリ消費量なのが衝撃でした。早いのも納得ですね。

今回の実装ではあまり機能量が多くなく、Goの軽さを活かせてはいませんが、もっと大規模になるとそれなりに効果が出そうで見てみたいなと思います。

Codexフロントエンドが下手くそ問題

一部インフラ以外はすべてCodex CLIで開発をしていますが、Codexくんあまりにもセンスがありません。Gemini Proの方が圧倒的にマシです。

Skillsの導入も考えましたが、Twitter(現X)で見た感じあんまり効果がなさそうだったので導入せず進めました。プロンプトで気合でゴリ押しています。

Cloud Deployがうまく動かない問題

最初に構築したパイプラインでは4つのパイプラインが1つのGKEに向き、かつリリースの作成など一部の役割がCloud Build, Cloud Deployで重複してしまいうまく動きませんでした。

Cloud Buildはskaffold.yamlだけでデプロイができる、というのに変にCloud Buildに役割を持たせてしまったのが原因だと考えられます。

エージェントを使ってインフラを実装させているとこういった問題も発生するため、やはりドキュメントは人の手で一度読み込む、もしくはNotebookLMなどに一通りぶち込んで1つの資料として読み取れるようにするべきですね。

WebSocketがシビア問題

最初は1つのWebSocket接続につき1つのセッション、つまり1ゲームとして扱っており、WiFiが一瞬切断されることでWebSocketが切断され、セッションが終了してしまうためRedisの状態も狂い、スレーブPODが再接続できないみたいな問題が発生していました。

対応策としてはユーザーが意図的に切断した場合とそうでない場合でWebSocketの切断の方法を区別し、もし不意に切断された場合はマスターサービス側で少しだけ再接続を待機するようにしました。

まだまだWebSocketやリアルタイム通信には精通できていないので、今後も勉強していきたいです。


image

くらず / kurazu

@kurazuuuuuu