This project aims to be an example of how a matchmaking system using Agones and Open Match could be implemented. It is heavily inspired by https://open-match.dev/site/docs/tutorials.
The matchmaking logic and allocation process assume a hypothetical game where players will be matched together based on their skills, latency, region, the desired world they want to play and the gameserver capacity.
This project does not target any game, company or specific audience. It has been mostly the result of a learning process that put together different tools and technologies.
Real games require way more input information, and a more complex matchmaking algorithm. However, the approach followed in here may be a good starting point.
An open source, batteries-included, multiplayer dedicated game server scaling and orchestration platform that can run anywhere Kubernetes can run.
Open Match is a flexible match making system built to scale with your game.
You can find great documentation about each one of these projects on their websites. - https://agones.dev/site/ - https://open-match.dev/site/
If you want to see the project in action, please make sure you have the requirements set. Basically you need a Kubernetes running Agones 1.9.0 and Open Match 1.0.0.
This project will work on any Kubernetes cluster that is supported by Agones 1.9.0 (K8S 1.16). That could be a cluster running in the cloud or locally.
The development and testing has been done using a Kubernetes cluster provisioned with k3s on a separated Linux server.
However, the exactly same results could be achieved running on Minikube, Kind or any cloud environment.
Running a Kubernetes cluster provisioned with k3s (Linux only):
export INSTALL_K3S_VERSION=v1.16.15+k3s1
curl -sfL https://get.k3s.io | sh -s - --docker
# Kubeconfig is written to /etc/rancher/k3s/k3s.yaml
This project has been developed and tested using Agones 1.9.0. I can't guarantee compatibility to any previous version since it has not been tested.
Although the documentation covers the deployment on a single cluster, the solution can be deployed on a multi cluster topology. The documentation for that scenario will be added soon.
The PlayerTracking will provide the information about Players (Capacity and Count) which is used by the Allocator when looking for available gameservers. Different matchmaking systems might not require that Agones feature. - Enable Player Capacity feature gate:
$ helm install agones --namespace agones-system --create-namespace agones/agones --set agones.featureGates=PlayerTracking=true
The manifests to install Open Match 1.0.0 can be found on deploy/openmatch folder, they also include Prometheus and Grafana.
These files have been downloaded from the official Open Match repo. The only difference is the number of replicas set to 1 for the core components.
To install all the Open Match components run the following command:
$ ./deploy/openmatch/deploy.sh
You can also check the official Open Match installation docs on https://open-match.dev/site/docs/installation/.
Below you can find details about the local setup used for the development and testing of this project.
NodePort but the same could be achieved using kubectl port-forwardDuring the development of this project I've exposed some Open Match services using services of type NodePort. That eliminates the need of using port-forward to communicate with those services running within the cluster.
# Check the manifest and update the nodePort field if required.
$ kubectl -n open-match apply -f open-match-services-nodeport.yaml
If you are a direnv user check the .envrc.template file.
Below there is a list of all the services and components that put together deliver the match making system. They can be part of this repo, Open Match built in or third party services.
Repo
Open Match
Third Party
The scenario considered for the matchmaking takes into consideration the following input:
Ticket Details: created by the Player simulator and pushed to the Game Service Frontend
MMF Criteria
Director Profiles
Important
The following documentation covers the use case where the Director is using the Octops Discover to find and allocated gameservers. Alternatively, this project also provides the option to use the Agones Allocator service. Check the docs/agones-allocator.md document for instructions.
The allocation service will try to find a GameServer that matches the criteria found on the Extension field of the AssignTicketsRequest. This information must match with Labels (Region and World) from the Fleet and GameServer.
Rules: - GameServer running on the desired Region - GameServer hosting the desired World - GameServer Capacity can accommodate all the tickets from the match - Assign connections to matches or clean up those not allocated
The Octops Discover service works like a central GameServers state store.
One of the core components of the Octops Discover is the https://github.com/Octops/agones-event-broadcaster together with a data store and an HTTP API to expose the GameServers states.
The director leverages the searching of the GameServers to the Octops/Discover service.
This service exposes an HTTP API to be consumed by clients looking for GameServer information using some filtering. That includes labels or any GameServer field present on the data struct.
More details about the Octops Discover project and how it could be used for multi cluster topology will be available soon.
The manifest to deploy the service can be found on deploy/third-party/octops.yaml.
An example of how to query for GameServers is shown below. The client requests the service endpoint passing the right query params.
# Decoded Version
GET http://octops-discover.agones-openmatch.svc.cluster.local:8081/api/v1/gameservers?fields=status.state=Ready&labels=region=us-east-1,world=Dune
# Encoded Version
GET http://octops-discover.agones-openmatch.svc.cluster.local:8081/api/v1/gameservers?fields=status.state%3DReady&labels=region%3Dus-east-1%2Cworld%3DDune
Example of a response:
{
"data": [
{
"uid": "6cb220c0-ca9d-4164-bca9-a8aa1a879fd3",
"name": "fleet-us-east-1-dune-8g8dl-vw9l7",
"namespace": "default",
"resource_version": "161047",
"labels": {
"agones.dev/fleet": "fleet-us-east-1-dune",
"agones.dev/gameserverset": "fleet-us-east-1-dune-8g8dl",
"region": "us-east-1",
"world": "Dune"
},
"status": {
"state": "Ready",
"address": "192.168.0.110:7019",
"players": {
"count": 0,
"capacity": 10,
"ids": null
}
}
}
]
}
The Allocator service will use the list of GameServers from the response and assign a connection to ticket based on the GameServer capacity. If any GameServer can accommodate the ticket, the connection will not be assigned.
Alternatively, the director could be extended and use any other sort of allocation mechanism. However, this is not covered by this project due to the game use case presented previously. Future work may include different games scenarios and will explore alternatives to the Octops Discover.
You can find more details of how to allocate GameServers using the Agones Allocation Service on https://agones.dev/site/docs/advanced/allocator-service/.
All the components will be built into a single binary. The passing argument when running the binary together with a few flags will start the proper process.
Make sure you have the expected environment variabels set and pointing to valid endpoints. Check the .envrc.template file for references.
Important Running this project requires the Octops Discover service. Details about this project and service will be added soon.
$ kubectl -n agones-openmatch apply -f deploy/third-party/octops.yaml
# port-forward - This endpoint will be used by the Director when allocating gameservers
$ kubectl -n agones-openmatch port-forward svc/octops-discover 8081
Player Simulator
# Generate 10 random profiles/players every 5 seconds
go run main.go player simulate --players-pool 10 --interval 5s
Matchmaking Function - MMF
$ go run main.go function --verbose
Director
# Generate profiles and Fetch matches every 5 seconds
$ go run main.go director --interval 5s --verbose
The steps below covers the deployment of the whole solution to a Kubernetes cluster.
$ kubectl create ns agones-openmatch
$ kubectl -n agones-openmatch apply -f deploy/third-party/octops.yaml
# Player simulator replicas=0 check section below
$ kubectl -n agones-openmatch apply -f deploy/install.yaml
# Default namespace
$ kubectl apply -f demo/fleets/fleets.yaml
The Player Simulator will generate 10 tickets every 5 seconds. The details of the ticket will be randomly assigned: World, Region, Skill and Latency.
Scale Players Simulator and check logs
$ kubectl -n agones-openmatch scale deployment agones-openmatch-players --replicas=1
$ kubectl -n agones-openmatch logs -f $(kubectl -n agones-openmatch get pods -l app=agones-openmatch-players -o jsonpath="{.items[0].metadata.name}")
Output:
DEBU[0094] ticketID=buaq0f9m0k8lmh52dp2g playerUID=1ac50018-4ae3-41eb-9540-91eec508fe6e stringArgs=map[region:us-west-2 world:Nova] doubleArgs=map[latency:25 skill:10]
DEBU[0094] ticketID=buaq0f9m0k8lmh52dp30 playerUID=2ee0179d-d0d1-4a63-aa09-e110255a46eb stringArgs=map[region:us-east-2 world:Pandora] doubleArgs=map[latency:25 skill:1000]
DEBU[0094] ticketID=buaq0f9m0k8lmh52dp3g playerUID=26bc9990-b5c1-4b6b-a3d2-7997e7c6244a stringArgs=map[region:us-east-1 world:Orion] doubleArgs=map[latency:50 skill:100]
DEBU[0094] ticketID=buaq0f9m0k8lmh52dp40 playerUID=36f07d2e-565f-4430-aa3f-efae59f5220e stringArgs=map[region:us-east-1 world:Orion] doubleArgs=map[latency:75 skill:10]
Check logs from the Director
kubectl -n agones-openmatch logs -f $(kubectl -n agones-openmatch get pods -l app=agones-openmatch-director -o jsonpath="{.items[0].metadata.name}")
Output:
time="2020-10-27T18:34:03Z" level=info msg="fetching matches for profile world_based_profile_Dune_us-east-2" command=fetch component=director
time="2020-10-27T18:34:03Z" level=info msg="fetching matches for profile world_based_profile_Nova_us-east-2" command=fetch component=director
time="2020-10-27T18:34:03Z" level=info msg="fetching matches for profile world_based_profile_Dune_us-west-1" command=fetch component=director
The logs from the Director will show 2 possible situations: - A match has been created but could not be assigned because a GameServer could not be found (based on the criteria region and world), or it does not have capacity to accommodate all the Players from the Ticket. ```bash
time="2020-10-25T17:10:39Z" level=debug msg="gameservers not found for request wit
$ claude mcp add agones-discover-openmatch \
-- python -m otcore.mcp_server <graph>