この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
オンプレとAWSにまたがるシステムの場合、オンプレ側の帯域を圧迫しないようサーバからの通信に帯域制限をかけたい場合があります。
今回はLinuxの機能を利用しアウトバウンド通信の帯域を制限してみました。 Windows Serverの帯域制限についてはこちらをご覧ください。
帯域制限が無い状態
制限が無い状態でのアウトバウンド速度を計測してみましょう。 5GBのファイルをS3にアップロードしてみます。
[ec2-user@ip-10-0-0-37 ~]$ mkdir files
[ec2-user@ip-10-0-0-37 ~]$ dd if=/dev/zero of=files/5GB count=5120 bs=1M > /dev/null
[ec2-user@ip-10-0-0-37 ~]$ aws s3 sync files s3://qos.test --region=ap-northeast-1
Completed 238 of 732 part(s) with 1 file(s) remaining
dstatで確認すると約80Mbpsでした。 'send'カラムが送信量(Byte)です。
[ec2-user@ip-10-0-0-37 ~]$ sudo yum install -y dstat > /dev/null
[ec2-user@ip-10-0-0-37 ~]$ dstat 1
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 15 18
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 15 18
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 12 12
17 5 75 0 1 2| 0 0 | 417k 83M| 0 0 | 28k 24k
13 5 77 0 1 3| 0 0 | 561k 135M| 0 0 | 40k 29k
8 3 87 0 1 2| 0 0 | 403k 96M| 0 0 | 22k 14k
13 5 78 0 1 3| 0 0 | 622k 135M| 0 0 | 41k 31k
12 6 76 0 2 4| 0 0 | 715k 142M| 0 0 | 45k 32k
14 7 74 0 1 4| 0 0 | 767k 135M| 0 0 | 47k 34k
10 10 74 0 1 5| 0 0 | 913k 136M| 0 0 | 49k 33k
7 4 85 0 1 3| 0 0 | 743k 82M| 0 0 | 32k 10k
7 4 85 0 1 3| 0 0 | 767k 82M| 0 0 | 32k 11k
7 4 84 0 1 3| 0 0 | 775k 82M| 0 0 | 33k 11k
8 2 86 0 1 3| 0 0 | 756k 82M| 0 0 | 31k 8837
8 3 84 0 1 4| 0 0 | 781k 82M| 0 0 | 33k 11k
6 4 85 0 1 3| 0 0 | 758k 82M| 0 0 | 33k 10k
7 3 86 0 1 3| 0 0 | 745k 82M| 0 0 | 30k 7820
7 3 86 0 1 3| 0 0 | 739k 82M| 0 0 | 32k 11k
6 4 86 0 1 4| 0 0 | 801k 82M| 0 0 | 31k 7875
帯域制限の方法
それでは制限をかけてみましょう。 今回は1MBpsで制限をかけてみます。
tcによる帯域制限
下記コマンドを実行します。
[ec2-user@ip-10-0-0-37 ~]$ sudo tc qdisc add dev eth0 root tbf limit 1Mb buffer 200Kb rate 1Mbps
tc(traffic control)コマンドのトークンバケットフィルタ(TBF)を利用しています。 トークンバケットフィルタは下記の方式でアウトバウンドの帯域を制限しています。
- 送信されるデータはデータキューに入れられる
- データキューとは別に、あるサイズのバケツ内にトークンが溜まっている
- トークンは一定の速度で補充される
- バケットがいっぱいのとき、トークンは溢れるのでそれ以上溜まらない
- アウトバウンドのデータは、データキューから出るとき(送信時)トークンを消費する
- トークンの補充速度より消費量が多い場合、バケット内のトークン残量は減少し、バケットが空になるとトークンの補充速度と同量のデータしか送信されなくなる
したがって、トークンの補充速度を設定することでアウトバウンドの速度を制限できます。
tcコマンドのオプションは
tc qdisc add dev <<インターフェース>> root tbf limit <<データキューのサイズ>> buffer <<バケットのサイズ>> rate <<トークンの補充速度>>
なので、rateの値で帯域幅を指定できます。
先ほど実行したコマンドは、下記のような設定になります。
- データキューのサイズ:1MB(tcコマンドでは'b'はbitではなくByteを意味します)
- バケツのサイズ:200KB
- トークン補充速度(帯域幅):1MBps
指定した帯域に制限されているか確認します。
[ec2-user@ip-10-0-0-37 ~]$ dstat 1
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
0 0 99 1 0 0| 12k 759k| 0 0 | 0 0 | 390 193
0 0 100 0 0 0| 0 0 | 52B 838B| 0 0 | 26 26
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 14 16
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 14 16
8 3 89 0 0 0| 0 0 | 52k 28k| 0 0 |7669 8721
0 0 100 0 0 0| 0 0 |7052B 305k| 0 0 | 401 130
0 0 100 0 0 0| 0 0 |5012B 266k| 0 0 | 357 115
0 0 100 0 0 0| 0 0 |4412B 232k| 0 0 | 313 113
0 0 100 0 0 0| 0 0 |3732B 172k| 0 0 | 266 96
0 0 100 0 0 0| 0 0 |2576B 125k| 0 0 | 211 84
0 0 100 0 0 0| 0 0 |2932B 141k| 0 0 | 228 92
0 0 100 0 0 0| 0 0 |3132B 151k| 0 0 | 246 98
0 0 100 0 0 0| 0 0 |2692B 130k| 0 0 | 214 88
0 0 100 0 0 0| 0 0 |2692B 136k| 0 0 | 220 90
0 0 100 0 0 0| 0 0 |3288B 150k| 0 0 | 241 96
0 0 100 0 0 0| 0 0 |2852B 135k| 0 0 | 221 88
0 0 100 0 0 0| 0 0 |2852B 139k| 0 0 | 232 104
0 0 100 0 0 0| 0 0 |2932B 147k| 0 0 | 239 100
1MBpsを指定したのですが、約150KBpsくらいしか出ていません。
TCP Segmentation Offloadの無効化
黒ぶちめがねさんのブログに解決策が書いてありました。
データの分割/結合をNICに移管するTCP Segmentation Offload / Generic Segmentation Offloadという機能が影響しているようです。
確認したところ今回の環境でも有効になっていました。
[ec2-user@ip-10-0-0-37 ~]$ sudo ethtool -k eth0 | grep segmentation-offload
tcp-segmentation-offload: on
generic-segmentation-offload: on
下記コマンドで無効にします。
[ec2-user@ip-10-0-0-37 ~]$ sudo ethtool -K eth0 tso off gso off
[ec2-user@ip-10-0-0-37 ~]$ sudo ethtool -k eth0 | grep segmentation-offload
tcp-segmentation-offload: off
generic-segmentation-offload: off
それではもう一度、制限をかけた後のアウトバウンド速度を見てみましょう。
[ec2-user@ip-10-0-0-37 ~]$ dstat 1
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
0 0 99 1 0 0| 11k 719k| 0 0 | 0 0 | 385 188
0 0 100 0 0 0| 0 0 | 182B 946B| 0 0 | 24 20
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 12 10
0 0 100 0 0 0| 0 16k| 52B 358B| 0 0 | 24 26
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 12 12
0 0 100 0 0 0| 0 0 | 52B 358B| 0 0 | 12 16
0 0 100 0 0 0| 0 0 | 364B 626B| 0 0 | 24 22
1 1 99 0 0 0| 0 0 | 192B 460B| 0 0 | 54 48
7 3 91 0 0 0| 0 0 | 61k 846k| 0 0 |8408 8462
0 0 99 0 0 0| 0 0 | 15k 977k| 0 0 |1761 150
0 0 100 0 0 0| 0 0 | 15k 976k| 0 0 |1753 111
0 0 100 0 0 0| 0 0 | 14k 976k| 0 0 |1720 115
0 0 100 0 0 0| 0 0 | 14k 977k| 0 0 |1719 107
0 0 100 0 0 0| 0 0 | 15k 977k| 0 0 |1741 118
0 0 100 0 0 0| 0 0 | 15k 977k| 0 0 |1742 100
0 0 100 0 0 0| 0 0 | 14k 977k| 0 0 |1729 94
0 0 100 0 0 0| 0 0 | 14k 976k| 0 0 |1724 92
約1MBpsと期待通りに制限がかかっていることが確認できました。
これまでの設定は、サーバを再起動すると無効になるので、/etc/rc.local等に記載し、起動時に設定されるようにしておきましょう。
[ec2-user@ip-10-0-0-37 ~]$ cat /etc/rc.local
#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.
touch /var/lock/subsys/local
# Traffic Control
tc qdisc add dev eth0 root tbf limit 1Mb buffer 200Kb rate 1Mbps
ethtool -K eth0 tso off gso off
また、tcで設定した内容の確認、削除は下記コマンドで出来ます。
[ec2-user@ip-10-0-0-37 ~]$ sudo tc qdisc show
qdisc tbf 8006: dev eth0 root refcnt 2 rate 8000Kbit burst 200Kb lat 843.8ms
[ec2-user@ip-10-0-0-37 ~]$ sudo tc qdisc del dev eth0 root
まとめ
今回は、tcコマンドを利用してLinuxサーバからのアウトバウンドに帯域制限をかけてみました。 利用したトークンバケットという仕組みはやや取っ付きにくいですが、SSDベースのEBSにおけるIOPSやT2インスタンスのCPUクレジットでも利用されている仕組みですので、理解しておいて損は無いと思います。
参考
tcコマンドによる帯域制限については下記ページの説明が分かりやすかったです。 Linux Advanced Routing & Traffic Control HOWTO
TCP Segmentation Offloadについては下記ページの説明が分かりやすかったです。 Segmentation and Checksum Offloading: Turning Off with ethtool