Amazon Linux 2017.03で新しいTCP輻輳制御アルゴリズムBBRを試してみた

Linux
107件のシェア(ちょっぴり話題の記事)

ども、大瀧です。
AWSが提供するLinuxディストリビューション Amazon Linuxの最新版であるAmazon Linux 2017.03がリリースされました。このリリースで採用しているLinuxカーネル バージョン4.9では、新しいTCP輻輳制御アルゴリズムBBRのサポートが追加されています。
しかしながらAmazon Linux 2017.03.0のカーネルパッケージではBBRモジュールが無効なため、今回はカーネルを再ビルドして試す手順をご紹介してみたいと思います。

お断り : 一般的にカーネルを再ビルドして利用することはディストリビュータのサポート範囲外になります。自己責任の元、検証用途にとどめ本番環境への適用はビルド済みカーネルパッケージのリリースを待ちましょう。

ビルド環境の準備

まずは、カーネルを再ビルドするための環境を整えましょう。一般的にカーネルの再ビルドにはローカルディスクを10GB程度消費しますので、EC2のルートボリュームを大きめにするか、インスタンスストアや追加のEBSボリュームを検討しましょう。今回は以下のEC2を準備しました。

  • インスタンスタイプ : i3.large
  • インスタンスの料金プラン : スポット
  • リージョン : 東京
  • ディスク構成
    • ルートボリューム : 既定のまま
    • 追加のボリューム : インスタンスストア0(/dev/nvme*n1)

インスタンスタイプは特に問いませんが、i3.largeだとc3.largeと同程度の価格帯で高速なNVMeタイプのインスタンスストアが利用できるのでお奨めです。インスタンスストアを利用する場合は、以下のようにあらかじめフォーマットし、カーネルの再ビルドで使用するディレクトリにマウントしておきましょう。

$ sudo mkfs -t ext4 /dev/nvme0n1
  :(略)
$ sudo mkdir /usr/src/rpm
$ sudo mount /dev/nvme0n1 /usr/src/rpm 

一般的なソフトウェアのビルドに必要なパッケージのインストールとカーネルのソースパッケージのダウンロードを、以下のドキュメントを参考に行います。

$ sudo yum groupinstall -y "Development Tools"
  :(略)
$ get_reference_source -p kernel-4.9.XX-XX.XX

Requested package: kernel-4.9.XX-XX.XX.amzn1
Found package from local RPM database: kernel-4.9.XX-XX.XX.amzn1.x86_64
Corresponding source RPM to found package : kernel-4.9.XX-XX.XX.amzn1.src.rpm

Are these parameters correct? Please type 'yes' to continue: yes
Source RPM downloaded to: /usr/src/srpm/debug/kernel-4.9.XX-XX.XX.amzn1.src.rpm
$ 

続いてrpmコマンドでソースを展開します。

$ sudo rpm -ivh ls /usr/src/srpm/debug/kernel-4.9.XX-XX.XX.amzn1.src.rpm 2> /dev/null
Updating / installing...
   1:kernel-4.9.XX-XX.XX.amzn1        ################################# [100%]
$

これで、/usr/src/rpm/SOURCES/にカーネルソース、/usr/src/rpm/SPECS/にスペックファイル(RPMパッケージの記述ファイル)が展開されます。

$ ls /usr/src/rpm/SOURCES/
0001-kbuild-AFTER_LINK.patch
0002-lib-cpumask-Make-CPUMASK_OFFSTACK-usable-without-deb.patch
  :(中略)
0029-sctp-deny-peeloff-operation-on-asocs-with-threads-sl.patch
config-generic
config-x86_32-generic
config-x86_64-generic
cpupower.config
cpupower.init
kconfig.py
linux-4.9.XX
linux-4.9.XX-patches
linux-4.9.XX-patches.tar
linux-4.9.XX.tar
Makefile.config
mod-extra.list
mod-extra.sh
mod-extra-sign.sh
x509.genkey
$ ls /usr/src/rpm/SPECS/
kernel.spec
$

