An alternate deployment model based on a Git repository in Resin 4.0.2
We’ve been quiet on the blog for a while, but pretty busy behind the scenes for the last couple of weeks. There’s lots going on, but what I’m working on specifically is a new deployment model for Resin 4. In addition to our current file system based hot-deploy, we’re now adding a deploy model based on Git. We introduced this in Resin 4.0.0 and 4.0.1, but we started exploring the benefits of Git repositories and realized that we have the ability to do some really powerful things like sophisticated application versioning and rolling out applications in stages. I’ll show you the new model and give a preview of how you can use it in upcoming versions of Resin.
Background and motivation
Until now, Resin essentially used the file system to deploy applications. In other words, there’s a deploy directory and any .war file or exploded .war in that directory is treated as a live webapp. This works great for many cases and is popular among numerous lightweight servlet engines and application servers. You can get up and running quickly and use the file system to inspect the state of the system.
The problem comes when you start clustering and updating applications throughout that cluster. If you’re deploying a new version of an application to a live site in a cluster using the file system, you either have to announce downtime or do some pretty clever tricks to make the transition work smoothly. Relying on networked file systems also has drawbacks.
A new model based on a Git repository
As you may know, Resin 4.0.0 introduced a server-managed Git repository for certain internal metadata and application files. If you’re not familiar with Git, it’s a bit like Subversion in terms of its goal, but implemented as a content-addressed data store. In other words, it’s kind of like using a revision control system where you can tag your data in different ways, but it’s really clever about avoiding redundancy and synchronizing data across the network. With Git, we can upload applications into the repository, then use tags to determine what should be deployed to the live site. That’s a bit of a mouthful though, so let’s look at some examples and see how this actually works.
Basics: Uploading an application
When you upload an application to the repository, you’re actually taking its files and storing their data in a tree, just like the structure of the application (e.g. static resources in the root, WEB-INF, META-INF, etc.). The head of this tree is “tagged” in the repository using a tag of the form
For a webapp deployed to “/foo”, this might look like “wars/default/default/foo”. (More on the stage and virtual host later…). Resin recognizes tags like these as representing webapps and expands them at runtime to the file system “webapps” directory. So far, this just sounds like a more complicated way of getting the same webapp on to the file system, but let’s go back to the clustered case.
When you upload your webapp to one Resin instance’s repository, it’s propagated to all the repositories in the cluster. If you recall from an earlier post, you also have your webapps distributed to new dynamic nodes in the cluster as they arrive.
Remember too that we’re using Git, 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.
Versioning and staging using tags
I think all of these features that I’ve mentioned so far are interesting, but we’ve been talking about these since 4.0.0. What’s really exciting to us is what’s coming up in 4.0.2. Take another look at the tag structure I laid out above. If you see, there’s an optional version portion of the tag. When you upload a webapp you can tag it with a version (e.g. wars/default/default/foo-1.0) as a convention. Now you can copy this tag to a “head” version tag (in this case, wars/default/default/foo) and Resin will server all requests to “/foo” from the foo-1.0 webapp. Now if we upgrade to foo-2.0, all we have to do is upload the new war, create a foo-2.0 tag, and reset the head foo tag to point at the new version. Does this sound like a source control management scheme? It is!
What’s really interesting about this approach is that we can do it all live. When foo-2.0 gets installed as the new version, there may still be foo-1.0 users with sessions. Resin allows them to continue with their existing sessions until they’re invalidated or timed out. All new users/sessions go to foo-2.0, so you have a graceful version rollover. What if you’re not happy with foo-2.0 and want to rollback? Just copy foo-1.0 back to the head foo tag. If you’re familiar with our webapp versioning in Resin 3.1, you’ll be happy to see this addition of rollback.
Now what about that “stage” portion of the tag? Many administrators and developers like to roll out a new webapp on a test machine before full deployment to check for any final issues. Ideally, you’d like to run on the same setup as the production machines to reveal problems. With 4.0.2, we’ve introduced a new concept that each Resin instance has a stage. Only applications which are deployed to the same stage as the Resin instance can be accessed on that instance. For example, all the applications we’ve seen have been in the default stage as indicated by the tags above, so these applications can be accessed from any Resin instance with the default stage.
Suppose we deploy an application to the “preview” stage by tagging it like this:
When we try to access “/bar” from a default stage server, we’ll get a 404. But we can start a server in our cluster dynamically with the “preview” stage. The server is just like any other in our cluster, so we can be sure that it will have the same runtime behavior, but we can also hide it from public view and access the bar webapp. This behavior lets you test your webapps before going live. Now notice that only the tag changed in this example. We still uploaded the webapp to a repository in the cluster and that means it’s still distributed throughout all the instances. As a consequence, when we’re ready to deploy the bar application live, we just copy its tag from the preview stage to the default stage and it instantly appears on all the live machines!
You can interplay staging and versioning as well by staging new versions before deployment.
I’ll mention the virtual host here quickly as well. Resin handles virtual hosts when it’s used as an HTTP server, so this portion of the tag will show which host the application belongs to in the future, but it’s not implemented at the moment. I’ll update once that feature goes in.
Interacting with the repository
I’ve gone over how deploying to the repository and manipulating tags works in the abstract, but now let’s see how it works in practice. The first thing we’ll need to do is show how to set up Resin with a user name and password on the repository, then how to start in various stages. After that, we’ll look at the tools we can use to upload and manipulate applications. The two main ways that we offer at the moment are Ant and Maven 2.0 plugins.
Resin setup
Setting up Resin to use the repository is pretty straightforward. You just need to add an administrator password and make sure the “RemoteAdminService” and “DeployService” are enabled. Your configuration will look something like this:
<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/>
...
We have a much more secure way of storing passwords using a password hash, but for these examples let’s keep it simple and show the password in clear text.
In order to start Resin, you’ll use the usual command line:
java -jar $RESIN_HOME/lib/resin.jar -server a start
This command now starts Resin in the “default” stage. To change the stage, just pass the -stage parameter at start up:
java -jar $RESIN_HOME/lib/resin.jar -server b -stage preview start
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.
That’s pretty much it for setting up the server. Now let’s look at our two tools for managing applications.
Ant
Let’s look at uploading a sample war file:
<?xml version="1.0"?>
<project name="test" default="test" basedir=".">
<property name="resin.home" value="/usr/share/resin"/>
<target name="test">
<taskdef name="resin-upload-war" classname="com.caucho.ant.ResinUploadWar">
<classpath>
<fileset dir="${resin.home}">
<include name="lib/*.jar"/>
<include name="plugins/resin-ant.jar"/>
</fileset>
</classpath>
</taskdef>
<resin-upload-war server="localhost"
port="8080"
user="admin"
password="myadminpass"
warFile="foo.war"/>
</target>
</project>
Hopefully it’s pretty straightforward: we define the resin-upload-war task, then invoke it by providing the server ip and port, a user name, a password, and a war file to upload. In a real project, you’d probably construct the .war file first, then use this as a final deploy step. When you execute this, you’ll see something like:
[resin-upload-war] Deployed foo.war to tag wars/default/default/foo
The tag is exactly as we mentioned above - this is a webapp deployed to the default stage, the default virtual host, and context root “foo”. All the files in the .war file have been uploaded and distributed around the cluster and the webapp is live on all servers in the default stage.
If we want to deploy foo as a versioned webapp, we can rewrite the task to:
<resin-upload-war server="localhost"
port="8080"
user="admin"
password="myadminpass"
warFile="foo.war"
version="1.0"/>
This should output something like:
[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
To upgrade to version 2.0, we just change the version attribute in the task.
If we want to deploy foo to the “preview” stage instead, we just add the “stage” attribute:
<resin-upload-war server="localhost"
port="8080"
user="admin"
password="myadminpass"
warFile="foo.war"
stage="preview"/>
Now to move this application from the “preview” stage to the “default” stage for live deployment, we’ll just use the resin-copy-tag task:
<?xml version="1.0"?>
<project name="test" default="test" basedir=".">
<property name="resin.home" value="/usr/share/resin"/>
<target name="test">
<taskdef name="resin-copy-tag" classname="com.caucho.ant.ResinCopyTag">
...
<resin-copy-tag server="localhost"
port="8080"
user="admin"
password="myadminpass"
sourceStage="preview"
sourceContextRoot="foo"
stage="default"
contextRoot="foo"/>
</target>
</project>
[resin-copy-tag] Copying wars/preview/default/foo to wars/default/default/foo
To see what versions of our webapp are available, we just use the resin-query-tags task:
<?xml version="1.0"?>
<project name="test" default="test" basedir=".">
<property name="resin.home" value="/usr/share/resin"/>
<target name="test">
<taskdef name="resin-query-tags" classname="com.caucho.ant.ResinQueryTags">
...
<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
Of course, this is a little weird to hardcode things like the version, stage, and especially query terms into a build.xml file, so you probably want either to automate the generation of these values (in the case of versions or stages) or use system properties defined on the command line. There’s not really a good way (as far as I know) to provide these in the plugin automatically, but I think this way of working is pretty consistent with Ant. That is, you have lots of control, but you need to do a little work to make it really automated.
Check out more examples and a reference chart for all the tasks on the Resin Ant task wiki page.
Maven
The Maven Mojos are probably a bit easier and cleaner to use than the Ant tasks because Maven already thinks in terms of .war files, but I’ll just give the equivalent Maven commands for the above tasks and let you decide. We’ll start by defining a war project:
<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 Webapp</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>
Here we define the Caucho maven repository and configure our plugin in the build section of the project. The plugin configuration sets the server, port, user, and password for all the goals. What’s cool about this is that we’re pretty much done with the XML and can invoke all of the goals from the command line.
To upload the war file we just do
mvn resin:upload-war
This will actually build the war too, if it’s not built. To use a version with the war, just specify the resin.version system property:
mvn resin:upload-war -Dresin.version="1.0"
And to upgrade:
mvn resin:upload-war -Dresin.version="2.0"
To use the preview stage:
mvn resin:upload-war -Dresin.stage="preview"
To redeploy from the preview stage to the default stage:
mvn resin:copy-tag -Dresin.sourceContextRoot='foo' -Dresin.sourceStage="preview" -Dresin.stage="default"
Finally to query:
mvn resin:query-tags -Dresin.version='.*
As with Ant, we have a big reference table and more examples on the Resin Maven wiki page.
Conclusion
What we’ve done with Resin in 4.0.2 is essentially make application deployment into another view of a Git repository. With Git’s distributed nature and Resin’s interpretations of the tags in the repository, you get a really powerful and useful approach to management and deployment that probably matches much of the way you think about your applications when you’re developing them.
