Minecraft Server - Part 3: Running on Kubernetes
In Part 2 of this series, we got the Minecraft server running in Docker containers, with persistence via volumes and automatic restart. In this article, will bringing the same server into a Kubernetes cluster, gaining managed storage via PersistentVolumeClaim, isolation via Namespace, and the flexibility to run on any available node in the cluster.
Minecraft Bedrock on Kubernetes
---
apiVersion: v1
kind: Namespace
metadata:
name: minecraft-br
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minecraft-bedrock-pvc
namespace: minecraft-br
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: minecraft-bedrock-server
namespace: minecraft-br
spec:
replicas: 1
selector:
matchLabels:
app: minecraft-bedrock
template:
metadata:
labels:
app: minecraft-bedrock
spec:
containers:
- name: minecraft-bedrock
image: itzg/minecraft-bedrock-server:latest
ports:
- containerPort: 19132
protocol: UDP
env:
- name: EULA
value: "TRUE"
- name: GAMEMODE
value: "survival"
- name: MAX_PLAYERS
value: "10"
- name: MOTD
value: "Bedrock Minecraft Server!"
- name: OPS
value: "your_username"
volumeMounts:
- mountPath: /data
name: minecraft-bedrock-data
volumes:
- name: minecraft-bedrock-data
persistentVolumeClaim:
claimName: minecraft-bedrock-pvc
---
apiVersion: v1
kind: Service
metadata:
name: minecraft-bedrock-service
namespace: minecraft-br
spec:
selector:
app: minecraft-bedrock
ports:
- name: bedrock-udp
port: 19132
targetPort: 19132
protocol: UDP
type: LoadBalancer
Notice the structure, which brings together several concepts already covered throughout the series:
-
Namespace (
minecraft-br): isolates the Minecraft Bedrock resources from the rest of the cluster; - PersistentVolumeClaim (10Gi): ensures the saved world survives pod restarts and recreations, without it, every new pod would start with an empty world;
-
Deployment: runs a single replica (
replicas: 1), multiple replicas of the same Minecraft world don't work, since the game wasn't designed for multiple instances accessing the same save simultaneously; -
LoadBalancer-type Service, on UDP port19132, exposing the server outside the cluster.
On on-premise clusters, the
LoadBalancertype usually doesn't provision an IP automatically (that's a native cloud provider feature), you need a controller like MetalLB for the Service to actually receive a usable external IP. Without it, the Service's external IP will stay<pending>indefinitely.
Minecraft Java on Kubernetes
apiVersion: v1
kind: Namespace
metadata:
name: minecraft-java
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minecraft-pvc
namespace: minecraft-java
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: minecraft-server
namespace: minecraft-java
spec:
replicas: 1
selector:
matchLabels:
app: minecraft
template:
metadata:
labels:
app: minecraft
spec:
containers:
- name: minecraft
image: itzg/minecraft-server:latest
ports:
- containerPort: 25565
env:
- name: EULA
value: "TRUE"
- name: MEMORY
value: "1G"
- name: VERSION
value: "latest"
- name: MAX_PLAYERS
value: "10"
- name: MOTD
value: "Welcome to the Minecraft server"
- name: ONLINE_MODE
value: "TRUE"
- name: WHITELIST
value: "false"
- name: OPS
value: "your_username"
volumeMounts:
- mountPath: /data
name: minecraft-data
volumes:
- name: minecraft-data
persistentVolumeClaim:
claimName: minecraft-pvc
---
apiVersion: v1
kind: Service
metadata:
name: minecraft-service
namespace: minecraft-java
spec:
selector:
app: minecraft
ports:
- name: mc-tcp
port: 25565
targetPort: 25565
protocol: TCP
type: LoadBalancer
Unlike Bedrock, here the containerPort and Service ports use TCP (the default, when protocol isn't specified on the container, but made explicit on the Service for clarity), consistent with Minecraft Java communicating over TCP, as we saw in Part 2.
Some environment fields specific to this image:
-
MEMORY: sets the memory limit passed to the JVM internally by the image (equivalent to the-Xmxflag seen in the bare metal installation); -
VERSION: lets you pin a specific Minecraft version (for example,"1.20.4") instead of always using the latest; -
ONLINE_MODE: when set to"TRUE", requires players to be authenticated with a valid Microsoft/Mojang account, only disable this if you know exactly why you need to (usually for servers with pirated clients, which carries security and legal implications).
Applying the manifests
kubectl apply -f minecraft-bedrock.yaml
kubectl apply -f minecraft-java.yaml
Checking whether the pods came up correctly:
kubectl -n minecraft-br get pods
and
kubectl -n minecraft-java get pods
Following the startup logs (the first start downloads the server files, which can take a few minutes):
kubectl -n minecraft-java logs -f deployment/minecraft-server
and
kubectl -n minecraft-br logs -f deployment/minecraft-bedrock-server
Series wrap-up
Throughout this series, we saw the same Minecraft server through three stages: manual bare metal installation, containerization with Docker, and finally orchestration on a Kubernetes cluster. Each stage solved a limitation of the previous one, from manual process management, through persistence and automatic restart, to managed storage and independence from a single physical host.
It's worth reinforcing an important point that applies to all three approaches: since this is a stateful game (the saved "world"), it's not possible to scale horizontally by running multiple replicas of the same server, unlike a stateless API, Minecraft needs a single, consistent instance accessing the data volume at a time.
Top comments (1)
The step-by-step approach makes a complex Kubernetes setup much easier to follow and actually reproduce. ~