At Octopus we have started a project to investigate how to better support Java developers. In a previous blog post I talked about how to deploy a WAR file to Tomcat using Octopus Deploy. Tomcat is easily the most popular Java application server in production today (with stats from Plumbr and Rebel Labs putting Tomcat at over 50% market share). The next most popular application server is JBoss/Wildfly, and in this post I'll show you how to deploy a WAR file to WildFly 11 (currently in Alpha).
Like Tomcat, deploying a WAR file to WildFly can be as simple as copying it to a special directory. In the case of WildFly, copying a WAR file in to the
standalone/deployments directory will trigger WildFly to deploy and run the application. This is a perfectly valid deployment method, and if this works for you then the blog post on deploying to Tomcat applies equally as well to WildFly.
However there are cases where simple file copies won't work in WildFly. In particular, deployments to WildFly domain controllers are often done via the JBoss CLI tool.
Challenges With CLI Deployments
When deploying a WAR file from Octopus Deploy, ideally we want the steps to be idempotent. Idempotent is just a fancy way of saying that the deployment can be run any number of times, and the end result is the same.
This is not how the deployment operations in the JBoss CLI tool work out of the box. You can not execute a CLI command like
deploy myApplication.war --all-server-groups
multiple times, because once the file
myApplication.war is in the WildFly content repository, it can not be overwritten without an option like
--force. Which naturally leads you to try and run a command like
deploy myApplication.war --all-server-groups --force
which doesn't work; you can not force the upload of the file and deploy it to a server group in one command.
What this means is that if you want to be able to deploy and redeploy an application into a domain environment, you need to do some extra work to ensure that the commands being run will work regardless of whether the application exists in the WildFly content repository or not, if it is assigned to a server group or not, and if it is enabled or disabled.
A Groovy Solution
When we talk about deploying to a WildFly domain, we want to do two tasks:
- Create or update the contents of the WAR file in the WildFly content repository.
- Optionally assign the WAR file to a server group in either an enabled or disabled state.
This Groovy script exposes these tasks via a few command line arguments. Behind the scenes a lot of work is done to allow deployments to be rerun as many times as necessary, as well as making use of the Spring retry framework to ensure that the deployment really does make it to the WildFly server.
Leveraging the Power of Octopus Deploy
There are two approaches we can use to get files into a WildFly domain controller.
The first approach is to upload the files to the WildFly server directly via the management interface. In this scenario the WildFly server would expose its management port on a public IP address, and Octopus Deploy server itself would run scripts that uploaded files to the WildFly server.
This solution has some drawbacks.
- These files uploads are not optimized, meaning when you upload a 100 MB WAR file, you will have to send every byte of that data each time you redeploy.
- Your WildFly server needs to have the management interface exposed on a public IP address.
- The burden of running scripts is placed on the Octopus Deploy server
The second approach is to distribute the files to the WildFly server via the Octopus deploy package step, and then upload the files using a local copy of the CLI tool to the WildFly server running on the same instance.
This solution addresses the drawbacks of the first scenario.
- Files distributed via Octopus Deploy can take advantage of delta copying. This means that potentially only the parts of the package that have changed are copied, reducing network traffic and improving deployment times.
- The WildFly server only needs to expose its management interface to the localhost adapter, as all CLI commands will be run from the local server.
- The Octopus Deploy server is only responsible for copying files and scripts, while the WildFly server itself will run the scripts locally.
Given the clear benefits of the second approach, this is how we will proceed.
The WildFly domain controller server needs to have the following packages installed:
- Groovy, used by the script that will deploy the WAR file.
- Mono, used by the Octopus Deploy Calamari.
Use SDKMAN to install Groovy. It will often do a better job than the packages that come with your Linux distribution.
You will need to have a WAR file packaged and pushed to the Octopus Deploy server. See the blog post on deploying to Tomcat to learn how to package up a WAR file and push it to Octopus.
You will also need to add the WildFly domain controller to an Octopus environment. As we did in the Tomcat example, the WildFly server will most likely use a SSH connection.
Finally the WildFly server will need to have the Groovy script and
jboss-cli.xml file copied to a location that is accessible to the SSH user that Octopus uses to connect to the WildFly server. I cloned the https://github.com/OctopusDeploy/JBossDeployment.git repo to
/opt, so all the files are available under
Deploying to a WildFly Domain Controller
Deploying a package to WildFly will be a two step process.
Deploy the package
The first step will make use of the
Deploy a package step to get the WAR file onto the WildFly domain controller filesystem. Click the
Configure features link, and check the
Custom installation directory option.
Then define the directory that will hold the WAR files in the
Install to field. I've used the directory
Make sure the SSH user that Octopus is connecting to the WildFly server with has the permissions to copy files to this directory, otherwise the step will fail.
Run the script
The second step will make use of the
Run a Script step to launch the Groovy script from Bash.
Run on option to
Deployment targets, select
Bash as the script type, and enter the following as the script body:
cd /opt/JBossDeployment ./deploy-app.groovy --controller=localhost --user=user --password=password --application=/var/deployments/demo.war --enabled-server-group=main-server-group
It might take a few minutes for the script to run the first time, as it will download the Maven dependencies (referenced by the
@Grab annotations in the Groovy script) and store them in a local cache.
If you want to see the download progress, run the script like this:
cd /opt/JBossDeployment groovy -Dgroovy.grape.report.downloads=true deploy-app.groovy --user=user --password=password --application=/var/deployments/demo.war --enabled-server-group=main-server-group
Once all the dependencies are downloaded, the deployment script will upload the WAR file to WildFly, add it to the
main-server-group server group, and enable it.
Deploying to a Standalone WildFly Instance
The process of deploying a package for a standalone WildFly instance is much the same as deploying to a domain controller. The only difference are the arguments passed to the Groovy script.
Because standalone servers don't have the concept of server groups, the
enabled-server-group argument is removed. Otherwise all steps stay the same.
cd /opt/JBossDeployment ./deploy-app.groovy --controller=localhost --user=user --password=password --application=/var/deployments/demo.war
We will use experiments like the script here to guide the future support Octopus will have for Java and application servers like WildFly. Over the next few weeks we will be putting together an RFC for the planned Java support, and if Java support is something that interests you then your feedback will be most appreciated.