cluster application deployment
Like other lightweight servlet containers and Java EE application servers, Resin supports file-system based deployment. In order to deploy an application, all you need to do is copy your file to the Resin deployment directory. As you might also know, Resin has supported hot deployment for quite a while, which is a great feature for agile development that often results in frequent incremental deployments. This deployment model is very simple, effective and popular. However, file-system based deployment has a few weaknesses that can arise in environments with very stringent availability and reliability requirements. It is very difficult to do deployment in a clustered environment because the same file must be deployed simultaneously to all servers in the cluster. Often this can result in some down-time that must be announced beforehand. No back-up facility is provided by the file system, so you must often save a backup copy of the old deployment somewhere yourself. File system based deployment also makes it very difficult to use the same server environment for different stages of development such as QA, user acceptance testing and production without following complicated deployment procedures. The remote deployment model introduced in Resin 4.0 goes a long way in solving these particular problems by supporting clustered, versioned and staged deployment. Instead of using the file system, you will need to use either the Resin Ant or Maven plug-ins to do remote deployment. There are a few simple steps to do this, the first of which is to enable remote deployment on the server, which is disabled by default. You do this using the following Resin configuration: <resin xmlns=http://caucho.com/ns/resin xmlns:resin="urn:java:com.caucho.resin"> <cluster id=""> <resin:AdminAuthenticator password-digest="none"> <resin:user name="admin" password="myadminpass"/> </resin:AdminAuthenticator> <resin:RemoteAdminService/> <resin:DeployService/> ... </resin> In the example above, both the remote admin service and the deployment service is enabled. Note, the admin authenticator most be enabled for any remote administration and deployment for obvious security reasons. To keep things simple, we used a clear-text password above, but you should likely use a password hash instead. Once you start Resin, you can use the Ant snippet below to do a remote deployment: <?xml version="1.0"?> <project name="test" default="test" basedir="." xmlns:resin="antlib:com.caucho.ant"> <target name="test"> <resin:upload-war server="localhost" port="8080" user="admin" password="myadminpass" warFile="foo.war"/> </target> </project> After you run the Ant script above, you will see output like this: [resin:upload-war] Deployed foo.war to tag wars/default/default/foo The output exposes a few important things about the underlying remote deployment implementation for Resin. Remote deployment for Resin uses Git under the hood. In case you are not familiar with it, Git is a newish version control system similar to Subversion. A great feature of Git is that it is really clever about avoiding redundancy and synchronizing data across a network, which comes in very handy for Resin. Under the hood, Resin stores deployed files as nodes in Git with tags representing the type of file, development stage, virtual server, web application context root name and version. The format used is this: <type>/<stage>/<virtual host>/<context root>[-<version>] In our example, all web applications are stored under wars, we didn’t specify a stage or virtual host in the Ant task so the default is used, the web application root is foo and no version is used since one was not specified. This format is key to the versioning and staging featured we will discuss shortly. As soon as your web application is uploaded to the Resin deployment repository, it is propagated to all the servers in the cluster - including dynamic nodes that are added to the cluster at a later point in time after initial propagation happens. This means that you can eliminate complicated scripts to deploy your application throughout each cluster member manually. Remember too that we’re using Git under the hood, which is pretty intelligent about the way it stores files. If you upload a new version of your application to one Resin instance, only the files that changed need to be retransmitted across to the other instances in order to bring them up to date. In other words, you only end up using as much network traffic as you have new material, which is a great performance boost. Doing remote deployment with the Resin Maven plug-in is just as simple. You’ll need to setup the plug-in like this: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.test</groupId> <artifactId>test</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>Test Maven Web Application</name> <url>http://maven.apache.org</url> <dependencies> </dependencies> <pluginRepositories> <pluginRepository> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>ignore</checksumPolicy> </snapshots> <id>caucho</id> <name>Caucho</name> <url>http://caucho.com/m2-snapshot</url> </pluginRepository> </pluginRepositories> <build> <finalName>foo</finalName> <plugins> <plugin> <groupId>com.caucho</groupId> <artifactId>resin-maven-plugin</artifactId> <version>4.0-SNAPSHOT</version> <configuration> <server>127.0.0.1</server> <port>8086</port> <user>admin</user> <password>myadminpass</password> </configuration> </plugin> </plugins> </build> </project> You can then remote deploy your application from the command line like so: mvn resin:upload-war In addition to automatically propagating a deployment to all clustered nodes, Resin remote deployment also supports versioning. When you upload an application, you can specify a version number for it. Resin then figures out what the latest version of an application is and shows that to application users as they arrive. All this is done in real time, so any users of the older version of the application can continue using the application until they log off or their session is timed out. Next time these users access the application they get the newer version instead. Versioning comes in very handy if you need to roll-back a deployment quickly. All you need to do is set the older version of the application already stored in the Resin deployment repository as the active one. Let’s see how this works by modifying our previous Ant example: <resin:upload-war server="localhost" port="8080" user="admin" password="myadminpass" warFile="foo.war" version="1.0"/> Notice in this case we’ve explicitly set a version number (in a real life Ant script you would of course likely externalize things like versions through properties instead of hard-coding them). After you run the Ant script, you should see something like this: [resin:upload-war] Deployed foo.war to tag wars/default/default/foo-1.0 [resin:upload-war] Wrote head version tag wars/default/default/foo The Maven equivalent of this command would look like the following where we would specify the resin.version system property: mvn resin:upload-war -Dresin.version="1.0" Now let’s suppose we want to upgrade the application. This is how we could do it: <resin:upload-war server="localhost" port="8080" user="admin" password="myadminpass" warFile="foo.war" version="2.0"/> In this case we upgraded the application version to 2.0, so the head tag will be updated to point to the newly uploaded file, as indicated by the Ant task output: [resin:upload-war] Deployed foo.war to tag wars/default/default/foo-2.0 [resin:upload-war] Wrote head version tag wars/default/default/foo The Maven version of the application upgrade would be like this: mvn resin:upload-war -Dresin.version="2.0" Keep in mind, although the head revision was updated so that users start to see the new version of the application, the older application remains intact in the deployment repository. This is extremely powerful if you need to back out the new deployment in a hurry. This can be done by simply copying back the older file to the head tag like this: <?xml version="1.0"?> <project name="test" default="test" basedir="." xmlns:resin="antlib:com.caucho.ant"> ... <resin:copy-tag server="localhost" port="8080" user="admin" password="myadminpass" sourceVersion="1.0" sourceContextRoot="foo" contextRoot="foo"/> </target> </project> The key here is specifying the source version to be 1.0. resin-copy-tag also has a version attribute to specify where the tag is being copied to, which was omitted because we want to copy to the head revision. The output from running the Ant task reveals the end result: [resin:copy-tag] Copying wars/default/default/foo-1.0 to wars/default/default/foo Here is the Maven version of the command: mvn resin:copy-tag \ -Dresin.sourceContextRoot='foo' \ -Dresin.sourceVersion="1.0" \ -Dresin.contextRoot="foo" If you feel you don’t need extra versions of applications in the repository, you can always delete them. For example, you can delete the faulty 2.0 deployment like this: <?xml version="1.0"?> <project name="test" default="test" basedir="." xmlns:resin="antlib:com.caucho.ant"> ... <resin:delete-tag server="localhost" port="8080" user="admin" password="myadminpass" version="2.0" contextRoot="foo"/> </target> </project> The Maven version is: mvn resin:delete-tag -Dresin.version="2.0" -Dresin.contextRoot="foo" You can also always check out what versions of the application are currently installed in the Resin deployment repository: <?xml version="1.0"?> <project name="test" default="test" basedir="." xmlns:resin="antlib:com.caucho.ant"> ... <resin:query-tags server="localhost" port="8080" user="admin" password="myadminpass" contextRoot="foo"/> </target> </project> [resin:query-tags] wars/default/default/foo-1.0 [resin:query-tags] wars/default/default/foo-2.0 The Maven version looks like this: mvn resin:query-tags -Dresin.version='.* In a sense, stages are application versioning applied at the server level. As you saw, you can apply a development stage to each uploaded application. On the other hand, each server instance in the cluster can have an associated stage. The server only publishes applications that match the stage it is currently in. Applications in all other stages are ignored even if it is propagated and stored in the repository for the server instance. This feature can be very useful if you think about the typical short-comings of having completely separate environments for QA, user acceptance testing and production. Because the environments are physically separated, there are invariably some subtle differences that only become apparent when an actual release happens – usually a terrible time to be doing trouble shooting. One way to avoid this problem is using an actual production cluster server for things like beta testing or user acceptance testing (of course, in some environments this is not a realistic option for logistical reasons, in which case the traditional method of physically separating environments can still be used). Staging can also simply be used as away to do a last minute reality check in the production environment before doing a final roll-out. In this technique, you first deploy an application in something other than the default stage. As you know, this is propagated to all instances in the cluster. This is how you do it using Ant: <resin:upload-war server="localhost" port="8080" user="admin" password="myadminpass" warFile="foo.war" stage="preview"/> Notice we explicitly specified the application stage in the Ant task. The Maven version of this looks like this: mvn resin:upload-war -Dresin.stage="preview" Now, all servers in the default stage will simply ignore this application even though it is in the repository. All servers are normally in the default stage when started as below: java -jar $RESIN_HOME/lib/resin.jar -server a start In order for a server to actually publish the application above, the server instance must be started in the “preview” stage (in general, you would probably want to start a preview stage server as a dynamic server, but to keep it simple we’ll omit those details here). You can do this as below: java -jar $RESIN_HOME/lib/resin.jar -server b -stage preview start You can use this server to do user acceptance testing or simply a last minute check (typically you will use IP blocking to isolate this server from normal production users). Once you are satisfied with the results, you can change the application to the default stage by doing a copy as below: <resin:copy-tag server="localhost" port="8080" user="admin" password="myadminpass" sourceStage="preview" sourceContextRoot="foo" stage="default" contextRoot="foo"/> [resin-copy-tag] Copying wars/preview/default/foo to wars/default/default/foo The Maven version of this looks like the following: mvn resin:copy-tag \ -Dresin.sourceContextRoot='foo' \ -Dresin.sourceStage="preview" \ -Dresin.stage="default" Because the application already resides in all clustered instances, switching things over to production happens almost instantaneously. You can interplay staging and versioning as well by staging new versions before deployment.
|