KubernetesでMinecraftサーバーを運用すると何が難しいのか
KubernetesでMinecraftサーバーを運用すると何が難しいのか
Minecraftサーバーは、Dockerを使えば比較的簡単に動かせます。
Docker Composeで環境変数を指定し、ボリュームをマウントして起動すれば、個人サーバーとしては十分に便利です。
しかし、これをKubernetes上で「運用」しようとすると、見える問題が変わります。
Podは再作成されます。Deploymentは再デプロイされます。world dataはPVCに残ります。設定やpluginsは更新されます。終了時に保存処理が失敗すると、worldやpluginsの状態に影響することもあります。
この記事では、MinecraftサーバーをKubernetes/GitOps前提で扱うときに難しくなる点と、その問題意識から作っているDocker imageについて整理します。
Docker Composeでは見えにくい問題
Docker ComposeでMinecraftサーバーを立てる場合、多くのケースでは「一度起動して、そのまま動かし続ける」運用になります。
この場合、便利なDocker imageを使い、必要な環境変数を入れれば十分なことが多いです。
一方、KubernetesではPodの再作成や再スケジューリングが普通に起きます。
GitOpsで管理している場合は、manifestに書かれた宣言的な状態と、PVC上に残っている実際の状態のズレも問題になります。
Minecraftサーバーは、statelessなWebアプリではありません。
/data にはworld、server.properties、plugins、configs、logsなど、サーバーの運用状態そのものが残ります。
そのため、コンテナ再作成時に何を再実行し、何を触らないかを明確にしないと、意図しない変更や削除につながります。
Kubernetesで難しくなるポイント
まず大きいのは、Pod再作成と初期化処理の関係です。
サーバーjarの取得、configの生成、pluginsの配置、worldの展開などは初期化処理ですが、Podが再作成されるたびに毎回同じように実行してよいとは限りません。
次にPVCの扱いです。
PVCにはworld dataが残るため、間違った初期化や同期処理で中身を消すと被害が大きくなります。特に、既存worldがある状態でTYPEやVERSIONを変更した場合、どこまで自動で置き換えるべきかは慎重に考える必要があります。
graceful shutdownも重要です。
KubernetesはPod終了時にSIGTERMを送りますが、Minecraftサーバー側の保存処理やpluginsの終了処理を考えると、それだけに任せるのは不安があります。RCONで明示的に save-all flush や stop 相当の処理を行い、十分な終了猶予を持たせる方が安全です。
さらに、plugins、configs、datapacks、resourcepacksの配布も問題になります。
imageに全部焼き込むと更新が重くなりますし、手動でPVCに置くと再現性が落ちます。そこで、S3互換ストレージやMinIOから同期する運用が候補になります。
ただし、S3/MinIO syncにも危険があります。
prefixを間違えたり、空のprefixを指定した状態でremove系の同期を行うと、ローカル側のファイルが削除される可能性があります。PVCを使う場合、この手の破壊的操作は特に慎重に扱うべきです。
install phaseとruntime phaseを分けたい
この問題を整理すると、Minecraftサーバーには大きく2つの責務があると考えられます。
1つは、サーバーを起動する前の準備です。
server jarの取得、plugins/configsの同期、worldの展開、server.propertiesの準備などです。
もう1つは、実際にMinecraftサーバーを起動して動かすruntimeです。
この2つを曖昧にすると、Pod再作成のたびに初期化処理が走り、既存データに意図しない変更を加える危険があります。
そのため、install phaseとruntime phaseを分け、既存の状態と要求された設定が矛盾している場合は、自動修復よりもfail-fastする方が安全だと考えています。
Kubernetesでは、壊れた状態でなんとなく起動するより、明確に失敗して原因をログに出す方が運用しやすいです。
RCON graceful shutdownの重要性
Minecraftサーバーの終了では、world保存やplugins stateの保存が重要です。
KubernetesのPod終了に合わせて、単にプロセスを落とすだけでは不安が残ります。
そこで、preStop hookから /entrypoint.sh rcon-stop を呼び出す設計にしています。
RCONを使ってサーバーに終了コマンドを送り、保存処理を行ってから停止させるためです。
また、terminationGracePeriodSeconds: 120 のように十分な終了猶予を持たせることで、Kubernetesが強制終了する前にサーバー側の終了処理を完了しやすくします。
たとえば、Kubernetes manifestでは次のように preStop と終了猶予、readiness probeを明示します。
spec:
template:
spec:
terminationGracePeriodSeconds: 120
containers:
- name: minecraft
lifecycle:
preStop:
exec:
command: ["/entrypoint.sh", "rcon-stop"]
readinessProbe:
exec:
command: ["test", "-f", "/data/.ready"]
periodSeconds: 10
failureThreshold: 3
この例では、Pod終了時にRCON経由の停止処理を走らせ、終了処理中はready状態から外れるようにしています。
readinessについても、単にプロセスが起動しているかではなく、/data/.ready のようなファイルを使って、runtimeが一定時間生存した後にreadyとみなす方が扱いやすいです。
終了時にはこのreadyファイルを消すことで、終了処理中のPodへ通信が流れにくくなります。
S3/MinIO asset syncという考え方
Minecraftサーバーを複数環境で運用する場合、pluginsやconfigsをどう配るかは意外と面倒です。
imageに焼き込むと、pluginsを1つ更新するだけでimage rebuildが必要になります。
PVCに手動配置すると、今度はGitOpsや再現性と相性が悪くなります。
そこで、pluginsやconfigsなどのassetをS3互換ストレージに置き、起動時に同期する方法を採っています。
MinIOを使えば、self-hosted環境でもS3互換のasset storeを用意できます。
たとえば、以下のようなprefixを用意します。
s3://minecraft-assets/paper/example/plugins/
s3://minecraft-assets/paper/example/configs/
サーバー側では、SecretからS3/MinIOの認証情報を受け取り、指定したbucket/prefixからpluginsやconfigsを同期します。
この方法により、imageとassetを分離できます。
Docker imageはruntimeとして管理し、pluginsやconfigsはobject storage側で管理する、という分担ができます。
remove-extraをopt-inにした理由
S3/MinIO syncで特に注意が必要なのが、remove-extra系の挙動です。
*_REMOVE_EXTRA=true は、remote prefixを正とみなし、remoteに存在しないローカルファイルを削除する可能性があります。
これはdesired stateを厳密に保つには便利ですが、prefixを間違えたときのリスクが大きいです。
特に、PVC-backedなMinecraftサーバーでは、安全側のデフォルトが重要です。
そのため、remove-extraはデフォルトでfalseにし、明示的に有効化した場合だけ削除を伴う同期を行う設計にしました。
つまり、次のような考え方です。
- 通常は同期して追加・更新するだけ
- S3 prefixを完全な正とみなしたい場合だけ
*_REMOVE_EXTRA=true - 有効化する前にbucket/prefixが正しいことを確認する
- 空prefixや間違ったprefixではfail-fastする
破壊的な挙動は、暗黙のデフォルトではなく、明示的なopt-inにすべきだと考えています。
このために作っているDocker image
この問題意識から、alexandergg-0520/minecraft-server というDocker imageを作っています。
リポジトリはこちらです。
このimageは、初心者向けに何でも自動設定することを目指しているわけではありません。
Kubernetes/GitOps/self-hosted infrastructureでMinecraftサーバーを扱う人向けに、以下を重視しています。
- 明示的なinstall/runtime lifecycle
- fail-fastなエラー
- PVC上のworld dataを安全に扱う設計
- S3/MinIO-backed asset workflow
- RCONによるgraceful shutdown
- Kubernetesで扱いやすいreadinessと終了処理
一言で言うと、Minecraftサーバーを「単発のローカル鯖」ではなく、「運用対象のインフラ」として扱うためのimageです。
examples
現在、いくつかの小さなexampleを用意しています。
主な例は以下です。
- Kubernetes + Paper + PVC の最小構成
- Kubernetes + Paper + MinIO asset sync の構成
- install-only jobによるvolume pre-warm
- Docker Composeでの小さなFabric構成
これらは、そのまま全環境でproduction-readyというより、設計パターンを理解するための出発点です。
実際のクラスタでは、StorageClass、Service公開方法、backup、monitoring、resource requests/limits、securityContextなどを環境に合わせて調整する必要があります。
既存の便利なimageとの関係
Minecraft server用の汎用Docker imageはすでに存在していて、とても便利です。
特に、Docker Composeや単一ホストで素早くサーバーを立てたい場合、それらのimageは非常に強い選択肢です。
このプロジェクトは、それらを置き換えるものではありません。
目指している方向が少し違います。
多くのimageが「手軽さ」や「広い機能対応」を重視するのに対し、このimageではKubernetes/GitOps運用における明示性、予測可能性、安全側のデフォルトを重視しています。
どちらが上という話ではなく、運用の前提が違うという話です。
おわりに
Minecraftサーバーは、遊びのためのサーバーでありながら、運用してみると意外とインフラらしい問題が出てきます。
Pod再作成、PVC、graceful shutdown、asset同期、削除の安全性などは、普通のWebアプリとはまた違った難しさがあります。
Kubernetesに載せることで、そうした隠れていたライフサイクル上の問題が見えやすくなりました。
その問題に対して、明示的で予測可能なMinecraft server imageを作っている、というのがこのプロジェクトです。
まだ発展途中ですが、Kubernetesやself-hosted環境でMinecraftサーバーを運用している人からのフィードバックを歓迎しています。
Discussion