更新
- 2024年11月2日
- Minecraftサーバの起動引数がおかしかったので修正
- Log4Shellの対策設定がおかしかったので修正(
-Dlog4j.configurationFile
) - GC作動時の停止時間が0.2秒が長かったので短縮(
MaxGCPauseMillis
)
- Log4Shellの対策設定がおかしかったので修正(
- 修正前:
ENTRYPOINT ["./run.sh", "-XX:+AlwaysPreTouch", "-XX:+DisableExplicitGC", "-XX:+ParallelRefProcEnabled", "-XX:+PerfDisableSharedMem", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseShenandoahGC", "-XX:+UseTransparentHugePages", "-XX:+UseNUMA", "-XX:InitiatingHeapOccupancyPercent=20", "-XX:MaxGCPauseMillis=200", "-XX:MaxTenuringThreshold=1", "-XX:SurvivorRatio=32", "-Xmx48G", "-Xms48G", "-jar", "-server", "-Dlog4j2_112-116.xml", "forge-1.12.2-14.23.5.2860.jar", "--nogui", "--universe", "/minecraft/."]
- 修正後
ENTRYPOINT ["./run.sh", "-XX:+AlwaysPreTouch", "-XX:+DisableExplicitGC", "-XX:+ParallelRefProcEnabled", "-XX:+PerfDisableSharedMem", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseShenandoahGC", "-XX:+UseTransparentHugePages", "-XX:+UseNUMA", "-XX:InitiatingHeapOccupancyPercent=20", "-XX:MaxGCPauseMillis=50", "-XX:MaxTenuringThreshold=1", "-XX:SurvivorRatio=32", "-Xmx48G", "-Xms48G", "-jar", "-server", "-Dlog4j.configurationFile=log4j2_112-116.xml", "forge-1.12.2-14.23.5.2860.jar", "--nogui", "--universe", "/minecraft/."]
- Minecraftサーバの起動引数がおかしかったので修正
きっかけ
友人に「Nomi CEU っていう Greg ベースのModpack やってみん?」と言われた。
それワタシがサーバを用意するやつよね?
Nomi CEuについてはこちら。
背景
E5-2697 v2とかDDR3 256GBとかがあったら流石にそうなる。
とりあえずポート開放が前提なので最低限やることやってかないとね。
構成
Proxmox VE8を動かしているので、その上にKVMとしてMinecraftサーバを建てる。
それだけだと面白味に欠けるので、Dockerの上でJVMを動かす。
マシン構成
↓構成はこんな感じか?
緑のgameserver
がVM、紫枠のmc-forge-main
がDocker上のMinecraftサーバになる。
gameserver | VM resources |
---|---|
CPU | 12 cores, 1 socket |
RAM | 64 GB |
Storage | 100 GB / SSD |
OS/ミドルウェア
thrust2799@gameserver:~$ docker -v
Docker version 27.3.1, build ce12230
thrust2799@gameserver:~$ docker compose version
Docker Compose version v2.29.7
thrust2799@gameserver:~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
構成の考え方
Docker上で動かすので、OpenJDKはDockerイメージに含めて環境が汚れないというメリットがある。また、インシデントが起こっても被害がコンテナ内に留まるのではないかという狙いもある。
さらに今回はdocker compose
コマンドで全体を管理するので、デーモン化を含めて管理が非常に楽になる。
あとは、サーバにアクセスする人を制限したりしたいが...。
Velocity Serverが使えない!(敗北)
最近のマイクラにはVelocityというプロキシサーバがあり、これを公開して多段構成にしようとした。
Velocityは複数サーバ間で移動できるコマンドがあるので、まずmc-forge-lobby
サーバに入ってから権限がある人のみmc-forge-main
サーバに移動する構成を考えていた。
また、Velocity対応の権限集中管理ツールとしてLuckPermsがあるので、これも使いたかった。(サーバ間の権限同期にはRDBが使えるので、MariaDBも建てようと思っていた。)
Velocityと連携できるのは、対応したプラグインサーバに加えForgeやFabricが導入されているサーバもある。
Forgeサーバで言うと、Minecraft 1.13以上だと専用Modで連携ができる。それ以下のバージョンだと Sponge Forge
というModを入れることで最低限は動くらしい。
今回入れるNomi CEuはMinecraft 1.12.2で動くForge用のModpack。
しかも、Sponge Forge
が微妙にサポートされていない。
Sponge forge
を入れてもNomi CEuは動くけど、設定を変えたりしないといけない。
そのあたりはGithubのWikiページにまとまっている。
なお、LuckPermsについてはそもそもForgeに対応していない。
結果
やりたいことが悉くできなかったので、諦めてMinecraft Forgeのサーバ1本で建てることにした。(その結果が最初の構成の話になる。)
Dockerfileで作るMinecraft Forgeなコンテナイメージ
まずはDockerfileでお手製のMinecraft Forgeなコンテナイメージを作成。ベースイメージはUbuntu 24.04を使っているが、多分busyboxとかでも行けるとは思う。
別の目的もあるためOpenJDKのイメージは使わない。(そもそも直近まで知らなかった。)
FROM ubuntu:noble
作成過程を試行錯誤した内容とともに振り返る。
パッケージ操作とcurl
RUN apt-get update \
&& apt-get install -y curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
この後Forgeのインストーラを取得するためにcurl
を使うが、実はベースイメージにこのコマンドが含まれていないという。Ubuntu 24.04 LTS Serverには入っているので直観に反する。(n年ぶりm敗目)
なお、apt-get clean
やrm -rf /var/lib/apt/lists/*
はパッケージ操作で生じた不要ファイルを削除する目的で実行する。
Debian系のベースイメージを使うときはパターンとして入れるようにしている。
目的はインストーラの取得なので、curl
の代わりにwget
を入れても良い。
Java環境をどうするかという問題 (with ShenandoahGC
)
COPY ./rh-openjdk /jre
今回サーバとして建てるベースはMinecraft 1.12.2なので、OpenJDK 8が必要になる。
通常なら先のパッケージ操作の段階でopenjdk-8-jre-headless
を入れるのだが、今回はRedHatがビルドしているOpenJDKを利用する。(ベースイメージがbusyboxでも良さそうな理由はここにある。)
というのも、OpenJDK 11.0.9から実装されているShenandoahGC
を使いたくなったが、通常のOpenJDK 8ではもちろん使えない...。(1敗)
が、RedHat版ではバックポートされているらしい。ShenandoahGC
は少ない停止時間で動くガベージコレクタとされているため、G1GC
の代わりに使いたくなった。
RedHat版OpenJDK 8はダウンロードにRedHatのアカウントが必要となる。
アクセスが限られているので、このイメージは個人利用にとどめるべきだろう...。
ダウンロードしたJREはホストのrh-openjdk
フォルダに展開した。このフォルダをコンテナイメージの/jre
へコピーして利用する。
minecraft
ユーザと/minecraft
フォルダ
RUN groupadd -g 500 -o minecraft
RUN useradd -u 500 -g 500 -M -o -r -s /sbin/nologin minecraft
RUN mkdir -p /minecraft/logs
RUN mkdir -p /minecraft/mods
RUN mkdir -p /minecraft/world && chown -R minecraft:minecraft /minecraft
USER minecraft:minecraft
WORKDIR /minecraft
このベストプラクティスでも示されている通り、コンテナ内とはいえrootユーザでプロセスを動かすのはリスクがある。そのためDockerイメージの作成においては専用ユーザと専用フォルダを作成し、それらを用いて動作するように構成を行う。
今回はユーザIDとグループIDが500:500
なminecraft
ユーザを作成する。この時、システムユーザかつ/sbin/nologin
をログインシェルとして指定し、ホームディレクトリは作成しないでおく。(気休め)
また、/minecraft
フォルダを作成し権限を与えておく。(logs
、mods
、world
フォルダはMinecraft用の基本セットという意識で作成しているが、別になくても問題ない。)
最後にUSER
命令とWORKDIR
命令で作業ユーザと作業フォルダを変更しておく。
Forgeサーバをインストール
WORKDIR /minecraft
RUN curl -fOsSL https://maven.minecraftforge.net/net/minecraftforge/forge/1.12.2-14.23.5.2860/forge-1.12.2-14.23.5.2860-installer.jar \
&& /jre/bin/java -jar forge-1.12.2-14.23.5.2860-installer.jar --installServer
RUN curl -fSsSL https://launcher.mojang.com/v1/objects/02937d122c86ce73319ef9975b58896fc1b491d1/log4j2_112-116.xml
RUN echo "eula=true" > ./eula.txt
今回はForgeを使ったMinecraftサーバを建てるため、ForgeインストーラのダウンロートURLを確認してコンテナ内で取得するよう構成する。ここで先ほど入れたcurl
を使う。
インストーラの取得に併せてForgeサーバをインストールしておく。これでいつでもMinecraft + Forgeなサーバの準備が整う。
なお、ForgeインストーラはNomi CEuで指定されているバージョンをダウンロードする。
ここで、java
は先ほど展開した/jre
配下のものを絶対パスで指定する。
間違えてjava -jar ...
なんてするとパスが通ってないのでコンテナイメージのビルドに失敗する。
また、大騒動になったLog4Shellを回避するために公式が出しているXMLファイルを取得しておく。
Nomi CEuのサーバファイルにも含まれているが、多分どちらでもよい...? 教えて有識者
eula=true
をファイルに書き出しているが、これがないとサーバ起動に失敗する。
大体のMinecraftサーバ初回起動でつまずくポイントだが、今回はあらかじめ対応済みのコンテナイメージにすることで回避する。
Minecraftサーバなコンテナイメージを作って配布する人はeula.txt
を削除して配布すべきである。なぜなら、このファイルをtrue
にすることがその名の通りエンドユーザライセンスへの合意であるからだ。
エントリポイントに指定する起動用スクリプトの作成
RUN cp -r libraries libraries_orig
RUN echo '#!/bin/bash' > run.sh \
&& echo 'set -x' >> run.sh \
&& echo 'if [[ ! -e ./libraries/org ]]; then' >> run.sh \
&& echo ' cp -rf libraries_orig/* libraries/.' >> run.sh \
&& echo 'fi' >> run.sh \
&& echo '/jre/bin/java "$@"' >> run.sh
RUN chmod o+x run.sh
このバージョンのForgeはlibraries
ファイルが生成される。この後docker compose
コマンドでコンテナにマウントするホストディレクトリを指定していくが、ホストディレクトリをマウントするとマウント先に存在したコンテナ内のファイルが使えなくなる。
今回はコンテナ内のファイルを利用しなければいけないため、この部分に対処しなければいけない。別のMinecraftバージョンでやった時はこんなフォルダなかったのに...。(5敗)
回避策として、今回はイメージ内でlibraries_orig
へ移動しておくことにする。こうすることでdocker compose
コマンドによってホストディレクトリがマウントされても削除されなくなる。
そして、この後起動用スクリプトでコピーしてこれば解決となる。
作った起動スクリプトはちゃんと動作するように実行権限を付与しておく。
シェルスクリプトについて
#!/bin/bash
set -x
if [[ ! -e ./libraries/org ]]; then
cp -rf libraries_orig/* liberaries/.
fi
/jre/bin/java "$@"
突っ込みどころしかない部分。
-
[[ ! -e ./libraries/org ]]
は正規表現を使っていないので[ ! -e ./libraries/org ]
で良い - 条件文の
-e
(ファイル存在)は-d
(ディレクトリ存在)の方が明示的 -
/jre/bin/java "$@"
、ダブルクォートで囲ってなくて動作しなかった。(1敗) -
/jre/bin/java
じゃなくてjava
にしたせいでコンテナ起動時にエラー(1敗)- コレさっき見た気がする
この部分の方式について少し反省する点
libraries
の扱いについて、コンテナイメージは最低限動作に必要なものが揃うのでコンテナイメージ側をベースにすべきだった。さらに、現状だとコンテナイメージ内のファイル内容がホスト側へ反映されるため「追加したファイルがどれかわからない!」という状態を引き起こしてしまう。
改善策としては、docker compose
コマンドでlibraries_host
ディレクトリとしてマウントしておき、起動用スクリプトでlibraries
に上書きする挙動が良さそう。
最後のほう
# RUN rm -f /minecraft/forge-1.12.2-14.23.5.2860-installer.jar /minecraft/forge-1.12.2-14.23.5.2860-installer.jar.log
EXPOSE 25565/tcp
STOPSIGNAL SIGTERM
ENTRYPOINT ["./run.sh", "-XX:+AlwaysPreTouch", "-XX:+DisableExplicitGC", "-XX:+ParallelRefProcEnabled", "-XX:+PerfDisableSharedMem", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseShenandoahGC", "-XX:+UseTransparentHugePages", "-XX:+UseNUMA", "-XX:InitiatingHeapOccupancyPercent=20", "-XX:MaxGCPauseMillis=50", "-XX:MaxTenuringThreshold=1", "-XX:SurvivorRatio=32", "-Xmx48G", "-Xms48G", "-jar", "-server", "-Dlog4j.configurationFile=log4j2_112-116.xml", "forge-1.12.2-14.23.5.2860.jar", "--nogui", "--universe", "/minecraft/."]
CMD ["--world", "world"]
コメントアウトしてるのはForgeインストーラとそのログを削除する行。最初は入れてたけどデバッグしてるうちにコメントアウトしていた。容量削減を目指すなら含めるべき。
EXPOSE
命令でMinecraftサーバのデフォルトポートを開放する設定を入れている。ポート変更はDocker側で設定できるのでコンテナイメージであえて変更する必要はない。
SOTPSIGNAL
命令でCtrl-Cを押したときにサーバを正常終了させれる方向に誘導する。
ENTRYPOINT
命令で先に記載したスクリプトを指定しつつ、JVM引数とMinecraftサーバの引数を入れる。
ここで先ほど展開したXMLファイルの指定やガベージコレクタの指定、メモリ使用量の指定を行う。詳細は割愛。
CMD
命令に--world
引数を指定しているのは、ワールド名を変えられる余地を残すため。このあたりはお好み。
JVM引数をテキストファイルにまとめて@user_jvm_args.txt
みたいな感じで指定するのはJava8ではできません!(1敗)
そのせいでENTRYPOINT
命令がとんでもない長さになってる!
Dockerfile
最終的に出来上がったものはこちらになります。
FROM ubuntu:noble
RUN apt-get update \
&& apt-get install -y curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY ./rh-openjdk /jre
RUN groupadd -g 500 -o minecraft
RUN useradd -u 500 -g 500 -M -o -r -s /sbin/nologin minecraft
RUN mkdir -p /minecraft/logs
RUN mkdir -p /minecraft/mods
RUN mkdir -p /minecraft/world && chown -R minecraft:minecraft /minecraft
USER minecraft:minecraft
WORKDIR /minecraft
RUN curl -fOsSL https://maven.minecraftforge.net/net/minecraftforge/forge/1.12.2-14.23.5.2860/forge-1.12.2-14.23.5.2860-installer.jar \
&& /jre/bin/java -jar forge-1.12.2-14.23.5.2860-installer.jar --installServer
RUN curl -fSsSL https://launcher.mojang.com/v1/objects/02937d122c86ce73319ef9975b58896fc1b491d1/log4j2_112-116.xml
RUN echo "eula=true" > ./eula.txt
RUN cp -r libraries libraries_orig
RUN echo '#!/bin/bash' > run.sh \
&& echo 'set -x' >> run.sh \
&& echo 'if [[ ! -e ./libraries/org ]]; then' >> run.sh \
&& echo ' cp -rf libraries_orig/* libraries/.' >> run.sh \
&& echo 'fi' >> run.sh \
&& echo '/jre/bin/java "$@"' >> run.sh
RUN chmod o+x run.sh
# RUN rm -f /minecraft/forge-1.12.2-14.23.5.2860-installer.jar /minecraft/forge-1.12.2-14.23.5.2860-installer.jar.log
EXPOSE 25565/tcp
STOPSIGNAL SIGTERM
ENTRYPOINT ["./run.sh", "-XX:+AlwaysPreTouch", "-XX:+DisableExplicitGC", "-XX:+ParallelRefProcEnabled", "-XX:+PerfDisableSharedMem", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseShenandoahGC", "-XX:+UseTransparentHugePages", "-XX:+UseNUMA", "-XX:InitiatingHeapOccupancyPercent=20", "-XX:MaxGCPauseMillis=50", "-XX:MaxTenuringThreshold=1", "-XX:SurvivorRatio=32", "-Xmx48G", "-Xms48G", "-jar", "-server", "-Dlog4j.configurationFile=log4j2_112-116.xml", "forge-1.12.2-14.23.5.2860.jar", "--nogui", "--universe", "/minecraft/."]
CMD ["--world", "world"]
compose.yamlで作るNomi CEuサーバ
これでDockerイメージができるので、サーバを自動構成するためのcomposeファイルを作る。
ここでも試行錯誤した内容とともに振り返る。
ディレクトリツリー
ディレクトリ構成は最終的にこんな感じ。
「(空) or (多数)」になっているフォルダがあるのは、初回起動時は空にしておき本番起動時にデータを格納する運用のため。
/srv/minecraft/
├── compose.yaml
├── forge-12.2-14.23.5.2860
│ ├── Dockerfile
│ └── rh-openjdk
│ └── (略)
└── main
├── banned-ips.json
├── banned-players.json
├── backups
│ └── (空) or (多数)
├── config
│ └── (空) or (多数)
├── crash-reports
│ └── (空) or (多数)
├── groovy
│ └── (空) or (多数)
├── libraries
│ └── (空) or (多数)
├── local
│ └── (空) or (多数)
├── logs
│ └── (空) or (多数)
├── mods
│ └── (空) or (多数)
├── ops.json
├── resources
│ └── (空) or (多数)
├── scripts
│ └── (空) or (多数)
├── server-icon.png
├── server.properties
├── usercache.json
└── whitelist.json
services
定義
動かすコンテナを列挙し、それぞれの構成を記載していく。今回は1個のコンテナmain
について記載する。
コンテナイメージのビルドとコンテナの基本
services:
main:
build: ./forge-12.2-14.23.5.2860
image: mc-forge:12.2-14.23.5.2860
container_name: mc-forge-main
hostname: mc-forge-main
tty: true
stdin_open: true
user: 500:500
# (略)
build
ではコンテナイメージのビルド設定を記載する。この書き方だとビルドするDockerfile
が存在するディレクトリを指定している。
先に作ったDockerfileとOpenJDKが入ったディレクトリを相対パスで指定する。
image
ではコンテナ起動に用いるイメージ名を指定する。build
でイメージタグを指定していなければ、ビルドされるコンテナイメージはこのイメージ名/タグが設定される。
container_name
とhostname
では個別のコンテナ名を指定する。
docker container
コマンドで指定する名前やDocker内でアクセスするときのホスト名に利用される。
tty: true
、stdin_open: true
はdocker run
コマンドにおける-it
に相当する。
今回Minecraftサーバをコンソールから操作できるようにあらかじめTTYと標準入力を有効化しておく。
user
はコンテナの実行UID/GIDを指定する。
すでにDockerfileに指定しているが、こちらでも念のために記載する。
ホストファイルの連携
services:
main:
# (略)
volumes:
- type: bind
source: ./main/backups
target: /minecraft/backups
- type: volume
source: main_world
target: /minecraft/world
# (略)
ミソPoint。マウント方法によって挙動が異なるので、したいことと記述をしっかりと一致させる。「プログラムは書いた通りにしか動かない」と認識する。(連敗)
今回長い書き方(Long Syntax)をしている。1行で短く記載(Short Syntax)するとマウント方式を混同する要因になるので、面倒でも複数行に明示的に記載していくのが良さそう。
Bindマウント
ホストのフォルダを指定してマウントし、そこにデータを格納する。
コンテナ内で利用している状態そのままにホスト側に反映されるので管理に注意が必要となる。
パーミッションや所有権も同様にコンテナ内の状態がホスト側に染み出す。
例えば、コンテナ内のUID:GID
は500:500
で動作する設定のため、コンテナを動かす際はホスト側で必ずchown -R 500:500 /srv/minecraft/main/*
を実行する必要がある。
Volumeマウント
Docker側で確保する領域をマウントし、そこにデータを格納する。
最後のほうに記載するvolumes
で領域設定を入れるので、そこに指定した名前を書く。
configs
は使えないのか?
configs
ではコンテナで利用する構成ファイルを定義しDockerに管理を任せるもの。
例えば、server.properties
ファイルをconfigs
に記載するとするとこのようになる。
services:
main:
configs:
- source: server-properties
target: /minecraft/server.properties
uid: "500"
gid: "500"
mode: 0644
# (略)
configs:
server-properties:
file: ./files/create/server.propertie
この記載でポイントとなるのは、「UIDやGID、パーミッションの指定」と「そもそもconfigs
の動作で良いのか」となる。
UIDやGID、パーミッションの指定
上記のように指定して起動すると起動時に以下のメッセージが表示される。
実際、これで動かそうとするとファイルがroot:root
で作成されるので、パーミッションエラーが出てサーバ起動に失敗する。(連敗)
WARN[0000] config `uid`, `gid` and `mode` are not supported, they will be ignored
一応公式ドキュメント上にはサポートされている記載があるが、実際には動いていないのが現状。
おかしいと思って古い環境(Docker Compose version V2.19.1
)で確認したところ、ちゃんと動いていた。
どうやらバージョンアップに伴って何かしらあったようだ...。
そもそもconfigs
の動作で良いのか
configs
だと指定したファイルや環境変数などをコンテナ内に書き込む動作をする。Bindマウントと違うのは「コンテナ内で上書きされた際に、ホスト側へ反映されない」という点にある。
今回だと、ホストからマウントするファイルはほぼすべてで実行時に上書きされる可能性があるため、configs
の動作は適さないことになる。
今回はサーバ側で動的に設定変更することを想定した。
server.properties
やops.json
などの設定を固定化したい場合は、むしろconfigs
に設定すべきかもしれない。
ポート開放
Dockerfile
内のEXPOSE
命令で指定したポートをホスト外にも公開する設定。今回はポートのみ指定することで[::]:25565
と0.0.0.0:25565
といった形でポート開放する。
services:
main:
# (略)
ports:
- 25565:25565
volumes
定義
volumes:
main_world:
Minecraftのワールドデータ格納用のボリュームを定義する。ここで書いたボリューム名が先のVolumeマウントで指定されている。
何も指定していないためDocker側で適当に領域が生成されているが、NFSなどを指定することも可能らしい。
networks
定義
networks:
default:
name: mc-servers
Docker内部の通信を構成する。(Velocity導入構想の名残)
設定しなくてもデフォルトで作成されるネットワークがある。
複数のネットワークを使いたい時やコンテナ同士で隔離したい時などに定義すると良い。
compose.yaml
最終的に出来上がったものはこちらになります。
services:
main:
build: ./forge-12.2-14.23.5.2860
image: mc-forge:12.2-14.23.5.2860
container_name: mc-forge-main
hostname: mc-forge-main
tty: true
stdin_open: true
user: 500:500
volumes:
- type: bind
source: ./main/backups
target: /minecraft/backups
- type: volume
source: main_world
target: /minecraft/world
- type: bind
source: ./main/libraries
target: /minecraft/libraries
- type: bind
source: ./main/logs
target: /minecraft/logs
- type: bind
source: ./main/mods
target: /minecraft/mods
- type: bind
source: ./main/config
target: /minecraft/config
- type: bind
source: ./main/local
target: /minecraft/local
- type: bind
source: ./main/groovy
target: /minecraft/groovy
- type: bind
source: ./main/scripts
target: /minecraft/scripts
- type: bind
source: ./main/resources
target: /minecraft/resources
- type: bind
source: ./main/crash-reports
target: /minecraft/crash-reports
- type: bind
source: ./main/banned-ips.json
target: /minecraft/banned-ips.json
- type: bind
source: ./main/banned-players.json
target: /minecraft/banned-players.json
- type: bind
source: ./main/ops.json
target: /minecraft/ops.json
- type: bind
source: ./main/usercache.json
target: /minecraft/usercache.json
- type: bind
source: ./main/whitelist.json
target: /minecraft/whitelist.json
- type: bind
source: ./main/server-icon.png
target: /minecraft/server-icon.png
- type: bind
source: ./main/server.properties
target: /minecraft/server.properties
ports:
- 25565:25565
volumes:
main_world:
networks:
default:
name: mc-servers
Minecraftサーバの起動
とりあえず初回起動ということで、この通りに空のフォルダ/ファイルを./main
配下に作成してから起動する。
docker compose
コマンドはカレントディレクトリのファイルを確認しに行く。
そのため、作成したcompose.yaml
が存在するディレクトリで実行する。
thrust2799@gameserver:/srv/minecraft$ docker compose up
正常にワールドが生成されたら、一旦Ctrl+Cでサーバを閉じる。これでMinecraftサーバの起動に必要な最低限のものが揃うので、Nomi CEuの起動準備に入る。
詳細は省くが、./main
配下に対してファイル/フォルダを配置する。配置後はここで触れた通り所有権の変更だけ確実に行う。
また、初回起動によりワールドデータが作成されてしまっているので削除しておく。
thrust2799@gameserver:/srv/minecraft$ docker volume ls
DRIVER VOLUME NAME
local minecraft_main_world
thrust2799@gameserver:/srv/minecraft$ docker volume rm minecraft_main_world
minecraft_main_world
次回以降の起動は末尾に-d
を付けてデーモン化した状態で起動する。
管理はdocker attach
コマンドやdocker logs -f
コマンドで対応する。
thrust2799@gameserver:/srv/minecraft$ docker compose up -d # 起動時
thrust2799@gameserver:/srv/minecraft$ docker compose down # 終了時
その他の設定
ここまででNomi CEuサーバはきちんと動作するが、どうせなら公開にあたってセキュリティ対策などを頑張っておきたい。
ということで、FWやウイルス対策ソフトなどを導入していく。
UFW
Ubuntu 24.04 LTSにはUFWがデフォルトで備わっている。
OSインストール時の状態だと無効化されているので、デフォルトポリシーと最低限の許可設定を投入して有効化しておく。
今回だと下記通り。< server ip >
と<local client pc ip>
には適切なIPアドレスが表示される。
thrust2799@gameserver:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
< server ip > 22 ALLOW <local client pc ip>
< server ip > 25565 ALLOW Anywhere
clamav
LinuxにはClamAV(Clam Anti-Virus)というウイルス対策ソフトがあるので、これを入れる。
インストールと初期設定
インストールはいつものパッケージ操作で。
thrust2799@gameserver:~$ sudo apt install clamav clamav-daemon
インストール後はスキャン設定をしておく。
デフォルトだと全ディレクトリを検査するので、スキャン除外設定を入れておく。ここではLinuxの場合で一般的に入れておくべきものや、UbuntuやDockerに特有の除外項目を設定しておく。
また、スキャン時に利用するCPUコア数がデフォルトだとすべてになるので、Minecraftサーバが重くならないように念のため減らしておく。(今回だと12
から8
に減らす。)
ここでオンアクセス(リアルタイム)スキャンを有効化できるが、今回は負荷を考えて有効にはしない。
追加設定した行は次の通り。
ExcludePath ^/proc/
ExcludePath ^/sys/
ExcludePath ^/dev/
ExcludePath ^/snap/
ExcludePath ^/run/
ExcludePath ^/var/lib/clamav/quarantine/
ExcludePath ^/var/lib/docker/overlay2/
ExcludePath ^/var/lib/docker/volumes/backingFsBlockDev/
ExcludePath ^/var/snap/lxd/common/lxd/unix.socket
ExcludePath ^/var/snap/lxd/common/lxd-user/unix.socket
MaxThreads 8
※/var/lib/clamav/quarantine/
はウイルス検知後の隔離フォルダにするので、重複検知しないよう除外する。
定期スキャンの設定
このままだとスキャンはできるけど自動的に回してくれないので、1日1回はスキャンするように設定する必要がある。
そこで、大体の人はスキャンスクリプトを作ってcronジョブとして登録するが、せっかくSystemdがあるのでこちらを活用してみる。
スキャンスクリプト
ログ出力先とウイルス隔離先を定義して、ルートディレクトリ(/
)配下をスキャンするようスクリプトにしている。
最後の2行は、存在するログからウイルスを検知した数をサマリとして書き出している(後で使う)。
/usr/local/bin/clamdscan-onschedule.sh
#!/bin/bash
SCAN_LOG_FILE=/var/log/clamav/clamdscan-`date +%Y%m%d`.log
QUARANTINE_DIR=/var/lib/clamav/quarantine/
/usr/bin/clamdscan -m --fdpass --log=$SCAN_LOG_FILE --move=$QUARANTINE_DIR /
SUM=$(grep Infected files: /var/log/clamav/clamdscan-*.log 2>/dev/null | awk '{s += $3} END {print s}')
echo `date +%Y/%m/%d`: $SUM infected files was found in this month. | tee /var/log/clamav/summary.txt
Systemd.ServiceとSystemd.Timer
Systemdでは/etc/systemd/system/
配下にユニットファイルを作成すると管理対象として認識してくれる。
また、cronと同じようなものとしてSystemd.Timerという機能が存在している。.service
ファイルと同じ名前の.timer
ファイルを作成し設定を書き込むと有効なSystemd.Timerユニットとして認識される。
今回は1日1回スキャンスクリプトを回す(デーモンではない)ので、.service
ファイルのTypeにはonechot
と指定しておく。
.timer
ファイルではカレンダー形式で開始時間を指定できる。cronよりも直観的に実行管理ができるが、もちろん、cronのような設定方法も存在している。
今回は毎日AM03:00に1回だけ実行するように記載する。
/etc/systemd/system/clamav-scan.service
[Unit]
Description = ClamAV OnSchedule Scan Service
RefuseManualStart = no
RefuseManualStop = yes
Requires = clamav-daemon.service
After = clamav-daemon.service
[Service]
Type = oneshot
ExecStart = /usr/local/bin/clamdscan-onschedule.sh
KillMode = process
KillSignal = SIGINT
[Install]
WantedBy = multi-user.target
/etc/systemd/system/clamav-scan.timer
[Unit]
Description = ClamAV OnSchedule Scan Timer
[Timer]
OnCalendar = *-*-* 03:00:00
Persistent = true
[Install]
WantedBy = timers.target
ファイルの作成が終わったら、最後にSystemdに各ユニットを認識させて有効化する。
thrust2799@gameserver:~$ sudo systemctl daemon-reload
thrust2799@gameserver:~$ sudo systemctl enable --now clamav-scan.timer
systemctl enable --now
で有効化と同時に起動ができるので便利。
systemctl enable
とsystemctl start
を個別に実行しても同じように動く。
systemctl enable
のみだと、次回OS起動時にスケジューリングされるだけで即時にスケジューリングしてくれない。ちゃんとsystemctl enable --now
とするかsystemctl start
を実行するようにしよう。(1敗)
検知したことを知りたい
でもメールとか通知を送る仕組みは面倒。じゃあログインしたときにサマリが通知できれば良いよね!
ということで、先のサマリと隔離フォルダの内容を表示するように~/.bashrc
に追記する。
thrust2799@gameserver:~$ tail -2 .bashrc
cat /var/log/clamav/summary.txt
ls -oa /var/lib/clamav/quarantine
thrust2799@gameserver:~$ . .bashrc
2024/10/29: 0 infected files was found in this month.
total 8
drwxr-xr-x 2 clamav 4096 Oct 29 02:51 .
drwxr-xr-x 3 clamav 4096 Oct 29 02:36 ..
thrust2799@gameserver:~$
いい感じ!
ログのローテーション
スキャンスクリプトを見返すと、このようなログ出力になっている。
SCAN_LOG_FILE=/var/log/clamav/clamdscan-`date +%Y%m%d`.log
これは、/var/log/clamav/
配下にclamdscan-<YYYYMMDD>.log
というファイルが毎日追加されることを意味している。
これではlogrotatedでも対応が難しいが、スキャン毎にログファイルは分けておきたい...。でも放置してると無限にログファイルが溜まっていく...。
ということで、この際ログの削除もSystemd.Timerで実現する。
実現することは下記通り。設定方法はスキャンスクリプトの時と同じなので詳細は割愛。
- 毎月1日のAM00:10に前月分のログファイルを
.tgz
で固める - 同じくオリジナルの前月分ログファイルを削除する
- 同じく13か月前のログファイルがあれば削除する
/usr/local/games/clamdscan-logarchive.sh
#!/bin/bash
set -x
tar -czvf ./clamdscan-`date --date '1 month ago' +%Y%m`.tgz ./clamdscan-`date --1 month ago +%Y%m`*
rm -f ./clamdscan-`date --date '1 month ago' +%Y%m`*.log
rm -f ./clamdscan-`date --date '13 month ago' +%Y%m`.tgz
/etc/systemd/system/clamav-scan.service
[Unit]
Description = ClamAV Scan Log Archiver
RefuseManualStart = no
RefuseManualStop = yes
[Service]
Type = oneshot
WorkingDirectory=/var/log/clamav
ExecStart = /usr/local/games/clamdscan-logarchive.sh
KillMode = process
KillSignal = SIGINT
[Install]
WantedBy = multi-user.target
/etc/systemd/system/clamav-scan.timer
[Unit]
Description = ClamAV Scan Log Archive Timer
[Timer]
OnCalendar = *-*-1 00:10:00
Persistent = true
[Install]
WantedBy = timers.target
サーバ側はこれでほぼ完了なのでは?
あとはルータ側で公開設定をするだけ。
どうせNAPT設定になっているので、ポート開放のついでにポート番号をランダムにずらして少しだけセキュアにすればいいかな?
この辺りの詳細は割愛。個別設定になるので自分のルータやネットワーク、ISPに応じて設定すれば良し!
友人からもインフラ力をお褒めいただけたので、今回はここまで。
P.S.
見た感じ敗北し続けてるな?