BPF を使うプログラムはコンテナで動かすには特権が必要であったり、Linux カーネルの特定のコンフィグが ON になっていないと(CONFIG_BPF=yとか)動かないなどあり、Circle CI や Travis CI のような有名どころの CI では十分にテストすることができない。

Semaphore CI は CI 環境として VM が提供されるので、Docker などの実行が可能である。
ただ、VMが使えるだけでは Linux カーネルのコンフィグ問題は解決しないのだが、rkt の KVM Stage1 が使える。
自前でビルドした Stage1 Image を KVM で動かし、その上で Pod を動かすことができる。

この方法は https://kinvolk.io/blog/2017/02/using-custom-rkt-stage1-images-to-test-against-various-kernel-versions/ に書いてある。

上記ブログでは ACI のビルドをしているが、 stage1-builder で作成されたイメージがあるので、それを使うのが楽。

カーネルのヘッダは /lib/modules/${kernel_version}-kinvolk-v1/source/include などにあるため、それを C_INCLUDE_PATH に追加しておく。あとは、ホスト側にあるリポジトリのパスをマウントしてあげれば良い。

    --environment=C_INCLUDE_PATH="${kernel_header_dir}/arch/x86/include:${kernel_header_dir}/arch/x86/include/generated:/lib/modules/${kernel_version}-kinvolk-v1/include" \

また、使う Docker Image で bcc がソースからビルドされている場合は BCC_KERNEL_MODULES_SUFFIXsource にしておく必要がある。

ref : https://github.com/iovisor/bcc/pull/430/files

困っているのは、たまに rkt から名前解決ができないことがあり、依存パッケージのダウンロードに失敗してしまうということ。
これはどういう問題なのだろうな… というのを調べるために、手元で環境を作って再現するまでがちょっと面倒。

cxray だと、こんな感じで CI 上でのビルドとテストができた。

#!/bin/bash

set -eu
set -o pipefail

# The kernel versions we want to run the tests on
readonly kernel_versions=("4.9.96")

# The rkt version which is set as a dependency for
# the custom stage1-kvm images
readonly rkt_version="1.30.0"

# Download rkt if not available yet as Semaphore CI
# doesn't have rkt at the time of writing
if [[ ! -f "./rkt/rkt" ]] ||
  [[ ! "$(./rkt/rkt version | awk '/rkt Version/{print $3}')" == "${rkt_version}" ]]; then

  curl -LsS "https://github.com/coreos/rkt/releases/download/v${rkt_version}/rkt-v${rkt_version}.tar.gz" \
    -o rkt.tgz

  mkdir -p rkt
  tar -xvf rkt.tgz -C rkt --strip-components=1
fi

# Pre-fetch stage1 dependency due to rkt#2241
# https://github.com/coreos/rkt/issues/2241
sudo ./rkt/rkt image fetch --insecure-options=image "coreos.com/rkt/stage1-kvm:${rkt_version}" >/dev/null

for kernel_version in "${kernel_versions[@]}"; do
  kernel_header_dir="/lib/modules/${kernel_version}-kinvolk-v1/source/include"
  # The stage1-kvm image to use for the tests
  stage1_name="kinvolk.io/aci/rkt/stage1-kvm:${rkt_version},kernelversion=${kernel_version}"

  # Make sure there's no stale rkt-uuid file
  rm -f ./rkt-uuid

  # You most likely want to provide source code to the
  # container in order to run the tests. You can do this
  # with volumes:
  # https://coreos.com/rkt/docs/latest/subcommands/run.html#mounting-volumes

  # Depending on the level of privileges you need,
  # `--insecure-options=all-run` might be necessary:
  # https://coreos.com/rkt/docs/latest/commands.html#global-options

  # timeout can be used to make sure tests finish in
  # a reasonable amount of time
  sudo timeout --foreground --kill-after=10 20m \
    ./rkt/rkt \
    run --interactive \
    --uuid-file-save=./rkt-uuid \
    --insecure-options=image,all-run \
    --dns=8.8.8.8 \
    --stage1-name="${stage1_name}" \
    --volume=cxray,kind=host,source="$PWD" \
    --mount=volume=cxray,target=/go/src/github.com/mrtc0/cxray \
    docker://mrtc0/bcc-docker:latest \
    --memory=1024M \
    --environment=GOPATH=/go \
    --environment=GO111MODULE=on \
    --environment=C_INCLUDE_PATH="${kernel_header_dir}/arch/x86/include:${kernel_header_dir}/arch/x86/include/generated:/lib/modules/${kernel_version}-kinvolk-v1/include" \
    --environment=BCC_KERNEL_MODULES_SUFFIX="source"
    --exec=/bin/sh -- -c \
    'printf "\n\nKernel Environment (on kernel $(uname -r))\n\n" && 
     cd /go/src/github.com/mrtc0/cxray &&
     mount -t tmpfs tmpfs /tmp &&
     mount -t debugfs debugfs /sys/kernel/debug/ &&
     make test'

  # Determine exit code from pod status due to rkt#2777
  # https://github.com/coreos/rkt/issues/2777
  test_status=$(sudo ./rkt/rkt status "$(<rkt-uuid)" | awk '/app-/{split($0,a,"=")} END{print a[2]}')
  if [[ $test_status -ne 0 ]]; then
    exit "$test_status"
  fi

  sudo ./rkt/rkt gc --grace-period=0
  echo "Test successful on ${kernel_version}"
done
bpf  test