Running Minecraft Java Server for Bedrock clients on Kubernetes
Minecraft Java Edition requires that servers match the version of the clients and updating the server each time is a bit of a chore, so it is more convenient to run it on the Kubernetes cluster.
Basic Setup
Always run Minecraft servers under their own dedicated, non-privileged user. Create also a dedicated directory in the partition with plenty of space available:
# useradd minecraft
# mkdir /home/k8s/minecraft-server
# chown -R minecraft.minecraft /home/k8s/minecraft-server
# ls -ldn /home/k8s/minecraft-server
drwxr-xr-x 1 1003 1003 0 May 29 14:43 /home/k8s/minecraft-server
A persistent volume is necessary because otherwise everything (server settings and the whole world) is lost when the container is restarted, including when the node is restarted.
Note that UID & GID 1003 will be needed later to run the server as this user.
Kubernetes Deployment
Create and apply the deployment in
minecraft-server.yaml using the
itzg/minecraft-server
docker image (GitHub:
itzg/docker-minecraft-server):
Kubernetes deployment: minecraft-server.yaml
Note
To make the server accessible to clients, the above NodePort
is required for each the Java and Bedrock protols separately:
- The Java server listens on TCP port 25565, the
cluster exposes the server to the local network as
192.168.0.6:32565 - The Bedrock server listens on UDP port 19132, the
cluster exposes the server to the local network as
192.168.0.6:32132
Both ports can then be exposed externally by adding port forwarding rules in the local router.
Once the deployment is applied, confirm everything is running and check the logs:
$ kubectl apply -f minecraft-server.yaml
namespace/minecraft-server unchanged
service/minecraft-server unchanged
persistentvolume/minecraft-server-pv unchanged
persistentvolumeclaim/minecraft-server-pv-claim unchanged
deployment.apps/minecraft-server configured
$ kubectl -n minecraft-server get all
NAME READY STATUS RESTARTS AGE
pod/minecraft-server-88f84b5fc-ptb5p 1/1 Running 0 2m38s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/minecraft-server ClusterIP 10.110.215.139 <none> 25565/TCP 2m38s
service/minecraft-server NodePort 10.110.215.139 <none> 25565:32565/TCP,19132:32132/UDP 2m38s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/minecraft-server 1/1 1 1 2m38s
NAME DESIRED CURRENT READY AGE
replicaset.apps/minecraft-server-88f84b5fc 1 1 1 2m38s
$ kubectl -n minecraft-server logs pod/minecraft-server-88f84b5fc-ptb5p
[init] Running as uid=1003 gid=1003 with /data as 'drwxrwxr-x 1 1003 1003 336 May 29 12:57 /data'
[init] Resolved version given LATEST into 1.19.4 and major version 1.19
[init] Resolving type given VANILLA
[init] Setting initial memory to 1G and max to 1G
[init] Starting the Minecraft server...
Starting net.minecraft.server.Main
[12:59:32] [ServerMain/INFO]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD'
[12:59:33] [ServerMain/INFO]: Loaded 7 recipes
[12:59:33] [ServerMain/INFO]: Loaded 1179 advancements
[12:59:34] [Server thread/INFO]: Starting minecraft server version 1.19.4
[12:59:34] [Server thread/INFO]: Loading properties
[12:59:34] [Server thread/INFO]: Default game type: SURVIVAL
[12:59:34] [Server thread/INFO]: Generating keypair
[12:59:34] [Server thread/INFO]: Starting Minecraft server on *:25565
[12:59:34] [Server thread/INFO]: Using epoll channel type
[12:59:34] [Server thread/INFO]: Preparing level "world"
[12:59:39] [Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[12:59:39] [Server thread/INFO]: Preparing spawn area: 0%
[12:59:39] [Worker-Main-2/INFO]: Preparing spawn area: 0%
[12:59:40] [Worker-Main-2/INFO]: Preparing spawn area: 91%
[12:59:40] [Worker-Main-1/INFO]: Preparing spawn area: 91%
[12:59:40] [Server thread/INFO]: Time elapsed: 1816 ms
[12:59:40] [Server thread/INFO]: Done (6.264s)! For help, type "help"
[12:59:40] [Server thread/INFO]: Starting remote control listener
[12:59:40] [Server thread/INFO]: Thread RCON Listener started
[12:59:40] [Server thread/INFO]: RCON running on 0.0.0.0:25575
Geyser plugin for Bedrock clients
GeyserMC
is a program that allows Bedrock clients to join
Java servers. Installing this requires a Paper or
Spigot server (source:
geysermc.org/download#spigot).
This is why the above deployment specifies the server to
be of type SPIGOT.
Install from latest binary release:
# su minecraft -c "wget -O /home/k8s/minecraft-server/plugins/Geyser-Spigot.jar https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest/downloads/spigot"
# ls -hal /home/k8s/minecraft-server/plugins/
total 14M
drwxrwxr-x 1 minecraft minecraft 60 May 29 16:08 .
drwxrwxr-x 1 minecraft minecraft 572 May 29 15:48 ..
-rw-rw-r-- 1 minecraft minecraft 14M May 27 15:19 Geyser-Spigot.jar
drwxrwxr-x 1 minecraft minecraft 20 May 29 15:47 PluginMetrics
Server Commands
To enter commands into the running server, see
Interacting with the server
and in particular the rcon-cli command that
can be run in the container.
No need to get the full name of the current pod, which changes when deployment restarts:
Note that using deploy/minecraft-server allows running
the command in the pod of this deployment
without having to get the full name
of the pod with kubectl -n minecraft-server get pods
With this, one can reload the configurations (e.g.
server.properties) or even restart the whole Java
server without restarting the pod or deployment.
Warning
Do not try attaching to the TTY of the container.
$ kubectl -n minecraft-server get pods
NAME READY STATUS RESTARTS AGE
minecraft-server-b89954df9-t9dxg 1/1 Running 2 (112s ago) 2m21s
$ kubectl -n minecraft-server attach -it minecraft-server-b89954df9-t9dxg
If you don't see a command prompt, try pressing enter.
/help
[15:27:18] [Server thread/INFO]: Unknown command. Type "/help" for help.
This only seems to show the logs, but commands are not accepted, not even /help.
Even worse: the only way to detach from the pod is
with Ctrl+C and that kills the server
without saving the worlds! So it seems the stdin
and tty options are not a good idea.
Server Config
To adjust server-wide or default settings, edit the
server.properties file and reload it, e.g.
# su minecraft -c "vi /home/k8s/minecraft-server/server.properties"
gamemode=creative
motd=Be good
pvp=false
difficulty=easy
max-players=5
Then send the reload command to reload the config without restarting the server.
Access Control
To restrict access to a few (trusted) users, add them
with their UUID to the whitelist.json file:
| vi/home/k8s/minecraft-server/whitelist.json | |
|---|---|
To find users’ UUID, check the server’s logs:
# grep -i uuid /home/k8s/minecraft-server/logs/latest.log
[14:35:10] [User Authenticator #2/INFO]: UUID of player L________a is ____1e97-____-____-____-f7187fd7____
[14:44:06] [User Authenticator #3/INFO]: UUID of player M________t is ____41f4-____-____-____-de0061cf____
Need to restart the deployment to make those changes
effective, but first must activate the whitelist by
setting the following values in
/home/k8s/minecraft-server/server.properties
Then send the reload command to reload the config without restarting the server.
If restarting the whole server becomes necessary:
An easier way would be to use the EasyWhitelist tool in SpigotMC, but it looks like it's broken.
Hourly Backups
Minecraft servers rely too much on players behaving, which of course is a strategy that has proven problematic many times over.
To recover from any disasters, create hourly backups as
the root user in the node. The following scripts
creates one full backup per hour, so that even within a
single day multiple backups are available to restore:
Put this script in a dedicated directory for the backups
and run it every hour with crontab:
# mkdir /home/k8s/minecraft-server-backups
# vi /home/k8s/minecraft-server-backups/backup.sh
# crontab -e
00 * * * * /home/k8s/minecraft-server-backups/backup.sh
Daily Server Restart
To keep the server up to date, it is easiest to just restart the whole deployment every day. Besides, there is no need to keep the server running overnight because this is a private server, not used from multiple timezones.
Find the minecraft-start-k8s
and minecraft-stop-k8s
script in the Appendix below.
Note
These commands must be run as the user who has
the credentials to run kubectl:
$ crontab -e
10 6 * * * /home/coder/bin/minecraft-start-k8s
30 22 * * * /home/coder/bin/minecraft-stop-k8s
World Reset
If at some point we want to start a new world, it is as
simple as renaming /home/k8s/minecraft-server/world
to any name when the server is not running.
The next time the server starts, a new
/home/k8s/minecraft-server/world folder will
be created with a whole new world.
# /home/coder/bin/minecraft-stop-k8s
# mv /home/k8s/minecraft-server/world \
/home/k8s/minecraft-server/old_world
# /home/coder/bin/minecraft-start-k8s
Appendix: more server commands
minecraft-server-fortune
The minecraft-server-fortune script quotes funny lines on everybody's console.
{% raw %}
minecraft-server-kick
The minecraft-server-kick script will kick the user
that is passed as argument:
| minecraft-server-kick | |
|---|---|
minecraft-server-make-creative
The minecraft-server-make-creative script changes the
server into creative mode.
| minecraft-server-make-creative | |
|---|---|
minecraft-server-make-survival
The minecraft-server-make-survival script changes the
server into survival mode.
| minecraft-server-make-survival | |
|---|---|
minecraft-server-say-shutdown
The minecraft-server-say-shutdown script shuts the
server down (necessary before making a backup).
| minecraft-server-say-shutdown | |
|---|---|
minecraft-server-tp-l--------a-m--------t
The minecraft-server-tp-l--------a-m--------t script
teleports (tp) a certain user wher the other one is.
| minecraft-server-tp-l--------a-m--------t | |
|---|---|
minecraft-server-tp-l--------a-m--------t
The minecraft-server-tp-S-----------e-m--------t script
teleports (tp) a certain user wher the other one is.
| minecraft-server-tp-l--------a-m--------t | |
|---|---|
minecraft-start-k8s
The minecraft-start-k8s script starts the server by
applying the deployment.
| minecraft-start-k8s | |
|---|---|
minecraft-stop-k8s
The minecraft-stop-k8s script stops the server by
removing the deployment.
minecraft-logs
The minecraft-logs script shows server logs as they are
produces (with -f).