Kpack and Paketo’s different stack types
Exploring building images with full, base, and tiny stacks
Alright, so before talking about building app images, let’s very briefly go over what Kpack is.
Kpack lets you leverage Kubernetes to build OCI images, with a platform implementation of Cloud Native Buildpacks as the underlying mechanism. Basically, your application images will be built on a Kuberentes cluster and pushed directly to a registry.
The following tutorial does have some prerequisites, which I’m going to list below:
- Some knowledge about Docker and Kubernetes.
- A basic understanding of CNB and Kpack concepts.
Once you’re all caught up, we can start building images with Kpack.
We will be using the repo linked above for this tutorial, so feel free to clone it (or star it if you want to 😇).
Setting things up
Kpack needs a Kubernetes cluster to run on, so there are some steps we need to perform before we dive deeper.
- Install
kubectl
following instructions here. - If you can access a Kubernetes cluster on cloud (EKS, GKE, etc.), authenticate to it otherwise you can install minikube and create one locally as follows:
minikube start --memory 6144 --cpus 3 --kubernetes-version=v1.24.13
Note: you can adjust the memory and cpu to allocate to the minikube cluster. I ran this tutorial with kubernetes v1.24.13, but you can try testing with newer versions too.
- Kpack would push
Builder
and app images to a registry. You can create repositories under your Dockerhub account to store these images.
For this tutorial, you’d need two repos named: kpack-image and kpack-builder.
Note: Make sure to replace the username semmet95
with your dockerhub account username throughout the repo.
Once you have created the repos and have access to a kubernetes cluster, create a docker-registry
secret to authenticate kpack resources to push images to your repo.
kubectl create secret docker-registry dockerhub-creds --docker-username=<dockerhub-username> --docker-password=<dockerhub-password>
Getting started with the repo
The repo contains kpack resources that are deployed by the script kpack-setup.sh
and the end result is an OCI image of an app. For this tutorial, we will be creating an OCI image for the spring-petclinic project, a simple tomcat app example. Although Kpack does let you build images directly from the source repo, we will be using the version of the project that is used by the sample Image resource in kpack’s repo.
https://storage.googleapis.com/build-service/sample-apps/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar
Let’s go over each of the files in the repo
clusterstack.yaml
Definition for a ClusterStack
resource that contributes OS layer to the app image. The properties of the resource are set based on the kind of stack being used, i.e., full
, base
, or tiny
.
clusterstore.yaml
Definition for a ClusterStore
resource that acts like a storage unit for buildpack references that are later added as layers to the app image by a Builder
. Since our app, spring-petclinic
, is a java based project, Paketo’s java buildpack, paketobuildpacks/java
, alone would be enough to provide all the run and build time dependencies for the app.
clusterbuilder.yaml
ClusterBuilder
resource brings ClusterStore
and ClusterStack
together and is the resource that’s directly used to build the app OCI image. A ClusterBuilder
is comprised of all the essential building blocks for an app image, which it packs as an image and pushes to a registry which is specified under the tag
field. This is where the dockerhub repo we talked about earlier, kpack-builder, comes into the picture. In order to authenticate the ClusterBuilder
to push images to your repo you’d need a serviceaccount
mounting the secret dockerhub-creds
created in the Setting things up section. This can be done by referring to it in the serviceAccountRef
field of the ClusterBuilder
.
serviceaccount.yaml
This is the serviceaccount
that mounts the dockerhub-creds
secret and can be used to authenticate kpack resources, ClusterBuilder
and Image
in this case, to push images to your dockerhub repos.
image.yaml
Kpack Image
resource corresponds to an OCI image with a particular tag (the tag
field of the resource is immutable). You can have multiple builds under the same Image
but all of them will push the built image with the same tag/URI. Image
resource gives you the flexibility to configure the build time environment with its build
field. In our case we are using the BP_JAVA_VERSION
environment variable to tell what java version to download and contribute the app image during the build, that being Java 8. We also have to specify:
- the
ClusterBuilder
we want to use here (providing all the run/build time dependencies) - cache size (optional) to store downloaded dependencies that could be reused during subsequent builds (stored in a
pvc
) serviceaccount
to authenticate theImage
resource to push app images to your dockerhub repo, kpack-imageurl
of the source blob, a jar file in this case, that was mentioned at the beginning of this section. This is the app we will be creating OCI image for.tag
to push the app image to. In this case, to your dockerhub account’s kpack-image repo
Apart from these kpack resources we also have a regular pod definition, app.yaml
in the examples directory, exposing port 8080, so you can access the container local port via port-forward. We will use this pod to test the images we build.
kpack-setup.sh
Now we come to the most important file in the repo. This script performs the following operations:
- installs kpack controller on the cluster
- deploys the
ClusterStore
andServiceAccount
resources - updates the
ClusterStack
definition based on the stack related environment variables and deploys it - updates the
ClusterBuilder
resource name and the name of theClusterStack
to use - updates the
Image
resource name to refer to the correctClusterBuilder
, updates thetag
field and name of the resource based on the kind of stack being used.
Executing the script
Time to get to the action part. The script reads environment variables to determine which kpack version to install on the cluster and which stack to use to build the app image.
First, set the version of kpack to install.
export KPACK_VERSION=0.10.1
Now, set the ID of the stack you want to use to build the app image. To start with, we will be using the Bionic Full stack.
export STACK_ID=io.buildpacks.stacks.bionic
We also need to set the run image, or the OS layer, to run the app on.
export RUN_IMAGE=paketobuildpacks/run:full-cnb
Now run the script
chmod +x ./kpack-setup.sh
./kpack-setup.sh
For those of you who don’t know, I’m using k9s tool here to quickly check on the kubernetes resources managed by the script.
If the app image is built successfully, you can now check your dockerhub repos to verify if they have been pushed.
The app image would be pushed with full-cnb
tag by the script because we are using the full-cnb
tag here.
Building the app image with base and tiny stacks
To use any stack, just change the STACK_ID
and RUN_IMAGE
accordingly and run the script.
To use the Bionic Base stack,
export STACK_ID=io.buildpacks.stacks.bionic
export RUN_IMAGE=paketobuildpacks/run:base-cnb
./kpack-setup.sh
Once the script is done, you’ll have the app image pushed to the kpack-image repo
And, to use the Bionic Tiny stack,
export STACK_ID=io.paketo.stacks.tiny
export RUN_IMAGE=paketobuildpacks/run:tiny-cnb
./kpack-setup.sh
Now that our app images are ready, let's run them in a pod and test if the app is accessible. For this, we will be using the image built with tiny-cnb
stack.
Update the app.yaml
file to pull the corresponding image from your dockerhub account’s kpack-image repo.
apiVersion: v1
kind: Pod
metadata:
name: petclinic
spec:
containers:
- image: <your-dockerhub-username>/kpack-image:tiny-cnb
name: petclinic
ports:
- containerPort: 8080
Run the following commands to deploy the pod and enable port-forward to access port 8080 of the app container.
kubectl apply -f ./examples/app.yaml
kubectl port-forward petclinic 8999:8080
And here is the app.
Stack Comparison
Alright, now that we have working images built on different stack layers, we can compare their sizes.
As you can see, the image size reduces to less than 1/3rd when we use the tiny-cnb
stack. That’s because while full-cnb
contributes an OS layer that’s described by Paketo as:
ubuntu:bionic with many common dependencies like rsync and openss
and base-cnb
stack contributes a layer that is:
ubuntu:bionic with some common dependencies like tzdata and openssl
tiny-cnb
is distroless like with no shell and very limited number of packages. It adds an OS layer ideal for most Golang apps and Java GraalVM Native Images.
Paketo recommends using the smallest stack that supports your app. Since our app is a simple spring project, we can deploy it using the tiny stack and significantly reduce the image size.
And there you go, you can follow this guide to create images for different apps using different stacks. Please share your results in the comments section below, I’m curious to see if you guys notice any difference depending on the stack used.
Also, if you have any questions or suggestions, drop them in the comments section as well.
See you on another post 🖖