A Helm chart for AzerothCore, a World of Warcraft server for Wrath of the Lich King (3.3.5a).
The chart runs MySQL, the authserver (the login server), and the worldserver. Two Jobs prepare the data: one creates the databases and applies the SQL updates, and one downloads the client data (the maps and game tables that the worldserver reads).
This repository builds the images with the Dockerfile of the core, in two flavors. The vanilla flavor is the core with mod-ale, the Lua engine, like the images of AzerothCore. The playerbots flavor is the mod-playerbots fork of the core.
You need a Kubernetes cluster, Helm 3.8 or later, and a 3.3.5a game client.
-
Write a values file. It tells the chart the flavor and which account to create.
# values.local.yaml flavor: vanilla # vanilla or playerbots dbInit: accounts: - username: admin password: change-me gmlevel: 3
-
Install the chart:
helm install azerothcore oci://ghcr.io/christianjacobsen/charts/azerothcore \ -n azerothcore --create-namespace -f values.local.yaml kubectl -n azerothcore get pods -w
-
Wait for the two Jobs to complete. Then the worldserver starts.
-
Make sure that the chart works:
helm test azerothcore -n azerothcore --logsThe test connects to both servers. If
dbInit.accountshas an account, the test also logs in with it, and the realm must be online. -
In the client folder, set
realmlist.wtfto the address of the authserver service. Then log in asadmin.
On a 10-core machine with a fast link, the first install took about 2 minutes. The world loads in seconds.
The chart values pin the images that the CI of this repository publishes to ghcr.io/christianjacobsen. To build your own images, see CONTRIBUTING.md.
The flavor value selects the images:
flavor |
Core | Module |
|---|---|---|
vanilla (default) |
azerothcore-wotlk | mod-ale |
playerbots |
mod-playerbots/azerothcore-wotlk, branch Playerbot |
mod-playerbots |
mod-playerbots needs changes to the core, so it works with the playerbots flavor only. The playerbots flavor has a fourth database, acore_playerbots. The worldserver fills it on the first start.
AzerothCore compiles its modules into the server binaries. For other modules from the catalogue, build your own images. List the modules in build/mods.local.yaml. Git ignores this file.
# build/mods.local.yaml
flavor: playerbots # vanilla or playerbots
mods:
- https://github.com/azerothcore/mod-transmog
- https://github.com/azerothcore/mod-autobalance@master # a branch, tag, or commitThen build the images and install the chart with the file that the build writes:
make images
helm install azerothcore charts/azerothcore -n azerothcore --create-namespace \
-f build/images.generated.yaml -f values.local.yamlmake catalogue lists the modules with the most stars. For the build variables, see CONTRIBUTING.md.
| Component | Kind | Purpose |
|---|---|---|
mysql |
StatefulSet | MySQL 9.7 with the AzerothCore databases (optional, see Database) |
db-init |
Job | Creates the databases, applies the SQL updates, sets the realm, and creates accounts |
client-data |
Job | Downloads the client data into the data volume |
authserver |
Deployment and Service | The login server (port 3724) |
worldserver |
Deployment and Service | The world server (port 8085) |
The server pods wait until the Jobs of their release revision are complete.
The worldserver needs four kinds of data from the game client: dbc files (game tables), maps, vmaps (models for line of sight), and mmaps (navigation meshes for path finding). The client-data Job downloads the release that the core expects from wowgaming/client-data. The download is 1.1 GB, and it unpacks to 3.1 GB. If the data volume holds that release already, the Job completes in seconds.
The clientData.source value selects what the Job does:
source |
What the Job does |
|---|---|
download (default) |
Downloads and unpacks the client data |
none |
No Job. The data volume holds the data already, for example data that you extracted from your own client |
The chart selects the data volume in this order:
clientData.volume: any Kubernetes volume source, for example NFS.clientData.existingClaim: a PVC that you manage.- A PVC that the chart creates from
clientData.storage(20 GiB, ReadWriteOnce, the default storage class).
By default, the chart deploys MySQL 9.7 with a 10 GiB volume. The image is the official MySQL image without gosu and MySQL Shell, and it runs as uid 999. It generates the root password and the password of the acore user, and it keeps both in a Secret.
To use your own MySQL or MariaDB server:
mysql:
enabled: false
externalDatabase:
host: mysql.example.com
port: 3306
adminUser: root # optional, see below
database:
user: acore
existingSecret: azerothcore-db # keys: password, admin-passwordIf you set adminUser, the db-init Job creates the databases and the acore user, and it grants the access. If you do not set it, the databases must exist, and database.user must be able to create tables in them. The databases are acore_auth, acore_world, acore_characters, and for the playerbots flavor acore_playerbots. database.names changes the names.
Do not use ; in the password. AzerothCore uses it to separate the fields of its connection strings.
The bundled MySQL runs with a few extra arguments, and mysql.extraArgs in values.yaml gives the reasons. Only MySQL 8.4 and 9.7 are tested. AzerothCore does not start on MariaDB.
The db-init Job runs on every install and upgrade, and it is safe to run many times. It creates the databases and the database user. Then it runs dbimport, a tool of the core. dbimport fills empty databases and applies the SQL updates of the core and the modules that a database does not have yet. Last, the Job sets the realm in the realm list and creates your accounts.
The base SQL of AzerothCore contains no accounts. The Job creates the accounts in dbInit.accounts:
dbInit:
accounts:
- username: admin
password: change-me # stored in a Secret by the chart
gmlevel: 3 # 0 player, 1 moderator, 2 game master, 3 administrator
- username: friend
existingSecret: my-accounts
passwordKey: friend-passwordIf an account does not exist, the Job creates it. The Job never changes the password of an existing account, because players can change their password in the game. It sets gmlevel for all realms on every run. Names have 17 characters at most, passwords have 16 at most, and neither is case-sensitive.
You can also use the worldserver console:
kubectl -n azerothcore attach -it deploy/azerothcore-worldserver -c worldserver
account create <user> <password>
account set gmlevel <user> 3 -1To detach, press ctrl-p ctrl-q. Do not press ctrl-c, because it stops the worldserver.
Warning: do not type passwords in the attached console. The console echoes your input, and the container log keeps it. Use dbInit.accounts, or the remote consoles (see Remote consoles).
You can set any key from worldserver.conf.dist and the module configuration files (worldserver.config), and from authserver.conf.dist (authserver.config). Use the key exactly as the file writes it:
worldserver:
config:
Rate.XP.Kill: 3
AllowTwoSide.Interaction.Calendar: 1
authserver:
config:
WrongPass.MaxCount: 5The chart renders each key as the environment variable that the core reads, for example AC_RATE_XP_KILL. It renders true and false as 1 and 0. The chart sets the database connections, the data folder, the ports, and the remote consoles. For environment variables without a configuration key, use worldserver.extraEnv and authserver.extraEnv.
flavor: playerbots
playerbots:
config:
AiPlayerbot.MinRandomBots: 100
AiPlayerbot.MaxRandomBots: 100
worldserver:
resources:
limits:
memory: 8GiThe chart lowers the number of random bots from 500 to 50. More bots need more memory for the worldserver. With 50 bots, the worldserver used about 3.9 GiB, and the default limit is 6 GiB.
The vanilla images include mod-ale. To load Lua scripts, put them in a ConfigMap or another volume, and set worldserver.luaScripts:
kubectl -n azerothcore create configmap lua-scripts --from-file=scripts/worldserver:
luaScripts:
configMap:
name: lua-scriptsThe chart points ALE.ScriptPath at the volume. After you change the scripts, restart the worldserver.
The worldserver has two remote consoles: SOAP (worldserver.soap.enabled) and a telnet console (worldserver.remoteAccess.enabled). The chart exposes them only inside the cluster, on the Service <release>-worldserver-admin. Both need an account with gmlevel 3:
kubectl -n azerothcore port-forward svc/azerothcore-worldserver-admin 3443:3443
telnet 127.0.0.1 3443 # log in with the GM account, then: account create <user> <password>The game protocols are raw TCP. Ingress routes HTTP only, so the chart does not offer an Ingress.
The client connects to two addresses:
- The address in
realmlist.wtf, which is the authserver service. The client uses port 3724. - The realm address and port from the realm list, which is the worldserver service.
dbInit.realm.addressanddbInit.realm.portset them.
Both Services are of type LoadBalancer by default, so they listen on the standard ports. On clusters without a load balancer, use NodePort services with fixed ports:
worldserver:
service:
type: NodePort
nodePort: 30085
dbInit:
realm:
address: 192.168.1.20 # a node address that the clients can reachIf dbInit.realm.port is empty, the chart uses the nodePort of the worldserver service (for NodePort) or its port. Clients expect the auth port 3724, so give the authserver port 3724 on the address in realmlist.wtf. Every upgrade restarts the authserver, so a new realm address takes effect at once.
To test both addresses without a game client, run the login test on your machine with Python 3:
python3 charts/azerothcore/files/auth-check.py --host <authserver address> \
--user admin --password change-me --check-worldA Gateway controller that supports TCPRoute can route the two ports. TCPRoute is in the experimental channel of the Gateway API, so the cluster needs the experimental CRDs. A TCP listener cannot tell two routes apart, so each game port needs its own listener on the Gateway:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: games
namespace: gateway-system
spec:
gatewayClassName: <your gateway class>
listeners:
- name: wow-auth
protocol: TCP
port: 3724
allowedRoutes:
namespaces:
from: All
- name: wow-world
protocol: TCP
port: 8085
allowedRoutes:
namespaces:
from: AllThe chart values attach one TCPRoute to each listener:
gateway:
enabled: true
parentRefs:
authserver:
- name: games
namespace: gateway-system
sectionName: wow-auth # a TCP listener on port 3724
worldserver:
- name: games
namespace: gateway-system
sectionName: wow-world # a TCP listener on port 8085
authserver:
service:
type: ClusterIP
worldserver:
service:
type: ClusterIP
dbInit:
realm:
address: <Gateway address>helm upgrade runs both Jobs again. The db-init Job applies the new SQL updates. The client-data Job finds the data release on the volume and completes in seconds. Every upgrade restarts both servers.
Chart 0.2.0 moves the bundled MySQL from 8.4 to 9.7. On its first start, MySQL 9.7 upgrades the data directory, and MySQL 8.4 cannot read it after that. Back up the databases with mysqldump before you upgrade.
helm uninstall azerothcore -n azerothcoreHelm keeps three things: the MySQL volume (data-azerothcore-mysql-0), the data volume (azerothcore-client-data), and the database Secret (azerothcore-db). A reinstall with the same release name uses them again. To delete everything, delete them by hand:
kubectl -n azerothcore delete pvc data-azerothcore-mysql-0 azerothcore-client-data
kubectl -n azerothcore delete secret azerothcore-dbEach chart version and image carries a keyless cosign signature from this repository. To make sure that a chart comes from here, run:
cosign verify ghcr.io/christianjacobsen/charts/azerothcore:<version> \
--certificate-identity-regexp '^https://github\.com/ChristianJacobsen/azerothcore-helm/\.github/workflows/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe same command works for the images.
- One realm per release, and one replica of each server.
- The chart has no backup job yet. Use
mysqldumpagainst the MySQL pod. - The data volume uses ReadWriteOnce by default. That works on one node. On clusters with more nodes, use a ReadWriteMany volume through
clientData.existingClaim, or keep the Job and the worldserver on one node.
The chart uses the GPL-2.0-or-later license, the same as AzerothCore. See LICENSE.