Linux TC (帯域制御、帯域保証) 設定ガイドライン

Abstract

このドキュメントはLinuxにおいて帯域制限のためにtcを用いる際のガイドラインです。
tcは様々な用途に活用できるものですが、プロダクションにおいて特定のserver daemonのトラフィックを制限するというシナリオで活用することを目的としています。

tcのより詳しい詳細については別にドキュメントを書きましたのでそちらを参照してください。
よくわかるLinux帯域制限

Root qdiscの選定

帯域制限を行いたい場合のqdiscは主に以下のようになるでしょう。

  • TBF
  • PRIO + 内部qdiscとしてTBF
  • HTB

 

それぞれ用途に合わせて適切なものがあるのですが、機能としてはHTBが前者2つの上位互換となるので、迷った場合にはHTBを使えば問題ありません。ということで以後HTBの設定について解説します。

class構造,トラフィックのclassify, filterの設定

すべてのトラフィックを一括で制限したい場合

HTBを使う場合は最低限のclass構造を設定します。filterはdefaultの設定のみ行います。

トラフィックを分類して特定のトラフィックを制限したい場合

特定のトラフィックのみを制限したい場合にはそのトラフィックを特定するためのfilterと言われる分類ルールを記述する必要があります。

filterには以下のような情報が活用できます。

  • 送信元(source)のIP
  • 送信元(source)のポート番号
  • 送信先(destination)のIP
  • 送信先(destination)のポート番号
  • プロトコル(tcp, udp, icmp, …)
  • device(eth0, …)
  • TOSフィールド
  • その他

残念ながら特定のprocessからの通信を抽出するようなfilterは(少なくとも簡単には)つくることができません。

以下のような2分割を行うclass構造に対応するfilterを考えます。

source / destination IP によるfilter

例として以下のケースを考えます。

tcを設定するコンピュータAのprivate IP: 192.168.1.5
主な通信先となるコンピュータBのprivate IP: 192.168.1.10
subnet mask: 255.255.255.0 (= /24)

filterを設置するclassは1:0、
filterにヒットした場合の分類先は1:20とします。

送信元がAの場合にヒットさせたいときは以下のようにします。(注:tcは送信するパケットの制御のみを行うため、大抵の場合このfilterには意味がありません)

送信先がBの場合にヒットさせたいときは以下のようにします。

送信先がLAN内のIPである場合にヒットさせたいときは以下のようにします。

source / destination port によるfilter

送信元portが5555の場合にヒットさせたいときは以下のようにします。

送信先portが6666の場合にヒットさせたいときは以下のようにします。

複合的なfilterの例

ANDの結合は一行に複数のルールを並べることで実現できます。
送信先IPが192.168.1.10で送信先portが6666である場合にヒットさせたいときは以下のようにします。

ORの結合は一つのclassに複数のルールを設置することで実現できます。
送信元portが5555 or 5556 or 5557の場合にヒットさせたいときは以下のようにします。

今回上げた以上に複雑なAND/ORを組み合わせたルールについては自分は検証したことがありません。

検証

classにどの程度のpacketが流れているかは以下のコマンドで確認できます。

実際にトラフィックを流しながらfilterの設定が正しいか確認しましょう。具体的な確認方法については「トラフィックの確認」で解説しています。

その他

このドキュメントでは一部のみを取り上げています。完全な情報については以下のURLを参照してください。
Linux Advanced Routing & Traffic Control HOWTO Chapter 9. Queueing Disciplines for Bandwidth Management
ただしこのfilterの設定、class構造の設定は極めて難解です。iproute2のtcコマンドの代わりに、こういった設定が簡単に行えるとされているTCNGを利用してみてもいいかもしれません。

rate, ceilの設定

rate, ceilは分類したclassにどの程度のトラフィックを割り当てるかをbitrateで指定する仕組みです。
一般的にトラフィックを制御するためにコントロールするものにはbitrateとパケット数があると思いますが、残念ながらパケット数を制御する方法はtcでは提供されていません。

表記について

tcコマンドにおいては単位の表記に注意が必要です。文脈によるが以下の様に解釈されます。

  • bps: Byte Per Second
  • bit: Bit Per Second
  • b: Byte
  • bit: Bit

bpsがByte Per Secondとなることは大きな誤解を生む原因となります。。この単位は使用しないことが推奨されます。このドキュメントでも以降Per Secondとなる単位についてはすべてbitまたはbits/sと表記します。

なおK, M, G等の単位については問題なく使用できます。ただしそれぞれ10^3, 10^6, 10^8として扱われ、2^10, 2^20, 2^30ではないことには注意が必要です。

rate

rateはclass構造の最上位とそれ以外で意味合いが異なります。

最上位では単にbitrateの上限を意味します。これはceilと同じです。そのため最上位classでは必ずrate=ceilとするようにしてください。

