Skip to content

Commit 2536a45

Browse files
committed
fix(ci): arm64 GB200 images + teardown-safe account derivation
Address review on #1774. - Derive+export AWS_ACCOUNT_ID (from .deployment.tenancy) before any lifecycle gate, and skip the TEST_CONFIG existence check for daytime-down, so a missing config can't block teardown re-auth and strand the Capacity Block. - Build main-cell images (snapshot-agent via ko, validators via buildx) for BUILD_PLATFORMS: multi-arch amd64+arm64 for GB200 (arm64 GPU pool), single-arch amd64 for h100. QEMU is set up only when cross-building arm64, so the common h100 nightly path is unaffected. Signed-off-by: Nathan Hensley <nhensley@nvidia.com>
1 parent 36bcfe2 commit 2536a45

1 file changed

Lines changed: 62 additions & 20 deletions

File tree

.github/workflows/uat-aws.yaml

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ jobs:
128128
EKS_IMAGE: "ghcr.io/mchmarny/cluster/eks@sha256:8bc33d14e6e5659d242aa2cdbcd69fca389cfcdb9e94f7a7a1b82fd33176befa"
129129
VALIDATOR_IMAGE_PREFIX: "ghcr.io/nvidia/aicr-validators"
130130
VALIDATOR_TAG: "uat-${{ github.run_id }}"
131+
# Target platform(s) for the main-cell image builds (snapshot-agent + the
132+
# validators). h100 GPU workers (p5.48xlarge) are amd64; GB200 GPU workers
133+
# (p6e-gb200.36xlarge) are Grace/arm64, and the agent + GPU-scheduled
134+
# validators are pinned to that pool — so an amd64-only image cannot start
135+
# there. GB200 therefore builds a multi-arch (amd64+arm64) manifest: amd64
136+
# still covers the m7i system pool, arm64 covers the GPU pool. h100 stays
137+
# single-arch so the common nightly path skips the slow QEMU arm64 cross
138+
# build. Release cells are unaffected (they pull the already-multi-arch
139+
# released images). Mirrors the release build's linux/amd64,linux/arm64.
140+
BUILD_PLATFORMS: ${{ inputs.accelerator == 'gb200' && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
131141
# Pin validator-image resolution for MAIN cells to this run's freshly
132142
# built+pushed validators (the `Build and push validator images` step
133143
# tags them `:uat-<run_id>`), mirroring how AICR_IMAGE pins the agent — so
@@ -165,22 +175,31 @@ jobs:
165175
nightly|daytime-up|daytime-down) ;;
166176
*) echo "::error::unknown lifecycle '${LIFECYCLE}' (want nightly|daytime-up|daytime-down)"; exit 1 ;;
167177
esac
168-
if [[ ! -f "${TEST_CONFIG}" ]]; then
169-
echo "::error::test config not found: ${TEST_CONFIG}"; exit 1
170-
fi
171-
# The account the OIDC role is assumed into (AWS_ACCOUNT_ID) is the
172-
# cluster-config's .deployment.tenancy — the single source of truth
173-
# for which tenancy this run provisions + keeps state in. Deriving it
174-
# here (rather than accepting it as an input) means the role can never
175-
# be assumed into a different account than the one being provisioned.
176-
# A cross-account reservation (e.g. a GB200 Capacity Block in a
177-
# separate tenancy) is selected purely by its cluster-config. Uses the
178-
# runner-preinstalled yq (Install tools runs later).
178+
# Derive + export the account FIRST, before any lifecycle-specific
179+
# gate below can exit. The account the OIDC role is assumed into
180+
# (AWS_ACCOUNT_ID) is the cluster-config's .deployment.tenancy — the
181+
# single source of truth for which tenancy this run provisions + keeps
182+
# state in. Deriving it here (rather than accepting it as an input)
183+
# means the role can never be assumed into a different account than
184+
# the one being provisioned; a cross-account reservation (e.g. a GB200
185+
# Capacity Block in a separate tenancy) is selected purely by its
186+
# cluster-config. Exporting it up front is load-bearing for teardown:
187+
# daytime-down re-authenticates and builds the role ARN from
188+
# AWS_ACCOUNT_ID, so a later early-exit here must not skip this — else
189+
# teardown cannot assume the role and the cluster + Capacity Block
190+
# leak. Uses the runner-preinstalled yq (Install tools runs later).
179191
TENANCY=$(yq eval '.deployment.tenancy' "${CLUSTER_CONFIG}")
180192
if [[ -z "${TENANCY}" || "${TENANCY}" == "null" ]]; then
181193
echo "::error::${CLUSTER_CONFIG} has no .deployment.tenancy"; exit 1
182194
fi
183195
echo "AWS_ACCOUNT_ID=${TENANCY}" >> "${GITHUB_ENV}"
196+
# The test config only matters for the run phases (nightly / daytime-up
197+
# provision+deploy). daytime-down just tears the held cluster down and
198+
# never reads it, so skip the existence check there — a missing sibling
199+
# config must not block teardown and strand the Capacity Block.
200+
if [[ "${LIFECYCLE}" != "daytime-down" && ! -f "${TEST_CONFIG}" ]]; then
201+
echo "::error::test config not found: ${TEST_CONFIG}"; exit 1
202+
fi
184203
echo "intent=${INTENT} lifecycle=${LIFECYCLE} test-config=${TEST_CONFIG} cluster=${DEPLOYMENT_ID} account=${TENANCY}"
185204
186205
# Versions
@@ -253,6 +272,18 @@ jobs:
253272
- name: Authenticate to GHCR
254273
uses: ./.github/actions/ghcr-login
255274

