Kubernetes Setup Guide
November 10, 2025 ยท View on GitHub
This guide covers deploying Valhalla to a Kubernetes cluster using Helm.
Why Kubernetes?
Kubernetes provides:
- High availability - Automatic restarts and health checks
- Scalability - Easy to scale channels horizontally
- Production-ready - Battle-tested orchestration platform
- Infrastructure as code - Declarative configuration
Prerequisites
- Kubernetes cluster - minikube, kind, K3s, or cloud provider (GKE, EKS, AKS)
- kubectl - Configured to connect to your cluster
- Helm 3 - Package manager for Kubernetes
- Data.nx file - See Installation Guide for conversion
- Container registry - Or ability to load images directly (minikube, kind)
Quick Start
Step 1: Prepare Your Cluster
Option A: Local Development with minikube
# Install minikube
# See https://minikube.sigs.k8s.io/docs/start/
# Start cluster
minikube start --memory=4096 --cpus=2
# Verify
kubectl get nodes
Option B: Local Development with kind
# Install kind
# See https://kind.sigs.k8s.io/docs/user/quick-start/
# Create cluster
kind create cluster --name valhalla
# Verify
kubectl get nodes
Option C: Cloud Provider
Use your cloud provider's tools:
- GKE:
gcloud container clusters create valhalla - EKS: Use eksctl or AWS console
- AKS:
az aks create --name valhalla
Step 2: Prepare Data.nx
For Kubernetes, you need to make Data.nx available to pods. You have two options:
Option A: Prebake into Docker image
This is easiest option, simply build your Docker images with the Data.nx file inside it.
Option B: Persistent Volume (Recommended)
Create a PersistentVolume and copy Data.nx to it. Example for local development:
# data-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: data-nx-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadOnlyMany
hostPath:
path: /data/valhalla
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-nx-pvc
namespace: valhalla
spec:
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 1Gi
Apply and copy file:
kubectl apply -f data-pv.yaml
# For minikube
minikube ssh
sudo mkdir -p /data/valhalla
# Then copy Data.nx to /data/valhalla/ using your preferred method
# For kind - mount when creating cluster
kind create cluster --config=kind-config.yaml
Step 3: Build and Load the Image
# Build image
docker build -t valhalla:latest -f Dockerfile .
# Load into cluster
# For kind:
kind load docker-image valhalla:latest
# For minikube:
minikube image load valhalla:latest
# For cloud providers:
# Tag and push to your container registry
docker tag valhalla:latest gcr.io/your-project/valhalla:latest
docker push gcr.io/your-project/valhalla:latest
Step 4: Deploy with Helm
# Create namespace
kubectl create namespace valhalla
# Install chart
helm install valhalla ./helm -n valhalla
# Watch pods start
kubectl get pods -n valhalla -w
Step 5: Expose Services
By default, all services use ClusterIP (internal only). To access from outside:
Option A: Port Forwarding (Development)
# Forward login server
kubectl port-forward -n valhalla svc/login-server 8484:8484
# In another terminal, forward channels
kubectl port-forward -n valhalla svc/channel-server-1 8685:8685
kubectl port-forward -n valhalla svc/channel-server-2 8686:8686
Option B: Ingress-Nginx (Recommended for Production)
See Exposing via Ingress below.
Helm Chart Configuration
values.yaml
The Helm chart can be customized via helm/values.yaml:
# Image configuration
image:
repository: valhalla
tag: latest
pullPolicy: IfNotPresent
# Replica counts
replicaCount:
login: 1
world: 1
cashshop: 1
channels: 2
# Database configuration
database:
address: "db"
port: "3306"
user: "root"
password: "password"
database: "maplestory"
# World settings
world:
message: "Welcome to Valhalla!"
ribbon: 2
expRate: 1.0
dropRate: 1.0
mesosRate: 1.0
# Channel settings
channel:
maxPop: 250
clientConnectionAddress: "127.0.0.1"
Installing with Custom Values
# Create custom values file
cat > my-values.yaml <<EOF
world:
expRate: 2.0
dropRate: 1.5
mesosRate: 1.5
channel:
maxPop: 500
EOF
# Install with custom values
helm install valhalla ./helm -n valhalla -f my-values.yaml
Upgrading Configuration
# Edit values.yaml or create new values file
vim helm/values.yaml
# Upgrade deployment
helm upgrade valhalla ./helm -n valhalla
# Rollback if needed
helm rollback valhalla -n valhalla
Service Discovery
In Kubernetes, services use DNS names instead of IP addresses:
| Service | Docker Compose | Kubernetes |
|---|---|---|
| Login Server | login_server | login-server |
| World Server | world_server | world-server |
| Database | db | db |
| Channel 1 | channel_server_1 | channel-server-1 |
The Helm chart automatically adjusts configurations to use hyphens for K8s service names.
Exposing Services with Ingress-Nginx
Ingress-Nginx allows you to expose TCP services (required for MapleStory):
Step 1: Install Ingress-Nginx
# Add Helm repo
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Install ingress-nginx with TCP service support
helm install ingress-nginx ingress-nginx/ingress-nginx \
--create-namespace \
--namespace ingress-nginx \
-f ingress-values.yaml
Step 2: Create ingress-values.yaml
tcp:
8484: valhalla/login-server:8484
8600: valhalla/cashshop-server:8600
8685: valhalla/channel-server-1:8685
8686: valhalla/channel-server-2:8686
# Add more channels as needed:
# 8687: valhalla/channel-server-3:8687
# 8688: valhalla/channel-server-4:8688
Important: Each channel needs its own port mapping. Port numbers decrease by 1 for each additional channel.
Step 3: Get External IP
kubectl get svc -n ingress-nginx ingress-nginx-controller
# Look for EXTERNAL-IP
# On cloud providers, this will be a public IP or hostname
# On minikube: minikube tunnel (in separate terminal)
# On kind: Use port mappings defined in cluster config
Step 4: Update Valhalla Configuration
Update helm/values.yaml with the external IP:
channel:
clientConnectionAddress: "<loadbalancer-ip>"
cashshop:
clientConnectionAddress: "<loadbalancer-ip>"
Upgrade Helm deployment:
helm upgrade valhalla ./helm -n valhalla -f helm/values.yaml
Step 5: Update MapleStory Client
Configure your client to connect to the ingress controller's external IP.
Scaling Channels
Add More Channels
Edit helm/values.yaml:
replicaCount:
channels: 5 # Increase from 2 to 5
Update ingress-values.yaml to include new channel ports:
tcp:
8484: valhalla/login-server:8484
8600: valhalla/cashshop-server:8600
8685: valhalla/channel-server-1:8685
8686: valhalla/channel-server-2:8686
8687: valhalla/channel-server-3:8687
8688: valhalla/channel-server-4:8688
8689: valhalla/channel-server-5:8689
Upgrade both:
# Upgrade ingress
helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
-n ingress-nginx \
-f ingress-values.yaml
# Upgrade valhalla
helm upgrade valhalla ./helm -n valhalla
Database
Using External MySQL
For production, use a managed database service:
# values.yaml
database:
address: "mysql.example.com"
port: "3306"
user: "valhalla"
password: "securePassword"
database: "maplestory"
Using In-Cluster MySQL
The Helm chart can deploy MySQL within the cluster (not recommended for production):
mysql:
enabled: true
persistence:
enabled: true
size: 10Gi
Monitoring
Prometheus
Install Prometheus to scrape Valhalla metrics:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
Configure ServiceMonitor:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: valhalla
namespace: valhalla
spec:
selector:
matchLabels:
app: valhalla
endpoints:
- port: metrics
path: /metrics
Grafana
Access Grafana (installed with Prometheus):
kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80
Default credentials: admin/prom-operator
Managing the Deployment
View Pods
kubectl get pods -n valhalla
View Logs
# Specific pod
kubectl logs -n valhalla login-server-xyz123
# Follow logs
kubectl logs -n valhalla -f login-server-xyz123
# All pods with label
kubectl logs -n valhalla -l app=channel-server
Execute Commands in Pod
kubectl exec -it -n valhalla channel-server-1-xyz123 -- sh
Restart Deployment
kubectl rollout restart deployment/login-server -n valhalla
Scale Manually
kubectl scale deployment/channel-server --replicas=3 -n valhalla
Troubleshooting
Pods Not Starting
Check pod status:
kubectl describe pod -n valhalla <pod-name>
Common issues:
- Image pull error: Check image name and pull policy
- Missing Data.nx: Verify ConfigMap or PV is correctly mounted
- Database connection: Check database service and credentials
CrashLoopBackOff
Check logs:
kubectl logs -n valhalla <pod-name> --previous
Common causes:
- Missing environment variables
- Database not ready
- Incorrect configuration
Service Not Reachable
Check service:
kubectl get svc -n valhalla
kubectl describe svc -n valhalla login-server
Test connectivity:
# From inside cluster
kubectl run -it --rm debug --image=alpine --restart=Never -n valhalla -- sh
apk add curl netcat-openbsd
nc -zv login-server 8484
ConfigMap/Secret Changes Not Reflected
Pods don't automatically restart when ConfigMaps/Secrets change:
# Force restart
kubectl rollout restart deployment/login-server -n valhalla
Security Best Practices
-
Use Secrets for sensitive data:
kubectl create secret generic db-credentials \ --from-literal=password=securePassword \ -n valhalla -
Set resource limits:
resources: limits: memory: "512Mi" cpu: "500m" requests: memory: "256Mi" cpu: "250m" -
Use RBAC for access control
-
Enable Network Policies to restrict traffic
-
Run as non-root user where possible
-
Keep images updated regularly
Backup and Recovery
Backup Database
# If using in-cluster MySQL
kubectl exec -n valhalla db-0 -- mysqldump -u root -ppassword maplestory > backup.sql
# If using PVC
kubectl exec -n valhalla db-0 -- mysqldump -u root -ppassword maplestory | gzip > backup.sql.gz
Restore Database
kubectl exec -i -n valhalla db-0 -- mysql -u root -ppassword maplestory < backup.sql
Production Checklist
- Use managed database service
- Set up SSL/TLS certificates
- Configure resource requests and limits
- Set up monitoring and alerting
- Configure automatic backups
- Use Secrets for sensitive data
- Set up logging aggregation
- Configure pod disruption budgets
- Test disaster recovery procedures
- Set up autoscaling (HPA) if needed
- Configure network policies
- Use separate namespaces for different environments
Next Steps
- Configure server settings: Configuration.md
- Learn about Docker deployment: Docker.md
- Understand local development: Local.md
- Build from source: Building.md
Useful Commands Reference
# Deploy
helm install valhalla ./helm -n valhalla
# Upgrade
helm upgrade valhalla ./helm -n valhalla
# Rollback
helm rollback valhalla -n valhalla
# Uninstall
helm uninstall valhalla -n valhalla
# View values
helm get values valhalla -n valhalla
# Check status
helm status valhalla -n valhalla
# View pods
kubectl get pods -n valhalla
# View services
kubectl get svc -n valhalla
# View logs
kubectl logs -f -n valhalla <pod-name>
# Port forward
kubectl port-forward -n valhalla svc/login-server 8484:8484
# Execute in pod
kubectl exec -it -n valhalla <pod-name> -- sh