それ以外ではclassに対して「ここまでのbitrateが出る」ことを保証する(=帯域保証する)数値です。
class構造の親のrateが子のrateの合計と同一かそれ以下になるようにしてください。

例として以下のようなclass図で1Gbits/sの回線に接続している場合を考えます。
1:10は確実に300Mbits/sが与えられて欲しい(=帯域保証したい)トラフィック、1:20はその他のトラフィックで特に帯域保証は必要ないものとします。

1:1には実際の回線の上限のトラフィックを設定します。
1:10には回線について「確実に出るであろうbitrate」を設定します。ここでは1Gbits/sなのですが、確実にその速度が出るかはわかりません。他のマシンが帯域を使いすぎている場合も考えられます。ここでは、おおまかな計算として8掛けしてrate: 800Mbits/sとします。
1:100は帯域保証が必要なトラフィックのclassです。rate: 300Mbits/sとします。
1:200はその他のトラフィックのclassです。帯域保証は必要ないため、rate: 1Mbits/sとします。(0は設定できないため)

ここで、1:200には帯域保証が不要ではなく、1:100に保証されている帯域以外については1:200が「確実に」使用したい(=帯域保証したい)場合には、800 – 300 = 500でrate: 500Mbits/sとします。

ceil

ceilはclassに対して「これ以上のbitrateが出ないようにする」(=帯域制限する)数値です。
class構造の親のceilを子のceilの最大のものが超えないようにしてください。

引き続きrateの時に利用した例を使って説明します。

1:10については300Mbits/sまで出て欲しいが逆に300Mbit/s以上は出ないで欲しい。
1:20については回線を目一杯使用しても構わない。
という場合

1:1は回線の最大値である1Gbits/sとします。ceil: 1000Mbits/s(注:それ以上の値でも構いません)
1:10は制限したい数値にします。ceil: 300Mbits/s
1:20も回線の最大値である1Gbits/sとします。ceil: 1000Mbits/s

burst, cburstの設定

burstはrateに、cburstはceilにそれぞれ対応するbufferのようなものの大きさです。詳細な理解については別ドキュメントを参照してください。

ここでは設定方法のみを説明したいと思います。
qdiscからのパケットの送信は常に行われているわけではなく0-100ms程度のランダムな間隔で行われています。また、qdiscへのパケットの到着も一定ではなくパケットを送信するapplication, server processの都合により時間ごとに偏りがでます。burstを大きくすることでこのような偏りに対応して、1sec, 10secなどの単位で設定したbitrateを「出し続ける」ことができます。

burstの設定は最小の値を計算して、それを測定しながら安定して設定したbitrateが出るまで大きくしていく作業となります。

quantum, r2qの設定

quantum, r2qの設定は通常必要ないです。デフォルトのr2q=10で問題ありません。
ただし設定によりkernelのwarningが出ることがあります。その場合も無視してしまっても大丈夫なのですが、r2q経由ではなく明示的quantumを指定することで警告を避けることができます。詳細は別ドキュメントを参照してください。

計算

burstの「最小でもこのくらいにしておかなくてはならない」という値については計算で得ることができます。

burst[Bytes] = bitrate[bits/s] / 8 * 10[msec]

となります。なぜ10msecなのか等については別ドキュメントを参照してください。

検証

最小の値を計算したら、実際に検証しながら数値を大きくしていきます。大抵の場合最小の値の3〜20倍程度の大きさとなるでしょう。

例としてrate, ceilのいずれかを100Mbits/sとしたい場合には以下のようにしていきます。

Step1. 最小の値を計算する。
= 100 * 10^6 / 8 * 10 * 10^-3
= 100 / 8 * 10 * 10^3
= 125kBytes

Step2. 実際に利用するHTBフィルタの該当classにrate=ceil=検証中のbitrate, burst=cburst=検証中の値となるように設定する

他の設定がボトルネックになったり、逆に他の設定のせいで制限が外れてしまうことを防ぐようにします。

次のようなフィルタで100Mbitの箇所について検証する場合、こうなります。

そして1:10にトラフィックを流しながらburst値を大きくしていきます。できれば実際のアプリケーションによるトラフィックが望ましいです。

以下は例です。

burst[Bytes] bitrate[Mbit/s]
125kB 50MB
250kB 89MB
500kB 98MB
1000kB 101MB
1500kB 101MB

 

この例では1000KB以上が必要であることがわかりました。

なお今回は1Gbit/sの回線において100Mbit/sの制限値であったため検証を行えましたが、実環境では他への影響を考慮して、限界値に近い800Mbit/s等の検証を行うのは危険な場合があります。その場合は他の測定値を参考にしながら、十分大きめの値を設定してください。
例えば今回の例では最終的には次のような設定としても構いません。

内部qdiscの設定

