DEV Community

loading...

Load-Balancing Minecraft Servers with Kong Gateway

Michael Bogan
I write tech articles about things I love. I also run DevSpotlight - we create tech content for tech companies. If you need tech content, or want to create tech content, reach out!
・12 min read

It's time to have some fun. Sure, tell your colleagues or your family that you're doing research, experimenting with some new tech—because that is what we'll be doing—but just don't let them see you playing Minecraft!

Here's the scenario: You're organizing a full-day Minecraft class for local STEM students. You need to run your own Minecraft servers to ensure a kid-friendly multiplayer environment, restricted only to your students. One server won't be enough, so you'll run two servers simultaneously, expecting your load balancer to handle sending students to Server A or Server B, depending on the load.

In this article, we're going to explore port forwarding and load balancing with Kong Gateway. We're going to do this by spinning up multiple Minecraft servers, and then placing Kong Gateway in front of these upstream services to handle port forwarding and load balancing.

Before we dive in, let's briefly cover a few important technology concepts.

Key Concepts

Port Forwarding

Port forwarding is receiving network requests on a certain port of a machine and forwarding those requests to a different port. A router, firewall or API gateway usually handles this task. For example, you might have a web server listening on port 3000 and a database server listening on port 5000. Your API gateway would listen for requests from outside your network. Requests addressed to port 80 would be forwarded by the gateway to your web server at port 3000. Meanwhile, requests addressed to port 5432 would be forwarded to your database server at port 5000.

Load Balancing

Load balancing is the task of distributing multiple requests to a server in a balanced manner across numerous replicas of that server. A specific piece of hardware or software called a load balancer usually handles this. The outside world is unaware that there are multiple replicas of a server running. They believe they’re making requests to a single server. The load balancer, however, distributes the request load to prevent any one server from being overwhelmed. In the case of a replica failing completely, the load balancer ensures that requests only go to healthy nodes.

Kong Gateway

Kong Gateway is a thin API gateway layer that sits in front of upstream services, capable of performing these port forwarding and load balancing tasks. Whether those upstream services are web servers or databases or even Minecraft game servers, Kong is the front-door greeter to all requests. In addition to traffic control, Kong Gateway can manage authentication, request transformations, analytics, and logging.

TCP Stream Support

One aspect of our Minecraft project that sets it apart from deploying a typical web server or database is that Minecraft requires an established connection between the Minecraft client (the gamer) and server. Rather than expecting stateless HTTP requests, we'll need to handle TCP connections with streaming data. Fortunately, Kong Gateway fully supports TCP streaming.

Our project approach

We're going to walk through this project step-by-step:

  1. Spin up a single, local Minecraft server without any port forwarding.
  2. Spin up a Minecraft server on a non-default port, configuring Kong Gateway to port forward requests to that server.
  3. Spin up two Minecraft servers on different ports, configuring Kong Gateway to load balance and port forward connection requests.

As you can see, we'll start simple, and we'll slowly build on complexity.

What you'll need to get started

You don't actually need a lot of familiarity with Minecraft to progress through this mini-project. Since it’s easiest to spin up Minecraft servers within Docker containers, basic familiarity with Docker may be helpful.

You will need to install Docker Engine on your local machine. Lastly, if you want to test that our project results are successful, you'll need to install the Minecraft game client and log in as a paid owner of the game. The free trial of Minecraft doesn't allow connecting to multiplayer servers, which is what we'll be running for our project.

Are you ready to do this? Here we go!

Step 1: Single Minecraft Server with Default Port

In this first step, we want to spin up a single Minecraft server on our local machine. We'll use the default port for the server, and then we'll connect our game client to the server. It’s simple to deploy the Minecraft server as a Docker container, with the Docker image found here.

In a terminal window, we'll run this command to pull down the server image and spin it up in a container:

~$ docker run -p 25000:25565 -e EULA=true itzg/minecraft-server

