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.
We can download the kernel when the workflow is finished.