From b21b2bd147a1f8bcaf4133cd3d5e54f9203468c9 Mon Sep 17 00:00:00 2001 From: MikelAlejoBR Date: Tue, 23 Dec 2025 09:43:06 -0500 Subject: [PATCH] feature: Makefile targets to build the operator in debug mode In order to be able to debug the operator live in the local cluster, we need the binary to be executed with Delve instead, and we need it to be built without any code optimizations or minimization so that the breakpoints work. For that purpose, new Dockerfiles are added which can build the images that way, and the corresponding Make targets make it easy to kick off the whole process with a single command. The Makefile targets also enable the "toolchain-e2e" repository to easily build the "debug" images. SANDBOX-1561 --- build/Dockerfile | 54 ++++++++++++++++++++++++++++++++++++--- build/Dockerfile.webhook | 55 +++++++++++++++++++++++++++++++++++++--- make/go.mk | 19 ++++++++++++++ make/podman.mk | 14 ++++++++++ 4 files changed, 136 insertions(+), 6 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 17797ce7c..bae2af301 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,7 +1,12 @@ -FROM registry.access.redhat.com/ubi8/ubi-minimal:latest +# Define the default build as "prod", so that in the case that no build +# arguments are provided, we build the optimized image. +ARG BUILD_TYPE=prod -LABEL maintainer "KubeSaw " -LABEL author "KubeSaw " +# Default production build steps for the operator. +FROM registry.access.redhat.com/ubi8/ubi-minimal:latest AS prod-build + +LABEL maintainer="KubeSaw " +LABEL author="KubeSaw " ENV OPERATOR=/usr/local/bin/member-operator \ USER_UID=1001 \ @@ -16,4 +21,47 @@ RUN /usr/local/bin/user_setup ENTRYPOINT ["/usr/local/bin/entrypoint"] +# A throwaway intermediate path which compiles Delve with the same Golang +# version as the host computer. That ensures no discrepancies in the code +# when setting up breakpoints when debugging. +FROM registry.access.redhat.com/ubi8/ubi-minimal:latest AS debug-initial-build + +# In this build path, the Golang version must be provided as a build argument, +# which will ensure that Delve gets built with the same version the operator's +# binary gets built with, to avoid any discrepancies. +ARG GOLANG_VERSION +RUN test -n "${GOLANG_VERSION}" || (echo 'The GOLANG_VERSION build parameter is required to build the debug image. Please provide it with "--build-arg GOLANG_VERSION=goX.Y.Z"' && exit 1) + +# Install the tools to be able to download and extract Golang. +RUN microdnf install --assumeyes \ + curl \ + gzip \ + tar \ + && microdnf clean all + +# The target architecture in which the binary will be built. +ARG TARGET_ARCH='linux-amd64' + +# Download and extract Golang. +RUN curl --location --output "/tmp/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" "https://go.dev/dl/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" \ + && tar --directory /usr/local --extract --file "/tmp/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" \ + && rm --force "/tmp/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" + +# Put Golang in the path so that we can compile Delve with it. +ENV PATH=$PATH:/usr/local/go/bin + +# Build Delve and leave it in a temporary directory. +RUN GOBIN=/tmp/bin go install github.com/go-delve/delve/cmd/dlv@latest + +# In the case that the "BUILD_TYPE=debug" build argument is provided, we +# "attempt to copy" the Delve executable into the production image. Since that +# creates a dependency with the "debug-initial-build", that path is executed +# by building the Delve executable. +FROM prod-build AS debug-build +COPY --from=debug-initial-build /tmp/bin/dlv /usr/local/bin/dlv + +# When the no build argument is provided, we simply fall back to the +# production build which comes with the optimized and minimized image. +FROM ${BUILD_TYPE}-build AS final-build + USER ${USER_UID} diff --git a/build/Dockerfile.webhook b/build/Dockerfile.webhook index aa3757817..63b70e093 100644 --- a/build/Dockerfile.webhook +++ b/build/Dockerfile.webhook @@ -1,7 +1,12 @@ -FROM registry.access.redhat.com/ubi8/ubi-minimal:latest +# Define the default build as "prod", so that in the case that no build +# arguments are provided, we build the optimized image. +ARG BUILD_TYPE=prod -LABEL maintainer "KubeSaw " -LABEL author "KubeSaw " +# Default production build steps for the operator. +FROM registry.access.redhat.com/ubi8/ubi-minimal:latest AS prod-build + +LABEL maintainer="KubeSaw " +LABEL author="KubeSaw " ENV WEBHOOK=/usr/local/bin/member-operator-webhook \ USER_UID=1001 \ @@ -16,4 +21,48 @@ RUN /usr/local/bin/user_setup ENTRYPOINT ["/usr/local/bin/entrypoint"] +# A throwaway intermediate path which compiles Delve with the same Golang +# version as the host computer. That ensures no discrepancies in the code +# when setting up breakpoints when debugging. +FROM registry.access.redhat.com/ubi8/ubi-minimal:latest AS debug-initial-build + +# In this build path, the Golang version must be provided as a build argument, +# which will ensure that Delve gets built with the same version the operator's +# binary gets built with, to avoid any discrepancies. +ARG GOLANG_VERSION +RUN test -n "${GOLANG_VERSION}" || (echo 'The GOLANG_VERSION build parameter is required to build the debug image. Please provide it with "--build-arg GOLANG_VERSION=goX.Y.Z"' && exit 1) + +# Install the tools to be able to download and extract Golang. +RUN microdnf install --assumeyes \ + curl \ + gzip \ + tar \ + && microdnf clean all + +# The target architecture in which the binary will be built. +ARG TARGET_ARCH='linux-amd64' + +# Download and extract Golang. +RUN curl --location --output "/tmp/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" "https://go.dev/dl/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" \ + && tar --directory /usr/local --extract --file "/tmp/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" \ + && rm --force "/tmp/${GOLANG_VERSION}.${TARGET_ARCH}.tar.gz" + +# Put Golang in the path so that we can compile Delve with it. +ENV PATH=$PATH:/usr/local/go/bin + +# Build Delve and leave it in a temporary directory. +RUN GOBIN=/tmp/bin go install github.com/go-delve/delve/cmd/dlv@latest + +# In the case that the "BUILD_TYPE=debug" build argument is provided, we +# "attempt to copy" the Delve executable into the production image. Since that +# creates a dependency with the "debug-initial-build", that path is executed +# by building the Delve executable. +FROM prod-build AS debug-build + +COPY --from=debug-initial-build /tmp/bin/dlv /usr/local/bin/dlv + +# When the no build argument is provided, we simply fall back to the +# production build which comes with the optimized and minimized image. +FROM ${BUILD_TYPE}-build AS final-build + USER ${USER_UID} diff --git a/make/go.mk b/make/go.mk index c905ce417..05a402476 100644 --- a/make/go.mk +++ b/make/go.mk @@ -23,6 +23,25 @@ $(OUT_DIR)/operator: -o $(OUT_DIR)/bin/member-operator-webhook \ cmd/webhook/main.go +.PHONY: build-debug +## Build the operator's image with Delve on it so that it is ready to attach a +## debugger to it. +build-debug: + @echo "building member-operator in ${GO_PACKAGE_PATH}" + $(Q)go version + $(Q)CGO_ENABLED=0 GOARCH=${goarch} GOOS=linux \ + go build ${V_FLAG} \ + -gcflags "all=-N -l" \ + -ldflags "-X ${GO_PACKAGE_PATH}/version.Commit=${GIT_COMMIT_ID} -X ${GO_PACKAGE_PATH}/version.BuildTime=${BUILD_TIME}" \ + -o $(OUT_DIR)/bin/member-operator \ + ./cmd/main.go + $(Q)CGO_ENABLED=0 GOARCH=${goarch} GOOS=linux \ + go build ${V_FLAG} \ + -gcflags "all=-N -l" \ + -ldflags "-X ${GO_PACKAGE_PATH}/version.Commit=${GIT_COMMIT_ID} -X ${GO_PACKAGE_PATH}/version.BuildTime=${BUILD_TIME}" \ + -o $(OUT_DIR)/bin/member-operator-webhook \ + cmd/webhook/main.go + .PHONY: vendor vendor: $(Q)go mod vendor diff --git a/make/podman.mk b/make/podman.mk index e637e7bc2..f63cd0b14 100644 --- a/make/podman.mk +++ b/make/podman.mk @@ -5,6 +5,7 @@ IMAGE ?= ${TARGET_REGISTRY}/${QUAY_NAMESPACE}/${GO_PACKAGE_REPO_NAME}:${IMAGE_TA QUAY_USERNAME ?= ${QUAY_NAMESPACE} WEBHOOK_IMAGE ?= ${TARGET_REGISTRY}/${QUAY_NAMESPACE}/${GO_PACKAGE_REPO_NAME}-webhook:${IMAGE_TAG} IMAGE_PLATFORM ?= linux/amd64 +TARGET_ARCH ?= $(subst /,-,${IMAGE_PLATFORM}) .PHONY: podman-image ## Build the binary image @@ -12,12 +13,25 @@ podman-image: build $(Q)podman build --platform ${IMAGE_PLATFORM} -f build/Dockerfile -t ${IMAGE} . $(Q)podman build --platform ${IMAGE_PLATFORM} -f build/Dockerfile.webhook -t ${WEBHOOK_IMAGE} . +.PHONY: podman-image-debug +## Build the operator's image with Delve on it so that it is ready to attach a +## debugger to it. +podman-image-debug: build-debug + $(Q) podman build --platform "${IMAGE_PLATFORM}" --build-arg BUILD_TYPE='debug' --build-arg GOLANG_VERSION="$$(go version | awk '{print $$3}')" --build-arg TARGET_ARCH="${TARGET_ARCH}" --file build/Dockerfile --tag "${IMAGE}" . + $(Q) podman build --platform "${IMAGE_PLATFORM}" --build-arg BUILD_TYPE='debug' --build-arg GOLANG_VERSION="$$(go version | awk '{print $$3}')" --build-arg TARGET_ARCH="${TARGET_ARCH}" --file build/Dockerfile.webhook --tag "${WEBHOOK_IMAGE}" . + .PHONY: podman-push ## Push the binary image to quay.io registry podman-push: check-namespace podman-image $(Q)podman push ${IMAGE} $(Q)podman push ${WEBHOOK_IMAGE} +.PHONY: podman-push-debug +## Push the image with the debugger in it to the repository. +podman-push-debug: check-namespace podman-image-debug + $(Q)podman push ${IMAGE} + $(Q)podman push ${WEBHOOK_IMAGE} + .PHONY: check-namespace check-namespace: ifeq ($(QUAY_NAMESPACE),${GO_PACKAGE_ORG_NAME})