buildContainerImage2: Support multiarch OCI images

The `buildContainerImage2` function is an improvement on the existing
`buildContainerImage` function, with a couple of enhancements.
Primarily, it supports building images for multiple architectures.  For
each listed architecture, a pod is launched on a matching worker node to
build the image natively.  The image is exported to an OCI archive and
"stashed" in the Jenkins workspace.  After images for all architectures
are built, another pod is launched and all of the stashed archives are
copied there and assembled into a manifest list.  Finally, the manifest
list is published to the OCI repository as usual, creating tags for the
Git branch and build number.

In addition to adding support for multiarch images, the
`buildContainerImage2` performs image builds in *unprivileged* pods.
Whereas the old function performed builds in a rootful container, the
new one configures requests pods with unique user namespaces.  The build
runs as *root* in the container, but that user is mapped to an arbitrary
unprivileged user on the host.
bci2-resources
Dustin 2023-10-05 21:04:53 -05:00
parent 356d9ecc1d
commit 96f4e59cbc
2 changed files with 163 additions and 0 deletions

View File

@ -0,0 +1,19 @@
spec:
containers:
- name: buildah
image: quay.io/containers/buildah:v1
command:
- cat
stdin: true
tty: true
securityContext:
capabilities:
add:
- SYS_ADMIN
- MKNOD
- SYS_CHROOT
- SETFCAP
resources:
limits:
github.com/fuse: 1
hostUsers: false

View File

@ -0,0 +1,144 @@
// vim: set sw=4 sts=4 ts=4 et :
def call(args) {
properties([
pipelineTriggers([cron('H H H * *')])
])
def registry = args?.registry
def project = args?.project
def name = args?.name
def tag = args?.tag
def archlist = args?.archlist
if (registry == null) {
registry = 'git.pyrocufflink.net'
}
if (project == null) {
project = 'containerimages'
}
if (name == null) {
name = env.JOB_NAME.
split('/')[1].
toLowerCase().
replaceAll('[^a-zA-z0-9._-]', '-').
replaceAll('^[.-]', '_')
}
if (tag == null) {
tag = env.BRANCH_NAME.
toLowerCase().
replaceAll('[^a-zA-z0-9._-]', '-').
replaceAll('^[.-]', '_')
}
if (archlist == null) {
archlist = ['amd64']
}
def repo = "${registry}/${project}/${name}"
def full_name = "${repo}:${tag}"
def stages = [:]
archlist.each { arch ->
def stageName = "Build ${arch}"
stages[stageName] = {
stage(stageName) {
buildStage(
name: name,
full_name: full_name,
registry: registry,
arch: arch,
)
}
}
}
parallel stages
runInPod {
container('buildah') {
withBuildahCreds(registry) {
if (archlist.size() > 1) {
stage('Build Manifest') {
sh "buildah manifest create '${full_name}'"
archlist.each { arch ->
unstash arch
sh "buildah manifest add '${full_name}' oci-archive:\${PWD}/${name}-${arch}.tar"
}
}
}
stage('Push') {
if (archlist.size() > 1) {
sh "buildah manifest push --all ${full_name} docker://${full_name}-${env.BUILD_NUMBER}"
sh "buildah manifest push ${full_name} docker://${full_name}"
if (env.BRANCH_NAME == 'main') {
sh "buildah manifest push ${full_name} docker://${repo}:latest"
}
} else {
sh "buildah push ${full_name} ${full_name}-${env.BUILD_NUMBER}"
sh "buildah push ${full_name}"
if (env.BRANCH_NAME == 'main') {
sh "buildah push ${full_name} ${repo}:latest"
}
}
}
}
}
}
}
def buildStage(args) {
def name = args.name
def full_name = args.full_name
def registry = args.registry
def arch = args.arch
runInPod(arch) {
checkout scm
container('buildah') {
withBuildahCreds(registry) {
sh "buildah build -t '${full_name}' ."
sh "buildah push '${full_name}' oci-archive:\${PWD}/${name}-${arch}.tar"
stash name: arch, includes: "${name}-*.tar"
}
}
}
}
def runInPod(... args) {
def arch = null
def block = args.last()
if (args.size() > 1) {
arch = args[0]
}
def podTemplateYaml = libraryResource('podTemplate2.yaml')
podTemplate(
yaml: podTemplateYaml,
nodeSelector: arch ? "kubernetes.io/arch=${arch}" : null,
workspaceVolume: emptyDirWorkspaceVolume(),
) {
node(POD_LABEL) {
block()
}
}
}
def withBuildahCreds(registry, block) {
withEnv([
"REGISTRY_AUTH_FILE=${env.WORKSPACE_TMP}/auth.json"
]) {
withCredentials([usernamePassword(
credentialsId: 'jenkins-packages',
usernameVariable: 'BUILDAH_USERNAME',
passwordVariable: 'BUILDAH_PASSWORD',
)]) {
sh """
buildah login \
--username \${BUILDAH_USERNAME} \
--password \${BUILDAH_PASSWORD} \
${registry}
"""
}
block()
}
}