2015年1月30日金曜日

tcを利用したトラフィックコントロール


tc(traffic control)は、qdisc(queueing discipline)に従いトラフィックの
帯域保証、帯域制御、フィルタリングなどのコントロールを行うために
linuxでよく使われるコマンドである。


【目的】
server1側に置いている100Gbyteのファイルをserver2側から
server1のssh(22/tcp)ポートへアクセスし、scp転送を一回実施する。

コマンドで書けばこうである。
[server2]$ scp user@server1@/var/tmp/file /var/tmp/file

 ---------
| server1 | ssh(22/tcp)
 ---------  eth0 ここにtcを設定 
     →      
   ↑  ↓
 ---------
| server2 |
 ---------

server1とserver2の間は1Gbit/sの帯域で結ばれている。
他の通信に影響を与えたくないため、
server1からの22/tcp発の通信に帯域制限をかける。

ただし、3時間で100Gのデータを転送することを目標とし帯域を保証させる。
帯域保証値は少し余裕をもたせ100Mbit/sとする。
(必要な転送速度)
100(Gbite) * 10^9 / 3 / 60 / 60 ≒ 10(Mbit/s) ≒ 80(Mbit/s)

帯域の制御と保証はserver2との接続口であるserver1のインターフェースeth0に対して行う。


〜注意〜
ボンディングを組んでいる場合、論理nicではなく、
それを構成する物理nicに対して実施する必要がある。
例えば、bond0をeth0とeth1で組んでいれば、
同じ設定をeth0とeth1に対して実施する。

〜注意2〜
scpには帯域制限をかけるオプションがある。
このオプションと使い方で十分であればそれでもいいだろう。
-l limit:Limits the used bandwidth, specified in Kbit/s.



【tcの設計方針】
設計方針の前にclass(階層による分類)について簡単に復習しておく。

階層を設定するため、classfulなqdiskとしてHTB(Hierarchical Token Bucket)を使用する。
HTBは以下の機能を提供できる。
 classによる階層構造(だからこそのclassfulという名称である)
 bitrateの制御(TBF(Token Bucket Filter)という仕組みを利用する)

TBFはbucketを指定することで送信されるパケットの特性をコントロールできる。


● class(階層による分類)の設計
要件を満たすために以下の階層を作る。
qdiscのハンドル(classのid)は、メジャー番号とマイナー番号をコロンでつないで表現する。
idは任意の番号を付与できるため、1:10000でも1:20000でもよい。
ただし、idの最大値は2^16乗=65536である。

1:0 → 1:1 → 1:10
          → 1:20

説明上このツリーをAと表現する。


サーバ間は1Gbit/sの帯域があることを指示しておく必要があるが、
階層のrootである1:0(1:とも表現される)には帯域周りの設定を入れられない。
そのため、1:1を設定している。

eth0の通信で送信元ポートが22/tcpの通信は1:10へ、
マッチしなかったパケットへ1:20へ流す。


● 内部qdiskの設計
1:0 → 1:1 → 1:10 → B(100:0)
          → 1:20 → C(200:0)

B、Cはclassless qdiscである。
仕組みとしてはPFIFO(Packet First-In First-Out)を利用する。
PFIFOは最初に入ったパケットを最初に出すシンプルな仕組みである。
順次実行されるようなジョブを考えているのでこれでよいだろう。

SFQ(Stochastic Fair Queuing)を使えばセッションごとに
公平にパケット送信機会を与えることもできるが、ここでは使わない。
複数のエンドユーザーからのアクセスによる並列でセッションが走る場合などに有効である。

PFIFO_FASTという仕組みもあるようである(詳細はmanを参考)。
(PFIFO_FAST)
# man tc-pfifo_fast

(PFIFO)
# man tc-pfifo

(SFQ)
# man tc-sfq


● filterの設計
フィルタは1:0に設定する。
先に書いたように、
eth0の通信で送信元ポートが22/tcpの通信は1:10へ、
マッチしなかったパケットへ1:20へ流す。

フィルタはその下段の1:1にも設定できるが、
出入り口側で停める方針をとりたかったため、1:0のrootに設定した。



【帯域保証と帯域制限の設計】
先にtc内の単位をおさておく(非常に混乱を招く表記である)。

b  : byte
bit: bit
bps: byte per second
bit: bit per second
m  : mega
g  : giga


帯域保証(rate)、帯域制限(ceil)は要件を満たすために以下の通りとする。
1:1   1000mbitの帯域保証  1000mbitまでの帯域制限(※1)
1:10   100mbitの帯域保証   100mbitまでの帯域制限
1:20                    1000mbitまでの帯域制限(※2)

rateを指定する際、子のrateの合計が親のrateと同一かそれ以下になるようにする。
ceilを指定する際、子のceilの中で最大のものが親のceilと同一かそれ以下にする。

※1
rateはclass構造の最上位とそれ以外で意味合いが異なる。
最上位では単にrateもceilもbitrateの上限を意味する。

※2
800mbitではなく、1000mbitとしている理由は、
1:10宛のトラフィックがなくなれば最大1000mbitのトラフィックが流れる可能性あるためである。



【設定】
● classの設定
対象のqdiscを参照する際に使うハンドル番号(メジャー番号とマイナー番号)を
指定して、classごとに設定を行っていく。

帯域保証(rate)と帯域制限(ceil)用のbucketのsizeとして、
それぞれをburst、cburstとして設定する。

(burst値の求め方)
burst[Bytes] = bitrate[bits/s] / 8 * タイマ解像度[sec]

