重力に縋るな

千種夜羽です

マイクラサーバをGitHubで運用する

こんちわ〜.珍しく1ヶ月という短期間でブログ記事を書くsksatです. 今回はここ数ヶ月運用しているマインクラフトサーバについて書きます.

Minecraft

みなさんマイクラやってますか?僕はそんなにやってないです.運用するのはまあまあおもしろい.

とはいえそんなにやってないわけでもなくて,5月頃はけっこうやっていて,空中から海をブチ抜いて地中まで刺さっている畑などがあります. まあかなりのcontribution(?)は他のオタク各位ですけどね.

で,このマイクラサーバなんですが,以下のようなかんじで運用しています.

  • サーバ: PaperMC 1.17.1
  • モード: サバイバル
  • Mod/Plugin: 基本バニラ.使ってるものは以下.
    • PrometheusExporter
    • DiscordSRV
    • CoreProtect
  • ユーザ管理: ホワイトリストGitHub repoへのPull Requestで追加

GitHubでのマインクラフト運用

というわけで本題ですが,このサーバはGitHubを使って運用しています.

github.com

どういうことだよ,となると思うんですが,見ればわかる. このサーバはdocker-composeを使って運用していて,このリポジトリにはマイクラサーバをデプロイするためのスクリプト群がある,ということです.かんたんだね.

じゃあこのリポジトリをcloneしてきて,docker-compose upすればマイクラサーバが立ち上がってうれしい,ということかと思うじゃないですか. それは半分正解です.しかしそれだけではありません.

正確には,このリポジトリは常にmainブランチが本番環境に同期されるようになっています.

そしてよく見てほしいのですが,このリポジトリにはdata/whitelist.jsonがあります. そしてこのサーバに参加しているオタクが登録されています. 極めつけはREADMEの一番下.

how to join

Edit whitelist.json and send a pull request

はい.GitHubで運用するということは,つまりそういうことです.

なので,このマイクラサーバに参加したい人がいる時,僕はサーバにSSHしてwhitelist.jsonを書き換える必要はまったくありません. 必要なのは,「あ,じゃあmc.yohane.suのREADME読んでプルリク送ってね」の一言と,マージボタンを押すことだけです.

CD(Continuous Deployment)

みなさんマイクラサーバ運用でCDしてますか?僕はしてる.

mainブランチが本番環境に同期されるというのはこれの話です.

CD,したいですよね.でもこれdocker-composeなんですよね. CDするための良さげなツールとかないんですよ(あったら教えてほしい). Kubernetes使ってたらArgoCDでポン,みたいなかんじでいいんですけどね. というかできるならそのうちそっちに移行したい.

じゃあどうしてるのかというと,雑なスクリプトを書きました. 「docker-composeでもCDをしたい!」ということで,その名もcompose-cd

github.com

これはmc.yohahe.suのためではなく,kuso-subdomain-adderというものの運用自動化のために書いたスクリプトなんですが,けっこう便利なので最近はよく使ってます.

kuso-subdomain-adderteleka.suというクソドメインサブドメインを生やすためだけのWebサービス(?)です. ここらへんの話はVRC-LT #9でやりました.

speakerdeck.com

というのはさておき,compose-cdの話に戻りますが,使い方は超簡単. 各リポジトリdocker-compose.ymlがあるディレクトリに.compose-cdという設定ファイルを置くだけです.

compose-cdでは,docker-composeなプロジェクトで使うために,デプロイが走るタイミングとしてリポジトリ自体が更新される場合と,リポジトリで使っているイメージが更新される場合を想定しています.

リポジトリが更新される場合というのは,リポジトリでコンテナからマウントされるような設定ファイル(マイクラであればwhitelist.json)を管理していて,それが更新される場合です.

次にイメージの更新ですが,これは新しいバージョンのイメージが出たら更新する,という話ではありません. CDなんてことをする以上,ある時点でのリポジトリからはそのリポジトリ内で設定されているバージョンのモノがデプロイされるべきです. 更新してupstreamのリポジトリも更新するようにすればよいかもしれませんが,それは管理のしやすさ的にもセキュリティ的にもよくないでしょう.

端的に言うと,compose-cdの責任範囲はupstreamのリポジトリとイメージの更新を確認し,更新があればデプロイを実行する,ということまでだということです. ただし,latestのようなイメージタグを使っている場合は例外です. まあlatestなんて基本的に使うべきではないわけですが,別にプロダクションではなく趣味なので雑に使いたい時もありますよね. 最近は後述するRenovateのおかげでほぼないけども...

