I'm seeing many posts achieving Tomcat session replication in docker swarm using traefik. But I just don't want to add another component. I use httpd as frontend for tomcat. Tomcat will be deployed as a service with 4 replicas and will be scaled when required. httpd is running as docker service and both are in same network. My question is, is there any way to achieve Tomcat session replication in docker swarm in this case. TIA.
As long as all Tomcat Docker containers are reachable you can achieve this by below Cluster setup.
The below setup is for tomcat 7 but should work in the same way for other versions also.
In case of Apache Tomcat 7 uncomment <Cluster className=”org.apache.catalina.ha.tcp.SimpleTcpCluster”/> tag and update cluster detail something like below.
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564" frequency="500"
dropTime="3000"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto" port="4000" autoBind="100"
selectorTimeout="5000" maxThreads="6"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" />
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener" />
</Cluster>
Also, we need to add workers.properties at apache/conf/ path.
Please note this file has to be placed in all the Tomcat servers which will run in a cluster.
You can read more in detail here.
You can do this without a front end, BUT you're going to have issues because if the ingress routes to a different node, that node has to request the session from another node.
The plain tomcat image is not sufficient to do clustering, it wouild need some customizations to /usr/local/tomcat/conf/server.xml to the cluster configuration to use the DNSMembershipProvider
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership
className="org.apache.catalina.tribes.membership.cloud.CloudMembershipService"
membershipProviderClassName="org.apache.catalina.tribes.membership.cloud.DNSMembershipProvider"
/>
</Channel>
</Cluster>
DNSMembershipProvider uses the environment variable DNS_MEMBERSHIP_SERVICE_NAME to look up the server so it should match the service name.
The following docker-compose.yml shows how to deploy it without any other component.
version: "3.8"
services:
tomcat:
build: ./tomcat
image: sample-tomcat
environment:
- DNS_MEMBERSHIP_SERVICE_NAME=tomcat
ports:
- 50000:80
deploy:
replicas: 6
update_config:
order: start-first
Proper way
As I stated earlier, using the above approach will satisfy the OP's requirements of not adding an additional component, but will have significant performance issues. Instead you need a proxy that can route to the servers with the following features:
session stickiness
Docker Swarm service label discovery
health check to reduce potential user impact by detecting a server is down beforehand.
Traefik handles all the requirements quite nicely even if itself does not support clustering due to their ACME storage not allowing concurrent access without going to their EE version.
The docker-compose.yml would be as follows.
version: "3.8"
services:
proxy:
image: traefik:2.6
command:
- "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.app.address=:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 50000:80
tomcat:
build: ./tomcat
image: sample-tomcat
environment:
- DNS_MEMBERSHIP_SERVICE_NAME=tomcat
deploy:
replicas: 6
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.entrypoints=app"
- "traefik.http.routers.app.rule=Host(`localhost`)"
- "traefik.http.services.app-service.loadBalancer.sticky.cookie=true"
- "traefik.http.services.app-service.loadBalancer.sticky.cookie.httponly=true"
- "traefik.http.services.app-service.loadbalancer.server.port=8080"
- "traefik.http.services.app-service.loadbalancer.healthcheck.path=/"
update_config:
order: start-first
endpoint_mode: dnsrr
One property of note is endpoint_mode: dnsrr is not available when exposing ports earlier. This enables a better replication of session data within the Tomcat cluster as each node will have individual IDs.
An example implementation of this is in my github repo https://github.com/trajano/tomcat-docker-swarm
Related
I'm out of ideas on this and appreciate any suggestions. I have a handful of dockerized springboot microservices which include a config server. Here are the characteristics:
Springboot version 2.3.0-RELEASE
Standard Springboot config server with basic auth turned on.
3 Springboot microservices that are also config clients to config server.
-- I use a simple Dockerfile model for microservices and springboot maven plugin with default docker layers capabilities.
SSL is enabled for all including the config server.
-- For dev and testing, I use a self signed cert.
All microservices use a JKS to sign JWTs
Docker image for java is openjdk8 alpine.
Docker compose is used to orchestrate container launch and settings.
The docker container for config server runs perfectly fine. I can even query for config via a browser following the HTTPS URL: https://app-dev.localhost.com:8443/config-server/shopping-svc/dev.
The Problem
I cannot manage to successfully start container 'shopping-svc'. It fails with this error.
2023-01-25T23:44:12.375221300Z
2023-01-25 23:44:12.575 INFO 1 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : https://app-dev.localhost.com:8443/config-server
2023-01-25 23:44:12.829 INFO 1 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - https://app-dev.localhost.com:8443/config-server. Will be trying the next url if available
2023-01-25 23:44:12.836 ERROR 1 --- [ main] o.s.boot.SpringApplication : Application run failed
2023-01-25T23:44:12.837487100Z
java.lang.IllegalStateException: Could not locate PropertySource and the fail fast property is set, failing
at org.springframework.cloud.config.client.ConfigServicePropertySourceLocator.locate(ConfigServicePropertySourceLocator.java:155) ~[spring-cloud-config-client-2.2.8.RELEASE.jar:2.2.8.RELEASE]
at org.springframework.cloud.bootstrap.config.PropertySourceLocator.locateCollection(PropertySourceLocator.java:52) ~[spring-cloud-context-2.2.9.RELEASE.jar:2.2.9.RELEASE]
at org.springframework.cloud.config.client.ConfigServicePropertySourceLocator.locateCollection(ConfigServicePropertySourceLocator.java:170) ~[spring-cloud-config-client-2.2.8.RELEASE.jar:2.2.8.RELEASE]
at org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration.initialize(PropertySourceBootstrapConfiguration.java:98) ~[spring-cloud-context-2.2.9.RELEASE.jar:2.2.9.RELEASE]
at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:626) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:370) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
at com.shopping.app.ShoppingApplication.main(ShoppingApplication.java:35) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_212]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_212]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_212]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_212]
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) [application/:na]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:109) [application/:na]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) [application/:na]
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88) [application/:na]
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://app-dev.localhost.com:8443/config-server/shopping-app/dev": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:748) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:583) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
Investigations
At first, I thought perhaps the port 8443 is blocked somehow by my OS firewall but that's not it. Clearing the port makes no difference
Then I thought, perhaps it's a cert issue so I tried supplying the cert differently with the JAVA_TOOL_OPTIONS argument with the override populated: -Djavax.net.ssl.trustStore=/path/to/cert, etc... No dice.
I read several posts and articles suggesting services inside docker containers should refer to others via service name. While this poses a bit of confusion for me since my certs are generated against a hostname, I tried swapping the URL of config server in shopping-app YML to something like: https://config-server:8443/config-server/ or the same without https to see if at least successful connection would be made.
Last thing I tried was to change the compose network driver to 'host' instead of 'bridge' so the containers would use the host machine network config. The rationale was that at least, it's obvious it's all on same network.
I am not sure what or where to look anymore
References
=====
Docker compose file:
version: "3"
networks:
default:
driver: bridge
frontend:
driver: bridge
backend:
driver: bridge
services:
config-server:
image: config-server
env_file: .env
hostname: app-dev.localhost.com # Not sure this is necessary I add this because the self signed cert was generated with this domain name
volumes: #I'm developping on windows, hence the backslash "\"
- shoppingapp:/var/opt
- shoppingapp\certs\server.jks:/etc/certs/server.jks
- shoppingapp\certs\ssl/app-dev.localhost.com.p12:/etc/certs/ssl/app-dev.localhost.com.p12
ports:
- "8443:8443"
networks:
- backend
shopping-svc:
image: shopping-svc
env_file: .env
hostname: app-dev.localhost.com # Not sure this is necessary I add this because the self signed cert was generated with this domain name
volumes:
- shoppingapp:/var/opt
- shoppingapp\certs\server.jks:/etc/certs/server.jks
- shoppingapp\certs\ssl\app-dev.localhost.com.p12:/etc/certs/ssl/app-dev.localhost.com.p12
ports:
- "8444:8444"
depends_on:
config-server:
condition: service_started
networks:
- backend
I making some tests for logging into an Elasticsearch instance using NLog in our API. The Elasticsearch instance is running inside a Docker, if the API is executed using IIS Express I can log into Elasticsearch without a problem and I can look at the "logstash" index created, but if I run the API inside a Docker container the logs never reach Elasticsearch and the index is never created.
My NLog config:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="info"
internalLogFile="c:\temp\internal-nlog-AspNetCore3.txt">
<extensions>
<add assembly="NLog.Targets.ElasticSearch"/>
</extensions>
<targets>
<target name="ElasticSearch" xsi:type="BufferingWrapper" flushTimeout="5000">
<target xsi:type="ElasticSearch"/>
</target>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="ElasticSearch" />
<logger name="Microsoft.*" maxlevel="Info" final="true" />
</rules>
</nlog>
And in my appsettings.json:
"ElasticsearchUrl": "http://192.168.0.9:9200",
Perhaps I'm missing something or I'm not understanding the interaction between the containers.
(1) Your question doesn't provide any details about the configuration of the two containers (one running your app, one running Elasticsearch).
I have an example logging to Elasticsearch, configured with Kibana to view the results, although it uses a different logger provider (Essential.LoggerProvider.Elasticsearch), however it has a docker-compose file that shows the connection between Elasticsearch and Kibana, https://github.com/sgryphon/essential-logging/tree/master/examples/HelloElasticsearch
# Docker Compose file for E-K stack
# Run with:
# docker-compose up -d
version: '3.7'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.6.1
...
networks:
- elastic-network
kibana:
image: docker.elastic.co/kibana/kibana-oss:7.6.1
...
environment:
ELASTICSEARCH_URL: http://elasticsearch:9200
networks:
- elastic-network
networks:
elastic-network:
driver: bridge
The relevant parts show setting up a network bridge between the two docker machines, and then the connection between them.
While "http://192.168.0.9:9200" might be the correct connection from outside (your IIS) into Elasticsearch, you would have to check if it is how your API docker sees the Elasticsearch machine, e.g. how Kibana sees Elasticsearch in the example above is "http://elasticsearch:9200"
You would need to update the question with details of your docker configuration, e.g. the command line you run to start them, or a docker compose file, etc. to work out why they can't see each other.
(2) You might want to check that it really is working from IIS, as it seems unusual that NLog would create an index "logstash-" ... normally Logstash would create that index and NLog should create it's own, e.g. log4net creates index "log-", Essential.LoggerProvider.Elasticsearch uses "dotnet-", etc.
Disclaimer: I am the author of Essential.LoggerProvider.Elasticsearch
I have few containers, brought up with docker-compose and I want to perform authentication on of the containers.
Below the piece that I assume should do that, but it doesn't go to the authentication-backend-nginx-private, directly lands on the mds-backend-nginx-private. I'm out of idea, what could be wrong about the config...
it works if authforward configured globally: in toml file under entrypoint section, but I want it to be per particular container..
mds-backend-nginx-private:
<<: *nginx-common
ports:
- 8186:80
networks:
- cloud_private
- mds-backend
restart: on-failure
environment:
- NGINX_SERVER_NAME=mds-backend-nginx-private
- WEBSITE_PROXY_NAME=mds-backend-web-private
- WEBSITE_PROXY_PORT=8000
labels:
- "traefik.http.middlewares.authf.ForwardAuth.Address=http://authentication-backend-nginx-private/api/v1/gateway/account?with_credentials=true"
- "traefik.docker.network=cloud_private"
- "traefik.http.routers.mds-backend.middlewares=authf"
- "traefik.frontend.rule=PathPrefix: /api/v1/mds/"```
Maybe, you are trying to use "middleware feature" with old traefik version.
Works in the toml file because you are using the "forward feature" present in old versions.
Check traefik tag image is equal or greater than 2.0
https://hub.docker.com/_/traefik
I have been trying to get the url for my tomcat server(running in a docker container) used in my Junit test and not just localhost. The reason this is important is my test runs fine locally, but when run on our jenkins node which is also run in Docker, localhost does not work.
I have configued the node to use Docker on Docker configs. I need the url to use the ip of the parent docker machine. The odd thing is the jmx url seems to work just fine to deploy the test war, however it is the unit test url itself that has an issue. I rewrote the test with the ip hard coded and this worked fine, but is really not an optimal solution in the event devs here want to run the test locally.
I also tried using #CubeIp and #DockerUrl or #HostIp, but they either returned just localhost or null, as it says it cannot find the container "tomcat"
Any ideas?
Here is my arquillian.xml
<extension qualifier="cube">
<property name="connectionMode">STARTORCONNECTANDLEAVE</property>
</extension>
<extension qualifier="docker">
<property name="serverVersion">1.14</property>
<property name="serverUri">unix:///var/run/docker.sock</property>
<!--<property name="serverUri">localhost:2375</property>-->
<property name="dockerInsideDockerResolution">false</property>
<property name="definitionFormat">CUBE</property>
<property name="dockerContainersFile">docker-compose.yml</property>
<property name="dockerRegistry">https://internalnexus.com:5000/</property>
<property name="username">user</property>
<property name="password">pass</property>
<property name="email">email</property>
</extension>
<container qualifier="tomcat" default="true">
<configuration>
<property name="host">10.0.20.1</property>
<property name="httpPort">8080</property>
<property name="user">user</property>
<property name="pass">pass</property>
</configuration>
</container>
And here is my docker compose file
`
tomcat:
image: internalnexus.com:5000/perf-tomcat:latest
exposedPorts: [8080/tcp,8089/tcp]
alwaysPull: false
await:
strategy: polling
env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.rmi.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dspring.config.location=/usr/local/tomcat/conf/application.properties]
portBindings: [8089/tcp, 8080/tcp]
links:
- database:database
database:
image: internalnexus.com:5000/perfstats-sqlserver:latest
exposedPorts: [1433/tcp]
env: [SA_PASSWORD=pass, ACCEPT_EULA=Y]
await:
strategy: log
match: 'ms sql server is done'
stdOut: true
stdErr: true
timeout: 30
portBindings: [1433/tcp]
`
I have one question and it is why you need parent docker host ip?
What I see is that parent docker host ip is the docker host ip where Jenkins node is using. This IP is the one required by external callers to access to Jenkins. Then inside that docker instance you are running another docker host or you are reusing the docker host where Jenkins is running?
Hi I am new to clustering concept, so I tried to establish a Tomcat cluster with 2 instances. I followed the examples, mainly at http://www.mulesoft.com/tcat/tomcat-clustering and few other web sources.
My sample set up is as,
in workers.properties
# Define worker names
worker.list=jkstatus, loadbalancer
# Create virtual workers
worker.jkstatus.type=status
worker.loadbalancer.type=lb
# Declare Tomcat server worker 1
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=7009
# Declare Tomcat server worker 2
worker.worker2.type=ajp13
worker.worker2.port=9009
worker.worker2.host=localhost
# Associate real workers with virtual LoadBalancer worker
worker.loadbalancer.balance_workers=worker1,worker2
And Apache httpd.conf as,
# ADDED CLUSTER CONFIG
# Load module
LoadModule jk_module modules/mod_jk.so
# Specify path to worker configuration file
JkWorkersFile C:/tomcat_clustered/workers.properties
# Configure logging and memory
JkShmFile C:/tomcat_clustered/log/location/mod_jk.shm
JkLogFile C:/tomcat_clustered/log/location/mod_jk.log
JkLogLevel info
# Configure monitoring
JkMount /jkmanager/* jkstatus
<Location /jkmanager>
Order deny,allow
deny from all
allow from localhost
</Location>
# Configure applications
JkMount /* loadbalancer
# END CLUSTER CONFIG
With each tomcat(worker) server.xml as,
<Engine name="Catalina" defaultHost="localhost" jvmRoute="worker1">
<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->
<!-- CLUSTER BEGIN -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<!---->
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="239.0.0.1"
port="45564" frequency="500"
dropTime="3000"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto" port="4000" autoBind="100"
selectorTimeout="5000" maxThreads="6"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<!-- CLUSTER END -->
<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
For worker1, worker2. I am working on Windows 7. It looks like some multicast option is needed but I am not sure. On starting up of tomcat, one is starting well, and on starting second(say worker2) both tomcat console's are throwing errors as,
at org.apache.catalina.tribes.group.ChannelInterceptorBase.heartbeat(Cha
nelInterceptorBase.java:103)
at org.apache.catalina.tribes.group.GroupChannel.heartbeat(GroupChannel.
ava:155)
at org.apache.catalina.tribes.group.GroupChannel$HeartbeatThread.run(Gro
pChannel.java:690)
ep 15, 2014 10:11:03 AM org.apache.catalina.tribes.group.interceptors.TcpFailur
Detector memberAlive
EVERE: Unable to perform failure detection check, assuming member down.
ava.net.SocketException: Permission denied: connect
at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketI
pl.java:85)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.ja
a:339)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocket
mpl.java:200)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java
Not sure if its due to some multicast permissions on my Windows or any config related issues. Please suggest some good pointers in this regard
You need to enable multicast. see this post how to enable multicast is windows, https://serverfault.com/questions/262634/how-do-i-know-if-ip-multicasting-is-enabled-on-my-network-in-windows
In Linux Environment most of the system kernel is capable to process the multicast address.
but we need to add route entry in kernel routing table.
sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0
The error that you're reporting looks like it might be OS / networking related. Hard to say exactly. I can tell you this though.
You've declared a TcpFailureDetector.
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
This is part of Tomcat's cluster configuration and it's responsibility is to make a TCP connection to any node that is suspected of failure. If the listener can't connect to the node, it'll be marked as down. If it can connect, then the node remains active.
In this case, the listener is attempting to make a connection to one of your nodes and it's failing with an error from the JDK. Googling that specific error turns up some suggestions, one of which is to use "-Djava.net.preferIPv4Stack=true".
I get java.net.SocketException: Permission denied: connect when sending an email in Jenkins
On a separate note, if you've note seen the official documentation on Tomcat clustering, I would strongly recommend you check that out. Here's the link.
http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html