希望するbitrateが100Mbits/s、タイマ解像度が10msの場合
100Mbits/s / 8 * 10ms
= 100 * 10^6 / 8 * 10 * 10^-3
= 100 / 8 * 10 * 10^3
= 125kBytes


quantumはMTUの値よりも小さくしてはいけないという制限があるため、
ethernetのMTUに合わせて1500とする。
classは親を指定しないといけないため、root側ではclassの設定はできない。

# tc qdisc add dev eth0 root handle 1: htb default 20

# tc class add dev eth0 parent 1:0 classid 1:1  htb rate 1000mbit ceil 1000mbit burst 125kb cburst 125kb quantum 1500

# tc class add dev eth0 parent 1:1 classid 1:10 htb rate  100mbit ceil  100mbit burst 125kb cburst 125kb quantum 1500

# tc class add dev eth0 parent 1:1 classid 1:20 htb rate          ceil 1000mbit burst 125kb cburst 125kb quantum 1500


● 内部qdisk(classfulなqdiscの内部で使用されるqdisc)の設計
# tc qdisc add dev eth0 parent 1:10 handle 100: pfifo limit 1000

# tc qdisc add dev eth0 parent 1:20 handle 200: pfifo limit 1000


● filterの設定
# tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip sport 22 0xffff flowid 1:10
※0xffffはマッチの前に取るANDマスクである。



【確認コマンド】
● classの確認
# tc -s class ls dev eth0
class htb 1:1 root prio 0 rate 1000Mbit ceil 1000Mbit burst 1250b cburst 1250b
 Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
 rate 0bit 0pps backlog 0b 0p requeues 0
 lended: 0 borrowed: 0 giants: 0
 tokens: 187 ctokens: 187

class htb 1:10 root prio 0 rate 100000Kbit ceil 100000Kbit burst 1250b cburst 1250b
 Sent 11803789634 bytes 7797588 pkt (dropped 723, overlimits 0 requeues 0)
 rate 0bit 0pps backlog 0b 0p requeues 0
 lended: 7797588 borrowed: 0 giants: 0
 tokens: 1922 ctokens: 1922

class htb 1:20 root prio 0 ceil 1000Mbit burst 1250b cburst 1250b
 Sent 6237429193 bytes 4372040 pkt (dropped 0, overlimits 0 requeues 0)
 rate 2120bit 4pps backlog 0b 0p requeues 0
 lended: 4372040 borrowed: 0 giants: 0
 tokens: 187 ctokens: 187


● filterの確認
# tc -s filter ls dev eth0
filter parent 1: protocol ip pref 1 u32
filter parent 1: protocol ip pref 1 u32 fh 800: ht divisor 1
filter parent 1: protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:10  (rule hit 12170247 success 7798311)
  match 02020000/ffff0000 at 20 (success 7798311 )



【切り戻し】
● 帯域制御設定(qdisc)の削除
qdiscが削除されると、class/filter設定も削除される。
# tc qdisc del dev eth0 root



【設定の永続化】
# vi /etc/rc.local
/sbin/tc qdisc add dev eth0 root handle 1: htb default 20
/sbin/tc class add dev eth0 parent 1: classid 1:1  htb rate 1000mbit ceil 1000mbit burst 125kb cburst 125kb quantum 1500
/sbin/tc class add dev eth0 parent 1: classid 1:10 htb rate  100mbit ceil  100mbit burst 125kb cburst 125kb quantum 1500
/sbin/tc class add dev eth0 parent 1: classid 1:20 htb rate          ceil 1000mbit burst 125kb cburst 125kb quantum 1500
/sbin/tc qdisc add dev eth0 parent 1:10 handle 100: pfifo limit 1000
/sbin/tc qdisc add dev eth0 parent 1:20 handle 200: pfifo limit 1000
/sbin/tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip sport 514 0xffff flowid 1:10



【TSO、GSOの扱い】
その他tcを利用する場合の検討事項として、
TSO(TCPsegmentation offload)、
GSO(Generic Segmentation Offload)がある。

TSOでは送信パケットの分割処理をNICにオフロードし、
GSOでは受信パケットをNICが結合してからOSに渡すオフロード処理を
ソフトウェアで実装した機能である。

処理を明示的にOS側で行わせる場合はGSO、TSOを無効化する。
無効化していなくてもNIC側で対応していれば問題ないが、
NICが対応しているかどうかを確認できなければ、
確実性を求めOS側に処理を任せる方針をとった方がいいだろう。

● 事前確認
# ethtool -k eth0
Offload parameters for eth0:
rx-checksumming: on
tx-checksumming: on
scatter-gather: on
tcp-segmentation-offload: on
udp-fragmentation-offload: off
generic-segmentation-offload: on
generic-receive-offload: on
large-receive-offload: off


● 設定
# ethtool -K eth0 tso off
# ethtool -K eth0 gso off
※ここでの例では送出する通信を制御していたため、tsoだけoffにすればよい。


● 事後確認
tcp-segmentation-offloadがoffとなっていることを確認
# ethtool -k eth0
Offload parameters for eth0:
rx-checksumming: on
tx-checksumming: on
scatter-gather: on
tcp-segmentation-offload: off
udp-fragmentation-offload: off
generic-segmentation-offload: off
generic-receive-offload: on
large-receive-offload: off


● 設定の永続化
# vi /etc/udev/rules.d/50-udev.rules
ACTION=="add", SUBSYSTEM=="net", KERNEL=="eth0", RUN+="/sbin/ethtool -K eth0 tso off"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="eth0", RUN+="/sbin/ethtool -K eth0 gso off"


● 切り戻し
TSO、GSOを有効化する。
# ethtool -K eth0 tso on
# ethtool -K eth0 gso on



◆ 参考