Модуль 16 / 18
CI/CD: buildx, multi-platform и immutable delivery
Содержание
1. Цели и результат CI/CD
После модуля вы сможете построить pipeline, который один раз собирает multi-platform image, проверяет его и продвигает один digest между окружениями.
- commit запускает сборку
- gates предшествуют публикации
- release задаётся digest
- promotion не пересобирает artifact
2. Теория: стадии pipeline
Pipeline разделяет lint и unit tests, проверку Dockerfile, build, integration tests, supply-chain gates, push и deployment. Ранний дешёвый отказ должен останавливать следующие стадии.
Runner входит в trusted computing base. Его image, Docker/Buildx versions, permissions и network access фиксируют. Общий долгоживущий daemon повышает риск утечки cache, credentials и privileged control.
3. Теория: Buildx и BuildKit
Buildx управляет builders, BuildKit выполняет build graph. docker-container driver даёт изолированный builder и поддерживает OCI, registry и cache exporters, но результат не загружается автоматически в классический image store.
Builder создают детерминированно, bootstrap проверяют до build, metadata и build record сохраняют как evidence.
4. Теория: multi-platform images
Multi-platform image — OCI index с manifest для каждой OS/architecture. Один digest index описывает весь набор, а registry выбирает подходящий manifest клиенту.
Стратегии: QEMU emulation, native nodes и cross-compilation. При cross-compilation build stage закрепляют за BUILDPLATFORM, компилятору передают TARGETOS, TARGETARCH и TARGETVARIANT.
5. Теория: cache и воспроизводимость
Для ephemeral runners используют registry или CI cache exporter. Cache лишь ускоряет build: scope учитывает platform, toolchain и lockfiles.
Base images и зависимости закрепляют digest/lockfile; secrets передают secret или SSH mounts, не ARG. SOURCE_DATE_EPOCH помогает, но одинаковый source не гарантирует digest при других inputs или builder.
6. Теория: immutable delivery
Tag удобен, но изменяем. После push pipeline получает digest из metadata или registry, проверяет repository@digest и передаёт его deployment-системе.
Promotion переносит проверенный manifest без rebuild. Production rebuild создаёт новый, ещё не проверенный artifact. Environment configuration хранится отдельно от image.
7. Методические указания
- Закрепляйте версии CI actions и builder
- Минимальные job permissions
- Не выдавайте secrets untrusted PR
- Tests до push
- SBOM, provenance, scan и signature
- Разные build/deploy identities
- Deployment concurrency control
- Сохраняйте digest и evidence
- Rollback на прежний digest
8. Лабораторная работа: multi-platform artifact
Создайте isolated builder и native build stage для двух TARGETPLATFORM.
rm -rf /tmp/docker-m16
mkdir -p /tmp/docker-m16/context /tmp/docker-m16/unpacked
cat > /tmp/docker-m16/context/Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM alpine:3.21 AS build
ARG BUILDPLATFORM
ARG TARGETPLATFORM
RUN printf 'built_on=%s\ntarget=%s\n' "$BUILDPLATFORM" "$TARGETPLATFORM" > /artifact.txt
FROM alpine:3.21
COPY --from=build /artifact.txt /artifact.txt
CMD ["cat", "/artifact.txt"]
EOF
docker buildx rm docker-m16 2>/dev/null || true
docker buildx create --name docker-m16 --driver docker-container --use --bootstrap
docker buildx inspect --bootstrapЭкспортируйте amd64 и arm64 в OCI archive. Provenance здесь отключён лишь для исследования index; в production включите его.
docker buildx build --builder docker-m16 \
--platform linux/amd64,linux/arm64 --provenance=false \
--metadata-file /tmp/docker-m16/metadata.json \
--output type=oci,dest=/tmp/docker-m16/image.oci.tar \
/tmp/docker-m16/context
tar -xf /tmp/docker-m16/image.oci.tar -C /tmp/docker-m16/unpacked
jq . /tmp/docker-m16/metadata.jsonРазрешите корневой digest и проверьте content-addressed manifests.
index_digest=$(jq -r '."containerimage.digest"' /tmp/docker-m16/metadata.json)
index_file="/tmp/docker-m16/unpacked/blobs/sha256/${index_digest#sha256:}"
test -f "$index_file"
jq -r '.manifests[].platform | "\(.os)/\(.architecture)"' "$index_file" | sort
actual="sha256:$(sha256sum "$index_file" | awk '{print $1}')"
test "$actual" = "$index_digest"
for digest in $(jq -r '.manifests[].digest' "$index_file"); do
blob="/tmp/docker-m16/unpacked/blobs/sha256/${digest#sha256:}"
test "sha256:$(sha256sum "$blob" | awk '{print $1}')" = "$digest"
printf 'verified_manifest=%s\n' "$digest"
donetest "$(jq -r '.manifests | length' "$index_file")" -eq 2
test "$(jq -r '[.manifests[].platform | select(.os=="linux" and (.architecture=="amd64" or .architecture=="arm64"))] | length' "$index_file")" -eq 2
printf 'release_candidate=%s\n' "$index_digest"Критерий: ровно две платформы, index и оба manifests прошли checksum.
9. Диагностика CI и release
Проверяйте цепочку checkout → bootstrap → dependencies → platform execution → cache → exporter → registry auth → policy → deploy.
docker buildx ls
docker buildx inspect docker-m16 --bootstrap
docker logs buildx_buildkit_docker-m160 --tail 100
jq . /tmp/docker-m16/metadata.json
tar -tf /tmp/docker-m16/image.oci.tar | sed -n '1,40p'
docker system df
df -h /var/lib/docker- exec format error — нет emulator
- no match for platform — нет base manifest
- failed to solve — найдите BuildKit vertex
- cache miss — scope/inputs
- denied — identity/scope
- digest изменился — изменились inputs, builder или был rebuild
10. Контроль и cleanup
- Buildx или BuildKit?
- Почему результат является index?
- Когда emulation, native или cross-build?
- Почему --load не для обычного multi-platform output?
- Почему tag недостаточен?
- Почему нельзя rebuild после tests?
- Какие evidence нужны?
docker buildx rm docker-m16
rm -rf /tmp/docker-m16
docker ps -a --filter name=buildx_buildkit_docker-m16Модуль освоен, если один index digest проходит build → verify → promote → deploy без пересборки.