tshell_blog

ソフトウェアと車輪がついた乗り物のはなし

Dockerでリアルタイム処理(cyclictest)を実行する

Dockerコンテナでリアルタイムタスクは動かせないの?と思ったので試してみました。

docker docs | Runtime options with Memory, CPUs, and GPUsにはConfigure the realtime schedulerという項目があり,リアルタイム処理を実現できそうな感じがします。

以下の手順でDockerコンテナ上でcyclictestを実行するところまでやってみます。

  1. カーネルのビルド
  2. リアルタイムスケジューラを使ったコンテナの起動
  3. cyclictestの実行

CONFIG_RT_GROUP_SCHEDを有効にしたカーネルをビルドする

docker docsを見るとCONFIG_RT_GROUP_SCHEDが有効になっているカーネルでないとコンテナがリアルタイムスケジューラを使用できないようです。RT PREEMPTIONパッチを当てがてら,CONFIG_RT_GROUP_SCHEDを有効にしてカーネルをビルドします。

パッチとカーネルソースのダウンロード

実はPreemption ModelをLowlatency DesktopとしないとCONFIG_RT_GROUP_SCHEDを有効にできないので,RT PREEMTTIONパッチは必要はなさそうですが一応当てておきます。

まずは以下からRT PREEMPTIONパッチを探します。

https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/5.4/

5.4.13-rt7が最新版のようなので,以下から5.4.13のソースをダウンロードします。

https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/

以下の.tar.gzファイルをダウンロードしました。 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.13.tar.gz

RT PREEMPTIONパッチの適用

カーネルソースとパッチを同じディレクトリに保存し,以下を実行します。

$ tar xf linux-5.4.13.tar.gz
$ cd linux-5.4.13
$ gzip -cd ../patch-5.4.13-rt7.patch.gz | patch -p1 --verbose

Preemption Modelの変更

カーネルソースが展開されたディレクトリで以下を実行します。

$ make menuconfig

以下のように General setup > Preemption Model > Preemptible Kernel(Low-Latency Desktop)を選択します。
f:id:tshell:20200127211016p:plain

以下のようにGeneral setup > Control Group support > CPUControllerのGroup scheduling for SCHED_RR/FIFOにチェックを入れます。
上記のPreemption ModelでFully Preemptible Kernel(Real-Time)を選択するとGroup scheduling for SCHED_RR/FIFOが選択できなくなります。 f:id:tshell:20200127211110p:plain

上記2つを設定したら下部メニューのExitを選択し,menuconfigを終了します。終了時に.configに内容を保存するか聞かれるのでYesを選択します。

カーネルイメージの作成

以下のコマンドを実行し,カーネルイメージとヘッダイメージを作成します。

$ make-kpkg -j 8 --rootcmd fakeroot --initrd kernel_image kernel_headers

ソースが展開されているディレクトリの一つ上の階層に.debファイルが作成されるので以下のコマンドを実行して.debファイルをインストールします。

$ cd ..
$ sudo dpkg -i linux-headers-5.4.13-rt7_5.4.13-rt7-10.00.Custom_amd64.deb linux-image-5.4.13-rt7_5.4.13-rt7-10.00.Custom_amd64.deb

GRUBメニューが表示されなくなることの対策

ビルドしたカーネルをインストールして再起動しGRUBメニューでカーネルを選択するわけですが,起動時にGRUBメニューが表示されない場合があります。

そのような場合は,/etc/default/grubファイルを編集してGRUBメニューが表示されるようにします。

$ sudo vi /etc/default/grub

以下のように先頭に#をつけて,2行をコメントアウトする
# GRUB_TIMEOUT_STYLE=hidden
# GRUB_TIMEOUT=0

編集後はupdate-grubを実行しておきます。

Invalid Signatureによりカーネルがロードできない場合の対策

