blob: e26481702937893d3f87878d0df44b178a4ede23 [file] [view] [edit]
# cos-dkms
`cos-dkms` is a tool designed for Google Container-Optimized OS (COS) to
dynamically build, install, and manage kernel modules. It is intended to be a
drop-in replacement for the upstream Dynamic Kernel Module Support (DKMS) tool,
but tailored for the unique environment of COS and GKE.
Key features include:
* **Cross-compilation**: Compile kernel modules against specific COS kernel
versions.
* **Toolchain Orchestration**: Automatically download and use the exact
compiler toolchain and kernel headers matching the target COS image.
* **GCS Caching**: Cache built modules and sources in a Google Cloud Storage
(GCS) bucket to speed up deployments and avoid recompilation.
* **Module Signing**: Sign compiled modules using local keys or Google Cloud
KMS to satisfy COS kernel module signature verification.
Code-level documentation is available
[here](https://pkg.go.dev/cos.googlesource.com/cos/tools.git/src/pkg/dkms).
Documentation for the high-level cos-dkms runner script is available
[here](https://cos.googlesource.com/cos/tools/+/refs/heads/master/src/cmd/cos_dkms_runner).
--------------------------------------------------------------------------------
## Table of Contents
1. [Running cos-dkms](#running-cos-dkms)
2. [Actions](#actions)
3. [Configuration (dkms.conf)](#configuration)
* [Configuration Examples](#configuration-examples)
4. [Module Signing](#module-signing)
5. [Example Workflows](#workflows)
* [Workflow 1: Installing Precompiled Modules](#workflow-precompiled-modules)
* [Workflow 2: Installing Modules Compiled by Google for COS](#workflow-google-compiled-modules)
* [Workflow 3: Installing Modules with modprobe](#workflow-modprobe)
* [Workflow 4: Building and Distributing Modules](#workflow-building-distributing)
--------------------------------------------------------------------------------
## Running cos-dkms {#running-cos-dkms}
Because COS is a minimal operating system without a pre-installed compiler
toolchain, `cos-dkms` is intended to be run inside a Docker container. Google
provides a pre-built container image: `gcr.io/cos-cloud/cos-dkms:latest`.
When running this container, the following directories should be mounted:
* The module source directory.
* The output directory for installed modules.
* `/etc/lsb-release` (to allow `cos-dkms` to automatically detect the host's
COS build ID and board).
* Optionally, a cache directory for kernel headers and toolchains to speed up
subsequent runs.
### Basic Docker Template
```bash
docker run --rm -it \
-v "/path/to/your/sources:/usr/src/my-package-1.0" \
-v "/path/to/install/output:/var/lib/dkms-install" \
-v /etc/lsb-release:/etc/lsb-release \
gcr.io/cos-cloud/cos-dkms:latest \
[action] [options] my-package/1.0
```
If you are using cos-dkms to load modules, then you will need to run the
container with `--privileged`. For modules which rely on networking, passing
`--net=host` may also be required.
--------------------------------------------------------------------------------
## Actions {#actions}
`cos-dkms` supports a set of actions to manage the lifecycle of a kernel module.
In most cases, you will only need the high-level `install` and `remove`
commands, which automatically orchestrate the lower-level actions.
```
+-----------+ calls +---------+ calls +---------+
| Install | -------> | Build | -------> | Add |
+-----------+ +---------+ +---------+
| undone by | undone by | undone by
v v v
+-----------+ +---------+ +---------+
| Uninstall | <------- | Unbuild | <------- | Remove |
+-----------+ calls +---------+ calls +---------+
```
### High-Level Actions
#### `install`
Installs all modules of a package into the install tree.
* **Behavior**: If the package is not yet added, it runs `add`. If it is not
yet built, it runs `build`. It then copies the compiled `.ko` files to the
install tree.
* **Version Safety**: By default, it checks if the version being installed is
older than an already installed version. If so, it skips installation. Use
`--force-version-override` to bypass.
* **GCS Integration**: If `--gcs-bucket` is provided, then it will attempt to
download sources and modules from the given bucket. If `--upload` is passed,
then it will attempt to upload the sources and modules to the bucket after
they are added and built, respectively.
* **Key Flags**:
* * `-i`, `--installtree`: The directory where the modules will be installed.
Defaults to /lib/modules/${kernel-version}.
* `-b`, `--kernelmodulestree`: The directory containing the in-tree kernel
modules. Only used with --insert-on-install. Defaults to ${installtree}.
The modules in this tree can be loaded as dependencies when inserting
modules installed by cos-dkms. If a module is present in both the kernel
modules tree and the install tree, then only the one in the install tree
will be loaded.
The reason to separate this value from the install tree is that the install
tree must be writable, while the kernel's module directory may not be
writable. This is the case in COS instances, where the /lib/modules
directory is read-only. See the examples below for correct usage of
--installtree and --kernelmodulestree in COS.
* `-I`, `--insert-on-install`: Inserts the modules directly into the
running kernel (using `insmod`-like behavior) with specified
`--module-arg` parameters. This takes into account the order of the
modules listed in the dkms.conf file in order to install earlier modules
first, when possible. This allows the installation of module lists with
implicit dependencies, such as when one module does not explicitly
import symbols from another but does require that the other module be
loaded first.
* `--module-arg`: Argument to pass to a specific kernel module when loading
it with `--insert-on-install`. Multiple args for multiple modules can be
specified at the same time by passing a comma separated list or passing
--module-arg multiple times. The format is
--module-arg=module1.param1=value1,module2.param1=value1, etc.
* `-M`, `--modprobe-on-install`: Automatically runs `depmod` and
`modprobe` to load the modules into the running kernel after
installation. This is useful for more complex installations that require
modprobe configurations in /etc/modprobe.d.
Note that this doesn't use --kernelmodulestree because depmod and modprobe
require all modules to be under a subdirectory of
/lib/modules/<kernel version>, so specifying a separate
--kernelmodulestree and --installtree would not work as expected.
* `--no-depmod`: Skip running `depmod` before `modprobe` (only used with
`-M`).
#### `remove`
Completely removes a package.
* **Behavior**: Automatically runs `uninstall` (removes from install tree) and
`unbuild` (cleans build artifacts), then removes the package sources from
the DKMS source tree.
* **GCS Integration**: If `--gcs-bucket` is provided and `--upload` is passed,
it also removes the package from the bucket.
--------------------------------------------------------------------------------
### Low-Level Actions
#### `add`
Adds a package to the local DKMS source tree.
* **Behavior**: Looks for source files in the source tree (default
`/usr/src`). If not found, it tries to copy them from a local directory
named `<package>-<version>`.
* **GCS Integration**: If `--gcs-bucket` is provided and no local sources are
found, it checks the bucket for sources. If `--upload` is specified, it
uploads the sources to the bucket.
#### `build`
Compiles the modules in the package.
* **Behavior**: Copies sources to a build directory, applies patches (if
specified in `dkms.conf`), and runs the `MAKE` command.
* **GCS Integration**: If `--gcs-bucket` is provided, it attempts to download
precompiled modules first. It only builds locally if they are missing. If
`--upload` is specified, it uploads the compiled modules to the bucket after
they are built.
* **Key Flags**:
* `--install-build-dependencies`: Automatically downloads the correct
kernel headers and compiler toolchain for the target COS version.
* `--make-variables=cos-default`: Appends default toolchain variables
(like `CC`, `LD`, etc.) matching the COS kernel build environment.
* `-j`, `--jobs`: Number of parallel make jobs (default 1).
* `--upload`: Uploads the compiled modules to the GCS bucket after a
successful build.
#### `status`
Displays the current status of a package.
* **Output Statuses**:
* `Added`: Sources are in the DKMS source tree.
* `Built`: Sources are added and modules are compiled.
* `Installed`: Modules are compiled and copied to the install tree.
* `Broken`: Package is in an inconsistent state (e.g., missing sources,
invalid config).
* **GCS Integration**: Reflects the status in the GCS bucket if `--gcs-bucket`
is provided.
#### `unbuild`
Cleans build artifacts.
* **Behavior**: Removes compiled modules from the build directory.
Automatically runs `uninstall` first if the package is installed.
* **GCS Integration**: If `--gcs-bucket` is provided and `--upload` is passed,
it also removes the compiled modules from the bucket.
#### `uninstall`
Removes compiled modules from the install tree.
--------------------------------------------------------------------------------
### Global Options
These flags can be passed to most commands:
* `-p`, `--package` / `-m`, `--module`: Name of the package.
* `--package-version` / `--module-version`: Version of the package.
* `-k`, `--kernel-version`: Target kernel version (defaults to running kernel,
or detected from headers if set to `cos-default`).
* `-a`, `--arch`: Target architecture (default: running host arch).
* `--build-id`: Target COS build ID (e.g., `18244.151.84`).
* `--board`: Target COS board (e.g., `lakitu`).
* `--gcs-bucket`: GCS bucket path (e.g., `gs://my-bucket/dkms`) to use as a
cache.
* `-f`, `--force`: Force the action even if `cos-dkms` thinks it is already
done.
* `-s`, `--sourcetree`: The directory where the module source tree is located.
Defaults to /usr/src.
Additionally, cos-dkms uses [glog](https://pkg.go.dev/github.com/golang/glog) for
logging, so arguments to configure glog logging (logtostderr, stderrthreshold,
etc.) are accepted.
--------------------------------------------------------------------------------
## Configuration (dkms.conf) {#configuration}
The `dkms.conf` file is a bash script that defines how `cos-dkms` should handle
the package. It supports Dell DKMS-compatible directives.
### Environment Variables Available in `dkms.conf`
When `dkms.conf` is sourced, the following variables are pre-populated and can
be referenced:
* `$module`: Package name.
* `$module_version`: Package version.
* `$kernelver`: Target kernel version.
* `$arch`: Target architecture.
* `$build_id` / `$board`: COS-specific build metadata.
* `$dkms_tree`: Path to the DKMS tree.
* `$source_tree`: Path to the source tree.
* `$kernel_source_dir`: Path to kernel headers/sources.
* `$build_dir`: Path to the temporary build directory.
### Directives
Each of these is a top-level variable in the dkms.conf file which controls the
behavior of cos-dkms in some way. Each directive has a default value which will
be overwritten by any user-specified values in the dkms.conf. The notation
`VARIABLE[#]` indicates that `VARIABLE` is a bash array which can contain
multiple values. If only one variable in the array is used, then a simple
assignment works, e.g., `BUILT_MODULE_NAME=my-module` has the same effect as
`BUILT_MODULE_NAME=(my-module)`.
#### Package Metadata
* `PACKAGE_NAME`: Name of the package (defaults to `--package`).
* `PACKAGE_VERSION`: Version of the package (defaults to `--package-version`).
#### Build Commands
* `MAKE[#]`: The command used to compile the modules. The first entry in this
array defaults to `make -C ${kernel_source_dir} M=${build_dir}`. You can
specify multiple `MAKE` commands indexed by number, paired with
`MAKE_MATCH[#]` to select a command based on the kernel version.
* `MAKE_MATCH[#]`: A regex matched against `$kernelver`. The first `MAKE[#]`
whose `MAKE_MATCH[#]` matches the target kernel version will be used.
#### Module Definitions
For packages containing multiple modules, use 0-indexed arrays:
* `BUILT_MODULE_NAME[#]`: The filename of the compiled module (without `.ko`).
The first entry in this array defaults to the package name.
* `BUILT_MODULE_LOCATION[#]`: Directory where the module is built, relative to
`$build_dir`. Default is empty (root of build dir).
* `DEST_MODULE_NAME[#]`: Destination filename for the module. Defaults to the
corresponding `BUILT_MODULE_NAME[#]`.
* `DEST_MODULE_LOCATION[#]`: Destination directory in the install tree,
relative to `$installtree`. **Must start with `/kernel`**. Defaults to
`/kernel/updates`.
* `STRIP[#]`: Whether to strip debug symbols for the module (`yes` or `no`).
Defaults to `yes`.
#### Dependencies and Constraints
* `BUILD_DEPENDS[#]`: List of other DKMS packages that must be built before
this one. Currently informational.
* `BUILD_EXCLUSIVE_KERNEL`: Regex. If the target `$kernelver` does not match,
the build will be skipped.
* `BUILD_EXCLUSIVE_ARCH`: Regex. If the target `$arch` does not match, the
build will be skipped.
* `AUTOINSTALL`: If set to `yes`, indicates the module should be rebuilt on
kernel upgrades. Currently informational.
#### Patches
* `PATCH[#]`: Filename of a patch (in `p1` format) located in the `patches/`
subdirectory of the package, to be applied before building.
* `PATCH_MATCH[#]`: Regex matched against `$kernelver`. The patch will only be
applied if it matches.
#### Hooks
Scripts (located in the package source) to run at various lifecycle stages:
* `POST_ADD`
* `PRE_BUILD` / `POST_BUILD`
* `PRE_INSTALL` / `POST_INSTALL`
* `POST_REMOVE`
--------------------------------------------------------------------------------
### Configuration Examples {#configuration-examples}
#### Simple Case (Single Module)
If your package contains a single module whose name matches the package name,
and it builds with a standard `make` command, **you do not need a `dkms.conf`
file at all**. The default values are sufficient.
#### Typical Case (Multiple Modules & Hooks)
A typical `dkms.conf` for a package with multiple modules and custom build
steps:
```bash
PACKAGE_NAME="my-driver"
PACKAGE_VERSION="1.0.0"
# First module
BUILT_MODULE_NAME[0]="my-core-driver"
DEST_MODULE_LOCATION[0]="/kernel/drivers/misc"
# Second module (depends on core)
BUILT_MODULE_NAME[1]="my-extension-driver"
BUILT_MODULE_LOCATION[1]="extensions/"
DEST_MODULE_LOCATION[1]="/kernel/drivers/misc"
# Custom build command
MAKE="make -C ${kernel_source_dir} M=${build_dir} EXTRA_CFLAGS=-DDEBUG"
# Hooks
PRE_BUILD="setup_env.sh"
POST_INSTALL="echo 'Driver installed successfully!'"
```
#### Real-world Case (Lustre Client Drivers)
This example shows how to configure a complex package that requires source
modification and autotools configuration, and has many modules with strict
load-order requirements. Note that it takes advantage of the implicit pairing of
the module name/location lists and the module destination defaults to keep the
file simple.
```bash
# Install build-time dependencies inside the builder container
POST_ADD="apt update && DEBIAN_FRONTEND=noninteractive apt install -y python3-dev libtool git flex bison build-essential libmpich-dev clang python3-setuptools llvm binutils lld libkeyutils-dev libmount-dev pkg-config libnl-3-dev libnl-genl-3-dev libyaml-dev libjson-c-dev"
# Configure the build directory
# Remove gcc-specific flags since COS uses clang.
clean_sources="sed -i -e 's/-Wno-stringop-overflow//g' -e 's/-Wno-stringop-truncation//g' -e 's/-Wno-format-truncation//g' config/lustre-toolchain.m4"
configure="LLVM=1 ./configure --disable-server --enable-client --disable-strict-errors --with-linux=/lib/modules/${kernelver}/build/"
PRE_BUILD="cd ${build_dir} && ${clean_sources} && bash autogen.sh && ${configure}"
# List of modules to build and their locations
BUILT_MODULE_NAME=(
ksocklnd ko2iblnd fid fld libcfs lmv lnet lov lustre
mdc mgc obdclass obdecho osc ptlrpc lnet_selftest
)
BUILT_MODULE_LOCATION=(
lnet/klnds/socklnd lnet/klnds/o2iblnd lustre/fid
lustre/fld libcfs/libcfs lustre/lmv lnet/lnet
lustre/lov lustre/llite lustre/mdc lustre/mgc
lustre/obdclass lustre/obdecho lustre/osc
lustre/ptlrpc lnet/selftest
)
```
--------------------------------------------------------------------------------
## Module Signing {#module-signing}
COS enforces kernel module signature verification. Modules that are not signed
by a trusted key will fail to load (`Required key not available`).
`cos-dkms` can automatically sign compiled modules during the `build` or
`install` phase.
By default, the `sha256` algorithm is used for signing. You can customize this
by passing `--hash-algorithm` (e.g., `sha384`, `sha512`).
### Signing Options
- **Local Private Key (Default)**: `cos-dkms` will look for a private key and
certificate.
* Default paths: `/var/lib/dkms/mok.key` (private key) and
`/var/lib/dkms/mok.cert` (certificate).
* You can override these using the environment variables
`MODULES_SIGN_KEY` and `MODULES_SIGN_CERT`.
- **Cloud KMS**: You can also use a key stored in Google Cloud KMS.
* Pass the KMS key resource name via the `--cloud-kms-key` flag:
`--cloud-kms-key=projects/my-project/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1`
* You must also provide the public certificate path via
`MODULES_SIGN_CERT` or have it at `/var/lib/dkms/mok.cert`.
--------------------------------------------------------------------------------
## Example Workflows {#workflows}
### Workflow 1: Installing Precompiled Modules {#workflow-precompiled-modules}
This workflow is intended to be used in production VMs to install modules. It
downloads precompiled modules from a GCS bucket cache and installs them. **No
compiler toolchain, kernel headers, or build dependencies are needed on the
VM.**
```
+-------------------+ Check GCS Cache +-------------------+
| Target VM | ----------------------> | GCS Bucket |
| (No Compiler req) | <---------------------- | (Precompiled .ko) |
+-------------------+ Download & Load +-------------------+
```
#### Steps:
1. On the COS VM, run the `cos-dkms` container.
2. Provide the `--gcs-bucket` where the precompiled modules are stored.
3. Use the `--insert-on-install` flag to automatically load them.
#### Example Command:
```bash
docker run --rm -it \
--privileged --net=host \
-v /etc/lsb-release:/etc/lsb-release \
-v /lib/modules:/host_modules \
gcr.io/cos-cloud/cos-dkms:latest \
install my-driver/1.0.0 \
--gcs-bucket=gs://my-company-cos-drivers \
--kernelmodulestree=/host_modules \
--insert-on-install
```
Using the high-level
[cos-dkms runner script](https://cos.googlesource.com/cos/tools/+/refs/heads/master/src/cmd/cos_dkms_runner),
this command can be simplified to:
```bash
sudo cos-dkms install my-driver/1.0.0 \
--gcs-bucket=gs://my-company-cos-drivers \
--insert-on-install
```
*What happens under the hood:*
1. `cos-dkms` detects the running kernel version, board, and build ID.
2. It checks `gs://my-company-cos-drivers/` for the precompiled `my-driver`
modules matching those exact parameters.
3. It downloads the precompiled `.ko` files.
4. It copies them to `/lib/modules/$(uname -r)/kernel/updates` in the container.
5. It dynamically computes the module insertion order from the dkms.conf file
and the modules' dependencies (including modules from the read-only
kernelmodulestree mapped from the host), then uses `insmod` to load each
module in the correct order.
--------------------------------------------------------------------------------
### Workflow 2: Installing Modules Compiled by Google for COS {#workflow-google-compiled-modules}
This is a special case of workflow 1 to install modules which are compiled as
part of the official COS image build and signed by Google. For these modules, a
value of `--gcs-bucket=cos-default` can be passed, and cos-dkms will infer the
correct COS artifacts bucket to download modules from.
#### Example Command (installing Lustre drivers):
```bash
docker run --rm -it \
--privileged --net=host \
-v /etc/lsb-release:/etc/lsb-release \
-v /lib/modules:/host_modules \
gcr.io/cos-cloud/cos-dkms:latest \
install lustre-client-drivers/2.14.0_p224 \
--gcs-bucket=cos-default \
--kernelmodulestree=/host_modules \
--insert-on-install
```
Using the high-level
[cos-dkms runner script](https://cos.googlesource.com/cos/tools/+/refs/heads/master/src/cmd/cos_dkms_runner),
this command can be simplified to:
```bash
sudo cos-dkms install lustre-client-drivers/2.14.0_p224 \
--gcs-bucket=cos-default \
--insert-on-install
```
*What happens under the hood:*
1. `cos-dkms` detects the running kernel version, board, and build ID.
2. It checks the `gs://cos-tools/` bucket for the precompiled modules (in this
case, Lustre) matching those exact parameters.
3. It downloads the precompiled `.ko` files.
4. It copies them to `/lib/modules/$(uname -r)/kernel/updates` in the container.
5. It dynamically computes the module insertion order from the dkms.conf file
and the modules' dependencies (including modules from the read-only
kernelmodulestree mapped from the host), then uses `insmod` to load each
module in the correct order.
--------------------------------------------------------------------------------
### Workflow 3: Installing Modules with modprobe {#workflow-modprobe}
Some modules require more complex configuration to install properly. In these cases,
using modprobe is more robust and configurable than the --insert-on-install option
that cos-dkms provides. However, it requires a little more setup on COS than using
--insert-on-install because the /lib/modules directory in COS is read-only.
At a high level, the workflow for using --modprobe-on-install on COS is to add
any necessary modprobe configurations in the host, copy the host's kernel modules
to a writable directory, and mount that directory into the cos-dkms container.
#### Example Command (installing Lustre drivers with modprobe):
```bash
# Write modprobe.d confs as normal; this one deals with the fact that
# lustre can't automatically load ksocklnd from the container context.
echo 'softdep lustre pre: ksocklnd post:' > /tmp/lustre.conf
# Copy the kernel's modules to a rw directory.
cp -ra /lib/modules/ /tmp/host_modules
# Run cos-dkms, pretending that the container's /lib/modules directory is the real
# one from this host. Doing it this way means that depmod/modprobe work as normal.
docker run --rm -it --privileged --net=host \
-v /etc/lsb-release:/etc/lsb-release \
-v /tmp/host_modules:/lib/modules \
-v /tmp/lustre.conf:/etc/modprobe.d/lustre.conf \
gcr.io/cos-cloud/cos-dkms:latest \
install lustre-client-drivers --latest --modprobe-on-install \
--gcs-bucket=cos-default \
--logtostderr -v2
```
*What happens under the hood:*
1. `cos-dkms` detects the running kernel version, board, and build ID.
2. It checks `gs://my-company-cos-drivers/` for the precompiled `my-driver`
modules matching those exact parameters.
3. It downloads the precompiled `.ko` files.
4. It copies them to `/lib/modules/$(uname -r)/kernel/updates`.
5. It runs depmod to build the module dependency graph, taking into account
the newly compiled modules and the host kernel's modules since they are
both in the "expected" /lib/modules/<kernel version> directory.
6. It runs modprobe to install the Lustre kernel drivers, taking into account
the modprobe configurations in the containers /etc/modprobe.conf, in this
case, the lustre.conf that we wrote in /tmp on the host.
--------------------------------------------------------------------------------
### Workflow 4: Building and Distributing Modules {#workflow-building-distributing}
This workflow can be used by developers or in CI/CD pipelines to compile module
sources for a specific COS version, sign them, and upload them to a GCS bucket
cache for use in Workflow 1.
```
+------------------+ Download Headers +--------------------+
| Builder Machine | <---------------------- | COS Repositories |
| (Runs cos-dkms) | & Compiler Toolchain +--------------------+
+------------------+
|
| Build & Sign Modules
v
+------------------+ Upload Artifacts +--------------------+
| Compiled .ko | ----------------------> | GCS Bucket |
+------------------+ | (Cache) |
+--------------------+
```
#### Steps:
1. Prepare your module source directory containing the code and a `dkms.conf`.
2. Run `cos-dkms build` with:
* Target `lsb-release`: An LSB release from a target COS image. The
variables in this file will be used to infer the correct toolchain,
kernel version, and arch to allow cross-compiling the modules for a
particular COS version.
* `--install-build-dependencies`: To fetch the matching COS kernel headers
and toolchain.
* `--make-variables=cos-default`: To use the official COS compiler flags.
* `--gcs-bucket` and `--upload`: To save the results to the cache.
* Signature keys (local or KMS) to sign the modules.
#### Example Command:
```bash
docker run --rm -it \
-v /$PWD/targets/lsb-release:/etc/lsb-release \
-v "$PWD/mok.key:/var/lib/dkms/mok.key" \
-v "$PWD/mok.cert:/var/lib/dkms/mok.cert" \
gcr.io/cos-cloud/cos-dkms:latest \
build my-module/v1.2.3 \
--install-build-dependencies \
--make-variables=cos-default \
--gcs-bucket=gs://my-company/my-cos-drivers \
--upload \
-v2 --logtostderr
```
After running this command, a user can install the modules on a COS VM with a
symmetric command:
```bash
docker run --rm -it \
--privileged --net=host \
-v /etc/lsb-release:/etc/lsb-release \
-v /lib/modules:/host_modules
gcr.io/cos-cloud/cos-dkms:latest \
install my-module/v1.2.3 \
--gcs-bucket=gs://my-company/my-cos-drivers \
--kernelmodulestree=/host_modules \
-v2 --logtostderr
```
*What happens under the hood:*
1. `cos-dkms` downloads the exact `clang` toolchain and kernel headers for the
COS version that matches the variables specified in the target LSB release.
2. It copies the module sources from the provided GCS bucket to a build
directory and applies any patches.
3. It builds the module using the `MAKE` command from the dkms.conf.
4. It signs all the compiled `.ko` files using the provided `mok.key` and
`mok.cert`.
5. It uploads both the source package and the compiled, signed `.ko` files to
`gs://my-company/my-cos-drivers/` under a path specific to the kernel
version, board, and build ID.