また、今回のカーネルのビルドには依存パッケージのインストールが求められたため、以下のパッケージも追加でインストールしておきます。

$ sudo yum install -y xmlto asciidoc openssl-devel elfutils-devel   zlib-devel binutils-devel newt-devel "perl(ExtUtils::Embed)" audit-libs-devel numactl-devel pciutils-devel
  :(略)
$

これで準備OKです。

BBRを含めたカーネルの再ビルド

では、BBRモジュールを有効にして、カーネルを再ビルドしましょう。/usr/src/rpm/SOURCES/config-genericの662行目にあります。

変更前

# CONFIG_TCP_CONG_BBR is not set

変更後

CONFIG_TCP_CONG_BBR=m

また、既存のカーネルと区別するために、ビルドするカーネルのエクストラバージョンを変更しておきましょう。スペックファイルを編集します。

%define buildid XX.XX.local

では、再ビルドを実行します。

$ sudo rpmbuild -bb /usr/src/rpm/SPECS/kernel.spec
  :(10〜20分程かかります)
Wrote: /usr/src/rpm/RPMS/x86_64/kernel-tools-devel-4.9.XX-XX.XX.local.amzn1.x86_64.rpm
Wrote: /usr/src/rpm/RPMS/x86_64/kernel-tools-debuginfo-4.9.XX-XX.XX.local.amzn1.x86_64.rpm
Wrote: /usr/src/rpm/RPMS/x86_64/kernel-devel-4.9.XX-XX.XX0.local.amzn1.x86_64.rpm
Wrote: /usr/src/rpm/RPMS/x86_64/kernel-debuginfo-4.9.XX-XX.XX.local.amzn1.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.EXTdbG
+ umask 022
+ cd /usr/src/rpm/BUILD
+ cd kernel-4.9.XX.amzn1
+ rm -rf /usr/src/rpm/BUILDROOT/kernel-4.9.XX-XX.XX.local.amzn1.x86_64
+ exit 0
$

ビルドしたRPMパッケージは/usr/src/rpm/RPMS/<アーキテクチャ名>/以下にあります。カーネルのRPMパッケージをインストールし、再起動しましょう。

$ sudo rpm -ivh /usr/src/rpm/RPMS/x86_64/kernel-4.9.XX-XX.XX.local.amzn1.x86_64.rpm
Preparing...                          ################################# [100%]
Updating / installing...
   1:kernel-4.9.XX-XX.XX.local.amzn1  ################################# [100%]
$ sudo reboot

再起動してきたら、起動しているカーネルバージョンを確認します。

$ uname -r
4.9.20-10.30.local.amzn1.x86_64
$ 

エクストラバージョンから、ビルドしたカーネルということが確認できますね。カーネルの準備はこれでOKです。

BBRの有効化

GitHubにあるBBRのドキュメントを参考に、カーネルパラメータを設定します。デフォルトのキューイング規則はカーネルパラメータを変更しても反映されなかったため、/etc/sysctl.confに記述し、再起動して反映します。

net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
$ sudo reboot

再起動したら、procファイルシステムやtcコマンドで反映されているか確認しましょう。以下のような表示になります *1

$ cat /proc/sys/net/ipv4/tcp_congestion_control
bbr
$ sudo tc qdisc
qdisc noqueue 0: dev lo root refcnt 2
qdisc mq 0: dev eth0 root
qdisc fq 0: dev eth0 parent :1 limit 10000p flow_limit 100p buckets 1024 orphan_mask 1023 quantum 3028 initial_quantum 15140 refill_delay 40.0ms
$

これでBBRが有効になりました。

動作比較

では、既定の輻輳制御アルゴリズムであるCUBICと今回のBBRを比較してみます。BBRの特徴はGitHubにあるGoogleによるプレゼン資料が詳しいですが、ラストワンマイルのアクセス回線やモバイル網などパケットロスやレイテンシの大きいクライアントとの通信が効率的にできるようです。