これらの2つの場合に対応するため,.compose-cdでは基本的にリポジトリ,イメージ,イメージタグを指定します. 例えば,kuso-subdomain-adderではこんなかんじ.ghcr.ioにも対応しています.

REPO="https://github.com/sksat/kuso-subdomain-adder"
REGISTRY="ghcr.io"
IMAGE="sksat/kuso-subdomain-adder"
IMG_TAG=master

compose-cdがやっていることはめちゃくちゃ単純です. 実際実装も300行程度のBashスクリプトとsystemd timerです.

compose-cd.timerがsystemd timerの設定で,毎分compose-cd.serviceを呼び出すだけです. compose-cd.serviceはcompose-cd updateを呼び出すだけです. compose-cd updateでは,SEARCH_ROOT以下のディレクトリで.compose-cdがあるディレクトリを探索し,.compose-cdにある設定に従ってリポジトリとイメージの更新を確認し,更新があればgit pullないしdocker-compose pullをして再起動(docker-compose down, docker-compose up -d)します.

まあ超単純なんでそんなに書くことはないですね. ghcr.ioだとうまくいくのにDockerHubだとうまくいかなかったりとか, うまくいっていたghcr.ioが突然動かなくなったりしたのは面白かったですが*1

github.com

ところで,今これを書いていてイメージだけの更新を使う場合に複数イメージ使うことを一切想定してないな,という気付きがあったので気が向いたら実装します. とはいってもそんな場合は自分で使っている分にはほぼないので別に要らないかな...ちゃんとdocker-compose.ymlでハッシュまで指定してくれ.

CI(Continuous Integration)

みなさんマイクラサーバ運用でCIしてますか?僕はしてる.

mc.yohane.suでは前述の通りwhitelist.jsonGitHubで管理しており,ユーザの追加はPull Requestで行うのですが, JSONって手書きするのダルいですよね.いやそんなにダルくない人もいるだろうし別にwhitelist.jsonは全然ダルくないんですが,プルリクが送られてきたのを何も見ずにマージするのも渋い.そこでCIです.

というわけでできたのがこちら.minecraft-whitelist-validatorです.

github.com

これはRustでちょろっと書きました. またしてもやってることは超単純で,whitelist.jsonをserde_jsonで読んで,Mojang APIを使ってユーザとそのUUIDを検証するだけです. そして,Docker Imageを作ってGitHub Action化もしたので,uses: sksat/minecraft-whitelist-validator@v0.2.0みたいなかんじで使えます.やったね.

そしてcargo initした後に気付いたんですが,これってcurlとjqでいいんですよね.まあRust書きたかったのでいいでしょう.

イクラマルチ鯖をGitHub repoでIaCしてwhitelist.jsonへのPRを募っていて, UUIDとかが合ってるかぐらいをCIでチェックしたい人がもし万が一僕以外にもいれば使ってあげてください(?)

コンテナイメージ

さてここまでwhitelist.jsonGitHubとプルリクでやっている話をしましたが,たぶんみなさんが気になるところがあるとすればdocker-composeでどんなイメージを使っているかでしょう. もちろんこれも自作です.

まあDocker/docker-composeでマイクラマルチサーバやるなら普通有名なitzg/docker-minecraft-server使えばいいとは思うんですが,ここまで自作の物体でガチャガチャやっておいてイメージも自作しないわけはありません.

github.com

というわけで作ったのがこちら.papermc-dockerです.

github.com

PaperMCというのはJava版マイクラサーバに大量のパッチを当てまくることでなんかいいかんじにすることで有名なSpigotのforkのPaperというやつのことです.

papermc.io

なんかパフォーマンスがいいらしく,オタクにも人気らしい.ということで使っています.詳しいことは知らない. ちなみにitzg/docker-minecraft-serverにもPaperMCのexampleはあります.マジで要らないね.

github.com

と思ったけど僕の方がイメージサイズが小さいな.じゃあ勝ちということで.

ちなみに,マイクラサーバをコンテナ化するにあたって,SpigotとかPaperMCみたいに公式のJava版サーバにパッチを当てまくる系の奴は注意が必要です. docker build時に元の公式サーバのjarファイルを含めた上でイメージをpublicにしてしまうと再配布になってしまいますからね. しかしPaperMCなら安心.Paperclipというものがあります.

github.com

