OpenShift Builds with Shipwright and Cloud Native Buildpacks
In this article, you will learn how to build the images of your apps on OpenShift with Shipwright and Cloud Native Buildpacks. Cloud Native Buildpacks allow us to easily build container images from the app source code. On the other hand, Shipwright is a Kubernetes-native tool that provides mechanisms for declaring and reusing several build image strategies. Such a strategy is correlated with the tool used for building images. Shipwright supports Kaniko, OpenShift Source-to-image (S2I), Buildah, ko, BuiltKit, and of course, Cloud Native Buildpacks.
You can also run Cloud Native Buildpacks on “vanilla” Kubernetes together with e.g. Tekton. In the following article on my blog, you will find information on how to use Buildpacks in your Tekton CI pipeline for building Java app images.
Introduction
If you already have some experience with OpenShift, you probably know that it comes with its own mechanism for building container images. Such a build is fully executed on the OpenShift cluster. OpenShift provides the BuildConfig
CRD responsible for a build configuration. There are three primary build strategies available: Docker, S2I, and custom build. By the way, you can build the image in any way you want and still run such a container on OpenShift. However, today we are discussing the OpenShift recommended approach for building container images called “OpenShift Builds”.
Recently, Red Hat has introduced a new supported way of using “OpenShift Builds” based on the Shipwright project. Therefore, you can currently find two chapters in the OpenShift documentation: “Builds with BuildConfig” and “Builds with Shipwright”. Maybe Shipwright will become a single, default strategy in the future, but for now, we can decide which of them to use.
In this article, I’ll show you how to install and manage Shipwright on OpenShift. By default, Red Hat supports Source-to-Image (S2I) and Buildah build strategies. We will create our own strategy for using the Cloud Native Buildpacks.
Install Builds for Red Hat OpenShift Operator
In order to enable Shiwright builds on OpenShift, we need to install the operator called “Builds for Red Hat OpenShift”. It depends on the OpenShift Pipelines operator. However, we don’t have to take care of it, since that operator is automatically installed together with the “Builds for Red Hat OpenShift” operator. In the OpenShift Console go to the Operators -> Operator Hub, find and install the required operator.
After that, we need to create the ShiwrightBuild
object. It needs to contain the name of the target namespace for running Shipwright in the targetNamespace
field. In my case, the target namespace is openshift-builds
.
apiVersion: operator.shipwright.io/v1alpha1
kind: ShipwrightBuild
metadata:
name: openshift-builds
spec:
targetNamespace: openshift-builds
We can easily create the object on the operator page in OpenShift Console. If the status is “Ready” it means that Shipwright is running on our cluster.
Let’s display a list of pods inside the openshift-builds
namespace. As you see, two pods are running there.
$ oc get po -n openshift-builds
NAME READY STATUS RESTARTS AGE
shipwright-build-controller-5d5b4f7d97-llj4f 1/1 Running 0 2m31s
shipwright-build-webhook-76775cf988-pjk6k 1/1 Running 0 2m31s
This step is optional. We can install the Shipwright CLI on our laptop to interact with the cluster. The release binary is available in the following repository under the Releases section. Once you install it, you can execute the following command for verification:
$ shp version
OpenShift Console supports Shipwright Builds. Once you install and configure the operator, you will have a dedicated space inside the “Builds” section. You can create a new build, run a build, and display a history of previous runs.
Configure Build Strategy for Buildpacks
Before we will define any build, we need to configure the build strategy. From a high-level perspective, build strategy defines how to build an application with an image-building tool. We can create the BuildStrategy
object for the namespace-scoped strategy, and the ClusterBuildStrategy
object for the cluster-wide strategy. The “Builds for Red Hat OpenShift” operator comes with two predefined global strategies: buildah and source-to-image.
However, our goal is to create a strategy for Cloud Native Buildpacks. Fortunately, we can just copy such a strategy from the samples provided in the Shiwright repository on GitHub. We won’t get into the details of that strategy implementation. The YAML manifest is visible below. It uses the paketobuildpacks/builder-jammy-full:latest
image during the build. We should apply that manifest to our OpenShift cluster.
apiVersion: shipwright.io/v1beta1
kind: ClusterBuildStrategy
metadata:
name: buildpacks-v3
spec:
volumes:
- name: platform-env
emptyDir: {}
parameters:
- name: platform-api-version
description: The referenced version is the minimum version that all relevant buildpack implementations support.
default: "0.7"
steps:
- name: build-and-push
image: docker.io/paketobuildpacks/builder-jammy-full:latest
env:
- name: CNB_PLATFORM_API
value: $(params.platform-api-version)
- name: PARAM_SOURCE_CONTEXT
value: $(params.shp-source-context)
- name: PARAM_OUTPUT_IMAGE
value: $(params.shp-output-image)
command:
- /bin/bash
args:
- -c
- |
set -euo pipefail
echo "> Processing environment variables..."
ENV_DIR="/platform/env"
envs=($(env))
# Denying the creation of non required files from system environments.
# The creation of a file named PATH (corresponding to PATH system environment)
# caused failure for python source during pip install (https://github.com/Azure-Samples/python-docs-hello-world)
block_list=("PATH" "HOSTNAME" "PWD" "_" "SHLVL" "HOME" "")
for env in "${envs[@]}"; do
blocked=false
IFS='=' read -r key value string <<< "$env"
for str in "${block_list[@]}"; do
if [[ "$key" == "$str" ]]; then
blocked=true
break
fi
done
if [ "$blocked" == "false" ]; then
path="${ENV_DIR}/${key}"
echo -n "$value" > "$path"
fi
done
LAYERS_DIR=/tmp/.shp/layers
CACHE_DIR=/tmp/.shp/cache
mkdir -p "$CACHE_DIR" "$LAYERS_DIR"
function announce_phase {
printf "===> %s\n" "$1"
}
announce_phase "ANALYZING"
/cnb/lifecycle/analyzer -layers="$LAYERS_DIR" "${PARAM_OUTPUT_IMAGE}"
announce_phase "DETECTING"
/cnb/lifecycle/detector -app="${PARAM_SOURCE_CONTEXT}" -layers="$LAYERS_DIR"
announce_phase "RESTORING"
/cnb/lifecycle/restorer -cache-dir="$CACHE_DIR" -layers="$LAYERS_DIR"
announce_phase "BUILDING"
/cnb/lifecycle/builder -app="${PARAM_SOURCE_CONTEXT}" -layers="$LAYERS_DIR"
exporter_args=( -layers="$LAYERS_DIR" -report=/tmp/report.toml -cache-dir="$CACHE_DIR" -app="${PARAM_SOURCE_CONTEXT}")
grep -q "buildpack-default-process-type" "$LAYERS_DIR/config/metadata.toml" || exporter_args+=( -process-type web )
announce_phase "EXPORTING"
/cnb/lifecycle/exporter "${exporter_args[@]}" "${PARAM_OUTPUT_IMAGE}"
# Store the image digest
grep digest /tmp/report.toml | tail -n 1 | tr -d ' \"\n' | sed s/digest=// > "$(results.shp-image-digest.path)"
volumeMounts:
- mountPath: /platform/env
name: platform-env
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 250m
memory: 65Mi
securityContext:
runAsUser: 1001
runAsGroup: 1000
As you see, a new build strategy is available under the buildpacks-v3
name.
Create Build for the Sample App
Once we create a strategy, we can proceed to the build creation. We will push the image to the registry on quay.io
. It requires to have an existing account there. The quay.io
registry allows us to create a robot account on a private account. Then we may use it to authenticate against the repository during the build. Firstly, go to your account settings and find the “Robot Accounts” section there. If you don’t have any robot account you need to create it. On the existing account choose the settings icon, and then the “View Credentials” item in the context menu.
In the “Kubernetes Secret” section download the YAML manifest containing the authentication credentials.
Our robot account must have the Write
access to the target repository. In my case, it is the pminkows/sample-kotlin-spring
registry.
Once you download the manifest you have to apply it to the OpenShift cluster. Let’s also create a new project with the demo-builds
name.
$ oc new-project demo-builds
$ oc apply -f pminkows-piomin-secret.yml
The manifest contains a single Secret
with our Quay robot account authentication credentials.
Finally, we can create the Build
object. It contains three sections. In the first of them, we are defining the address of the container image repository to push our output image (1). We need to authenticate against the repository with the previously created pminkows-piomin-pull-secret
Secret
. In the source section, we should provide the address of the repository with the app source code (2). The repository with a sample Spring Boot app is located on my GitHub account. In the last section, we need to set the build strategy (3). We choose the previously created buildpacks-v3
strategy for Cloud Native Buildpacks.
apiVersion: shipwright.io/v1beta1
kind: Build
metadata:
name: sample-spring-kotlin-build
namespace: demo-builds
spec:
# (1)
output:
image: quay.io/pminkows/sample-kotlin-spring:1.0-shipwright
pushSecret: pminkows-piomin-pull-secret
# (2)
source:
git:
url: https://github.com/piomin/sample-spring-kotlin-microservice.git
# (3)
strategy:
name: buildpacks-v3
kind: ClusterBuildStrategy
Once you apply the sample-spring-kotlin-build
Build
object you can switch to the “Builds” section in the OpenShift Console. In the “Shipwright Builds” tab, we can display a list of builds in the demo-builds
namespace. In order to run the build, we just need to choose the “Start” option in the context menu.
Running the Shipwright Build on OpenShift
Before we run the build, we have to set some permissions for the ServiceAccount
. By default, Shipwright uses the pipeline
ServiceAccount
to run builds. You should already have the pipeline
account created in the demo-builds
namespace automatically with the project creation.
Shipwright requires a privileged
SCC assigned to the pipeline
ServiceAccount
to run the build pods. Let’s add the required permission with the following commands:
$ oc adm policy add-scc-to-user privileged -z pipeline -n demo-builds
$ oc adm policy add-role-to-user edit -z pipeline -n demo-builds
We can start the build in the OpenShift Console or with the Shipwright CLI. Firstly, let’s display a list of builds using the following shp
command:
$ shp build list
NAME OUTPUT STATUS
sample-spring-kotlin-build quay.io/pminkows/sample-kotlin-spring:1.0-shipwright all validations succeeded
Let’s run the build with the following command:
$ shp build run sample-spring-kotlin-build --follow
We can switch to the OpenShift Console. As you see, our build is running.
Shipwright runs the pod with Paketo Buildpacks Builder according to the strategy.
Here’s the output of the shp
command. Once the image is ready, it is pushed to the Quay registry.
The build has been finished successfully. We can see it in the Shipwright “Builds” section in the OpenShift Console (status Succeeded
).
Let’s switch to the quai.io
dashboard. As you see, the image tag 1.0-shipwright
is already there.
Of course, we can build many times. The history of executions is available in the BuildRuns
tab for each Build
.
Final Thoughts
With Shipwright you can easily switch between different build image strategies on OpenShift. You are not limited only to the Source-to-image (S2I) or Docker strategy, but you can use different tools for that including e.g. Cloud Native Buildpacks. For now, the strategy based on Buildpacks is not supported by Red Hat as the Shipwright builds feature. However, I hope it will change soon 🙂
Leave a Reply