Shipping native Linux x64 binaries and libs with JVM apps

While CloudCaptain makes it dead easy to run your JVM applications (Spring Boot, JHipster, Grails, Dropwizard, Tomcat, TomEE or executable jar) on AWS, sometimes those applications also depend on other native Linux x64 binaries and libs. Typically these would be native applications for audio, video or image processing, but they could be anything.

Until now this meant writing some custom code to manually extract those binaries to the filesystem, giving them execute permissions and so on. Not exactly complicated. But certainly more complicated than things could and should be.

No more. Today we are introducing dead easy built-in support for shipping native Linux x64 binaries with your JVM apps.

How does it work?

Simply place your binaries under a special native/bin directory and CloudCaptain will automatically add them to the PATH at runtime in your instances.

If those binaries also depend on additional shared libraries beyond the C library, place the .so files of your libraries under native/lib and CloudCaptain will automatically add them to the LD_LIBRARY_PATH at runtime in your instances.

And that's all!

Seeing in action

For this post, we're going to build a remote version of the Linux cowsay utility using Spring Boot.

First let's start by taking the sources of this simple c port of cowsay:

#include <stdio.h>

int main(int argc, char **argv)
{
  int i;

  if (argc == 1)
    printf("< moOh >\n");
  for (i = 1; i < argc; i++)
    if (i == 1)
      printf("/ %s \\\n", argv[i]);
    else if (i == argc - 1)
      printf("\\ %s /\n", argv[i]);
    else
      printf("| %s |\n", argv[i]);
  printf("  \\ ^__^\n");
  printf("    (oo)\\_______\n");
  printf("    (__)\\       )\\/\\\n");
  printf("        ||----w |\n");
  printf("        ||     ||\n");
  return (0);
}

and compiling them into a native Linux x64 binary:

$ gcc -Wall -g cowsay.c -o cowsay.elf64

Now let's create a simple Spring Boot app to expose this:

$ curl 'https://start.spring.io/starter.zip?type=maven-project&bootVersion=1.4.3.RELEASE&baseDir=remote-cowsay&groupId=com.boxfuse.demo&artifactId=remote-cowsay&version=1.0&name=remote-cowsay&description=Remote+cowsay&packageName=com.boxfuse.demo&packaging=jar&javaVersion=1.8&language=java&autocomplete=&generate-project=&style=web' -o remote-cowsay.zip && unzip remote-cowsay.zip

Next add our freshly compiled cowsay.elf64 Linux x64 binary to our project under the src/main/resources/native/bin directory to ensure it will be added to the PATH at runtime:

 remote-cowsay
   src
     main
       java
         com
           boxfuse
             demo
               RemoteCowsayApplication.java
         native
   bin
     cowsay.elf64
     test
   .mvn
   .gitignore
   mvnw
   mvnw.cmd
   pom.xml

And finally let's add a controller to our RemoteCowsayApplication class:

package com.boxfuse.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

@SpringBootApplication
@RestController
public class RemoteCowsayApplication {
    @RequestMapping(path = "/")
    public String cowsay(@RequestParam(value = "t", defaultValue = "Moo") String text) throws IOException {
        return FileCopyUtils.copyToString(new InputStreamReader(
            // No need to specify absolute path as binary is available on $PATH
            new ProcessBuilder("cowsay.elf64", text).start().getInputStream(),
            StandardCharsets.UTF_8));
    }

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

Now it is time to package our jar:

$ mvnw package

And let CloudCaptain create an image and launch an instance on VirtualBox:

$ boxfuse run

Creating remote-cowsay ...
Mapping remotecowsay-dev-axelfontaine.boxfuse.io to 127.0.0.1 ...
Successfully created app remote-cowsay (type: single-instance, db: none, logs: cloudwatch-logs)
Fusing Image for remote-cowsay-1.0.jar (Spring Boot) ...
Image fused in 00:03.320s (58621 K) -> axelfontaine/remote-cowsay:1.0
Launching Instance of axelfontaine/remote-cowsay:1.0 on VirtualBox ...
Forwarding http port localhost:8080 -> vb-f3f3a0a4:8080
Instance launched in 00:04.597s -> vb-f3f3a0a4
Waiting for payload to start on vb-f3f3a0a4:8080 (expecting HTTP 200 at / within 300s) ...
Successfully started payload in 00:12.134s -> https://127.0.0.1:8080

Last but not least, let's see it in action!

$ curl https://localhost:8080?t=Moooooohh
/ Moooooohh \
  \ ^__^
    (oo)\_______
    (__)\       )\/\
        ||----w |
        ||     ||

And there you have it! Our native Linux x64 binary was automatically added to the PATH with the correct execute permissions. And all we needed to do to invoke it was a simple one-liner.

Available today

The CloudCaptain built-in support for shipping native Linux x64 binaries with your JVM apps is available today at no charge to all customers. Enjoy!

So if you haven't already, sign up for your CloudCaptain account now (simply log in with your GitHub id, it's free), start deploying your application effortlessly to AWS today and enjoy the dead easy integration for shipping native binaries.

« Logback and Log4J2 appender for AWS CloudWatch Logs
Custom Domains »