Skip to main content

Tomcat 9 Cluster & Loadbalancing

Siem Reap, Cambodia

I now want to create several instances of the Tomcat server on a single LINUX server. This is the kind of setup you would use for an microservice driven web app. For this case I will create 1:1 copies of the original server and latter add NGINX in front of it to balance out the load.

Creating Multiple Server Instances

Duplicating our Server Code

Start by copying everything but the /lib and /bin directory from your Tomcat installation dir into 3 new folder next to the installation dir - instance1, instance2 and instance3:

Directories
├── apache-tomcat-9.0.41
│ ├── bin
│ ├── conf
│ ├── lib
│ ├── logs
│ ├── temp
│ ├── virtual-hosts
│ │ ├── host1
│ │ ├── host2
│ │ └── host3
│ ├── webapps
│ │ ├── boilerplate
│ │ ├── docs
│ │ ├── examples
│ │ ├── host-manager
│ │ ├── manager
│ │ └── ROOT
│ └── work
├── instance1
│ ├── conf
│ ├── logs
│ ├── temp
│ ├── virtual-hosts
│ │ ├── host1
│ │ ├── host2
│ │ └── host3
│ ├── webapps
│ │ ├── boilerplate
│ │ ├── docs
│ │ ├── examples
│ │ ├── host-manager
│ │ ├── manager
│ │ └── ROOT
│ └── work
├── instance2
│ ├── conf
│ ├── logs
│ ├── temp
│ ├── virtual-hosts
│ │ ├── host1
│ │ ├── host2
│ │ └── host3
│ ├── webapps
│ │ ├── boilerplate
│ │ ├── docs
│ │ ├── examples
│ │ ├── host-manager
│ │ ├── manager
│ │ └── ROOT
│ └── work
├── instance3
│ ├── conf
│ ├── logs
│ ├── temp
│ ├── virtual-hosts
│ │ ├── host1
│ │ ├── host2
│ │ └── host3
│ ├── webapps
│ │ ├── boilerplate
│ │ ├── docs
│ │ ├── examples
│ │ ├── host-manager
│ │ ├── manager
│ │ └── ROOT
│ └── work

Assigning Server Ports

Now we need to give each instance a unique set of ports to operate on. This can be set inside the ./conf/server.xml of each instance, e.g. nano /opt/tomcat/instance1/conf/server.xml:

<Server port="8105" shutdown="SHUTDOWN">

...

<Connector port="8180" protocol="HTTP/1.1" connectionTimeout="20000" />

...

<!-- <Connector protocol="org.apache.coyote.http11.Http11NioProtocol"
port="8443" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="/opt/tomcat/ssl/sslKey.jks" keystorePass="instar"
clientAuth="false" sslProtocol="TLS"/> -->

...

<Connector protocol="AJP/1.3" port="8109" />

I am removing the self-signed certificate as I am going to use NGINX later to handle my CA certificate.

InstanceShutdown PortHTTP PortAJP Port
Base800580808009
Instance1810581808109
Instance2820582808209
Instance3830583808309

Creating Startup Scripts

Now we need a new scripts folder inside the tomcat directory to create startup and stop scripts for our new instances - mkdir /opt/tomcat/scripts:

nano /opt/tomcat/scripts/start-instance1.sh
#!/bin/bash

export CATALINA_HOME="/opt/tomcat/latest"
export CATALINA_BASE="/opt/tomcat/instance1"

cd $CATALINA_HOME/bin
./startup.sh
nano /opt/tomcat/scripts/stop-instance1.sh
#!/bin/bash

export CATALINA_HOME="/opt/tomcat/latest"
export CATALINA_BASE="/opt/tomcat/instance1"

cd $CATALINA_HOME/bin
./shutdown.sh

Create both scripts for all your instances:

scripts
├── start-instance1.sh
├── start-instance2.sh
├── start-instance3.sh
├── stop-instance1.sh
├── stop-instance2.sh
└── stop-instance3.sh

And make those scripts executable with chmod -R 755 /opt/tomcat/scripts. You can now start all instances by running each startup script like:

/opt/tomcat/scripts/start-instance1.sh

Using CATALINA_BASE: /opt/tomcat/instance1
Using CATALINA_HOME: /opt/tomcat/latest
Using CATALINA_TMPDIR: /opt/tomcat/instance1/temp
Using JRE_HOME: /usr
Using CLASSPATH: /opt/tomcat/latest/bin/bootstrap.jar:/opt/tomcat/latest/bin/tomcat-juli.jar
Using CATALINA_OPTS:
Tomcat started.

After making sure that the ports you specified for those 3 instances: ufw allow 8180/tcp, ufw allow 8280/tcp, ufw allow 8380/tcp - try accessing the 4 different versions of the virtual host we created earlier with your browser:

http://virtual-host1.com:8180/
http://virtual-host1.com:8280/
http://virtual-host1.com:8380/

Loadbalancing - Vertical Cluster

I now have 3 virtual hosts (instances) that I can target by a unique port:

  • Port 8180 -> Instance 1
  • Port 8280 -> Instance 2
  • Port 8380 -> Instance 3

Each of them hosts 3 web applications that will respond to one of the following addresses:

  • virtual-host1.com
  • virtual-host2.com
  • virtual-host3.com

Before I only added these addresses to the PC I am working on. I now also have to add them to /etc/hosts on my LINUX server and resolve them to 127.0.0.1. Verify that you can reach your virtual host before proceeding:

curl virtual-host1.com:8180

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Tomcat Boilerplate</title>
</head>
<body>

<h1>Hello from Virtual Host 1</h1>
<h3>Virtual Host Address: http://virtual-host1.com:8180/</h3>
</body>
</html>#

Setting Up NGINX

I am going to use the NGINX Upstream Module to set up a Round-Robin traffic balancing for the 3 virtual hosts that we just created:

NGINX Loadbalancer

server {
listen 80;
listen [::]:80;

server_name virtual-host1.com virtual-host2.com virtual-host3.com;

return 301 https://$server_name$request_uri;
}


upstream tc1_servlets {
server virtual-host1.com:8180;
server virtual-host1.com:8280;
server virtual-host1.com:8380;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include conf.d/self-signed.conf;
include conf.d/ssl-params.conf;
include conf.d/header.conf;

server_name virtual-host1.com;

location = / {
proxy_pass http://tc1_servlets;
}

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}


upstream tc2_servlets {
server virtual-host2.com:8180;
server virtual-host2.com:8280;
server virtual-host2.com:8380;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include conf.d/self-signed.conf;
include conf.d/ssl-params.conf;
include conf.d/header.conf;

server_name virtual-host2.com;

location = / {
proxy_pass http://tc2_servlets;
}

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}


upstream tc3_servlets {
server virtual-host3.com:8180;
server virtual-host3.com:8280;
server virtual-host3.com:8380;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include conf.d/self-signed.conf;
include conf.d/ssl-params.conf;
include conf.d/header.conf;

server_name virtual-host3.com;

location = / {
proxy_pass http://tc3_servlets;
}

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}