LagopusがDockerコンテナとDPDK virtio_userドライバでつながりました!
DPDKでコンテナ接続ができるようになったことで、DPDKアプリケーションをコンテナで仮想化することができるようになります。
概要
この記事では、DockerベースのCI環境であるGitLab CIを用いて、Lagopusを題材にDPDKアプリケーションの自動テストをおこなう方法を紹介します。
ここでは、単体テストではなくDPDK接続を利用した統合テストを扱います。
物理接続による統合テストでは結線や接続本数などのハードウェア制約がありますが、仮想ドライバを用いることで柔軟な接続や切り替えが実現できるようになります。
統合テストの例として、OpenFlowのプロトコル準拠テストであるRyu CertificationをLagopusに対して実行する方法を紹介します。
GitLab, GitLab CIのインストール
GitLabはオンプレ版のCommunity Editionを利用します。
GitLabとGitLab CIについては、以下の公式ドキュメントを参考にしてインストールしてください。
- GitLabのインストール
- GitLab CI Multi Runnerのインストール
Runner環境の設定
Runnerのサーバ環境では、LagopusをDockerで動かす設定をする必要があります。
主に以下の2点についての設定が必要になります。
- Hugepagesの確保
- hugetlbfsの確保
- コンテナごとに確保する必要がある
詳細は以下のドキュメントを参考にしてください。
CI Multi Runnerの立ち上げ
公式の手順と同様で、executorにはDockerを使うように設定してください。
$ sudo gitlab-ci-multi-runner register
sudo gitlab-ci-multi-runner register
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
https://gitlab.com
Please enter the gitlab-ci token for this runner
xxx
Please enter the gitlab-ci description for this runner
my-runner
INFO[0034] fcf5c619 Registering runner... succeeded
Please enter the executor: shell, docker, docker-ssh, ssh?
docker
Please enter the Docker image (eg. ruby:2.1):
ruby:2.1 # なんでもよい ubuntu:16.04など
INFO[0037] Runner registered successfully. Feel free to start it, but if it's
running already the config should be automatically reloaded!
CI Multi Runnerの設定
Docker executorでDockerイメージをビルドする場合などは、Docker in Dockerの構成で利用することが多いですが、ここではホストのDockerを使う設定を利用します。
DPDKの動作にはHugepages等の割当が必要で、ホストに直接アクセスできるほうが都合がよいためです。
CI Multi Runnerの設定で、volumes
の項目にホストの/var/run/docker.sock
を追加して、ホストのDockerソケットをコンテナにマウントするようにします。
/etc/gitlab-runner/config.toml
concurrent = 1
check_interval = 0
[[runners]]
name = "my-runner"
token = "xxxxxxxxxxxx"
executor = "docker"
[runners.docker]
tls_verify = false
image = "ubuntu:16.04"
privileged = false
disable_cache = false
volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
.gitlab-ci.yml
の準備
今回題材となるLagopusのRyu Certificationを実行する.gitlab-ci.yml
は以下の通りです。
CIは次の流れで実行されます。
- test
- テスト実行
- cleanup_test
- コンテナの停止と削除
なお、LagopusのDockerイメージはこちらにあります。
https://hub.docker.com/r/falcon8823/lagopus-docker/
# .gitlab-ci.yml
variables:
CONT_TEST_IMAGE: falcon8823/lagopus-docker
RYU_NAME: ci-${CI_PIPELINE_ID}-ryu01
LAGO1_NAME: ci-${CI_PIPELINE_ID}-lago1
LAGO2_NAME: ci-${CI_PIPELINE_ID}-lago2
MAX_TIME: '1800' # judge.shのタイムアウト時間(秒)
stages:
- test
- cleanup_test
# テスト
test:
stage: test
image: docker:latest
script:
# イメージをpull
- docker pull $CONT_TEST_IMAGE
# vhostのsocketファイルを削除
- docker run --rm -v /tmp/dpdk:/tmp/dpdk busybox sh -c 'rm -rf /tmp/dpdk/vhost*'
# テスターのRyuを起動(デタッチモード)
- docker run -d --rm --name $RYU_NAME osrg/ryu ryu-manager ryu/ryu/tests/switch/tester.py --test-switch-dir ryu/ryu/tests/switch/of13/action/00_OUTPUT.json
# Lagopusを起動(デタッチモード)
- docker run -d --link=$RYU_NAME:ryu01 --name $LAGO1_NAME -v /path/to/vhost.dsl:/tmp/lago/vhost.dsl -v /tmp/dpdk:/tmp/dpdk -v /mnt/huge_c0:/mnt/huge_c0 $CONT_TEST_IMAGE lagopus -d -l /dev/stderr -C /tmp/lago/vhost.dsl -- -c 0xf -n 2 -m 1024 --no-pci --huge-dir=/mnt/huge_c0 --
# Lagopusを起動(デタッチモード)
- docker run -d --link=$RYU_NAME:ryu01 --name $LAGO2_NAME -v /path/to/virtio.dsl:/tmp/lago/virtio.dsl -v /tmp/dpdk:/tmp/dpdk -v /mnt/huge_c1:/mnt/huge_c1 $CONT_TEST_IMAGE lagopus -d -l /dev/stderr -C /tmp/lago/virtio.dsl -- -c 0xf0 -n 2 -m 1024 --no-pci --huge-dir=/mnt/huge_c1 --
# テスト判定スクリプトを実行
- ./judge.sh
# コンテナの終了と片付け
cleanup_test:
stage: cleanup_test
image: docker:latest
script:
- docker kill $RYU_NAME $LAGO1_NAME $LAGO2_NAME || true
- docker rm $RYU_NAME $LAGO1_NAME $LAGO2_NAME || true
when: always
Lagopusの設定ファイルの配置
docker runコマンドで、-v /path/to/vhost.dsl:/tmp/lago/vhost.dsl
と指定して、LagopusのDSLファイルをコンテナ内に渡しています。
この部分はイケてないのですがホストのDockerで動かす以上、Runnerが動いているコンテナにあるデータを別のコンテナに渡すことが難しくなります。
従って、ここでは妥協的にホストマシンの/path/to
に、vhost.dslとvirtio.dslをあらかじめ配置しておきます。
channel channel01 create -dst-addr ryu01 -protocol tcp
controller controller01 create -channel channel01 -role equal -connection-type main
interface interface1 create -type ethernet-dpdk-phy -device eth_vhost0,iface=/tmp/dpdk/vhost0
interface interface2 create -type ethernet-dpdk-phy -device eth_vhost1,iface=/tmp/dpdk/vhost1
interface interface3 create -type ethernet-dpdk-phy -device eth_vhost2,iface=/tmp/dpdk/vhost2
port port01 create -interface interface1
port port02 create -interface interface2
port port03 create -interface interface3
bridge bridge01 create -controller controller01 -port port01 1 -port port02 2 -port port03 3 -dpid 0x1
bridge bridge01 enable
channel channel01 create -dst-addr ryu01 -protocol tcp
controller controller01 create -channel channel01 -role equal -connection-type main
interface interface1 create -type ethernet-dpdk-phy -device virtio_user0,path=/tmp/dpdk/vhost0
interface interface2 create -type ethernet-dpdk-phy -device virtio_user1,path=/tmp/dpdk/vhost1
interface interface3 create -type ethernet-dpdk-phy -device virtio_user2,path=/tmp/dpdk/vhost2
port port01 create -interface interface1
port port02 create -interface interface2
port port03 create -interface interface3
bridge bridge01 create -controller controller01 -port port01 1 -port port02 2 -port port03 3 -dpid 0x2
bridge bridge01 enable
テストの成功・失敗判定
GitLab CIでは、scriptの終了コードに基づいてSuccess, Failedを判定します。
従って、テストの正否を判定するためには、テストスクリプトが終了コードを返す必要があります。
Ryu Certificationを実行するtester.py
は、テスト結果を標準出力にテキストで出力する上に、起動し続けるため終了コードを得ることができません。
従って、ログを定期的に読み込み、終了時の判定を行う以下のスクリプトを作成しました。
judge.sh
# !/bin/sh
# Ryuのテスターはテストが完了しても終了しないため、
# この監視スクリプトでsuccess or failedの判定をする
MAX_TIME=${MAX_TIME:-120}
# logが流れるようにバックグラウンド動作
docker logs -f $RYU_NAME &
log_pid=$!
# MAX_TIMEまでログを監視
for i in `seq 1 $MAX_TIME`; do
sleep 1
# 終了チェック
docker logs $RYU_NAME 2>&1 | grep 'Test report' 1> /dev/null
if test $? = 0; then
# OKとエラーの数を取得
result=`docker logs $RYU_NAME 2>&1 | grep 'OK\(.*\) / ERROR\(.*\)'`
ok_num=`echo "$result" | sed -e "s/OK(\(.*\)) \/ ERROR(\(.*\))/\1/"`
ng_num=`echo "$result" | sed -e "s/OK(\(.*\)) \/ ERROR(\(.*\))/\2/"`
# docker logsでログを表示していたプロセスをkill
kill $log_pid
# NGが0でなければエラー(failed),そうでなければ正常終了(success)
if test $ng_num != 0; then
exit 1
else
exit 0
fi
fi
done
CIの実行
あとは、.gitlab-ci.yml
の入ったGitリポジトリをGitLabにPushすることで、CIが実行されます。
このドキュメントで紹介したファイルは以下においてあります。
https://github.com/falcon8823/lagopus-gitlab-ci