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?
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:
Issues with typical container images:
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
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
$ 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!
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.
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.
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. *
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:
Running applications in production isn’t easy. Using buildpacks can help to reduce the operational burden, for everyone.
* Hat tip to my colleague Adib Saikali for information on the Java memory calculator. Watch his Toronto Java meetup talk on Spring and Kubernetes.