Paperclipはマインクラフトサーバのランチャーであると同時に,Paperのパッチをバニラのサーバに適用するシステムです. これは普通に起動するだけでマイクラサーバとして使えるのですが, 初回起動時にバニラのサーバのjarファイルをダウンロードしてきて,それにパッチを当てて起動します. 2回目以降の起動時はパッチ適用後のjarファイルを直接呼び出します.

なので,これを使ってpapermc-dockerを作ったというわけです.

papermc-dockerでは,GitHub ActionsでイメージをビルドしてGitHub Container RegistryとDockerHubにイメージをpushしています.

で,このビルドなんですが,Paperは普通にいいかんじにビルドされて各commit毎のビルド済みバイナリが公開されているので,それを取ってきてイメージに埋め込めばいいだけです.

papermc.io

だけなんですが,そんなつまらないことはしておらず,ソースからビルドしています. しかも,masterに追従するようにしています. 毎日自動でmasterのPaperをcloneして,更新があったらupdate/paperブランチに自動でcommitされるようにはなっているので,適当なタイミングでプルリクを作ってマージしています. マージはともかくプルリク作るのは自動化したいですね.

papermc-dockerの自動更新

さて,そんなわけでmc.yohane.suではpapermc-dockerを使っているのですが,papermc-dockerでやっているイメージのビルドなどは元々mc.yohane.suでやっていました. これを分離したのは,単にゴチャッとしていて気持ち悪かったのもありますが,イメージの自動更新をいいかんじに,というかRenovateを使ってやりたかったからです.

Renovateはdependencyの更新をするPull Requestを自動で生やしてくれる奴です.

github.com

そして,Renovateはdocker-composeに対応しているので,docker-compose.yml内で指定されているイメージが更新されたらプルリクを作ってくれます. これをcompose-cdと組み合わせるとCI/CDってやつで優勝できるってワケよ.

papermc-docker分離以前は,

mc.yohane.suでPaperのcommitを更新→イメージのビルド開始→compose-cdgit pull・再起動→イメージのビルド終了→compose-cddocker-compose pull・再起動

となっていたのが,

papermc-dockerupdate/paperをマージ→mainでのイメージビルド→Renovateがdocker-compose.ymlの更新PRを生成→PRをマージ→compose-cdgit/docker-compoe pull・再起動

というかんじになりました.無意味な再起動が減ってうれしいですね.

ちなみに,mc.yohane.su.compose-cdの中身はこんなかんじです.

REPO="https://github.com/sksat/mc.yohane.su"
UPDATE_REPO_ONLY=true
UPDATE_IMAGE_BY_REPO=true

UPDATE_REPO_ONLYはイメージの更新をスキップしてリポジトリの更新のみをcompose-cdが行うオプションで, UPDATE_IMAGE_BY_REPOはイメージの更新の責任をリポジトリが負うことにするオプションです. これらはつまり,イメージのマニフェストを見に行ってdocker-compose pullすることはしないが, git pull時にdocker-compose pullもする,ということです.

こんなことをやってるので,mc.yohane.suのdocker-compose.ymlではイメージの指定にタグではなくハッシュ値を指定しています.

ちなみに,compose-cdはログをDiscordに流す機能もあるので,こんなかんじの見た目になります.

f:id:sksat:20210826012307p:plain

papermc-dockerもRenovateで自動更新(2021/09/11追記)

実はRenovate先生は独自フォーマットのファイルの更新もできちゃいます.

swfz.hatenablog.com

ということで,papermc-dockerもRenovateで更新できるのでは?ということに気付きました.

まあ元々はこれできるの知らなくて,自分でGitHub Actions組んでやろうとしていたんですけどね. 書くの忘れてたけどupdate/paperブランチなるものはまさにそれで, GitHub Actionsのcronで毎晩update/paperブランチの,envファイル(Paperのcommit hashが入っている)を更新していました. なので定期的にupdate/paperをマージすればよかった,というわけです.

github.com

本当はこういうPull Requestも自動で出したかったんですけど, GitHub Actionsからsecrets.GITHUB_TOKENを使ってプルリクを出してしまうとCIが走ってくれないという罠があったりするんですよね. まあやってなかったのは面倒だったというだけなんですが.

workflowはこんなかんじ. github.com

で,これもRenovateでできた,というわけです.Renovateすげ〜. 実際のrenovate.jsonはこんなかんじになりました.

papermc-docker/renovate.json at bf275c89842a9cb1cf3f26d6cf81c3799bf7a35b · sksat/papermc-docker · GitHub

