Skip to content

ROX-26272 Enable ccache for faster builds #1838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/collector-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ env:
COLLECTOR_TAG: ${{ inputs.collector-tag }}
DEFAULT_BUILDER_TAG: master
ANSIBLE_CONFIG: ${{ github.workspace }}/ansible/ansible.cfg
USE_CCACHE: ${{ !contains(github.event.pull_request.labels.*.name, 'no-ccache') }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to run with ccache on the master branch and nightlies? That way PRs could just pull the cache from there and see a speed up from the very first commit that gets pushed, otherwise the first build will be as slow as the ones we have now. Maybe we can check if the branch being used is master somewhere in our build and clean the cache before building? That way the master branch would always be built from a clean slate, but the cache would still be provided for PRs.

Suggested change
USE_CCACHE: ${{ !contains(github.event.pull_request.labels.*.name, 'no-ccache') }}
USE_CCACHE: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'no-ccache') }}

https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#example-of-search-priority

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good idea. We could skip cache restore on master but always save the cache.


jobs:
builder-needs-rebuilding:
Expand Down Expand Up @@ -74,6 +75,24 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Set up builder ccache
if: env.USE_CCACHE
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/builder/.ccache
key: builder-ccache-${{ matrix.arch }}-${{ github.sha }}
restore-keys: |
builder-ccache-${{ matrix.arch }}-

- name: Set up builder ccache in docker cache
if: env.USE_CCACHE && matrix.arch != 's390x'
uses: reproducible-containers/buildkit-cache-dance@v3.1.2
with:
cache-map: |
{
"${{ github.workspace }}/builder/.ccache": "/root/.ccache"
}
Comment on lines +87 to +94
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you had to implement the logic that this action provides for s390x, I wonder if it makes sense to just handle all archs the same way in our ansible playbooks and drop this step, mostly for consistency, this approach is fine.


- uses: actions/setup-python@v5
with:
python-version: "3.10"
Expand Down Expand Up @@ -128,6 +147,7 @@ jobs:
echo "rhacs_eng_password: ${{ secrets.QUAY_RHACS_ENG_RW_PASSWORD }}"
echo "collector_git_ref: ${{ github.ref }}"
echo "collector_builder_tag: ${{ env.COLLECTOR_BUILDER_TAG }}"
echo "use_ccache: ${{ env.USE_CCACHE }}"
} > ${{ github.workspace }}/ansible/secrets.yml

- name: Build images
Expand Down Expand Up @@ -156,6 +176,7 @@ jobs:
-i ansible/ci \
-e build_hosts='job_id_${{ env.JOB_ID }}' \
-e arch='${{ matrix.arch }}' \
-e github_workspace='${{ github.workspace }}' \
-e @'${{ github.workspace }}/ansible/secrets.yml' \
ansible/ci-build-builder.yml

Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/collector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ env:
ANSIBLE_CONFIG: ${{ github.workspace }}/ansible/ansible.cfg
TRACE_SINSP_EVENTS: ${{ github.event_name == 'pull_request' }}
ADDRESS_SANITIZER: ${{ contains(github.event.pull_request.labels.*.name, 'address-sanitizer') }}
USE_CCACHE: ${{ !contains(github.event.pull_request.labels.*.name, 'no-ccache') }}

jobs:
build-collector-image:
Expand All @@ -45,6 +46,12 @@ jobs:
with:
submodules: true

- name: Set up ccache
if: env.USE_CCACHE
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ matrix.arch }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

Expand All @@ -67,6 +74,7 @@ jobs:
collector_tag: ${{ inputs.collector-tag }}
debug_mode: ${{ github.event_name == 'pull_request' }}
driver_version: ${DRIVER_VERSION}
use_ccache: ${{ env.USE_CCACHE }}
EOF

- name: Build images
Expand Down Expand Up @@ -123,6 +131,15 @@ jobs:
vm-type: rhel-${{ matrix.arch }}
job-tag: builder