HTBのclass構造で葉(末端)となるclassには必ず内部qdiscが付属します。特に指定しない場合も暗黙的に付きますが、tcのshowコマンドでデバッグが行いにくくなることや、どのようなパラメータが設定されているか不明となることなどデメリットが多くあります。そのため、内部qdiscは必ず明示的に指定してください。

内部qdiscの種類

内部qdiscには以下のようなものがあります。

  • PFIFO
    • HTBの内部qdiscには一般的にこれを用いる
  • PFIFO_FAST
    • PFIFOでTOSを考慮して優先度の高いIPパケットが先に送信されるようにしたもの
    • ただしHTB内ではclass番号が小さいものが先に処理されるルールがあり、こちらが優先されるため、使うメリットが弱くなっている。具体的には1:10と1:20があった場合には、「1:10の優先パケット→1:10の通常パケット→1:20の優先パケット→1:20の通常パケット」の順に処理される
  • SFQ
    • 複数のセッションに公平に送信機会を与える仕組みを持つ
    • 制限目一杯のパケットを送信することが多いclass内で、すべてのセッションのlatencyを公平にしたい場合には役立つ
    • 一方通常の通信でも処理のオーバーヘッドによりlatencyが増えてしまうため、必要性がない場合は使用しない方が良い。

 

設定方法

PFIFOを設定する場合は以下のようにします。

limitはqdiscにいくつのpacketを貯めておけるかというパラメータです。通常は1000や10000などにしておけば問題ありません。
limitが小さいとbitrateがでなかったりpacketのdropが発生する原因となります。
limitが大きいと帯域を目一杯使用している場合にlatencyが悪化する原因となる場合があります。

qdiscでどの程度のdropが発生していて、どの程度のpacketが貯まっているかは以下のコマンドで確認できます。

実際にトラフィックを流しながら設定を確認しましょう。具体的な確認方法については「トラフィックの確認」で解説しています。

トラフィックの確認

実際に流れているトラフィックを確認するにはtc -sコマンドを利用します。具体例を挙げながら解説していきます。

tc -s classではclassにおけるトラフィックを確認できます。tc -s qdiscでは各qdisc (HTB, HTBの内部qdisc)におけるトラフィックを確認できます。
これらにおいて、主に確認すべき点は各classのSent byte, dropped, overlimitsです。
Sent XXX bytesはそのclass, qdiscが送信したパケットの累計バイト数を表しています。流したトラフィックが想定したclassを通っているかがわかります。
droppedはenqueueできなかったパケット数です。latencyより安定したtroughputを重視する場合は内部qdiscのlimitを増やすことでdropを抑えることができます。
overlimitsはdequeueしようとした際にrateやceilなど何らかの制限に引っかかりdequeueできなかった回数です。速度制限が有効かどうかの目安となります。

 

設定例

ここでは完全な設定例をいくつか示します。

すべてのトラフィックをまとめて帯域制限する

すべての項目を500Mbit/sに制限する例です。

この例では帯域保証は必要ないため、rateはceilと同一の値としてしまっています。
rateを0にしても同様の動作となります。

先頭のqdisc delは既に設定が存在した場合にそれを削除するものです。安全のため、かならず記述しておくことをおすすめします。

特定のトラフィックにのみ帯域制限をかける

192.168.1.10宛のトラフィックのみを100Mbit/sに制限する例です。帯域保証はありません。
DBにおいて定期的にバックアップを行う際の帯域制限などのようなイメージでしょうか。

特定のトラフィックにのみ帯域制限をかけながら帯域保証も行う

source portが5555, 5556のトラフィックを200Mbit/sに制限する例です。帯域保証も同200Mbit/sとします。
一応その他のトラフィックについてもある程度(200Mbit/s)の帯域保証を行うようにします。

イメージとしてRedisのreplicationを想定しています。レプリケーションが切れたり遅延したりしないように帯域保証が必要で、かつ新規レプリケーションの際には膨大なトラフィックが流れるため、帯域制限も必要となります。

1:20のceilを1000Mbitにしているため、1:10のトラフィックが少ない場合は1:20が1:10に保証された分も使用することができるようになっています。

なお、上記の例では回線全体で保証できるbitrateが明示されていません。やや冗長な表現となりますが、以下のように修正することで回線全体の保証を明示できます。(回線全体での保証を800Mbits/sとする)

チェックリスト

作成したtcの設定をプロダクションに適用する前に、必ず以下の項目を確認してください。

  • htbの各classについてrate, ceil, burst, cburstが明示的に設定されている
  • 最上位classではrate==ceilとなっている
  • htbの各末端classについて内部qdiscとその設定が明示的に設定されている
  • 帯域の制限(XXXbits/s以上出ないこと)、帯域の保証(XXXbits/sまで出ること)について実環境で検証されている

 

注意

このガイドラインは策定されたばかりで十分にプロダクションでの実績をもつものではありません。個々のケースにおいては無条件に利用するのではなく、あくまで参考程度にお考えください。

Author: yuya.yaguchi