DockerでのMinecraft MODサーバ運用におけるファイル管理の最適化

この記事は [ドワンゴ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/dwango) 8日目の記事です。

## はじめに

こんにちは！ @azutake です。

普段はニコニコのサブスクを一画面で管理出来るようにする [定期お支払いの管理](https://subs.nicovideo.jp/my/subscriptions) というものに関わっていたり、[カスタムキャスト](https://customcast.jp/)というアバター作成アプリの開発をしていたりします。

アドベントカレンダー4日目にも書いているので、興味があればそちらも読んでね！

https://qiita.com/azutake/items/4acb9ec44e20e1788b22

さて今回は、MinecraftのMOD入りサーバをDockerで運用していると直面する課題の解決方法について紹介します。

サーバの永続化すべきファイルの管理、特にmodpackを使用する場合は変更されるファイルが多く、コンテナ化する上で管理コストが増大化しやすいです。この記事では、overlayfsを活用してこの課題を解決してみた話をします。

## 課題

Minecraftサーバをコンテナ化して運用していた際、以下のような課題に直面しました。

- 本来、バニラサーバであれば`world`ディレクトリのみを永続化すれば十分
- しかしForge等のMODサーバでは、多数のファイルやディレクトリがサーバ稼働中に動的に変更されていく
- 上記を解決するために、単純にすべてのディレクトリをボリュームマウントしてしまうと、せっかくイメージにmodpackを展開してもイメージとボリューム両方に同等のデータを保持することとなってしまう…
- modpackのアップデート時、何を上書きしても大丈夫なのか分からない…

以前dockerの内部構造について調べていた時、overlayfsというものがあるのを知り、もしかしてこれに応用できるのでは…？というのが今回の発端です。

## overlayfsって？？？

![overlay-construct](https://docs.docker.jp/_images/overlay-construct.png)

Ref: https://docs.docker.jp/storage/storagedriver/overlayfs-driver.html

まずOverlayFSについて一言で説明すると、Dockerの内部でもデフォルトとして使われている、Linuxカーネルに搭載されているレイヤー型のファイルシステムの一つです

OverlayFSではレイヤー構造でファイル・ディレクトリの変更を管理することが出来るファイルシステムになっています。

1. Upper Layer
    - Read-Writeなレイヤー
    - ここに最終レイヤーの変更内容が入る
1. Lower Layer
    - Read-Onlyなレイヤー
    - ここでは複数のレイヤーが存在出来る
    - Dockerでは、ここをイメージレイヤーとして使っている。
1. Merged View
    - Lower Layerを一つずつ重ねていき、最後にUpper Layerを重ねた状態。実際に見えるファイルシステムはこれ。

この構造のおかげで、Forge関連など基本的なファイルはdockerイメージに、それ以外のワールドデータやconfig、マップなどはボリュームに保存されるようになるわけです。わぁい！

## 実装例：modpack(ATM9)サーバの構築

```dockerfile:Dockerfile
FROM openjdk:17-slim

WORKDIR /minecraft

# 必要なパッケージを入れておく
RUN apt-get update && apt-get install -y \
    curl wget unzip \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# ATM9 0.3.5をダウンロード＆展開
RUN wget https://mediafilez.forgecdn.net/files/5900/31/Server-Files-0.3.5.zip \
    && unzip Server-Files-0.3.5.zip \
    && mkdir /minecraft/base \
    && mv Server-Files-0.3.5/* /minecraft/base/ \
    && rmdir Server-Files-0.3.5 \
    && rm Server-Files-0.3.5.zip \
    && chmod +x /minecraft/base/startserver.sh

WORKDIR /minecraft/base

# インストールのみモードでシェルを実行してforge等を展開しておく
RUN ATM9_INSTALL_ONLY=true ./startserver.sh

WORKDIR /minecraft

CMD ["./startserver.sh"]
```

```yaml:docker-compose.yaml
services:
  atm9:
    build: 
      context: /opt/minecraft/atm9/build
    ports:
      - "25565:25565"
    volumes:
      - type: bind
        source: /opt/minecraft/atm9/data
        target: /minecraft/data
    command: >
      sh -c "mkdir -p /minecraft/data/changes &&
             mkdir -p /minecraft/data/work &&
             mkdir -p /minecraft/data/merged &&
             mount -t overlay overlay -o lowerdir=/minecraft/base,upperdir=/minecraft/data/changes,workdir=/minecraft/data/work /minecraft/data/merged &&
             cd /minecraft/data/merged &&
             ./startserver.sh"
    cap_add:
      - SYS_ADMIN
    security_opt:
      - apparmor:unconfined
    restart: unless-stopped
    stdin_open: true
    tty: true
```

## 上記dockerfile, docker composeを実行すると

- /opt/minecraft/atm9/data/changes に **変更されたファイルのみが** 格納されるようになる
- forgeやmods, librariesなどはdockerイメージ上でread-onlyとして保持される

ことが実現できます！

私が運用しているサーバでは、このおかげで、特にmodpackのアップデートの際、管理負荷を大幅に軽減することが出来ました。

## おわりに

今回の記事は完全に趣味の話でしたが、普段は課金システムの部署に所属しており、一緒にニコニコの課金やサブスクなどのシステムを手伝ってくれる仲間を募集しております。

ぜひお気軽にご応募ください！

* [新卒採用はこちら](https://dwango.snar.jp/index.aspx)
* [キャリア採用はこちら](https://hrmos.co/pages/dwango)