54. Deploying to the cloud

Spring Boot’s executable jars are ready-made for most popular cloud PaaS (platform-as-a-service) providers. These providers tend to require that you “bring your own container”; they manage application processes (not Java applications specifically), so they need some intermediary layer that adapts your application to the cloud’s notion of a running process.

Two popular cloud providers, Heroku and Cloud Foundry, employ a “buildpack” approach. The buildpack wraps your deployed code in whatever is needed to start your application: it might be a JDK and a call to java, it might be an embedded web server, or it might be a full-fledged application server. A buildpack is pluggable, but ideally you should be able to get by with as few customizations to it as possible. This reduces the footprint of functionality that is not under your control. It minimizes divergence between development and production environments.

Ideally, your application, like a Spring Boot executable jar, has everything that it needs to run packaged within it.

In this section we’ll look at what it takes to get the simple application that we developed in the “Getting Started” section up and running in the Cloud.

54.1 Cloud Foundry

Cloud Foundry provides default buildpacks that come into play if no other buildpack is specified. The Cloud Foundry Java buildpack has excellent support for Spring applications, including Spring Boot. You can deploy stand-alone executable jar applications, as well as traditional .war packaged applications.

Once you’ve built your application (using, for example, mvn clean package) and installed the cf command line tool, simply deploy your application using the cf push command as follows, substituting the path to your compiled .jar. Be sure to have logged in with your cf command line client before pushing an application.

$ cf push acloudyspringtime -p target/demo-0.0.1-SNAPSHOT.jar

See the cf push documentation for more options. If there is a Cloud Foundry manifest.yml file present in the same directory, it will be consulted.

[Note]Note

Here we are substituting acloudyspringtime for whatever value you give cf as the name of your application.

At this point cf will start uploading your application:

Uploading acloudyspringtime... OK
Preparing to start acloudyspringtime... OK
-----> Downloaded app package (8.9M)
-----> Java Buildpack source: system
-----> Downloading Open JDK 1.7.0_51 from .../x86_64/openjdk-1.7.0_51.tar.gz (1.8s)
       Expanding Open JDK to .java-buildpack/open_jdk (1.2s)
-----> Downloading Spring Auto Reconfiguration from  0.8.7 .../auto-reconfiguration-0.8.7.jar (0.1s)
-----> Uploading droplet (44M)
Checking status of app 'acloudyspringtime'...
  0 of 1 instances running (1 starting)
  ...
  0 of 1 instances running (1 down)
  ...
  0 of 1 instances running (1 starting)
  ...
  1 of 1 instances running (1 running)

App started

Congratulations! The application is now live!

It’s easy to then verify the status of the deployed application:

$ cf apps
Getting applications in ...
OK

name                 requested state   instances   memory   disk   urls
...
acloudyspringtime    started           1/1         512M     1G     acloudyspringtime.cfapps.io
...

Once Cloud Foundry acknowledges that your application has been deployed, you should be able to hit the application at the URI given, in this case acloudyspringtime.cfapps.io/.

54.1.1 Binding to services

By default, metadata about the running application as well as service connection information is exposed to the application as environment variables (for example: $VCAP_SERVICES). This architecture decision is due to Cloud Foundry’s polyglot (any language and platform can be supported as a buildpack) nature; process-scoped environment variables are language agnostic.

Environment variables don’t always make for the easiest API so Spring Boot automatically extracts them and flattens the data into properties that can be accessed through Spring’s Environment abstraction:

@Component
class MyBean implements EnvironmentAware {

    private String instanceId;

    @Override
    public void setEnvironment(Environment environment) {
        this.instanceId = environment.getProperty("vcap.application.instance_id");
    }

    // ...

}

All Cloud Foundry properties are prefixed with vcap. You can use vcap properties to access application information (such as the public URL of the application) and service information (such as database credentials). See VcapApplicationListener Javadoc for complete details.

[Tip]Tip

The Spring Cloud Connectors project is a better fit for tasks such as configuring a DataSource. Spring Boot includes auto-configuration support and a spring-boot-starter-cloud-connectors starter POM.

54.2 Heroku

Heroku is another popular PaaS platform. To customize Heroku builds, you provide a Procfile, which provides the incantation required to deploy an application. Heroku assigns a port for the Java application to use and then ensures that routing to the external URI works.

You must configure your application to listen on the correct port. Here’s the Procfile for our starter REST application:

web: java -Dserver.port=$PORT -jar target/demo-0.0.1-SNAPSHOT.jar

Spring Boot makes -D arguments available as properties accessible from a Spring Environment instance. The server.port configuration property is fed to the embedded Tomcat, Jetty or Undertow instance which then uses it when it starts up. The $PORT environment variable is assigned to us by the Heroku PaaS.

Heroku by default will use Java 1.8. This is fine as long as your Maven or Gradle build is set to use the same version (Maven users can use the java.version property). If you want to use JDK 1.7, create a new file adjacent to your pom.xml and Procfile, called system.properties. In this file add the following:

java.runtime.version=1.7

This should be everything you need. The most common workflow for Heroku deployments is to git push the code to production.

$ git push heroku master

Initializing repository, done.
Counting objects: 95, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (78/78), done.
Writing objects: 100% (95/95), 8.66 MiB | 606.00 KiB/s, done.
Total 95 (delta 31), reused 0 (delta 0)

