Shipwright build scheduler features
New Build Scheduler Features
A new set of build scheduling features introduced in v0.15 allows users to specify node selectors, custom schedulers, and tolerations for builds.
These make it easier to schedule builds on clusters with nodes of multiple CPU architectures, use a scheduler that is tuned to a certain workflow, or just more general control of which nodes builds run on.
CLI flags to use build scheduler features
With these features also comes new CLI flags (introduced in v0.16) that allow specifying nodeSelectors and custom schedulers on the command line when using shp
with Builds
or BuildRuns
.
In the following commands, --node-selector
and --scheduler-name
sets these fields on the Build
or BuildRun
objects:
shp build create
shp build run
shp build upload
shp buildrun create
Example with shp build run
and --node-selector
We can specify a build to be scheduled to a node with certain labels by using a node selector. Here, we’ll schedule a build to a node with an ARM CPU architecture. Starting with an example build:
$ shp build create test-golang-build \
--output-image=kind.local/test/test-golang-build \
--source-git-url=https://github.com/shipwright-io/sample-go \
--source-context-dir=docker-build \
--strategy-name=buildah-shipwright-managed-push
We’ll run the build and specify the arm64 architecture in the node selector flag:
$ shp build run test-golang-build --node-selector=kubernetes.io/arch=arm64
and see that the created BuildRun now has a nodeSelector specified and has been scheduled on the arm64 node:
apiVersion: shipwright.io/v1beta1
kind: BuildRun
metadata:
creationTimestamp: "2025-05-20T17:27:01Z"
generateName: test-golang-build-
generation: 1
labels:
build.shipwright.io/generation: "1"
build.shipwright.io/name: test-golang-build
name: test-golang-build-glghq
namespace: default
resourceVersion: "31512392"
uid: 19f02c99-1d24-44c0-a5f9-ab544fdf55ae
spec:
build:
name: test-golang-build
nodeSelector:
kubernetes.io/arch: arm64
$ kubectl get pod test-golang-build-glghq-tfhnn-pod -o jsonpath='{.spec.nodeName} {.spec.nodeSelector}'
arm-node.compute.internal {"kubernetes.io/arch":"arm64"}
$ kubectl get node arm-node.compute.internal -o jsonpath='{.metadata.labels.kubernetes\.io/arch}'
arm64
Example with shp build create
and --scheduler-name
This time we’ll specify a scheduler name test-scheduler
that is assumed to be a custom scheduler that has been deployed to the cluster already.
Instead of specifying these options when running the build, we can also specify them when creating the build:
$ shp build create test-golang-build \
--output-image=kind.local/test/test-golang-build \
--source-git-url=https://github.com/shipwright-io/sample-go \
--source-context-dir=docker-build \
--strategy-name=buildah-shipwright-managed-push \
--scheduler-name=test-scheduler
and see that the scheduler name appears in the Build yaml:
apiVersion: shipwright.io/v1beta1
kind: Build
metadata:
creationTimestamp: "2025-05-20T17:49:07Z"
generation: 1
name: test-golang-build
namespace: default
resourceVersion: "31518385"
uid: bd2333fb-71eb-4228-bc5f-5a466032fcc5
spec:
output:
image: kind.local/test/test-golang-build
schedulerName: test-scheduler
source:
contextDir: docker-build
git:
url: https://github.com/shipwright-io/sample-go
type: Git
strategy:
kind: ClusterBuildStrategy
name: buildah-shipwright-managed-push
status:
message: all validations succeeded
reason: Succeeded
registered: "True"
After running the build with shp build run test-golang-build
, we see that the schedulerName got picked up by the build pod:
$ kubectl get pods test-golang-build-j9vbd-qr9cr-pod -o jsonpath='{.spec.schedulerName}'
test-scheduler
Example with setting tolerations on a BuildRun
We’ll start with the same example build as above:
$ shp build create test-golang-build \
--output-image=kind.local/test/test-golang-build \
--source-git-url=https://github.com/shipwright-io/sample-go \
--source-context-dir=docker-build \
--strategy-name=buildah-shipwright-managed-push
apiVersion: shipwright.io/v1beta1
kind: Build
metadata:
creationTimestamp: "2025-05-20T18:29:43Z"
generation: 1
name: test-golang-build
namespace: default
resourceVersion: "31529978"
uid: f393de53-e389-4e25-a29a-a434505bd82e
spec:
output:
image: kind.local/test/test-golang-build
source:
contextDir: docker-build
git:
url: https://github.com/shipwright-io/sample-go
type: Git
strategy:
kind: ClusterBuildStrategy
name: buildah-shipwright-managed-push
In this example we have a three node cluster, so let’s taint all of the nodes with test-key
and test-value
. This will prevent any pod from scheduling on these nodes unless it tolerates this taint:
$ kubectl get nodes -o name
node/test-node-1
node/test-node-2
node/test-node-3
$ kubectl taint nodes test-node-1 test-key=test-value:NoSchedule
node/test-node-1 tainted
$ kubectl taint nodes test-node-2 test-key=test-value:NoSchedule
node/test-node-2 tainted
$ kubectl taint nodes test-node-3 test-key=test-value:NoSchedule
node/test-node-3 tainted
We see that if we create a BuildRun now, it will fail to schedule:
$ shp build run test-golang-build
$ kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
...
2m54s Normal Pending taskrun/test-golang-build-bqw9s-hbkds pod status "PodScheduled":"False"; message: "0/3 nodes are available: 3 node(s) had untolerated taint {test-key: test-value}. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling."
...
Let’s patch the Build with the following toleration in order to have it tolerate the node taint:
tolerations:
- key: "test-key"
operator: "Equal"
value: "test-value"
effect: "NoSchedule"
$ kubectl patch Build test-golang-build --type=merge -p '{"spec":{"tolerations":[{"key":"test-key","operator":"Equal","value":"test-value"}]}}'
$ shp build run test-golang-build
Now we see that the build is successfully scheduled:
$ kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
...
109s Normal Scheduled pod/test-golang-build-k7zc4-492d2-pod Successfully assigned default/test-golang-build-k7zc4-492d2-pod to ip-10-0-2-69.us-east-2.compute.internal
...
Conclusion
Shipwright’s new build scheduling options make it much easier to control where builds get placed in the cluster and give more options to those who are operating in multi-arch environments or with other constraints.