Cloud Native Buildpacks

How does code turn into an application running on Kubernetes? Good question. Many things have to happen: Dockerfiles written and rewritten, base images picked, builds pushed to repositories, and, of course, much k8s YAML wrangled. These simple phrases, e.g. “Dockerfile written,” represent considerable complexity.

How can we make getting code into production simpler for everyone?

One way is to use buildpacks, which help to make the generation of OCI images easier. What if I told you that with buildpacks you don’t even NEED a Dockerfile?

Building Container Images is Hard - Use Buildpacks

For quite a while Docker was the only easy way to build container images. It’s still responsible for building the vast majority container images. However, with the creation of the OCI image spec other tools have been developed.

It can’t be denied that giving development teams the ability to create their own runtime images improved the developer experience. They could run the same image locally as what would, in theory, go into production. They knew the apps dependencies and could add them to the Dockerfile.

However, in the socio-technical realm of enterprise organizations, operations typically does not allow arbitrary container images in production, for various reasons, some valid, some not. Further to that, many ops organizations will try to manage container images exactly like they manage virtual machine templates (if they manage them at all) which is an anti-pattern.

Ultimately Dockerfiles make building operationally resilient images look easy, but it’s not.

Here are a few considerations one has to make when building container images:

  • Keeping image sizes small
  • Dealing with dependencies
  • Picking the right base image
  • Ensuring appropriate application memory settings
  • Day 2 ops - e.g. How to update the JDK without breaking the image cache or rebasing on a new base image
  • Defining who is responsible for the images - Ops, Dev, or DevOps?
  • Dealing with CVEs

Issues with typical container images:

  • Lack of app awareness
  • Not composable - How do we combine images? (Only have multistage builds in Dockerfiles)
  • Leaky abstraction - e.g. Container images mix operational concerns with application development concerns
  • Non-reproducible/untestable builds
  • Security - e.g. many Dockerfiles assume running as root
  • Treating containers images as VMs

Enter Buildpacks.io

Buildpacks are pluggable, modular tools that translate source code into OCI images.

Buildpacks are currently a CNCF Sandbox project supported by companies like Pivotal and Heroku.

Buildpacks have always been about the developer experience. We want buildpacks to make your job easier by eliminating operational and platform concerns that you might otherwise need to consider when using containers.

Buildpacks are a higher level abstraction than Dockerfiles and are really meant for developers.

What do you get?

  • Provide a balance of control that reduces the operational burden on developers and supports enterprise operators who manage apps at scale
  • Ensure that apps meet security and compliance requirements without developer intervention
  • Provide automated delivery of both OS-level and application-level dependency upgrades, efficiently handling day-2 app operations that are often difficult to manage with Dockerfiles
  • Rely on compatibility guarantees to safely apply patches without rebuilding artifacts and without unintentionally changing application behavior

Using Buildpacks

I run on Linux so I just downloaded the binary into my home bin directory and made it executable.

$ which pack
~/bin/pack

Let’s clone a repo, spring-music, and build an image.

$ git clone https://github.com/cloudfoundry-samples/spring-music

Now cd into spring-music. Note no Dockerfiles exist. Just plain code and build files.

$ cd spring-music
$ ls
build.gradle  gradle  gradle.properties  gradlew  gradlew.bat  LICENSE  manifest.yml  README.md  src

Now, with one simple command, let’s build a hardened container image that has been run millions of times on the Pivotal Platform and Heroku.

$ pack build spring-music

Here’s the resulting image…264MB:

$ docker images spring-music
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
spring-music        latest              0ea601302547        15 minutes ago      264MB

Run it.

$ docker run --rm -d -p 8080:8080 spring-music

Curl localhost:8080 to test.

 $ curl -s localhost:8080 | grep title
    <meta name="title" content="Spring Music">
    <title>Spring Music</title>

Well, that was pretty easy…and no Dockerfiles!

A Bit More

While there is a lot to…er…“unpack,” here, I’d like to point out a couple of things that I think are interesting and important about buildpacks.

  • Disconnection of Operating System from Application

With standard Docker images when a security issue is discovered in a operating system package many, if not all, of the image layers have to be rebuilt. This makes redeploying applications slow. It also handcuffs the OS requirements to the app requirements. With buildpacks these concerns are separated.

  • Java BuildPack Memory Calculator

I find that it’s quite easy to ignore or forget JVM memory settings…especially in a container centric world. Buildpacks use the Java BuildPack Memory Calculator to dynamically set requirements for memory. I have yet to see a Dockerfile that implements this or anything like it. *

Conclusion

Building hardened, operationally reliable container images is difficult. Using Buildpacks not only makes Dockerfiles unnecessary, but provides access to images that have been in constant, heavy production use for years.

Using pack and buildpacks you don’t need to:

  • Write your own Dockerfiles
  • Add individual files before app code to improve caching
  • Dance around for user permissions and root access
  • Force docker to rebuild all layers for a security patch
  • Understand the base operating system
  • Couple applications to the build pipeline
  • Burn in images over time to feel that they are trustworthy

Running applications in production isn’t easy. Using buildpacks can help to reduce the operational burden, for everyone.

I recommend watching a couple of videos for more in-depth information on buildpacks.


* Hat tip to my colleague Adib Saikali for information on the Java memory calculator. Watch his Toronto Java meetup talk on Spring and Kubernetes.