ビルドしたカーネルで起動しようとした際にInvalid Signatureエラーによりカーネルがロードできない場合があります。
以下のようにしてSecure Bootを無効にすることで回避できます。

PCをシャットダウンする前に以下のコマンドを実行します。

$ sudo mokutil --disable-validation

再起動後にmokutilを実行するか確認するダイアログが表示されます。mokutilを実行すると上記のmokutilコマンド実行時に設定したパスワード入力を求められます。
入力したパスワードをすべて入力するのではなく,パスワードの何文字目は何かという形式で1文字ずつ入力します。

リアルタイムスケジューラを使うコンテナの起動

起動前の確認

以下のファイルがあることを確認します。

/sys/fs/cgroup/cpu.rt_runtime_us

Dockerデーモンが動作していることを確認します。

$ docker ps

起動していない場合,以下コマンドにより再起動します。

$ sudo service docker restart

Dockerfileの作成とイメージのビルド

以下のようなDockerfileを作成します。ubuntu18.04にRT-Testsをインストールしています。

FROM ubuntu:18.04

RUN apt-get update && apt-get upgrade -y && apt-get install -y build-essential libnuma-dev git python
RUN git clone git://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git && \
    cd rt-tests && \
    git checkout stable/v1.0 && \
    make all && \
    make install

Dockerファイルをビルドします。

$ docker build -t rttest:latest .

コンテナの起動

以下のコマンドによりrttestイメージを起動します。

$ docker run -it --cpu-rt-runtime=950000 --ulimit rtprio=99 --cap-add=sys_nice rttest:latest

起動時に以下のようなエラーが出る場合があります。

docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:281: applying cgroup configuration for process caused \"failed to write 950000 to cpu.rt_runtime_us: write /sys/fs/cgroup/cpu,cpuacct/docker/e2a68990c554b501acfdee02a2d6be94240edd8860335c6ce5b64f559e82eba3/cpu.rt_runtime_us: invalid argument\"": unknown.

上記のようなエラーが出るのは/sys/fs/cgroup/cpu/docker/cpu.rt_runtime_usに設定されているリアルタイムタスクの実行時間が0になっているためのようなので,以下のコマンドにより0以上の値を設定します。

$ sudo -s
# echo 950000 > /sys/fs/cgroup/cpu/docker/cpu.rt_runtime_us

/sys/fs/cgroup/cpu/docker/cpu.rt_period_usには1000000が設定されており,これとの比率で割り当てられたCPU処理時間の何%をリアルタイム処理に回すかを設定できます。上記の場合はcpu.rt_runtime_usが950000なので,95%がリアルタイム処理に回されます。

上記を実行後,再度docker runコマンドを実行し,rttestコンテナのコンソールに接続します。

cyclictestの実行

起動したコンテナ上でcyclictestを実行します。

# cyclictest --smp --priority=80 --interval=200 --distance=0

以下のように表示され,cyclictestが実行できました。

WARN: stat /dev/cpu_dma_latency failed: No such file or directory
policy: fifo: loadavg: 6.18 3.00 1.65 6/1057 23           

T: 0 (   20) P:80 I:200 C: 618500 Min:      2 Act:    7 Avg:    8 Max:    2419
T: 1 (   21) P:80 I:200 C: 617407 Min:      2 Act:    6 Avg:    8 Max:    2513
T: 2 (   22) P:80 I:200 C: 618459 Min:      2 Act:   12 Avg:    7 Max:    2540
T: 3 (   23) P:80 I:200 C: 616966 Min:      2 Act:    6 Avg:    9 Max:    2431

しかし,スレッドの起動周期200us(cyclictestの引数--inteval=200)に対して最大のレイテンシが2540usとなっています。
平均は約10usなので概ね大丈夫そうですが,たまに遅れるようです。

まとめ

Dockerコンテナ上でcyclictestを実行するところまで試しました。
次回はcyclictestの結果をヒストグラム表示してもう少し詳しく見てみます。