Skip to content

Commit 1e749b5

Browse files
authored
build: Introduce "min" version of CI builder (#30694)
Adds a "min" flavor of the CI builder image which is ~1/10th the size of the full image. Because our Bazel build is fully hermetic, it already pulls in the necessary toolchains and thus the image we use can be quite small. The "min" image can be fetched in ~30 seconds, whereas the full image takes ~2 minutes and 30 seconds. This should save about 4 minutes end-to-end in CI because both the initial mkpipeline step and the Build steps will each save 2 minutes. Similarly most CI jobs just run other Docker images built by the initial step, so they also can use this "min" image, which should save another 2 minutes. ### Motivation Reduce CI time by ~4 minutes ### Checklist - [ ] This PR has adequate test coverage / QA involvement has been duly considered. ([trigger-ci for additional test/nightly runs](https://trigger-ci.dev.materialize.com/)) - [ ] This PR has an associated up-to-date [design doc](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/README.md), is a design doc ([template](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/00000000_template.md)), or is sufficiently small to not require a design. <!-- Reference the design in the description. --> - [ ] If this PR evolves [an existing `$T ⇔ Proto$T` mapping](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/command-and-response-binary-encoding.md) (possibly in a backwards-incompatible way), then it is tagged with a `T-proto` label. - [ ] If this PR will require changes to cloud orchestration or tests, there is a companion cloud PR to account for those changes that is tagged with the release-blocker label ([example](https://github.com/MaterializeInc/cloud/pull/5021)). <!-- Ask in #team-cloud on Slack if you need help preparing the cloud PR. --> - [ ] If this PR includes major [user-facing behavior changes](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/guide-changes.md#what-changes-require-a-release-note), I have pinged the relevant PM to schedule a changelog post.
1 parent 6d39009 commit 1e749b5

File tree

6 files changed

+137
-74
lines changed

6 files changed

+137
-74
lines changed

bin/ci-builder

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ cd "$(dirname "$0")/.."
2424

2525
if [[ $# -lt 2 ]]
2626
then
27-
echo "usage: $0 <command> <stable|nightly> [<args>...]
27+
echo "usage: $0 <command> <stable|nightly|min> [<args>...]
2828
2929
Manages the ci-builder Docker image, which contains the dependencies required
3030
to build, test, and deploy the code in this repository.
@@ -40,25 +40,33 @@ For details, consult ci/builder/README.md."
4040
fi
4141

4242
cmd=$1 && shift
43-
channel=$1 && shift
43+
flavor=$1 && shift
4444

4545
rust_date=
46-
case "$channel" in
47-
stable) rust_version=$(sed -n 's/rust-version = "\(.*\)"/\1/p' Cargo.toml) ;;
46+
case "$flavor" in
47+
min)
48+
docker_target=ci-builder-min
49+
rust_version=$(sed -n 's/rust-version = "\(.*\)"/\1/p' Cargo.toml)
50+
;;
51+
stable)
52+
docker_target=ci-builder-full
53+
rust_version=$(sed -n 's/rust-version = "\(.*\)"/\1/p' Cargo.toml)
54+
;;
4855
nightly)
56+
docker_target=ci-builder-full
4957
rust_version=nightly
5058
rust_date=/$NIGHTLY_RUST_DATE
5159
;;
5260
*)
53-
printf "unknown rust channel %q\n" "$channel"
61+
printf "unknown CI builder flavor %q\n" "$flavor"
5462
exit 1
5563
;;
5664
esac
5765

5866
arch_gcc=${MZ_DEV_CI_BUILDER_ARCH:-$(arch_gcc)}
5967
arch_go=$(arch_go "$arch_gcc")
6068

61-
cid_file=ci/builder/.${channel%%-*}.cidfile
69+
cid_file=ci/builder/.${flavor%%-*}.cidfile
6270

