diff --git a/README.md b/README.md index d606070..b653253 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,18 @@ # devops-base-images (GKE) -Mirrors minimized base images into Harbor's `base-images` project, so -language builds — `devops-lib`'s `buildDocker.groovy` fallback templates — -never depend on Docker Hub at build time, and the images that ship are the -leanest official variant for each language. +Owns every image the build pipeline pulls, in Harbor's `base-images` +project: + +- **Mirrored language base images** (`images.txt`), so builds — `devops-lib`'s + `buildDocker.groovy` fallback templates — never depend on Docker Hub at + build time, and what ships is the leanest official variant per language. +- **`build-tools`** (`build-tools.Dockerfile`), the image the build pod runs + its own shell steps in. + +Both are built or mirrored by hand from here, and `devops-lib` only ever +references the results by tag. `build-tools` lives here rather than in +devops-lib because it is the same kind of artefact as the mirrors, and +because a Jenkins job cannot build the image its own build pod needs. GKE counterpart of the homelab repo of the same name. The manifest and the script are identical; what differs is the registry host and, more @@ -73,6 +82,7 @@ data: maven:3-eclipse-temurin-21-alpine maven:3-eclipse-temurin-21-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine php:8.3-cli-alpine php:8.3-cli-alpine + docker:27-cli docker:27-cli mirror.sh: | #!/bin/sh set -eu @@ -176,6 +186,98 @@ If the push fails with a certificate error, the mount path is the usual cause: it must be `/etc/docker/certs.d/harbor.35.238.248.203.nip.io/ca.crt` exactly, hostname included, in the dind container. +## Building `build-tools` + +Do this after the mirror, since it builds `FROM` a mirrored image. + +The pod is the same shape as the mirror pod — privileged dind, the CA +mounted so pushes are trusted, a docker-cli sidecar — with the Dockerfile +supplied through a ConfigMap. Create it from the file in this repo so the +two cannot drift: + +``` +kubectl -n jenkins create configmap build-tools-src \ + --from-file=Dockerfile=build-tools.Dockerfile + +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +kind: Pod +metadata: + name: build-tools-build + namespace: jenkins +spec: + restartPolicy: Never + containers: + - name: docker + image: docker:27-dind + securityContext: + privileged: true + env: + - name: DOCKER_TLS_CERTDIR + value: "" + volumeMounts: + - name: docker-graph-storage + mountPath: /var/lib/docker + - name: registry-ca + mountPath: /etc/docker/certs.d/harbor.35.238.248.203.nip.io + readOnly: true + - name: docker-cli + image: docker:27-cli + command: ["cat"] + tty: true + env: + - name: DOCKER_HOST + value: tcp://localhost:2375 + # The classic builder. BuildKit wants to write its own state under + # /root/.docker, which is read-only wherever the push credentials are + # mounted as a file — the homelab hit exactly this. + - name: DOCKER_BUILDKIT + value: "0" + volumeMounts: + - name: src + mountPath: /src + volumes: + - name: docker-graph-storage + emptyDir: {} + - name: src + configMap: + name: build-tools-src + - name: registry-ca + configMap: + name: registry-ca +EOF +``` + +Then log in and build. The tag is deliberate: **bump it rather than +overwriting**, because `devops-lib-gcp`'s `dind-pod.yaml` pins this tag and +an overwritten tag rolls out silently on the next pod start. + +``` +kubectl wait --for=condition=Ready pod/build-tools-build -n jenkins --timeout=180s +kubectl exec -it -n jenkins build-tools-build -c docker-cli -- sh -c ' + for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break; sleep 2; done + docker login harbor.35.238.248.203.nip.io' + +kubectl exec -n jenkins build-tools-build -c docker-cli -- sh -c ' + cd /src && + docker build -t harbor.35.238.248.203.nip.io/base-images/build-tools:1 . && + docker push harbor.35.238.248.203.nip.io/base-images/build-tools:1' + +kubectl delete pod build-tools-build -n jenkins +kubectl delete configmap build-tools-src -n jenkins +``` + +Verify it has what the stages expect: + +``` +kubectl run bt --rm -it --restart=Never \ + --image=harbor.35.238.248.203.nip.io/base-images/build-tools:1 \ + -- sh -c 'git --version && yq --version && python3 -V && bash --version | head -1' +``` + +That pull needs no credentials — `base-images` is public — and the nodes +already trust the CA, which is what makes an ordinary pod able to run it. + ## Adding a new image or updating a version Add a line to `images.txt`, commit, push, then re-run step 3 — the diff --git a/build-tools.Dockerfile b/build-tools.Dockerfile new file mode 100644 index 0000000..1d2889e --- /dev/null +++ b/build-tools.Dockerfile @@ -0,0 +1,37 @@ +# The docker-cli image the pipeline's build pod runs its shell steps in. +# +# Bakes in everything the stages need — git, yq, bash, python3 with pip and +# venv for runHooks' python hooks, curl — so nothing is installed on demand +# on every single build. Installing tools per build was slow and, worse, +# quietly undermined reproducibility: a build's behaviour depended on +# whatever the package mirror served that morning. +# +# Lives here rather than in devops-lib because it is the same kind of thing +# as everything else in this repo: an image built by hand, occasionally, +# and pushed into Harbor for builds to pull. devops-lib only references the +# result by tag. +# +# Built and pushed manually — never by a Jenkins job, which would need this +# image to already exist in order to run. See README.md. +# +# Pushed to base-images, not homelab: that project is public, so build pods +# pull this with no credentials at all, which is the same reason the +# mirrored language images live there. +# +# The tag is pinned explicitly by devops-lib-gcp's dind-pod.yaml, so +# rebuilding this does not roll anything out until that pin is bumped too. +# Bump the tag when this file changes; do not overwrite an existing tag. +FROM harbor.35.238.248.203.nip.io/base-images/docker:27-cli + +# Pinned rather than the homelab's "releases/latest": an image that resolves +# a different yq every time it is built is not reproducible, and this is +# exactly the sort of thing that changes under you months later. Bump it +# deliberately. +ARG YQ_VERSION=v4.44.3 + +RUN apk add --no-cache git bash python3 py3-pip py3-virtualenv curl \ + && curl -sL -o /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ + && chmod +x /usr/local/bin/yq \ + && yq --version \ + && git --version diff --git a/images.txt b/images.txt index 974bc02..d7459ac 100644 --- a/images.txt +++ b/images.txt @@ -25,3 +25,8 @@ python:3.12-alpine python:3.12-alpine maven:3-eclipse-temurin-21-alpine maven:3-eclipse-temurin-21-alpine eclipse-temurin:21-jre-alpine eclipse-temurin:21-jre-alpine php:8.3-cli-alpine php:8.3-cli-alpine +# Not a language runtime: this is what build-tools.Dockerfile builds FROM. +# Mirrored for the same reason as everything else here — so building the +# build image does not depend on Docker Hub either. Keep the tag in step +# with the FROM line in build-tools.Dockerfile. +docker:27-cli docker:27-cli