Saturday, April 12, 2014

Spring Boot on OpenShift

I have a Spring Boot application that I would like to deploy to OpenShift. Unfortunately the Spring Boot documentation is silent about OpenShift, although it does contain information about other cloud providers (of course Pivotal's Clound Foundry but also Heroku).

This is a how-to on how I deployed my Spring Boot application to OpenShift as a prebuilt WAR file. This entry is pieced together from various resources from the OpenShift and Spring Boot documentation.

Spring Boot Configuration


General:

In the application you need to have a class like the following (note the extends part and the configure method that you don't normally have in Spring Boot - the main method would suffice):

@Configuration
@EnableAutoConfiguration
@EnableMongoRepositories
@ComponentScan
public class Booter extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Booter.class);
    }


    public static void main(String[] args) throws Exception {
        SpringApplication.run(Booter.class, args);
    }

}

You also need to add this to your pom.xml:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

For local development I use the console to fire up the application with the maven command:
mvn spring-boot:run

Read more about the pom.xml changes and the SpringBootServletInitializer here and here.

Actuator:

I use the actuator plugin in Spring Boot. However, on OpenShift you must rename the /health endpoint (read on to know why). The easiest way to do this is simply to change all actuator endpoints using an application property with a line like this (make an application.properties file and put it in the src/main/resources folder in the application):

management.context-path=/manage

That will map for instance /health to /manage/health instead.

Mongo:

To use Mongo you need to use the url, port, user and password given by OpenShift. I use the following bean definition to shift between OpenShift and my test system.

@Bean
public MongoTemplate mongoTemplate() throws Exception {

    if (System.getenv("OPENSHIFT_MONGODB_DB_HOST") != null) {

        LOG.info("Connecting to OpenShift Mongo");

        String openshiftMongoDbHost = System.getenv("OPENSHIFT_MONGODB_DB_HOST");
        int openshiftMongoDbPort = Integer.parseInt(System.getenv("OPENSHIFT_MONGODB_DB_PORT"));
        String username = System.getenv("OPENSHIFT_MONGODB_DB_USERNAME");
        String password = System.getenv("OPENSHIFT_MONGODB_DB_PASSWORD");
        Mongo mongo = new MongoClient(openshiftMongoDbHost, openshiftMongoDbPort);
        UserCredentials userCredentials = new UserCredentials(username, password);
        String databaseName = System.getenv("OPENSHIFT_APP_NAME");
        MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongo, databaseName, userCredentials);
        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory);
        return mongoTemplate;
        
    } else {

        LOG.info("Connecting to test Mongo");

        return new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "test"));
    }
}


OpenShift


Application type:

I use the Tomcat 7 (JBoss EWS 2.0) cartridge with scaling plus the MongoDB cartridge. I started by checking out the code using git clone, removed the src folder and the pom.xml file (as I am deploying WAR style).

The actual deployment is pretty simple. After building with mvn package I copy the generated WAR file to the webapps folder, rename it to ROOT.war as I want it to be mapped to my-app.rhcloud.com (and not my-app.rhcloud.com/some-other-thing). Then I git add, commit and push. That is also described here.

HAProxy (scaling):

If you use scaling in you application you need to know about HAProxy which monitors you application by calling the / path unless you do something. HAProxy expects a 200 OK HTTP answer back. My app returns 404 when accessing /, hence HAProxy thinks the application is down. 503 is returned for everything except for calls to /health, but more on that later.

To fix it, ssh into your app and go to the haproxy folder. Then edit the file conf/haproxy.conf, specifically you need to modify the line shown below (almost at the bottom of the file) to whatever path you want HAProxy to monitor:

option httpchk GET /

You should probably choose the path carefully and not a path that requires a lot of server power to process as HAProxy polls the path rather often to check the application state. Afterwards do this in the console:

bin/control restart

to make the change have effect. HAProxy is described here.

/health:

/health has a special meaning on OpenShift, luckily you have already mapped the /health from Spring Boot actuator to /manage/health.

The OpenShift /health mapping is described here.


That's all folks.