note

This article was last updated on September 21, 2023, 1 year ago. The content may be out of date.

Google released BBR v3 nearly two months ago. To try it out, we have to use a kernel which is compiled from a source that has this patch. We can compile the kernel ourselves or be lazy and use a kernel compiled by others. But what fun is there if we take the easy way?

About GitHub Actions

GitHub Actions can be used to automate software workflows, from building to deploying. It’s free of charge for standard GitHub-hosted runners in public repositories and has 2000 minutes limit per month for private repositories. Their runners have decent hardware resources for building linux kernels.

Setting up Build Environment

Although GitHub Actions have Ubuntu installed on their linux runners and Ubuntu can be used to build linux kernels, we choose to use Docker instead because Docker makes it easy to test a configuration and make the build process portable and reproducible.

Docker has multi-stage builds that allow us to write a Dockerfile in logical steps. It will help us to debug and maintain the Dockerfile. We use the latest stable version of debian as the build environment.

Adding deb-src

Because the sources.list of the debian docker doesn’t have deb-src in them, we have to add them back. This is useful later when we need to install build dependencies.

FROM debian:12.1 AS deb-src
COPY <<"EOF" /etc/apt/sources.list
deb http://deb.debian.org/debian bookworm main
deb-src http://deb.debian.org/debian bookworm main

deb http://deb.debian.org/debian-security/ bookworm-security main
deb-src http://deb.debian.org/debian-security/ bookworm-security main

deb http://deb.debian.org/debian bookworm-updates main
deb-src http://deb.debian.org/debian bookworm-updates main
EOF

note

This uses heredocs in Dockerfiles, more info can be found here.

Installing Build Dependencies

According to debian’s official documentation, we just need to run these three commands to install build dependencies:

FROM deb-src AS install-dependency
RUN <<"EOF"
apt-get update
apt-get install build-essential wget git -y
apt-get build-dep linux -y
EOF

Downloading the Kernel Config

To compile a kernel, we also need a configuration file that records what parts of the kernel should be compiled. We extract the one from the official kernel:

FROM install-dependency AS download-boot
RUN <<"EOF"
cd /
mkdir debian_config
cd debian_config
wget http://security.debian.org/debian-security/pool/updates/main/l/linux-signed-amd64/linux-image-6.1.0-12-cloud-amd64_6.1.52-1_amd64.deb -q -O kernel.deb
ar -x kernel.deb
tar xf data.tar.xz
EOF

note

We could parse the output of apt download --print-uris to determine the download url for the package.

Cloning the BBR Source

We then need to use git to clone the source of BBR:

FROM download-boot as download-bbr
RUN <<"EOF"
cd /
git clone https://github.com/google/bbr.git -b v3
EOF

Building and Packaging the Kernel

Finally, we need to build and package the kernel:

FROM download-bbr as builder
RUN <<"EOF"
cd /bbr
cp /debian_config/boot/config-6.1.0-12-cloud-amd64 .config
export BRANCH=`git rev-parse --abbrev-ref HEAD | sed s/-/+/g`
export SHA1=`git rev-parse --short HEAD`
export LOCALVERSION=+${BRANCH}+${SHA1}+GCE
export GCE_PKG_DIR=${PWD}/gce/${LOCALVERSION}/pkg
export GCE_INSTALL_DIR=${PWD}/gce/${LOCALVERSION}/install
export GCE_BUILD_DIR=${PWD}/gce/${LOCALVERSION}/build
export KERNEL_PKG=kernel-${LOCALVERSION}.tar.gz2
export MAKE_OPTS="-j`nproc` \
           LOCALVERSION=${LOCALVERSION} \
           EXTRAVERSION="" \
           INSTALL_PATH=${GCE_INSTALL_DIR}/boot \
           INSTALL_MOD_PATH=${GCE_INSTALL_DIR}"
mkdir -p ${GCE_BUILD_DIR}
mkdir -p ${GCE_INSTALL_DIR}/boot
mkdir -p ${GCE_PKG_DIR}
make olddefconfig
make ${MAKE_OPTS} prepare
make ${MAKE_OPTS}
make ${MAKE_OPTS} modules
make ${MAKE_OPTS} install
make ${MAKE_OPTS} modules_install
cd ${GCE_INSTALL_DIR}
tar -cvzf /kernel.tar.gz2 boot/* lib/modules/* --owner=0 --group=0
EOF

note

This part is adapted from gce-install.sh.

Setting up GitHub Actions

To use GitHub Actions, we need to create a repository. Put the Dockerfile in the root and create a yaml file at the path .github/workflows under the root of the repository. We can name the yaml file whatever we want.

Here is what our workflow file looks like:

# This is a basic workflow to help you get started with Actions

name: Compile Linux Kernel with BBRv3

# Controls when the workflow will run
on:
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - name: build docker image
        run: |
          docker build -t kernel-bbrv3 .          

      - name: retrieve artifact from docker image
        run: |
          docker run -it --name kernel -d kernel-bbrv3:latest
          docker cp kernel:/kernel.tar.gz2 ${{ github.workspace }}          

      - uses: actions/upload-artifact@v3
        with:
          name: kernel
          path: ${{ github.workspace }}/kernel.tar.gz2

note

Cloning a repository and uploading build artifacts are two very commonly used actions. They have detailed documentation at their respective GitHub repos, checkout and upload-artifact.

Every step begins in the ${{ github.workspace }} directory. If we are referring to files between steps we need to keep their paths in mind.

This workflow runs on a Ubuntu runner and is triggered to run manually. We can run it under the Actions tab of the repository.

Running Workflow Manually

We can download the kernel when the workflow is finished.

Build Artifact