6371
rust_components=rustc,cargo,rust-std-$arch_gcc-unknown-linux-gnu,llvm-tools-preview
6472
if [[ $rust_version = nightly ]]; then
@@ -86,6 +94,7 @@ build() {
8694
--build-arg "BAZEL_VERSION=$bazel_version" \
8795
--tag materialize/ci-builder:"$tag" \
8896
--tag materialize/ci-builder:"$cache_tag" \
97+
--target $docker_target \
8998
"$@" ci/builder
9099
}
91100

@@ -111,6 +120,7 @@ files+="
111120
rust-version:$rust_version
112121
rust-date:$rust_date
113122
arch:$arch_gcc
123+
flavor:$flavor
114124
"
115125
tag=$(echo "$files" | python3 -c '
116126
import base64
@@ -121,7 +131,7 @@ input = sys.stdin.buffer.read()
121131
hash = base64.b32encode(hashlib.sha1(input).digest())
122132
print(hash.decode())
123133
')
124-
cache_tag=cache-$rust_version-$arch_go
134+
cache_tag=cache-$flavor-$rust_version-$arch_go
125135

126136

127137
case "$cmd" in

ci/builder/Dockerfile

Lines changed: 109 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,112 @@
77
# the Business Source License, use of this software will be governed
88
# by the Apache License, Version 2.0.
99

10-
# Build a cross-compiling toolchain that targets the oldest version of Linux
11-
# that we support.
10+
# Stage 1: Build a minimum CI Builder image that we can use for the initial
11+
# steps like `mkpipeline` and `Build`, as well as any tests that are self
12+
# contained and use other Docker images.
13+
FROM ubuntu:noble-20241015 AS ci-builder-min
1214

15+
WORKDIR /workdir
16+
17+
ARG ARCH_GCC
18+
ARG ARCH_GO
19+
20+
# Environment variables that should be set for the entire build container.
21+
22+
# Ensure any Rust binaries that crash print a backtrace.
23+
ENV RUST_BACKTRACE=1
24+
# Ensure that all python output is unbuffered, otherwise it is not
25+
# logged properly in Buildkite.
26+
ENV PYTHONUNBUFFERED=1
27+
# Set a environment variable that tools can check to see if they're in the
28+
# builder or not.
29+
ENV MZ_DEV_CI_BUILDER=1
30+
31+
# Faster uncompression
32+
ARG XZ_OPT=-T0
33+
34+
# Absolute minimum set of dependencies needed for a CI job.
35+
#
36+
# Please take care with what gets added here. The goal of this initial layer is to be as small as
37+
# possible since it's used for the `mkpipeline` and `Build` CI jobs, which block __all other__
38+
# jobs.
39+
RUN apt-get update --fix-missing && TZ=UTC DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
40+
ca-certificates \
41+
curl \
42+
docker.io \
43+
gdb \
44+
git \
45+
gnupg2 \
46+
libxml2 \
47+
python3 \
48+
&& rm -rf /var/lib/apt/lists/*
49+
50+
# Install Python dependencies. These are necessary to run some of our base tooling.
51+
COPY requirements.txt /workdir/
52+
RUN curl -LsSf https://astral.sh/uv/0.4.25/install.sh | UV_INSTALL_DIR=/usr/local UV_UNMANAGED_INSTALL=1 sh \
53+
&& uv pip install --system --break-system-packages -r /workdir/requirements.txt && rm /workdir/requirements*.txt
54+
55+
# Install extra tools not available in apt repositories.
56+
57+
COPY rust.asc .
58+
RUN gpg --import rust.asc \
59+
&& rm rust.asc \
60+
&& echo "trusted-key 85AB96E6FA1BE5FE" >> ~/.gnupg/gpg.conf
61+
62+
ARG BAZEL_VERSION
63+
ARG RUST_DATE
64+
ARG RUST_VERSION
65+
66+
RUN \
67+
# 1. autouseradd
68+
#
69+
# Ensures that the UID used when running the container has a proper entry in
70+
# `/etc/passwd`, and writable home directory.
71+
curl -fsSL https://github.com/benesch/autouseradd/releases/download/1.3.0/autouseradd-1.3.0-$ARCH_GO.tar.gz \
72+
| tar xz -C / --strip-components 1 \
73+
# 2. Bazel
74+
#
75+
# We primarily build Materialize via Bazel in CI, and Bazel pulls in its own dependencies.
76+
&& arch_bazel=$(echo "$ARCH_GCC" | sed -e "s/aarch64/arm64/" -e "s/amd64/x86_64/") bazel_version=$(echo "$BAZEL_VERSION") \
77+
&& curl -fsSL -o /usr/local/bin/bazel https://github.com/bazelbuild/bazel/releases/download/$bazel_version/bazel-$bazel_version-linux-$arch_bazel \
78+
&& if [ "$arch_bazel" = arm64 ]; then echo 'fac4b954e0501c2be8b9653a550b443eb85284e568d08b102977e2bf587b09d7 /usr/local/bin/bazel' | sha256sum --check; fi \
79+
&& if [ "$arch_bazel" = x86_64 ]; then echo '48ea0ff9d397a48add6369c261c5a4431fe6d5d5348cfb81411782fb80c388d3 /usr/local/bin/bazel' | sha256sum --check; fi \
80+
&& chmod +x /usr/local/bin/bazel \
81+
# 3. Docker
82+
#
83+
# If you upgrade Docker (Compose) version here, also update it in misc/python/cli/mzcompose.py.
84+
&& mkdir -p /usr/local/lib/docker/cli-plugins \
85+
&& curl -fsSL https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-linux-$ARCH_GCC > /usr/local/lib/docker/cli-plugins/docker-compose \
86+
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-compose \
87+
&& curl -fsSL https://github.com/christian-korneck/docker-pushrm/releases/download/v1.9.0/docker-pushrm_linux_$ARCH_GO > /usr/local/lib/docker/cli-plugins/docker-pushrm \
88+
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-pushrm \
89+
# 4. Cargo
90+
#
91+
# Some parts of our stack use 'cargo' to read metadata, so we install just that. Importantly we
92+
# do not install 'rustc' or any of the other tools, this keeps the Docker image small.
93+
&& mkdir rust \
94+
&& curl -fsSL https://static.rust-lang.org/dist$RUST_DATE/rust-$RUST_VERSION-$ARCH_GCC-unknown-linux-gnu.tar.gz > rust.tar.gz \
95+
&& curl -fsSL https://static.rust-lang.org/dist$RUST_DATE/rust-$RUST_VERSION-$ARCH_GCC-unknown-linux-gnu.tar.gz.asc > rust.asc \
96+
&& gpg --verify rust.asc rust.tar.gz \
97+
&& tar -xzf rust.tar.gz -C rust --strip-components=1 \
98+
&& rust/install.sh --components=cargo \
99+
&& rm -rf rust.asc rust.tar.gz rust
100+
101+
# Make the image as small as possible.
102+
RUN find /workdir /root -mindepth 1 -maxdepth 1 -exec rm -rf {} +
103+
104+
# Remove Ubuntu user causing UID collisions.
105+
# https://bugs.launchpad.net/cloud-images/+bug/2005129
106+
RUN userdel -r ubuntu
107+
108+
ENTRYPOINT ["autouseradd", "--user", "materialize"]
109+
110+
# Stage 2. Build a cross-compiling toolchain that targets the oldest version of
111+
# Linux that we support.
112+
#
113+
# TODO(parkmycar): This shouldn't be necessary anymore with Bazel.
13114
FROM ubuntu:noble-20241015 as crosstool
115+
14116
ARG ARCH_GCC
15117
ARG ARCH_GO
16118

@@ -68,10 +170,10 @@ RUN DEFCONFIG=crosstool-$ARCH_GCC.defconfig ct-ng defconfig \
68170
&& rm crosstool-$ARCH_GCC.defconfig \
69171
&& ct-ng build
70172

71-
# Import the cross-compiling toolchain into a fresh image, omitting the
72-
# dependencies that we needed to actually build the toolchain.
173+
# Stage 3: Build a full CI Builder image that imports the cross-compiling
174+
# toolchain and can be used for any CI job.
175+
FROM ci-builder-min as ci-builder-full
73176

74-
FROM ubuntu:noble-20241015
75177
ARG ARCH_GCC
76178
ARG ARCH_GO
77179

@@ -135,9 +237,7 @@ RUN gpg --dearmor < nodesource.asc > /etc/apt/keyrings/nodesource.gpg \
135237
&& apt-get update \
136238
&& apt-get install -y --no-install-recommends nodejs
137239

138-
RUN curl -fsSL https://github.com/benesch/autouseradd/releases/download/1.3.0/autouseradd-1.3.0-$ARCH_GO.tar.gz \
139-
| tar xz -C / --strip-components 1 \
140-
&& curl -fsSL https://github.com/koalaman/shellcheck/releases/download/v0.8.0/shellcheck-v0.8.0.linux.$ARCH_GCC.tar.xz > shellcheck.tar.xz \
240+
RUN curl -fsSL https://github.com/koalaman/shellcheck/releases/download/v0.8.0/shellcheck-v0.8.0.linux.$ARCH_GCC.tar.xz > shellcheck.tar.xz \
141241
&& tar -xJf shellcheck.tar.xz -C /usr/local/bin --strip-components 1 shellcheck-v0.8.0/shellcheck \
142242
&& rm shellcheck.tar.xz \
143243
&& curl -fsSL https://github.com/bufbuild/buf/releases/download/v1.18.0/buf-Linux-$ARCH_GCC.tar.gz > buf.tar.gz \
@@ -148,12 +248,6 @@ RUN curl -fsSL https://github.com/benesch/autouseradd/releases/download/1.3.0/au
148248
&& tar -xf kail.tar.gz -C /usr/local/bin kail \
149249
&& rm kail.tar.gz \
150250
&& chmod +x /usr/local/bin/kail \
151-
&& mkdir -p /usr/local/lib/docker/cli-plugins \
152-
# If you upgrade Docker (Compose) version here, also update it in misc/python/cli/mzcompose.py \
153-
&& curl -fsSL https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-linux-$ARCH_GCC > /usr/local/lib/docker/cli-plugins/docker-compose \
154-
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-compose \
155-
&& curl -fsSL https://github.com/christian-korneck/docker-pushrm/releases/download/v1.9.0/docker-pushrm_linux_$ARCH_GO > /usr/local/lib/docker/cli-plugins/docker-pushrm \
156-
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-pushrm \
157251
&& curl -fsSL https://github.com/parca-dev/parca-debuginfo/releases/download/v0.11.0/parca-debuginfo_0.11.0_Linux_$(echo "$ARCH_GCC" | sed "s/aarch64/arm64/").tar.gz \
158252
| tar xz -C /usr/local/bin parca-debuginfo
159253

@@ -212,8 +306,6 @@ RUN mkdir rust \
212306
RUN ln -s /usr/bin/lld /opt/x-tools/$ARCH_GCC-unknown-linux-gnu/bin/$ARCH_GCC-unknown-linux-gnu-ld.lld \
213307
&& ln -s /usr/bin/lld /opt/x-tools/$ARCH_GCC-unknown-linux-gnu/bin/$ARCH_GCC-unknown-linux-gnu-lld
214308

215-
RUN curl -LsSf https://astral.sh/uv/0.4.25/install.sh | UV_INSTALL_DIR=/usr/local UV_UNMANAGED_INSTALL=1 sh
216-
217309
# Shims for sanitizers
218310
COPY sanshim/$ARCH_GCC /sanshim
219311

@@ -223,13 +315,6 @@ COPY sanshim/$ARCH_GCC /sanshim
223315
COPY pyright-version.sh /workdir/
224316
RUN npx pyright@$(sh /workdir/pyright-version.sh) --help
225317

226-
# Install Python dependencies. These are so quick to install and change
227-
# frequently enough that it makes sense to install them last.
228-
229-
COPY requirements.txt /workdir/
230-
231-
RUN uv pip install --system --break-system-packages -r /workdir/requirements.txt && rm /workdir/requirements*.txt
232-
233318
# Install APT repo generator.
234319

235320
RUN curl -fsSL https://github.com/deb-s3/deb-s3/releases/download/0.11.3/deb-s3-0.11.3.gem > deb-s3.gem \
@@ -258,21 +343,7 @@ RUN if [ $ARCH_GCC = x86_64 ]; then \
258343
&& rm hugo.tar.gz; \
259344
fi
260345

261-
# Install Bazel.
262-
#
263-
# TODO(parkmycar): Run Bazel in a Docker image that does not have access to clang/gcc or any other tools.
264-
265-
ARG BAZEL_VERSION
266-
267-
# Download the bazel binary from the official GitHub releases since the apt repositories do not
268-
# contain arm64 releases.
269-
RUN arch_bazel=$(echo "$ARCH_GCC" | sed -e "s/aarch64/arm64/" -e "s/amd64/x86_64/") bazel_version=$(echo "$BAZEL_VERSION") \
270-
&& curl -fsSL -o /usr/local/bin/bazel https://github.com/bazelbuild/bazel/releases/download/$bazel_version/bazel-$bazel_version-linux-$arch_bazel \
271-
&& if [ "$arch_bazel" = arm64 ]; then echo 'fac4b954e0501c2be8b9653a550b443eb85284e568d08b102977e2bf587b09d7 /usr/local/bin/bazel' | sha256sum --check; fi \
272-
&& if [ "$arch_bazel" = x86_64 ]; then echo '48ea0ff9d397a48add6369c261c5a4431fe6d5d5348cfb81411782fb80c388d3 /usr/local/bin/bazel' | sha256sum --check; fi \
273-
&& chmod +x /usr/local/bin/bazel
274-
275-
# Install KinD, kubectl, helm, helm-docs & terraform
346+
# Install KinD, kubectl, helm & helm-docs
276347

277348
RUN curl -fsSL https://kind.sigs.k8s.io/dl/v0.14.0/kind-linux-$ARCH_GO > /usr/local/bin/kind \
278349
&& chmod +x /usr/local/bin/kind \
@@ -340,28 +411,11 @@ ENV CARGO_TARGET_DIR=/mnt/build
340411
ENV CARGO_INCREMENTAL=0
341412
ENV HELM_PLUGINS=/usr/local/share/helm/plugins
342413

343-
# Set a environment variable that tools can check to see if they're in the
344-
# builder or not.
345-
346-
ENV MZ_DEV_CI_BUILDER=1
347-
348414
# Set up for a persistent volume to hold Cargo metadata, so that crate metadata
349415
# does not need to be refetched on every compile.
350-
351416
ENV CARGO_HOME=/cargo
352417
RUN mkdir /cargo && chmod 777 /cargo
353418
VOLUME /cargo
354419

355-
# Ensure any Rust binaries that crash print a backtrace.
356-
ENV RUST_BACKTRACE=1
357-
358420
# Make the image as small as possible.
359421
RUN find /workdir /root -mindepth 1 -maxdepth 1 -exec rm -rf {} +
360-
361-
# remove Ubuntu user causing UID collisions
362-
# https://bugs.launchpad.net/cloud-images/+bug/2005129
363-
RUN userdel -r ubuntu
364-
365-
# Ensure that all python output is unbuffered, otherwise it is not
366-
# logged properly in Buildkite
367-
ENV PYTHONUNBUFFERED=1

ci/mkpipeline.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ pipeline=${1:-test}
2323
bootstrap_steps=
2424

2525
for arch in x86_64 aarch64; do
26-
for toolchain in stable nightly; do
27-
if ! MZ_DEV_CI_BUILDER_ARCH=$arch bin/ci-builder exists $toolchain; then
26+
for flavor in stable nightly min; do
27+
if ! MZ_DEV_CI_BUILDER_ARCH=$arch bin/ci-builder exists $flavor; then
2828
queue=builder-linux-x86_64
2929
if [[ $arch = aarch64 ]]; then
3030
queue=builder-linux-aarch64-mem
3131
fi
3232
bootstrap_steps+="
33-
- label: bootstrap $toolchain $arch
34-
command: bin/ci-builder push $toolchain
33+
- label: bootstrap $flavor $arch
34+
command: bin/ci-builder push $flavor
3535
agents:
3636
queue: $queue
3737
"
@@ -47,7 +47,7 @@ steps:
4747
env:
4848
CI_BAZEL_BUILD: 1
4949
CI_BAZEL_REMOTE_CACHE: "https://bazel-remote.dev.materialize.com"
50-
command: bin/ci-builder run stable bin/pyactivate -m ci.mkpipeline $pipeline $@
50+
command: bin/ci-builder run min bin/pyactivate -m ci.mkpipeline $pipeline $@
5151
priority: 200
5252
agents:
5353
queue: hetzner-aarch64-4cpu-8gb

ci/nightly/pipeline.template.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ steps:
2020
steps:
2121
- id: build-x86_64
2222
label: ":bazel: Build x86_64"
23-
command: bin/ci-builder run stable bin/pyactivate -m ci.test.build
23+
command: bin/ci-builder run min bin/pyactivate -m ci.test.build
2424
inputs:
2525
- "*"
2626
artifact_paths: bazel-explain.log
@@ -31,7 +31,7 @@ steps:
3131

3232
- id: build-aarch64
3333
label: ":bazel: Build aarch64"
34-
command: bin/ci-builder run stable bin/pyactivate -m ci.test.build
34+
command: bin/ci-builder run min bin/pyactivate -m ci.test.build
3535
inputs:
3636
- "*"
3737
artifact_paths: bazel-explain.log

ci/release-qualification/pipeline.template.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ steps:
2020
steps:
2121
- id: build-aarch64
2222
label: ":bazel: Build aarch64"
23-
command: bin/ci-builder run stable bin/pyactivate -m ci.test.build
23+
command: bin/ci-builder run min bin/pyactivate -m ci.test.build
2424
inputs:
2525
- "*"
2626
artifact_paths: bazel-explain.log
@@ -39,7 +39,7 @@ steps:
3939
label: ":bazel: Build x86_64"
4040
env:
4141
CI_BAZEL_BUILD: "1"
42-
command: bin/ci-builder run stable bin/pyactivate -m ci.test.build
42+
command: bin/ci-builder run min bin/pyactivate -m ci.test.build
4343
inputs:
4444
- "*"
4545
artifact_paths: bazel-explain.log

ci/test/pipeline.template.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
dag: true
1616

1717
env:
18-
CI_BUILDER_SCCACHE: 1
1918
CI_BAZEL_BUILD: 1
2019
CI_BAZEL_REMOTE_CACHE: $BAZEL_REMOTE_CACHE
2120
# Note: In .cargo/config we set the default build jobs to -1 so on developer machines we keep
@@ -90,7 +89,7 @@ steps:
9089
- id: build-x86_64
9190
label: ":bazel: Build x86_64"
9291
env:
93-
command: bin/ci-builder run stable bin/pyactivate -m ci.test.build
92+
command: bin/ci-builder run min bin/pyactivate -m ci.test.build
9493
inputs:
9594
- "*"
9695
artifact_paths: bazel-explain.log
@@ -102,7 +101,7 @@ steps:
102101
- id: build-aarch64
103102
label: ":bazel: Build aarch64"
104103
env:
105-
command: bin/ci-builder run stable bin/pyactivate -m ci.test.build
104+
command: bin/ci-builder run min bin/pyactivate -m ci.test.build
106105
inputs:
107106
- "*"
108107
artifact_paths: bazel-explain.log

0 commit comments

Comments
 (0)