Unable to find image 'itzg/minecraft-server:latest' locally
latest: Pulling from itzg/minecraft-server
...

Status: Downloaded newer image for itzg/minecraft-server:latest
...

[init] Checking for JSON files.
[init] Setting initial memory to 1G and max to 1G
[init] Starting the Minecraft server...
...

[Server thread/INFO]: Preparing start region for dimension minecraft:overworld
[Worker-Main-5/INFO]: Preparing spawn area: 1%
...


...

Enter fullscreen mode Exit fullscreen mode

As our container starts up, it downloads the Docker image for the Minecraft server. Once the image downloads, it starts up the server, and we see the log messages of the server startup. Here's an explanation of flags and options we provided to docker run in our command:

  • -p specifies a port on the host (your local machine) that Docker should bind to a port on the container. In this case, our local machine's port 25000 will point to the container's port 25565. By default, Minecraft servers run on port 25565. Typically, you will always bind to the container's port 25565, regardless of the port on the host that you choose to use.
  • -e EULA=true provides an environment variable that the Docker container needs to use when starting up the server within the container. The Minecraft server application requires that you accept the EULA upon startup. Providing this environment variable is the Docker way to do that.
  • Lastly, we specify the name of the Docker image (on DockerHub), which contains the Minecraft server.

With our server running, let's see if we can connect to the server at localhost:25000. Open up the Minecraft Launcher client and click on "Play".

The actual Minecraft game should launch. For game options, click on "Multiplayer".

Next, click on "Direct Connection".

For server address, enter localhost:25000. Our local port 25000, of course, is bound to the container running our Minecraft server. Finally, we click on "Join Server".

And… we're in!

If you look back at the terminal with the docker run command, you'll recall that it continues to output the log messages from the Minecraft server. It might look something like this:

...
 