275+
# QEMU registers binfmt handlers so the amd64 runner can build the arm64
276+
# layer of a GB200 multi-arch image. Only needed when BUILD_PLATFORMS
277+
# includes arm64 (GB200); h100 builds native amd64 and skips it, so the
278+
# common nightly path pays no emulation cost. buildx (below) is always set
279+
# up because the multi-platform build path uses `buildx build --push`.
280+
- name: Set up QEMU
281+
if: inputs.aicr_version == '' && inputs.skip_tests != true && inputs.lifecycle != 'daytime-down' && contains(env.BUILD_PLATFORMS, 'arm64')
282+
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
283+
- name: Set up Docker Buildx
284+
if: inputs.aicr_version == '' && inputs.skip_tests != true && inputs.lifecycle != 'daytime-down'
285+
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
286+
256287
# Main cell only: release cells use the released validator images the
257288
# released binary self-resolves to (skipped when aicr_version is set).
258289
- name: Build and push validator images
@@ -271,20 +302,27 @@ jobs:
271302
# until the job timeout (#1636). It uses a different Dockerfile path
272303
# and takes no GO_VERSION build-arg. Keep the phase set in sync with
273304
# VALIDATOR_PHASES in on-push.yaml / on-tag.yaml.
305+
#
306+
# buildx build --push emits a multi-arch manifest for GB200
307+
# (BUILD_PLATFORMS=linux/amd64,linux/arm64) and a single-arch image
308+
# for h100 (linux/amd64) through one code path. Multi-platform builds
309+
# cannot be loaded into the local daemon, so we push directly (the
310+
# GHCR login above authenticates the push).
274311
for phase in deployment performance conformance aiperf-bench; do
275312
IMAGE="${VALIDATOR_IMAGE_PREFIX}/${phase}:${VALIDATOR_TAG}"
276313
case "${phase}" in
277314
aiperf-bench)
278-
docker build -f validators/performance/aiperf-bench.Dockerfile \
279-
-t "${IMAGE}" .
315+
docker buildx build --platform="${BUILD_PLATFORMS}" \
316+
-f validators/performance/aiperf-bench.Dockerfile \
317+
-t "${IMAGE}" --push .
280318
;;
281319
*)
282-
docker build -f "validators/${phase}/Dockerfile" \
320+
docker buildx build --platform="${BUILD_PLATFORMS}" \
321+
-f "validators/${phase}/Dockerfile" \
283322
--build-arg "GO_VERSION=${GO_VERSION}" \
284-
-t "${IMAGE}" .
323+
-t "${IMAGE}" --push .
285324
;;
286325
esac
287-
docker push "${IMAGE}"
288326
done
289327
290328
# Auth
@@ -418,9 +456,13 @@ jobs:
418456
# build — so any artifact-schema change in the PR under test (e.g. an
419457
# apiVersion bump) produces a snapshot the freshly-built binary rejects.
420458
# Build the agent from the PR source and point AICR_IMAGE at it so the
421-
# snapshot is stamped by the code under test, not a stale release. Single
422-
# arch: the GPU worker (p5.48xlarge) is amd64. Lives in the aicr-validators
423-
# namespace (not the release ghcr.io/nvidia/aicr repo); cleaned up below.
459+
# snapshot is stamped by the code under test, not a stale release. Built
460+
# for BUILD_PLATFORMS: amd64 for h100 (p5.48xlarge), amd64+arm64 for GB200
461+
# (the snapshot agent is pinned to the arm64 p6e-gb200 GPU pool). ko
462+
# cross-compiles Go natively — no QEMU — and emits a manifest list at the
463+
# single tag, so the kubelet pulls the node's arch. Lives in the
464+
# aicr-validators namespace (not the release ghcr.io/nvidia/aicr repo);
465+
# cleaned up below.
424466
- name: Build and push snapshot-agent image
425467
if: inputs.aicr_version == '' && inputs.skip_tests != true && inputs.lifecycle != 'daytime-down'
426468
env:
@@ -430,7 +472,7 @@ jobs:
430472
set -euo pipefail
431473
ko build ./cmd/aicr \
432474
--bare \
433-
--platform=linux/amd64 \
475+
--platform="${BUILD_PLATFORMS}" \
434476
--image-label="org.opencontainers.image.revision=${{ github.sha }}" \
435477
--tags="${VALIDATOR_TAG}"
436478

0 commit comments

Comments
 (0)