diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 730b1fd9..e1739883 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -21,6 +21,9 @@ jobs: with: go-version-file: go.mod + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 + - name: Login to Quay.io uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: @@ -28,5 +31,5 @@ jobs: username: ${{ secrets.APP_QUAY_USERNAME }} password: ${{ secrets.APP_QUAY_TOKEN }} - - name: Build and push latest image - run: make image-build image-push -e IMG=quay.io/llamastack/llama-stack-k8s-operator:latest + - name: Build and push multi-arch image + run: make image-buildx -e IMG=quay.io/llamastack/llama-stack-k8s-operator:latest diff --git a/.github/workflows/generate-release.yml b/.github/workflows/generate-release.yml index 324370fb..46e57e1e 100644 --- a/.github/workflows/generate-release.yml +++ b/.github/workflows/generate-release.yml @@ -165,6 +165,9 @@ jobs: run: | make test + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 + - name: Check E2E Test Results run: | echo "E2E Test Results:" @@ -183,7 +186,7 @@ jobs: - name: Validate build release image shell: bash run: | - make image-build IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ steps.validate.outputs.operator_version }} + make image-buildx IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ steps.validate.outputs.operator_version }} - name: Commit and push changes shell: bash @@ -270,10 +273,8 @@ jobs: with: go-version-file: go.mod - - name: Build release image - shell: bash - run: | - make image-build IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 - name: Log in to Quay.io uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 @@ -282,11 +283,11 @@ jobs: username: ${{ secrets.APP_QUAY_USERNAME }} password: ${{ secrets.APP_QUAY_TOKEN }} - - name: Push release image + - name: Build and push multi-arch release image shell: bash run: | - make image-push IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }} - echo "✅ Pushed: quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}" + make image-buildx IMG=quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }} + echo "✅ Pushed multi-arch image: quay.io/llamastack/llama-stack-k8s-operator:v${{ env.operator_version }}" - name: Release complete shell: bash diff --git a/.github/workflows/release-image.yml b/.github/workflows/release-image.yml index ae2d35f9..034403c1 100644 --- a/.github/workflows/release-image.yml +++ b/.github/workflows/release-image.yml @@ -30,6 +30,9 @@ jobs: with: ref: ${{ github.event.inputs.release_branch }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 + - name: Login to Quay.io uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: @@ -37,6 +40,6 @@ jobs: username: ${{ secrets.APP_QUAY_USERNAME }} password: ${{ secrets.APP_QUAY_TOKEN }} - - name: Build and push release image + - name: Build and push multi-arch release image run: | - make image -e IMG=quay.io/llamastack/llama-stack-k8s-operator:${{ github.event.inputs.version }} + make image-buildx -e IMG=quay.io/llamastack/llama-stack-k8s-operator:${{ github.event.inputs.version }} diff --git a/Dockerfile b/Dockerfile index 8db16319..23929279 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,17 @@ # Build the manager binary ARG GOLANG_VERSION=1.24 -FROM registry.access.redhat.com/ubi9/go-toolset:${GOLANG_VERSION} as builder +# Use BUILDPLATFORM to run the builder natively (avoid QEMU emulation for Go compilation) +# This dramatically improves build reliability and speed for cross-platform builds +FROM --platform=$BUILDPLATFORM registry.access.redhat.com/ubi9/go-toolset:${GOLANG_VERSION} as builder ARG TARGETOS=linux ARG TARGETARCH -ARG CGO_ENABLED=1 -ARG GOTAGS=strictfipsruntime,openssl +ARG BUILDPLATFORM +ARG TARGETPLATFORM + +# FIPS compliance settings +# For native builds: CGO_ENABLED=1 with full FIPS OpenSSL support +# For cross-builds: CGO_ENABLED=0 with pure Go FIPS (strictfipsruntime) ENV GOEXPERIMENT=strictfipsruntime WORKDIR /workspace @@ -24,19 +30,37 @@ COPY pkg/ pkg/ COPY distributions.json distributions.json # Build the manager binary +# Cross-compilation is handled natively by Go via GOOS and GOARCH +# This runs on the build host's native architecture, not under QEMU emulation USER root -# GOARCH is intentionally left empty to automatically detect the host architecture -# This ensures the binary matches the platform where image-build is executed -RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -tags=${GOTAGS} -o manager main.go +# Determine if we're cross-compiling by comparing BUILDPLATFORM and TARGETPLATFORM +# - Native builds (same platform): CGO_ENABLED=1 with openssl tag for full FIPS OpenSSL support +# - Cross builds (different platform): CGO_ENABLED=0 with pure Go FIPS (no CGO = no cross-compiler needed) +RUN echo "Building for TARGETPLATFORM=${TARGETPLATFORM} on BUILDPLATFORM=${BUILDPLATFORM}" && \ + if [ "${BUILDPLATFORM}" = "${TARGETPLATFORM}" ]; then \ + echo "Native build detected - using CGO_ENABLED=1 with OpenSSL FIPS"; \ + CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build -a -tags=strictfipsruntime,openssl -o manager main.go; \ + else \ + echo "Cross-compilation detected - using CGO_ENABLED=0 with pure Go FIPS"; \ + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build -a -tags=strictfipsruntime -o manager main.go; \ + fi -# Use distroless as minimal base image to package the manager binary -# Refer to https://github.com/GoogleContainerTools/distroless for more details +# Use UBI minimal as the runtime base image +# This stage runs under QEMU for cross-platform builds, but the workload is minimal FROM registry.access.redhat.com/ubi9/ubi-minimal:latest +ARG TARGETARCH + WORKDIR / COPY --from=builder /workspace/manager . COPY --from=builder /workspace/controllers/manifests ./manifests/ -RUN microdnf install -y openssl && microdnf clean all + +# Install openssl - use minimal options for reliability under QEMU emulation +RUN microdnf install -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs openssl && \ + microdnf clean all + USER 1001 ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile index d6014f98..4655fb53 100644 --- a/Makefile +++ b/Makefile @@ -179,21 +179,41 @@ image-push: ## Push image with the manager. $(CONTAINER_TOOL) push ${IMG} # PLATFORMS defines the target platforms for the manager image be built to provide support to multiple -# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# architectures. (i.e. make image-buildx IMG=myregistry/myoperator:0.0.1). To use this option you need to: # - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ # - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ # - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) # To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. -PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le -.PHONY: docker-buildx -docker-buildx: ## Build and push docker image for the manager for cross-platform support - # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile - sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - - $(CONTAINER_TOOL) buildx create --name x-builder +# Note: The Dockerfile uses --platform=$BUILDPLATFORM for native cross-compilation, avoiding QEMU emulation +# for Go builds which dramatically improves reliability and speed. +PLATFORMS ?= linux/amd64,linux/arm64 + +.PHONY: image-buildx +image-buildx: ## Build and push multi-arch image with the manager (uses native cross-compilation) + @echo "Building multi-arch image for platforms: $(PLATFORMS)" +ifeq ($(CONTAINER_TOOL),docker) + - $(CONTAINER_TOOL) buildx create --name x-builder 2>/dev/null || true $(CONTAINER_TOOL) buildx use x-builder - - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - $(CONTAINER_TOOL) buildx rm x-builder - rm Dockerfile.cross + $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} . +else + # Podman: Use manifest-based multi-arch build + $(CONTAINER_TOOL) manifest rm ${IMG} 2>/dev/null || true + $(CONTAINER_TOOL) manifest create ${IMG} + @for platform in $$(echo $(PLATFORMS) | tr ',' ' '); do \ + echo "Building for $$platform..."; \ + $(CONTAINER_TOOL) build --platform $$platform --manifest ${IMG} . ; \ + done + $(CONTAINER_TOOL) manifest push ${IMG} +endif + @echo "Successfully pushed multi-arch image: ${IMG}" + +.PHONY: image-build-arm +image-build-arm: ## Build ARM64 image with the manager + $(CONTAINER_TOOL) build --platform linux/arm64 -t ${IMG} . + +# Legacy docker-buildx target (deprecated, use image-buildx instead) +.PHONY: docker-buildx +docker-buildx: image-buildx ## Deprecated: use image-buildx instead # Installer directory is updated to `release` from operator-sdk default `dist` directory. .PHONY: build-installer diff --git a/README.md b/README.md index a10a9533..77c5dd64 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,48 @@ Set `enabled: false` (or remove the block) to turn the feature back off; the ope The default image used is `quay.io/llamastack/llama-stack-k8s-operator:latest` when not supply argument for `make image` To create a local file `local.mk` with env variables can overwrite the default values set in the `Makefile`. +- Building multi-architecture images (ARM64, AMD64, etc.) + + The operator supports building for multiple architectures including ARM64. To build and push multi-arch images: + + ```commandline + make image-buildx IMG=quay.io//llama-stack-k8s-operator: + ``` + + By default, this builds for `linux/amd64,linux/arm64`. You can customize the platforms by setting the `PLATFORMS` variable: + + ```commandline + # Build for specific platforms + make image-buildx IMG=quay.io//llama-stack-k8s-operator: PLATFORMS=linux/amd64,linux/arm64 + + # Add more architectures (e.g., for future support) + make image-buildx IMG=quay.io//llama-stack-k8s-operator: PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le + ``` + + **Note**: + - The `image-buildx` target works with both Docker and Podman. It will automatically detect which tool is being used. + - **Native cross-compilation**: The Dockerfile uses `--platform=$BUILDPLATFORM` to run Go compilation natively on the build host, avoiding QEMU emulation for the build process. This dramatically improves build speed and reliability. Only the minimal final stage (package installation) runs under QEMU for cross-platform builds. + - **FIPS adherence**: Native builds use `CGO_ENABLED=1` with full OpenSSL FIPS support. Cross-compiled builds use `CGO_ENABLED=0` with pure Go FIPS (via `GOEXPERIMENT=strictfipsruntime`). Both approaches are Designed for FIPS. + - For Docker: Multi-arch builds require Docker Buildx. Ensure Docker Buildx is set up: + + ```commandline + docker buildx create --name x-builder --use + ``` + + - For Podman: Podman 4.0+ supports `podman buildx` (experimental). If buildx is unavailable, the Makefile will automatically fall back to using podman's native manifest-based multi-arch build approach. + - The resulting images are multi-arch manifest lists, which means Kubernetes will automatically select the correct architecture when pulling the image. + +- Building ARM64-only images + + To build a single ARM64 image (useful for testing or ARM-native systems): + + ```commandline + make image-build-arm IMG=quay.io//llama-stack-k8s-operator: + make image-push IMG=quay.io//llama-stack-k8s-operator: + ``` + + This works with both Docker and Podman. + - Once the image is created, the operator can be deployed directly. For each deployment method a kubeconfig should be exported