[User Authenticator #1/INFO]: UUID of player familycodingfun is 8b4fb92b-24c6-423a-bf32-fea282fc8a25
[Server thread/INFO]: familycodingfun[/172.17.0.1:53248] logged in with entity id 282 at (243.5, 65.0, -138.5)
[Server thread/INFO]: familycodingfun joined the game
Enter fullscreen mode Exit fullscreen mode

The server notes that a new player (my username is familycodingfun) has joined the game. Our single game server setup is complete. Now, let's add Kong Gateway and port forwarding to the mix. For now, we'll exit the game and then kill our Docker container with the server.

Step 2: Minecraft Server with Kong Gateway and Port Forwarding

Next, we'll put Kong Gateway in front of our Minecraft server and take advantage of port forwarding. If you were running a private network, you might forbid requests from outside the network to reach your Minecraft server port. At the same time, you might expose a single port on which Kong listens. Kong, as the API gateway, would listen to requests on that port and then forward those requests to your Minecraft server. Doing so ensures that any requests that want to go to a Minecraft server must go through Kong first.

Although we'll be working within localhost, we'll set up this kind of port forwarding through Kong. Just like in our previous step, we want our Minecraft server to run on port 25000. Meanwhile, Kong will listen on port 20000. Kong will take TCP connection requests on port 20000 and forward them to the Minecraft server at port 25000.

Install and Setup Kong

The first step is to install Kong Gateway. The installation steps vary depending on your unique setup. After installing Kong, we'll need to set up the initial configuration file. In your /etc/kong folder, you'll see a template file called kong.conf.default. We will copy this file and rename it as kong.conf, which is the file that Kong will look to for its startup configuration:

~$ cd /etc/kong
/etc/kong$ sudo cp kong.conf.default kong.conf
/etc/kong$ tree
.
├── kong.conf
├── kong.conf.default
└── kong.logrotate

0 directories, 3 files
Enter fullscreen mode Exit fullscreen mode

In kong.conf, we'll need to make the following three edits:

# PATH: /etc/kong/kong.conf

#
# Around line 368. Uncomment and set for TCP listening on port 20000
#

stream_listen = 127.0.0.1:20000
 
#
# Around line 922. Uncomment and set database to off
#
 
database = off

#
# Around line 1106. Uncomment and set to a file we're about to create.
#                   Use the absolute path to a project folder
#

declarative_config = /PATH/TO/MY/project/minecraft-kong.yml
Enter fullscreen mode Exit fullscreen mode

The stream_listen configuration tells Kong to listen for streaming TCP traffic. We're telling Kong to listen on port 20000 for this. For the needs of this mini project, we can configure Kong using its DB-less and Declarative configuration style. Kong will not need to use a database (database = off), and all of our configurations for port forwarding and load balancing will be stored in a single YAML file. That is the declarative_config file path that we've set above.

Write Declarative Configuration File

Before we start up Kong, we need to write that minecraft-kong.yml file with our port forwarding configuration. In a project folder (that matches the path you specified above), open up a new file called minecraft-kong.yml:

# PATH: ~/project/minecraft-kong.yml

_format_version: "2.1"

services:
  - name: Minecraft-Server-A
    url: tcp://localhost:25000
    routes:
      - name: Server-A-TCP-Routes
        protocols: ["tcp", "tls"]
        destinations:
          - ip: 127.0.0.1
            port: 20000
Enter fullscreen mode Exit fullscreen mode

In this file, we declare a new Service entity named Minecraft-Server-A. The server uses the TCP protocol, listening on localhost port 25000, so we set these values together as the service's url. Next, we define a Route for the service, which associates our service with a URL path or an incoming connection destination that Kong will listen for. We provide a name for our route, telling Kong to listen for requests using TCP/TLS on the destination that we specified in our kong.conf file: ip 127.0.0.1 and port 20000.

Start Up Minecraft Server and Kong

We’ve written all of our configuration for this step. Let's start up our Minecraft server in Docker. Remember, we want our host (our local machine) to be ready on port 25000, binding that port to the standard Minecraft server port of 25565 on the container:

~/project$ docker run -p 25000:25565 -e EULA=true itzg/minecraft-server
Enter fullscreen mode Exit fullscreen mode

That command might take a little time to run as the server starts up. Now, in a separate terminal window, we'll start up Kong:

~/project$ sudo kong start
Enter fullscreen mode Exit fullscreen mode

With our server up and running, we go back to our game client and, just like above, choose "Multiplayer" and try to establish a "Direct Connection" with a game server. We know that we could connect directly to localhost:25000, since that's the actual host port bound to the container's port; rather, we want to test Kong's port forwarding. We want to connect to the supposed game server on localhost:20000, pretending that we're the casual user who is unaware that port 20000 points to a port forwarding gateway.

Click on "Join Server." Like Step 1, your connection should be successful, and you'll have entered the Minecraft world. Our TCP connection request to localhost:20000 went to Kong Gateway, which then forwarded that request to port 25000, our actual Minecraft server. We have port forwarding up and running!

Step 3: Load-Balancing Two Minecraft Servers

We will spin up two Minecraft servers for the final step in our mini-project, listening on ports 25000 and 26000. Previously, when we only had one Minecraft server, Kong would naturally forward TCP requests at port 20000 to that sole Minecraft server's port. Now, with two Minecraft server ports to choose from, we'll need to use port forwarding and load balancing. Kong Gateway will take TCP connection requests that come to port 20000 and distribute connections evenly between Minecraft Server A and Minecraft Server B.

Start Up Minecraft Servers

If you haven't done so already, terminate the single Minecraft server that was running in the previous step. We'll start everything up again from a clean state, spinning up each server in its own terminal window. In your first terminal window, run the Docker container for Server A, binding the host's port 25000 to the container's port 25565:

~/project$ docker run -p 25000:25565 -e EULA=true itzg/minecraft-server
Enter fullscreen mode Exit fullscreen mode

Then, in a separate terminal window, we will start up Server B, this time binding the host's port 26000 to the container's port 25565:

~/project$ docker run -p 26000:25565 -e EULA=true itzg/minecraft-server
Enter fullscreen mode Exit fullscreen mode

Now, we have Servers A and B running, accessible at ports 25000 and 26000, respectively.

Edit Declarative Configuration File

Next, we want to edit our declarative configuration file (minecraft-kong.yml), configuring Kong for load balancing. Edit your file to reflect the following:

# PATH: ~/project/minecraft-kong.yml
_format_version: "2.1"

upstreams:
  - name: Minecraft-Servers
    targets:
      - target: localhost:25000
        weight: 100
      - target: localhost:26000
        weight: 100
services:
  - name: Load-Balancing-Service
    host: Minecraft-Servers
    protocol: tcp
    routes:
      - name: Load-Balancing-Route
        protocols: ["tcp", "tls"]
        destinations:
          - ip: 127.0.0.1
            port: 20000
Enter fullscreen mode Exit fullscreen mode

Let's walk through what we've done here. First, we added an Upstream Object (arbitrarily named Minecraft-Servers), which functions as a virtual host for load balancing to multiple services. That's exactly the functionality that we need. We added two Target Objects to our upstream service. Each target has an address with host and port; in our case, our two targets point to localhost:25000 (Minecraft Server A) and localhost:26000 (Minecraft Server B). Then, we set a weight for each target, which the load balancer uses to distribute load. Even though we've explicitly set the weights evenly to 100, the default for this optional configuration is 100.

Next, we declared our Service Object, which in this case is our load balancer service. Requests that satisfy the routes we establish will be forwarded to the Minecraft-Servers host, our load balancing upstream object. Similar to our previous step, we configured a route, telling Kong Gateway to listen for TCP/TLS requests destined for 127.0.0.1:20000.

Restart Kong

Since our Kong configuration has changed, we need to restart Kong for the changes to take effect:

~/project$ sudo kong restart
Enter fullscreen mode Exit fullscreen mode

At this point, everything is up and running. We have our two Minecraft servers (Server A and Server B) running in Docker containers in two separate terminal windows. We have Kong configured to listen for TCP on port 20000, forwarding those requests to our load balancer, distributing connections across our two servers.

Open the Minecraft game client again. Similar to previous steps, we will attempt to connect to the multiplayer server at localhost:20000 directly. As you connect, keep an eye on your two server terminal windows. As you repeatedly connect to the game, disconnect, and then reconnect, you will at times see a connection log message for Server A, and then at other times a message for Server B.

And just like that, we have set up our load balancer to distribute connection requests across our two Minecraft servers!

Ready to Play Work

To recap, we slowly progressed in complexity for our mini-project:

  1. We started by simply spinning up a single Minecraft server in a Docker container, using port 25000 for accepting game client connections.
  2. Next, we configured Kong Gateway to sit in front of our single server to perform port forwarding. Kong listened on port 20000 for game client connections, forwarding those requests to the port on our host where the Minecraft server was accessible.
  3. Lastly, we set up two Minecraft servers to run concurrently. Then, we configured Kong Gateway to act as a load balancer. Kong listened on port 20000 for game client connections, this time funneling them through its load balancing service to distribute connections across our two servers.

From here, you have many opportunities for adding complexity. You can add more game servers. For example, if some servers run on machines that can handle a heavier load of connections than others, then you can configure the load balancer to distribute the load unevenly. You can configure health check rules for Kong's load balancer to ensure requests are only forwarded to those servers which are presently healthy. You can even select from a handful of load balancing algorithms besides the default "round-robin" strategy.

So, we've had a little fun and learned to use some important tools and concepts along the way. We have discovered that load balancing and port forwarding with Kong Gateway is simple and easy to set up. Yet, even with such ease, these features are extremely powerful. Now that you've got a handle on it, it's time to get to work and face the Ender Dragon.

Discussion (0)