When talking about Continuos Delivery and Continuous Deployment, zero downtime deployments are most surely a must. Deploying the application on irregular intervals will keep breaking your running tests or your interaction with the system unless you handle zero downtime deployments and you handle them well.
The basic flow when deploying an application in Tomcat is the following:
- Stop the running application (all the sessions are terminated, users are cut-off from your service)
- Un-deploy the old version
- Deploy the new version
- Start the new version (users can now connect to your application, but their previous session was destroyed)
Unless you consider A-B Deployments (Blue-Green), and have a second cluster running the new version of the application gradually receive new connections until the old version doesn’t have any active connection and can be safely disposed, there aren’t so many options to be consider in order to achieve a clean zero downtime deployment.
Tomcat is a big player in the world of Web Servers, and most of the time it get’s the job done quite well. I thought many times that something must be wrong with their deployment process since there’s now way to deploy something without downtime, but I’m using Tomcat only with my personal projects and i haven’t given to much importance to this.
Tomcat 7 has a pretty nice feature called Parallel Deployment which is not so known, probably because of it’s poor (documentation, community support and marketing). Emphasize the common factor poor.
Tomcat must have autoDeploy or deployOnStartup enabled in the server.xml file, so your Host tag should look something like this
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
Enabling autoDeploy will be translated to a small performance downgrade since Tomcat will periodically scan your appBase folder to check for new wars, but in case you’re taking advantage of this feature, you’ll be happy to know that it’s working well with the Parallel Deployment method.
When deploying an application, for example test.war, Tomcat will automatically create the test folder (if unpackWARs is set to true) and you’re application will be available with the test context: http://localhost:8080/test.
There’s also the special case of a ROOT folder or ROOT.war, which gives us our root context.
When using Parallel Deployment, there’s a tighter relationship to the deployed file and the context. The addition to naming the deployment file is the ##version of the context.
This translates into deploying a WAR file using the name of our file with the additional version, such as foo##0.war or in a folder named foo#0. The ##version is compared as a string, so it can be anything that makes sense to you (foo##dev01.war), as long as the names are ascending string comparisons.
Something to watch out for even on case-insensitive systems is name and version collision. Tomcat takes care of collisions by replacing the first deployment with later variations of the same context by changing the case to whatever was there first. That is, if you deploy a version of an application, say foo##A.war, to your server, and then later try to update it with a different version but the same, say foo##a.war, not only is the comparison done with a case-insensitive comparison, but the new deployment will replace the old deployment. Additionally you can also specify Foo##A.war and it will replace the old deployment.
Deploying using versions
Now that we’ve got a handle on getting the applications deployed, it’s important to understand how Tomcat distributes the requests to the applications. With the old way of deploying things, there was always at most one version of an application running in a specific context on a server. With Parallel Deployment, however, can have many versions running at the same time.
Since the objective is zero-downtime deployments of updated applications, we’ll make the assumption that the application is in use. Of course, if an application is not in use, the old manner of remove and reinstall deployment still work, although we can still use Parallel Deployment. A key thing to remember about how Tomcat handles this is that it is tied to the user’s session.
If the user doesn’t have a session, it’s either because they are a new user or because they haven’t done anything in the application to involve a session, and they will always be routed to the newest version of an application
In more robust applications, something always seems to be tucked in a session. When a session is established, Parallel Deployment will keep current users going to the same version of the application for as long as that version is running. If an updated version is deployed, new users will use the new application while old users will continue using the old version until their session expires.
While operating in the same application server, and more importantly in the same JVM, static resources(JNDI, database connection pools that may be managed by Tomcat) are not instances of the same class. Each version of the application being deployed is treated as a separate web application, so each has its own classloader. Also, any access to any kind of file system resource or even network services need to be considered as well. They need to be able to allow access from multiple applications.
A good guideline would be if you can deploy the application twice with different contexts, you should be safe to deploy then as separate versions of the same context.
You need sessions to be enabled
Tomcat uses its own session management to determine what requests should be handled by what version of the web application. If you’ve gone off to implement session handling yourself, or if you switch session handling off in Tomcat, parallel deployment won’t work for you.
Where does logging go?
You probably specified that your logging be written to a log file somewhere. If you do not use the full context name of the application in the log file name, you may end up in a situation where both versions of a web application are writing into the same log file. The problem with that is that you may not know what version of your application generated the output you find in that file.
Disk files and directories need to be sharable
The intention of the designers of Java EE have always intended for web applications to be independent of the underlying machine and file systems. If your application makes use of data files, please take a moment to consider what happens when two versions of your web application start reading and writing them.
In particular, consider that Java’s monitors and locks are confined to a single context. Thus, if you guard access to a file with a lock of some sort, having two versions of that web application means that you have two locks in the JVM, potentially allowing two thread to access it.
No TCP socket listeners
Some applications serve more than just HTTP requests. They have their own TCP socket handlers to serve clients. By deploying more than one version of a web application, you get more than one listener. Obviously this will not work. Only one listener can listen on any given port.
One benefit of Parallel Deployment is that the old application stays running until we stop it. Further, it’s still deployed, until we un-deploy it. This allows us to stop new applications and have the old versions pick up new sessions.
A better approach, if a rollback is required, is to redeploy the old app as a new version. There might need to be some management to track these repeated versions, but it allows the users to keep working without losing their sessions.
Of course, if the new application is flawed, it might be the case that it shouldn’t keep running, so the forced stop will be better way to handle the situation.
Another important consideration is that the application should be able to be shutdown cleanly. This is a good thing to have in an application anyway, but there have been more than a few applications that can only be stopped by killing the server. It isn’t critical that an application be able to be shutdown cleanly, but it is important for the purpose of cleaning up the server without having to force it to stop. If we have to stop the server to remove old versions, we lose our zero-downtime capabilities.
As the new versions are deployed, new sessions will go to them. As old versions of the application are no longer used, they can be stopped and undeployed. This can be done through the Tomcat manager or by removing the appropriate WAR file from the server; Tomcat will remove the related directory when the application is shutdown. It’s a little rougher on the server to remove applications deployed in exploded folders, but that works by deleting the folder; note that some operating systems may hold file locks inside the folder that inhibits its deletion.
Since it’s a Tomcat-specific feature, there’s no straight-up Java way to get the version of the application that is handling your request.
There are a few reasons one might want to know the running version, like logging and reporting errors. The best solution I can make is to take apart the Servlet’s deployed path and parse out the string between the ## and the / or end of the string. This is easily done in a pair of substrings with Apache Commons StringUtils.
runningVersion = StringUtils.substringBefore(StringUtils.substringAfter(servletConfig.getServletContext().getRealPath("/"),"##"),"/");
Of course, this only works with exploded JARs, but since that’s the default mechanism Tomcat uses, we should be OK. It also only works if there is version information in the path; this example is ineffective for deployments that don’t contain version information, and will instead contain an empty string.
For a lot of applications, Parallel Deployment will work fantastically. It will allow additional features to be put into place, bugs to be fixed, and even changes in style, all without interrupting active visitors. They will continue to use the version they’re on, but won’t lose their session and other unsaved activities. Sure, they won’t benefit from the enhancements or fixes until they end their visit, but interrupting the user might be drastic and result in lost work.
A common upgrade has to do with underlying resources. It may be that a reason for an updated application has to do with changes to a database or a file system resource or an external system. If the impact is drastic enough it may be the case that Parallel Deployment will not meet the needs of these kinds of upgrades, as the old application may start to fail as the underlying resources are stopped and upgraded or otherwise changed. For these changes, of course, there’s always stop and re-deploy.