-----> Java app detected
-----> Installing OpenJDK 1.8... done
-----> Installing Maven 3.3.1... done
-----> Installing settings.xml... done
-----> Executing: mvn -B -DskipTests=true clean install

       [INFO] Scanning for projects...
       Downloading: http://repo.spring.io/...
       Downloaded: http://repo.spring.io/... (818 B at 1.8 KB/sec)
        ....
       Downloaded: http://s3pository.heroku.com/jvm/... (152 KB at 595.3 KB/sec)
       [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/target/...
       [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/pom.xml ...
       [INFO] ------------------------------------------------------------------------
       [INFO] BUILD SUCCESS
       [INFO] ------------------------------------------------------------------------
       [INFO] Total time: 59.358s
       [INFO] Finished at: Fri Mar 07 07:28:25 UTC 2014
       [INFO] Final Memory: 20M/493M
       [INFO] ------------------------------------------------------------------------

-----> Discovering process types
       Procfile declares types -> web

-----> Compressing... done, 70.4MB
-----> Launching... done, v6
       http://agile-sierra-1405.herokuapp.com/ deployed to Heroku

To [email protected]:agile-sierra-1405.git
 * [new branch]      master -> master

Your application should now be up and running on Heroku.

54.3 OpenShift

OpenShift is the RedHat public (and enterprise) PaaS solution. Like Heroku, it works by running scripts triggered by git commits, so you can script the launching of a Spring Boot application in pretty much any way you like as long as the Java runtime is available (which is a standard feature you can ask for at OpenShift). To do this you can use the DIY Cartridge and hooks in your repository under .openshift/action_scripts:

The basic model is to:

  1. Ensure Java and your build tool are installed remotely, e.g. using a pre_build hook (Java and Maven are installed by default, Gradle is not)
  2. Use a build hook to build your jar (using Maven or Gradle), e.g.

    #!/bin/bash
    cd $OPENSHIFT_REPO_DIR
    mvn package -s .openshift/settings.xml -DskipTests=true
  3. Add a start hook that calls java -jar …​

    #!/bin/bash
    cd $OPENSHIFT_REPO_DIR
    nohup java -jar target/*.jar --server.port=${OPENSHIFT_DIY_PORT} --server.address=${OPENSHIFT_DIY_IP} &
  4. Use a stop hook (since the start is supposed to return cleanly), e.g.

    #!/bin/bash
    source $OPENSHIFT_CARTRIDGE_SDK_BASH
    PID=$(ps -ef | grep java.*\.jar | grep -v grep | awk '{ print $2 }')
    if [ -z "$PID" ]
    then
        client_result "Application is already stopped"
    else
        kill $PID
    fi
  5. Embed service bindings from environment variables provided by the platform in your application.properties, e.g.

    spring.datasource.url: jdbc:mysql://${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/${OPENSHIFT_APP_NAME}
    spring.datasource.username: ${OPENSHIFT_MYSQL_DB_USERNAME}
    spring.datasource.password: ${OPENSHIFT_MYSQL_DB_PASSWORD}

There’s a blog on running Gradle in OpenShift on their website that will get you started with a gradle build to run the app.

54.4 Boxfuse and Amazon Web Services

Boxfuse works by turning your Spring Boot executable jar or war into a minimal VM image that can be deployed unchanged either on VirtualBox or on AWS. Boxfuse comes with deep integration for Spring Boot and will use the information from your Spring Boot configuration file to automatically configure ports and health check URLs. Boxfuse leverages this information both for the images it produces as well as for all the resources it provisions (instances, security groups, elastic load balancers, etc).

Once you have created a Boxfuse account, connected it to your AWS account, and installed the latest version of the Boxfuse Client, you can deploy your Spring Boot application to AWS as follows (ensure the application has been built by Maven or Gradle first using, for example, mvn clean package):

$ boxfuse run myapp-1.0.jar -env=prod

See the boxfuse run documentation for more options. If there is a boxfuse.com/docs/commandline/#configuration [boxfuse.conf] file present in the current directory, it will be consulted.

[Tip]Tip

By default Boxfuse will activate a Spring profile named boxfuse on startup and if your executable jar or war contains an boxfuse.com/docs/payloads/springboot.html#configuration [application-boxfuse.properties] file, Boxfuse will base its configuration based on the properties it contains.

At this point boxfuse will create an image for your application, upload it, and then configure and start the necessary resources on AWS:

Fusing Image for myapp-1.0.jar ...
Image fused in 00:06.838s (53937 K) -> axelfontaine/myapp:1.0
Creating axelfontaine/myapp ...
Pushing axelfontaine/myapp:1.0 ...
Verifying axelfontaine/myapp:1.0 ...
Creating Elastic IP ...
Mapping myapp-axelfontaine.boxfuse.io to 52.28.233.167 ...
Waiting for AWS to create an AMI for axelfontaine/myapp:1.0 in eu-central-1 (this may take up to 50 seconds) ...
AMI created in 00:23.557s -> ami-d23f38cf
Creating security group boxfuse-sg_axelfontaine/myapp:1.0 ...
Launching t2.micro instance of axelfontaine/myapp:1.0 (ami-d23f38cf) in eu-central-1 ...
Instance launched in 00:30.306s -> i-92ef9f53
Waiting for AWS to boot Instance i-92ef9f53 and Payload to start at http://52.28.235.61/ ...
Payload started in 00:29.266s -> http://52.28.235.61/
Remapping Elastic IP 52.28.233.167 to i-92ef9f53 ...
Waiting 15s for AWS to complete Elastic IP Zero Downtime transition ...
Deployment completed successfully. axelfontaine/myapp:1.0 is up and running at http://myapp-axelfontaine.boxfuse.io/

Your application should now be up and running on AWS.

There’s a blog on deploying Spring Boot apps on EC2 as well as documentation for the Boxfuse Spring Boot integration on their website that will get you started with a Maven build to run the app.

54.5 Google App Engine

Google App Engine is tied to the Servlet 2.5 API, so you can’t deploy a Spring Application there without some modifications. See the Servlet 2.5 section of this guide.