In this article I'll show how you can run Mule natively on OpenShift wihout using a Servlet container and show you how I got over a few implementation hurdles.
If you are familiar with Mule you know it gives you many deployment options including both standalone deployment or embedding itself within a Java application or Webapp. The recommended approach is to run Mule ESB standalone from the command prompt, as a service or daemon, or from a script. This is the simplest architecture, so it reduces the number of points where errors can occur. It's typically best for performance as well, since it reduces the number of layers and eliminates the inherent performance impact of an application server on the overall solution. With Mule 3.x, you can also now run multiple applications side by side in a Mule instance using the new deployment model can support live deployment and hot redeployment of applications. Lastly, standalone mode has full support for the Mule High Availability module and the Mule management console.
OpenShift gives you many choices for developing and deploying applications in the cloud. You can pick among PHP, Ruby, Perl, Python, Node.js or Java. As Mule is Java based, we are pretty much covered. OpenShift provides an end to end Java application stack including: Java EE6, CDI/WeldSpring and Spring. You can choose between multiple application server's for Webapps including JBoss AS7, JBoss EAP6, Tomcat, and GlassFish. But if you want to run Mule natively in standalone mode for the aforementioned benefits, you will need to create a "DIY" cartridge/application.
A "DIY" application is just a barebones app, with no server preloaded, ready to be tailored to your needs. With this app type, OpenShift is begining to blur the line between an IaaS and a PaaS, providing you with a controlled and scalable environment, and at the same time giving you the freedom to implement the technology that best suits your needs.
Getting Started
Before attempting to create a DIY application on OpenShift, you should familiarize yourself with the technology you are about to use. You should have a clear understanding of the steps needed to set it all up on your workstation and then reproduce it on OpenShift.
For a Mule application we won't need JBoss, or any application sever, not any servlet container at all. We just have to install Mule and start it up.
Doing this on your own workstation is as easy as downloading Mule, unzipping it, and then running:
And to stop Mule:
Now we'll have to do the same on our server at OpenShift.
First, let's create a new application named "mule":
Now let's see what we created. Running the following script:
Should output similar to he following:
You can browse to http://mule-yourdomain.rhcloud.com/ to see the default index page running. It's just the same static page you can find at raw/index.html
Now let's see what we have in our repo:
It's a pretty barebone app, but there's a folder that's quite interesting for us - .openshift/action_hooks:
These are the scripts that OpenShift uses for building, deploying, starting and stopping our app. These scripts are executed on the remote OpenShift server. These are the scripts we will need to ammend to download Mule and perform any configuration as well as starting and stopping our Mule Server. Let's take a look at a simplified version of the script that we used to install Mule.
Installing Mule
.openshift/action_hooks/pre_build
The pre_build script is used for downloading the required Mule installation and unzipping it.
.openshift/action_hooks/start
Then to start the Mule server:
.openshift/action_hooks/stop
And to stop the Mule server:
Upgrading the Java Service Wrapper
When you run the mule command, it launches the mule.bat or mule.sh script in your MULE_HOME/bin directory. These scripts invoke the Java Service Wrapper. The Java Service Wrapper from Tanuki Software is s fancy, little tool which helps with managing your application and JVM it is running in. By default it uses sockets to communicate back and forth with the JVM. But OpenShift is very restrcitive on what IP's and ports you are allowed to listen on.
By default the current Mule 3.3.1 release uses version 3.5.7 of the Java Service Wrapper. If you try running the default Mule instalation on OpenShift, you will get the following error:
"unable to bind listener to any port in the range 32000-32999. (Permission denied)"
The Java Service Wrapper is controlled by a wrapper.conf file that can be found in you MULE_HOME/conf directory and has a host of configuration of options, including setting the rang eof ports that the wrapper can listen on. Ports aside, OpenShift only allows applications to bind on a specific IP address via the environment variable OPENSHIFT_INTERNAL_IP. Unfortunately there is no configuration option to override this IP address. Game Over!
Extra Life! In a later version of the wrapper, there is a new configuration option: wrapper.backend.type=PIPE to allow you to avoid using sockets and use pipes instead to get around this problem.
To upgrade the wrapper we simply download the later wrapper libraries and replace them within the MULE_HOME/lib directory.
.openshift/action_hooks/pre_build
To update the wrapper.conf file with the new configuration. We take a copy of the original wrapper.conf file, ammended to contain the
wrapper.backend.type=PIPE option and includ it within our git repo so that we can replace the original when building the instalation.
.openshift/action_hooks/pre_build
Deploying a Mule application
Deploying the application is as simple as copying a Mule application archive to the required appsdirectory:
.openshift/action_hooks/deploy
Where helloworld.zip is a simple Mule applicaion exposed over HTTP that returns "Hello World".
HTTP Binding
The only thing to take note of here is that we are using the ${OPENSHIFT_INTERNAL_IP} environment variable. This is the suggested IP address for creating socket connections on localhost. Typical values like "localhost", "127.0.0.1" and "0.0.0.0" are all locked down.
However, if you try using this environment variable as your host you will get an error similar to the following:
Permission denied (java.net.BindException) java.net.PlainSocketImpl:-2 (null) 2. Failed to bind to uri "http://127.8.109.1:8080"
As you can see; the internal IP resolves fine and we are using 8080 which is the suggested port for HTTP connections, but still no dice.
Hacking the TCP transport
After digging through the source, there is a slight issue with Mules' TCP transport.
Here, the internal IP is a loopback address, so Mule forces it down the path of creating a Socket that listens on all interfaces for that port. Fortunately there is already af fix in the upcoming 3.4 release -
MULE-6584: HTTP/ TCP bound to 127.0.0.1 listens on all interfaces.
Unfortunately, it's only upcoming at the moment. So instead I have ammended the source of this transport myself for the same functionality and included the resulting jar as part of my diy project to replace the orignal transport jar.
.openshift/action_hooks/pre_build
And that's it!
If you now take a look at your app at: http://mule-yourdomain.rhcloud.com/ you should now see "Hello World"! The full diy project for this with instructions can be found on GitHub: https://github.com/ryandcarter/mule-diy-cartridge