そこで今回はLinuxカーネルのNetworkEmulator(netem)でWebサーバーからの送信パケットでパケットロスを発生させ、パフォーマンスに与える影響を比較してみます。以下のシェルスクリプトを流してみました。

#!/bin/bash
echo "task start."

echo -n "set loss rate 0% : "
if ssh 172.31.30.109 sudo tc qdisc | grep netem > /dev/null; then
  ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 0%
else
  ssh 172.31.30.109 sudo tc qdisc add dev eth0 root netem loss 0%
fi
(time curl 172.31.30.109/hugefile > /dev/null) 2>&1 | grep real

ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 0.1%
echo -n "set loss rate 0.1% : "
(time curl 172.31.30.109/hugefile > /dev/null) 2>&1 | grep real

ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 1%
echo -n "set loss rate 1% : "
(time curl 172.31.30.109/hugefile > /dev/null) 2>&1 | grep real

ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 2%
echo -n "set loss rate 2% : "
(time curl 172.31.30.109/hugefile > /dev/null) 2>&1 | grep real

ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 4%
echo -n "set loss rate 4% : "
(time curl 172.31.30.109/hugefile > /dev/null) 2>&1 | grep real

ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 8%
echo -n "set loss rate 8% : "
(time curl 172.31.30.109/hugefile > /dev/null) 2>&1 | grep real

ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 16%
echo -n "set loss rate 16% : "
(time curl 172.31.30.109/hugefile > /dev/null) 2>&1 | grep real

ssh 172.31.30.109 sudo tc qdisc change dev eth0 root netem loss 0%
echo "task all finished."

以下のインスタンスで実行しました。

  • Webサーバー : BBRを有効化したt2.largeインスタンス、nginxでddで作成した2GBのファイルをホスト
  • クライアント : i3.largeインスタンス、上記スクリプトを実行

Webサーバーは当初i3インスタンスで試していたのですがENAとnetemによるパケットロス発生の組み合わせが良くなかったようで、cubic/bbr問わず極端にスループットが落ちてしまったため、EC2拡張ネットワーキングが無効で同クラスのt2.largeを選定しました。パケットロスなしでも100Mbps強のスループットなので、t2インスタンスのCPUクレジットやインスタンスタイプに拠るNICの帯域制限の影響は軽微と判断しています。

デフォルト(cubic)の場合

$ bash task.bash
task start.
set loss rate 0% : real 0m21.128s
set loss rate 0.1% : real       0m17.413s
set loss rate 1% : real 0m17.851s
set loss rate 2% : real 0m19.851s
set loss rate 4% : real 0m42.203s
set loss rate 8% : real 4m26.207s
set loss rate 16% : real        56m24.347s
task all finished.
$

bbrの場合

$ bash task.bash
task start.
set loss rate 0% : real 0m17.093s
set loss rate 0.1% : real       0m17.336s
set loss rate 1% : real 0m17.696s
set loss rate 2% : real 0m18.132s
set loss rate 4% : real 0m18.681s
set loss rate 8% : real 0m19.798s
set loss rate 16% : real        0m21.224s
task all finished.

Googleの触れ込み通り、ロスレートがある程度高くなる場合にcubicは極端にスループットが落ちる(今回はダウンロード時間が長くなる)のに対して、bbrはそこまで長くならない様子が分かりました。

まとめ

Amazon Linuxのカーネルを再ビルドし、カーネル4.9で追加されたBBRの様子をレポートしました。豊富なリソースを駆使してパフォーマンス課題を解決することの多いクラウド環境ですが、用途によってはこのようなLinuxカーネルのチューニングも役立つことがあるかもしれません。LinuxのTCPスタックは枯れた分野だと思っていたのですが、アクティブに新機能が追加されているのが個人的には新鮮でした。

なにかのお役に立つと幸いです。

参考URL

脚注

  1. `sudo tc qdisc`コマンドはNICのドライバやvCPU数によって複数行表示される場合があります