diff --git a/.github/workflows/push_image.yml b/.github/workflows/push_image.yml index 72eff44bd..f2f1846bc 100644 --- a/.github/workflows/push_image.yml +++ b/.github/workflows/push_image.yml @@ -5,11 +5,10 @@ on: env: REGISTRY_USER: netobserv+github_ci - REGISTRY_PASSWORD: ${{ secrets.QUAY_SECRET }} REGISTRY: quay.io/netobserv IMAGE: netobserv-ebpf-agent - IMAGE_ORG: netobserv - TAGS: main + ORG: netobserv + VERSION: main jobs: push-image: @@ -27,22 +26,13 @@ jobs: go-version: ${{ matrix.go }} - name: checkout uses: actions/checkout@v3 - - name: build images - run: make ci-images-build - - name: podman login to quay.io - uses: redhat-actions/podman-login@v1 + - name: docker login to quay.io + uses: docker/login-action@v2 with: username: ${{ env.REGISTRY_USER }} - password: ${{ env.REGISTRY_PASSWORD }} + password: ${{ secrets.QUAY_SECRET }} registry: quay.io - - name: get short sha - run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - name: push to quay.io - id: push-to-quay - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ env.IMAGE }} - tags: ${{ env.TAGS }} ${{ env.short_sha }} - registry: ${{ env.REGISTRY }} + - name: build and push manifest with images + run: IMAGE_ORG=${{ env.ORG }} VERSION=${{ env.VERSION }} make ci - name: print image url run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}" \ No newline at end of file diff --git a/.github/workflows/push_image_pr.yml b/.github/workflows/push_image_pr.yml index a7e44de1b..7b78f2a7c 100644 --- a/.github/workflows/push_image_pr.yml +++ b/.github/workflows/push_image_pr.yml @@ -7,7 +7,8 @@ env: REGISTRY_USER: netobserv+github_ci REGISTRY: quay.io/netobserv IMAGE: netobserv-ebpf-agent - IMAGE_ORG: netobserv + ORG: netobserv + VERSION: temp jobs: push-pr-image: @@ -28,23 +29,18 @@ jobs: uses: actions/checkout@v3 with: ref: "refs/pull/${{ github.event.number }}/merge" - - name: build images - run: SW_VERSION=temp make ci-images-build - - name: podman login to quay.io - uses: redhat-actions/podman-login@v1 + - name: docker login to quay.io + uses: docker/login-action@v2 with: username: ${{ env.REGISTRY_USER }} password: ${{ secrets.QUAY_SECRET }} registry: quay.io - name: get short sha run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - name: push to quay.io - id: push-to-quay - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ env.IMAGE }} - tags: ${{ env.short_sha }} - registry: ${{ env.REGISTRY }} + - name: build and push images + run: IMAGE_ORG=${{ env.ORG }} IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ env.short_sha }} make images + - name: build and push manifest + run: IMAGE_ORG=${{ env.ORG }} VERSION=${{ env.VERSION }} make ci-manifest - uses: actions/github-script@v6 with: github-token: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ad5373f1..ea18a8ca4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,20 +37,13 @@ jobs: uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - - name: build images - run: SW_VERSION="${{ env.tag }}" make image-build - - name: podman login to quay.io - uses: redhat-actions/podman-login@v1 + - name: docker login to quay.io + uses: docker/login-action@v2 with: username: ${{ env.REGISTRY_USER }} password: ${{ env.REGISTRY_PASSWORD }} registry: quay.io - - name: push to quay.io - id: push-to-quay - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ env.IMAGE }} - tags: ${{ env.tag }} - registry: ${{ env.REGISTRY }} + - name: build and push images + run: VERSION="${{ env.tag }}" make images - name: print image url run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}" diff --git a/.mk/shortcuts.mk b/.mk/shortcuts.mk new file mode 100644 index 000000000..cea71037b --- /dev/null +++ b/.mk/shortcuts.mk @@ -0,0 +1,31 @@ +##@ shortcuts helpers + +.PHONY: build +build: prereqs fmt lint test vendors compile ## Test and Build ebpf agent + +.PHONY: build-image +build-image: image-build ## Build MULTIARCH_TARGETS images + +.PHONY: push-image +push-image: image-push ## Push MULTIARCH_TARGETS images + +.PHONY: build-manifest +build-manifest: manifest-build ## Build MULTIARCH_TARGETS manifest + +.PHONY: push-manifest +push-manifest: manifest-push ## Push MULTIARCH_TARGETS manifest + +.PHONY: images +images: image-build image-push manifest-build manifest-push ## Build and push MULTIARCH_TARGETS images and related manifest + +.PHONY: build-ci-manifest +build-ci-manifest: ci-manifest-build ## Build CI manifest + +.PHONY: push-ci-manifest +push-ci-manifest: ci-manifest-push ## Push CI manifest + +.PHONY: ci-manifest +ci-manifest: ci-manifest-build ci-manifest-push ## Build and push CI manifest + +.PHONY: ci +ci: images ci-manifest ## Build and push CI images and manifest \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1fa4974b4..9a53a01f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,12 @@ +# We do not use --platform feature to auto fill this ARG because of incompatibility between podman and docker +ARG TARGETPLATFORM=linux/amd64 +ARG BUILDPLATFORM=linux/amd64 # Build the manager binary -FROM docker.io/library/golang:1.19 as builder +FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.19 as builder -ARG SW_VERSION="unknown" +ARG TARGETPLATFORM +ARG TARGETARCH=amd64 +ARG VERSION="unknown" WORKDIR /opt/app-root @@ -14,12 +19,13 @@ COPY vendor/ vendor/ COPY go.mod go.mod COPY go.sum go.sum COPY Makefile Makefile +COPY .mk/ .mk/ # Build -RUN make compile +RUN GOARCH=$TARGETARCH make compile # Create final image from minimal + built binary -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.1 +FROM --platform=$TARGETPLATFORM registry.access.redhat.com/ubi9/ubi-minimal:9.1 WORKDIR / COPY --from=builder /opt/app-root/bin/netobserv-ebpf-agent . USER 65532:65532 diff --git a/Makefile b/Makefile index efb3dafd7..2ef7fdcd0 100644 --- a/Makefile +++ b/Makefile @@ -1,67 +1,103 @@ -# SW_VERSION defines the project version for the bundle. +# VERSION defines the project version for the bundle. # Update this value when you upgrade the version of your project. # To re-generate a bundle for another specific version without changing the standard setup, you can: -# - use the SW_VERSION as arg of the bundle target (e.g make bundle SW_VERSION=0.0.2) -# - use environment variables to overwrite this value (e.g export SW_VERSION=0.0.2) -SW_VERSION ?= main -BUILD_VERSION := $(shell git describe --long HEAD) +# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) +# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) +VERSION ?= main BUILD_DATE := $(shell date +%Y-%m-%d\ %H:%M) +TAG_COMMIT := $(shell git rev-list --abbrev-commit --tags --max-count=1) +TAG := $(shell git describe --abbrev=0 --tags ${TAG_COMMIT} 2>/dev/null || true) BUILD_SHA := $(shell git rev-parse --short HEAD) +BUILD_VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + BUILD_VERSION := $(BUILD_VERSION)-$(BUILD_SHA) +endif +ifneq ($(shell git status --porcelain),) + BUILD_VERSION := $(BUILD_VERSION)-dirty +endif + +# Go architecture and targets images to build +GOARCH ?= amd64 +MULTIARCH_TARGETS ?= amd64 arm64 ppc64le # In CI, to be replaced by `netobserv` IMAGE_ORG ?= $(USER) # IMAGE_TAG_BASE defines the namespace and part of the image name for remote images. -# This variable is used to construct full image tags for bundle and catalog images. IMAGE_TAG_BASE ?= quay.io/$(IMAGE_ORG)/netobserv-ebpf-agent # Image URL to use all building/pushing image targets -IMG ?= $(IMAGE_TAG_BASE):$(SW_VERSION) -IMG_SHA = $(IMAGE_TAG_BASE):$(BUILD_SHA) +IMAGE ?= $(IMAGE_TAG_BASE):$(VERSION) +IMAGE_SHA = $(IMAGE_TAG_BASE):$(BUILD_SHA) -LOCAL_GENERATOR_IMAGE ?= ebpf-generator:latest +# Image building tool (docker / podman) +OCI_BIN_PATH := $(shell which podman || which docker) +OCI_BIN ?= $(shell v='$(OCI_BIN_PATH)'; echo "$${v##*/}") +LOCAL_GENERATOR_IMAGE ?= ebpf-generator:latest CILIUM_EBPF_VERSION := v0.10.0 GOLANGCI_LINT_VERSION = v1.50.1 - CLANG ?= clang CFLAGS := -O2 -g -Wall -Werror $(CFLAGS) GOOS ?= linux PROTOC_ARTIFACTS := pkg/pbflow - # regular expressions for excluded file patterns EXCLUDE_COVERAGE_FILES="(/cmd/)|(bpf_bpfe)|(/examples/)|(/pkg/pbflow/)" -# Image building tool (docker / podman) -ifndef OCI_BIN - ifeq (,$(shell which podman 2>/dev/null)) - OCI_BIN=docker - else - OCI_BIN=podman - endif -endif +.DEFAULT_GOAL := help + +# build a single arch target provided as argument +define build_target + echo 'building image for arch $(1)'; \ + DOCKER_BUILDKIT=1 $(OCI_BIN) buildx build --load --build-arg TARGETPLATFORM=linux/$(1) --build-arg TARGETARCH=$(1) --build-arg BUILDPLATFORM=linux/amd64 -t ${IMAGE}-$(1) -f Dockerfile .; +endef + +# push a single arch target image +define push_target + echo 'pushing image ${IMAGE}-$(1)'; \ + DOCKER_BUILDKIT=1 $(OCI_BIN) push ${IMAGE}-$(1); +endef + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) .PHONY: vendors -vendors: +vendors: ## Check go vendors @echo "### Checking vendors" go mod tidy && go mod vendor .PHONY: prereqs -prereqs: - @echo "### Check if prerequisites are met, and installing missing dependencies" +prereqs: ## Check if prerequisites are met, and install missing dependencies + @echo "### Checking if prerequisites are met, and installing missing dependencies" test -f $(shell go env GOPATH)/bin/golangci-lint || GOFLAGS="" go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION} test -f $(shell go env GOPATH)/bin/bpf2go || go install github.com/cilium/ebpf/cmd/bpf2go@${CILIUM_EBPF_VERSION} test -f $(shell go env GOPATH)/bin/protoc-gen-go || go install google.golang.org/protobuf/cmd/protoc-gen-go@latest test -f $(shell go env GOPATH)/bin/protoc-gen-go-grpc || go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest test -f $(shell go env GOPATH)/bin/kind || go install sigs.k8s.io/kind@latest +##@ Develop + .PHONY: fmt fmt: ## Run go fmt against code. @echo "### Formatting code" go fmt ./... .PHONY: lint -lint: prereqs +lint: prereqs ## Lint the code @echo "### Linting code" golangci-lint run ./... --timeout=3m @@ -72,28 +108,25 @@ lint: prereqs .PHONY: generate generate: export BPF_CLANG := $(CLANG) generate: export BPF_CFLAGS := $(CFLAGS) -generate: prereqs +generate: prereqs ## Generate artifacts of the code repo (pkg/ebpf and pkg/proto packages) @echo "### Generating BPF Go bindings" go generate ./pkg/... @echo "### Generating gRPC and Protocol Buffers code" protoc --go_out=pkg --go-grpc_out=pkg proto/flow.proto .PHONY: docker-generate -docker-generate: +docker-generate: ## Create the container that generates the eBPF binaries @echo "### Creating the container that generates the eBPF binaries" $(OCI_BIN) build . -f scripts/generators.Dockerfile -t $(LOCAL_GENERATOR_IMAGE) $(OCI_BIN) run --rm -v $(shell pwd):/src $(LOCAL_GENERATOR_IMAGE) -.PHONY: build -build: prereqs fmt lint test vendors compile - .PHONY: compile -compile: +compile: ## Compile ebpf agent project @echo "### Compiling project" - GOOS=$(GOOS) go build -ldflags "-X main.version=${SW_VERSION} -X 'main.buildVersion=${BUILD_VERSION}' -X 'main.buildDate=${BUILD_DATE}'" -mod vendor -a -o bin/netobserv-ebpf-agent cmd/netobserv-ebpf-agent.go + GOARCH=${GOARCH} GOOS=$(GOOS) go build -ldflags "-X main.version=${VERSION} -X 'main.buildVersion=${BUILD_VERSION}' -X 'main.buildDate=${BUILD_DATE}'" -mod vendor -a -o bin/netobserv-ebpf-agent cmd/netobserv-ebpf-agent.go .PHONY: test -test: +test: ## Test code using go test @echo "### Testing code" GOOS=$(GOOS) go test -mod vendor -a ./... -coverpkg=./... -coverprofile cover.all.out @@ -102,33 +135,67 @@ cov-exclude-generated: grep -vE "(/cmd/)|(bpf_bpfe)|(/examples/)|(/pkg/pbflow/)" cover.all.out > cover.out .PHONY: coverage-report -coverage-report: cov-exclude-generated +coverage-report: cov-exclude-generated ## Generate coverage report @echo "### Generating coverage report" go tool cover --func=./cover.out .PHONY: coverage-report-html -coverage-report-html: cov-exclude-generated +coverage-report-html: cov-exclude-generated ## Generate HTML coverage report @echo "### Generating HTML coverage report" go tool cover --html=./cover.out -.PHONY: image-build -image-build: ## Build OCI image with the manager. - $(OCI_BIN) build --build-arg SW_VERSION="$(SW_VERSION)" -t ${IMG} . - -.PHONY: ci-images-build -ci-images-build: image-build - $(OCI_BIN) build --build-arg BASE_IMAGE=$(IMG) -t $(IMG_SHA) -f scripts/shortlived.Dockerfile . - -.PHONY: image-push -image-push: ## Push OCI image with the manager. - $(OCI_BIN) push ${IMG} - .PHONY: tests-e2e .ONESHELL: -tests-e2e: prereqs +tests-e2e: prereqs ## Run e2e tests go clean -testcache # making the local agent image available to kind in two ways, so it will work in different # environments: (1) as image tagged in the local repository (2) as image archive. $(OCI_BIN) build . -t localhost/ebpf-agent:test $(OCI_BIN) save -o ebpf-agent.tar localhost/ebpf-agent:test GOOS=$(GOOS) go test -p 1 -timeout 30m -v -mod vendor -tags e2e ./e2e/... + +##@ Images + +# note: to build and push custom image tag use: IMAGE_ORG=myuser VERSION=dev s +.PHONY: image-build +image-build: ## Build MULTIARCH_TARGETS images + trap 'exit' INT; \ + $(foreach target,$(MULTIARCH_TARGETS),$(call build_target,$(target))) + +.PHONY: image-push +image-push: ## Push MULTIARCH_TARGETS images + trap 'exit' INT; \ + $(foreach target,$(MULTIARCH_TARGETS),$(call push_target,$(target))) + +.PHONY: manifest-build +manifest-build: ## Build MULTIARCH_TARGETS manifest + @echo 'building manifest $(IMAGE)' + DOCKER_BUILDKIT=1 $(OCI_BIN) manifest create ${IMAGE} $(foreach target,$(MULTIARCH_TARGETS),--amend ${IMAGE}-$(target)); + +.PHONY: manifest-push +manifest-push: ## Push MULTIARCH_TARGETS manifest + @echo 'publish manifest $(IMAGE)' +ifeq (${OCI_BIN}, docker) + DOCKER_BUILDKIT=1 $(OCI_BIN) manifest push ${IMAGE}; +else + DOCKER_BUILDKIT=1 $(OCI_BIN) manifest push ${IMAGE} docker://${IMAGE}; +endif + +.PHONY: ci-manifest-build +ci-manifest-build: manifest-build ## Build CI manifest + $(OCI_BIN) build --build-arg BASE_IMAGE=$(IMAGE) -t $(IMAGE_SHA) -f scripts/shortlived.Dockerfile . +ifeq ($(VERSION), main) +# Also tag "latest" only for branch "main" + $(OCI_BIN) build -t $(IMAGE) -t $(IMAGE_TAG_BASE):latest -f scripts/shortlived.Dockerfile . +endif + +.PHONY: ci-manifest-push +ci-manifest-push: ## Push CI manifest + $(OCI_BIN) push $(IMAGE_SHA) +ifeq ($(VERSION), main) +# Also tag "latest" only for branch "main" + $(OCI_BIN) push ${IMAGE} + $(OCI_BIN) push $(IMAGE_TAG_BASE):latest +endif + +include .mk/shortcuts.mk diff --git a/README.md b/README.md index 9295c246a..f184e5916 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The Network Observability eBPF Agent allows collecting and aggregating all the ingress and egress flows on a Linux host (required a Kernel 4.18+ with eBPF enabled). -* [How to compile](#how-to-compile) +* [How to build](#how-to-build) * [How to configure](#how-to-configure) * [How to run](#how-to-run) * [Development receipts](#development-receipts) @@ -13,16 +13,24 @@ egress flows on a Linux host (required a Kernel 4.18+ with eBPF enabled). * [Frequently-asked questions](#frequently-asked-questions) * [Troubleshooting](#troubleshooting) -## How to compile +## How to build -``` +To build the agent image and push it to your Docker / Quay repository, run: +```bash +# compile project make build -``` -To build the agent image and push it to your Docker / Quay repository, run: +# build the default image (quay.io/netobserv/netobserv-ebpf-agent:main): +make image-build -```bash -IMG=quay.io/myaccount/netobserv-ebpf-agent:dev make image-build image-push +# push the default image (quay.io/netobserv/netobserv-ebpf-agent:main): +make image-push + +# build and push on your own quay.io account (quay.io/myuser/netobserv-ebpf-agent:dev): +IMAGE_ORG=myuser VERSION=dev make images + +# build and push on a different registry +IMAGE=dockerhub.io/myuser/plugin:tag make images ``` ## How to configure