{
  "extends": [
    "config:base"
  ],
  "regexManagers": [
    {
      "fileMatch": [".env"],
      "matchStrings": ["depName=(?<depName>.*?)?\\s.*?_COMMIT=(?<currentValue>)(?<currentDigest>.*?)\\s"],
      "versioningTemplate": "git",
      "datasourceTemplate": "git-refs"
    }
  ]
}

papermc-dockerはPaperMC/Paperの(めちゃくちゃ更新される)masterブランチに追従しようとしている狂ったイメージなので,datasourceにはgit-refsを使っています. そもそもPaperはタグ打ちもリリースもしてくれないのでこうするしかないんですがね!(公式サイトにcommit毎のビルド済みバイナリあるんだからそれでいいんだよなあ)

で,無事にRenovate先生がプルリクを出してくれるようになりました.やったぜ.

github.com

プラグインの自動更新(2021/09/11追記)

みなさんマイクラサーバ運用でプラグインアップデート自動化してますか?僕はしてる.

Renovateで独自フォーマットのファイルも更新できることが分かりました. あっこれプラグインの更新もできますねえ.できるならやるしかない.

実は元々プラグインってやつの管理どうしたらいいかな〜とは思っていたんですよね. マイクラプラグインってやつはフォーラムにバイナリがポン置きされてるとかいうマジで狂ったWindowsとかいうクソボケOSみたいな文化が蔓延っているらしいんですが, 幸いにも僕が欲しいものはGitHub上で開発されてタグ打ちされてリリースも出されているようなやつだったので,なんとかなりました*2

これはどうやって実装したかというと,特定バージョンのプラグインをダウンロードして配置するシェルスクリプトを書き, これをcompose-cdのカスタムスクリプト機能で呼び出す,という方法です. compose-cdには再起動前ないし後にカスタムスクリプトを実行する機能がある*3ので, このスクリプトを再起動前に実行します.

プラグインのデプロイスクリプトのバージョン指定はこんなかんじ.

mc.yohane.su/deploy-plugin.sh at 43c4f50829a6153ccfbddef1bd9e9a0218f5da04 · sksat/mc.yohane.su · GitHub

PLUGINS=(
  # datasource=github-releases versioning=docker
  "PlayPro/CoreProtect v20.1 CoreProtect"
  # datasource=github-releases
  "DiscordSRV/DiscordSRV v1.24.0 DiscordSRV-Build"
  # datasource=github-releases
  "sladkoff/minecraft-prometheus-exporter v2.4.2 minecraft-prometheus-exporter"
)

あとはrenovate.jsonからこんなかんじで拾って上げれば大丈夫です.

mc.yohane.su/renovate.json at 43c4f50829a6153ccfbddef1bd9e9a0218f5da04 · sksat/mc.yohane.su · GitHub

{
  "extends": [
    "config:base"
  ],
  "regexManagers": [
    {
      "fileMatch": ["deploy-plugin.sh"],
      "matchStrings": [
        "datasource=(?<datasource>.*?)( versioning=(?<versioning>.*?))?\n  \"(?<depName>.*?) (?<currentValue>.*) .*?\"\n"
      ],
      "versioningTemplate": "{{#if versioning}}{{versioning}}{{else}}semver{{/if}}"
    }
  ]
}

というわけで,こんなかんじのプルリクが生えてくれるようになりました.やったね!

github.com

ちなみに,CoreProtectのバージョニングがdockerなのはバージョニングがクソボケだからです. クソボケなのでクソボケな正規表現versioning=regex:~~~みたいに書いたろ,と思ったんですがそれは無理でした.

github.com

無理だったんですが,みなさんだいすきなドッカーイメージってやつも頻繁によくクソボケなバージョニングがされているので, dockerを指定してお茶を濁しています. 問題はなかった.

docs.renovatebot.com

あと,プラグインを色々入れるなら入れた後もちゃんと起動するか確認したいですよね. というわけでCIの出番です.

github.com

まあこれはpapermc-dockerで既にやってたんでそれをパクってきただけですね*4

*1:結局,これはDockerHubがちゃんと認証をやっていたのにghcr.ioの挙動が雑で何も考えずに使えていたのが後から修正されたということっぽかったんですけどね.まあちゃんと両方いけるようになったのでOK.

*2:CoreProtectとかいうやつはかなりちょっとだいぶ微妙なんですが,まあ途中からとはいえGitHubに上がって今回使えたのはいいことではあるので許してあげましょう.

*3:これはセキュリティ的にどうなんだという気持ちがややあり,あんまりautomergeする気になれなかったりする.

*4:master追従なんてしてたら平気で起動しなくなることがあるので