- name: Set up ccache
if: env.USE_CCACHE
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.ccache
key: ccache-${{ matrix.arch }}-${{ github.sha }}
restore-keys: |
ccache-${{ matrix.arch }}-

- name: Create Build VMs
run: |
make -C "${{ github.workspace }}/ansible" create-build-vms
Expand All @@ -143,6 +160,7 @@ jobs:
collector_image: ${{ inputs.collector-image }}
collector_tag: ${{ inputs.collector-tag }}
debug_mode: ${{ github.event_name == 'pull_request' }}
use_ccache: ${{ env.USE_CCACHE }}
EOF

- name: Build ${{ matrix.arch }} image
Expand All @@ -152,6 +170,7 @@ jobs:
-i ansible/ci \
-e arch='${{ matrix.arch }}' \
-e build_hosts='job_id_${{ env.JOB_ID }}' \
-e github_workspace='${{ github.workspace }}' \
-e @'${{ github.workspace }}/ansible/secrets.yml' \
ansible/ci-build-collector.yml

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.idea/
.rox/
.ccache/
builder/.ccache/

integration-tests/container-logs/
integration-tests/*.log
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ NPROCS ?= $(shell nproc)
DEV_SSH_SERVER_KEY ?= $(CURDIR)/.collector_dev_ssh_host_ed25519_key
BUILD_BUILDER_IMAGE ?= false

CCACHE_DIR?=$(CURDIR)/.ccache

export COLLECTOR_VERSION := $(COLLECTOR_TAG)

.PHONY: tag
Expand All @@ -25,6 +27,7 @@ container-dockerfile-dev:
builder:
ifneq ($(BUILD_BUILDER_IMAGE), false)
docker buildx build --load --platform ${PLATFORM} \
--build-arg USE_CCACHE="$(USE_CCACHE)" \
-t quay.io/stackrox-io/collector-builder:$(COLLECTOR_BUILDER_TAG) \
-f "$(CURDIR)/builder/Dockerfile" \
"$(CURDIR)/builder"
Expand Down Expand Up @@ -84,6 +87,7 @@ start-builder: builder teardown-builder
--name $(COLLECTOR_BUILDER_NAME) \
--pull missing \
--platform ${PLATFORM} \
-v $(CCACHE_DIR):/root/.ccache \
-v $(CURDIR):$(CURDIR) \
$(if $(LOCAL_SSH_PORT),-p $(LOCAL_SSH_PORT):22 )\
-w $(CURDIR) \
Expand Down
1 change: 1 addition & 0 deletions Makefile-constants.mk
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ HOST_ARCH := $(shell uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')
PLATFORM ?= "linux/$(HOST_ARCH)"

USE_VALGRIND ?= false
USE_CCACHE ?= false
ADDRESS_SANITIZER ?= false
CMAKE_BUILD_TYPE ?= Release
CMAKE_BASE_DIR = cmake-build-$(shell echo $(CMAKE_BUILD_TYPE) | tr A-Z a-z)-$(HOST_ARCH)
Expand Down
1 change: 1 addition & 0 deletions ansible/ansible.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ display_skipped_hosts = false
host_key_checking = false
remote_tmp = /tmp/ansible
forks = 20
callbacks_enabled = profile_tasks

[ssh_connection]
ssh_args = -o StrictHostKeyChecking=no -C -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30 -o ServerAliveCountMax=10
Expand Down
49 changes: 47 additions & 2 deletions ansible/ci-build-builder.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
---
- name: Build and push collector image
- name: Build and push builder image
hosts: "{{ build_hosts | default('all') }}"

environment:
BUILD_BUILDER_IMAGE: "true"
COLLECTOR_BUILDER_TAG: "{{ collector_builder_tag }}"
PLATFORM: "linux/{{ arch }}"
USE_CCACHE: "{{ use_ccache|bool|lower }}"

vars:
collector_root: "{{ ansible_env.HOME }}/collector"
local_branch: local
ccache_dir: "builder/.ccache"
ccache_archive: "docker.tar.gz"
ccache_path: "{{ ccache_dir }}/{{ ccache_archive }}"
container_ccache_dir: "/root/.ccache"

tasks:
- name: Clone repository
Expand All @@ -21,14 +26,54 @@
# than with commit hashes, prevents "reference is not a tree" errors
version: "{{ local_branch }}"
refspec: "+{{ collector_git_ref | replace('refs/', '') }}:{{ local_branch }}"
recursive: true
depth: 1
recursive: false
when: arch == "s390x"

- name: Clone submodules
ansible.builtin.shell: |
git submodule update --init --depth 1
args:
chdir: "{{ collector_root }}"
when: arch == "s390x"

- name: Check if ccache exists
delegate_to: localhost
ansible.builtin.stat:
path: "{{ github_workspace }}/{{ ccache_path }}"
register: ccache_check
when: use_ccache and arch == "s390x"

- name: Copy ccache
ansible.builtin.copy:
src: "{{ github_workspace }}/{{ ccache_path }}"
dest: "{{ collector_root }}/{{ ccache_dir }}/"
when: use_ccache and arch == "s390x" and ccache_check.stat.exists

- name: Inject docker cache with ccache
ansible.builtin.shell:
cmd: ansible/scripts/inject_docker_cache.sh "{{ collector_root }}/{{ ccache_dir }}" "{{ container_ccache_dir }}"
chdir: "{{ collector_root }}"
when: use_ccache and arch == "s390x" and ccache_check.stat.exists

- name: Build the collector builder image
community.general.make:
chdir: "{{ ansible_env.GITHUB_WORKSPACE | default(collector_root) }}"
target: builder

- name: Extract ccache from docker cache
ansible.builtin.shell:
cmd: ansible/scripts/extract_docker_cache.sh "{{ collector_root }}/{{ ccache_dir }}" "{{ container_ccache_dir }}"
chdir: "{{ collector_root }}"
when: use_ccache and arch == "s390x"

- name: Fetch ccache
ansible.builtin.fetch:
src: "{{ collector_root }}/{{ ccache_path }}"
dest: "{{ github_workspace }}/{{ ccache_dir }}/"
flat: yes
when: use_ccache and arch == "s390x"

- name: Retag collector builder image to arch specific
community.docker.docker_image:
name: "quay.io/stackrox-io/collector-builder:{{ ansible_env.COLLECTOR_BUILDER_TAG }}"
Expand Down
54 changes: 53 additions & 1 deletion ansible/ci-build-collector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
COLLECTOR_TAG: "{{ collector_tag }}"
DISABLE_PROFILING: "{{ disable_profiling }}"
CMAKE_BUILD_TYPE: "{{ 'Debug' if debug_mode else 'Release' }}"
USE_CCACHE: "{{ use_ccache|bool|lower }}"
CCACHE_DIR: "{{ ansible_env.GITHUB_WORKSPACE | default(collector_root) }}/.ccache"

vars:
collector_root: "{{ ansible_env.HOME }}/collector"
local_branch: local
ccache_dir: ".ccache"
ccache_archive: "docker.tar.gz"

tasks:
- debug: var=collector_root
Expand All @@ -24,9 +28,43 @@
# than with commit hashes, prevents "reference is not a tree" errors
version: "{{ local_branch }}"
refspec: "+{{ collector_git_ref | replace('refs/', '') }}:{{ local_branch }}"
recursive: true
depth: 1
recursive: false
when: arch == "s390x"

- name: Clone submodules
ansible.builtin.shell: |
git submodule update --init --depth 1
args:
chdir: "{{ collector_root }}"
when: arch == "s390x"

- name: Check if ccache exists
delegate_to: localhost
ansible.builtin.stat:
path: "{{ github_workspace }}/{{ ccache_dir }}/{{ ccache_archive }}"
register: ccache_check
when: use_ccache and arch == "s390x"

- name: Create cccache directory
ansible.builtin.file:
path: "{{ collector_root }}/{{ ccache_dir }}"
state: directory
when: use_ccache and arch == "s390x" and ccache_check.stat.exists

- name: Copy ccache
ansible.builtin.copy:
src: "{{ github_workspace }}/{{ ccache_dir }}/{{ ccache_archive }}"
dest: "{{ collector_root }}/"
when: use_ccache and arch == "s390x" and ccache_check.stat.exists

- name: Unarchive ccache
ansible.builtin.unarchive:
src: "{{ collector_root }}/{{ ccache_archive }}"
dest: "{{ collector_root }}/{{ ccache_dir }}"
remote_src: true
when: use_ccache and arch == "s390x" and ccache_check.stat.exists

- name: Run the builder image
community.general.make:
chdir: "{{ ansible_env.GITHUB_WORKSPACE | default(collector_root) }}"
Expand All @@ -40,6 +78,20 @@
# ensure this action is printed
tags: [print_action]

- name: Create ccache archive
ansible.builtin.shell: |
rm -f "{{ collector_root }}/{{ ccache_archive }}"
cd "{{ collector_root }}/{{ ccache_dir }}"
tar czf "{{ collector_root }}/{{ ccache_archive }}" .
when: use_ccache and arch == "s390x"

- name: Fetch ccache
ansible.builtin.fetch:
src: "{{ collector_root }}/{{ ccache_archive }}"
dest: "{{ github_workspace }}/{{ ccache_dir }}/"
flat: yes
when: use_ccache and arch == "s390x"

- name: Retag collector image to arch specific
community.docker.docker_image:
name: "{{ collector_image }}"
Expand Down
62 changes: 62 additions & 0 deletions ansible/scripts/extract_docker_cache.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/bash
# based on https://github.com/reproducible-containers/buildkit-cache-dance

set -e

usage() {
echo "Usage: $0 <cache_destination_directory> <target_path_inside_container>"
exit 1
}

if [ "$#" -ne 2 ]; then
usage
fi

CACHE_DEST="$1"
TARGET_PATH="$2"

CCACHE_ARCHIVE="docker.tar.gz"

SCRATCH_DIR=$(mktemp -d)
SCRATCH_ARCHIVE=$(mktemp)
# shellcheck disable=SC2064
trap "rm -rf ${SCRATCH_DIR}; rm -f ${SCRATCH_ARCHIVE}" EXIT

# Timestamp to bust cache
date -Iseconds > "${SCRATCH_DIR}/buildstamp"

cat << EOF > "${SCRATCH_DIR}/Dockerfile.extract"
FROM busybox:1
COPY buildstamp buildstamp
RUN --mount=type=cache,target=${TARGET_PATH} \\
mkdir -p /var/docker-cache/ \\
&& cp -p -R ${TARGET_PATH}/. /var/docker-cache/ || true
EOF

echo "Generated Dockerfile.extract"
cat "${SCRATCH_DIR}/Dockerfile.extract"

# Build the image and load it into Docker
docker buildx build -f "${SCRATCH_DIR}/Dockerfile.extract" --tag cache:extract --load "${SCRATCH_DIR}"
docker images

# Remove any existing cache-container
docker rm -f cache-container || true

# Create a container from cache:extract
docker create --name cache-container cache:extract
docker ps

# Extract the cache from the container
docker cp -L cache-container:/var/docker-cache - | tar -H posix -x -C "${SCRATCH_DIR}"
ls "${SCRATCH_DIR}"

# Compress the cache from the container
(cd "${SCRATCH_DIR}/docker-cache" && chmod -R 777 . && tar czf "${SCRATCH_ARCHIVE}" .)

# Move the cache into its dest
rm -f "${CACHE_DEST}/${CCACHE_ARCHIVE}"
mv "${SCRATCH_ARCHIVE}" "${CACHE_DEST}/${CCACHE_ARCHIVE}"
chmod 666 "${CACHE_DEST}/${CCACHE_ARCHIVE}"

echo "Docker cache extraction completed successfully."
Loading
Loading