帯域幅の輻輳への対処
誰もが経験したことがあるはずです。50GB のデータベースバックアップが始まった途端、ウェブサーバーのレスポンスタイムが 20ms から 2 秒に跳ね上がるような状況を。デフォルトの Linux 環境では、カーネルはパケットを先入れ先出し(FIFO)で処理します。つまり、巨大な ISO ファイルのダウンロードが、重要な SSH セッションやデータベースクエリを簡単に列の後ろに追いやることができるのです。
iproute2 スイートに含まれる Traffic Control (tc) は、これを解決するための業界標準ツールです。トラフィックのシェーピングと優先順位付けを行うことで、Quality of Service (QoS) を実装できます。「ベストエフォート」の配信から脱却することで、ネットワーク回線の使用率が 99% に達していても、優先度の高いサービスの応答性を維持できるようになります。
クイックスタート:5分でできる帯域幅制限
常に複雑な階層が必要なわけではありません。時には、特定のインターフェースが共有の 1Gbps アップリンクを使い果たすのを止めるだけで十分なこともあります。Token Bucket Filter (TBF) は、これを実現する最も速い方法です。
例えば、負荷の高いバックアップノードを抑制するために eth0 を 10Mbps に制限したいとします。root 権限で以下のコマンドを実行します:
# eth0 上の既存のルールを削除
sudo tc qdisc del dev eth0 root 2>/dev/null
# 帯域を 10mbit に制限する TBF qdisc を追加
sudo tc qdisc add dev eth0 root tbf rate 10mbit burst 32kbit latency 400ms
パラメータの解説:
- rate: 維持される速度制限。
- burst: 制限が適用される前に、ハードウェア速度で通過できる最大データ量。短時間のスパイクに対する小さなバッファと考えてください。
- latency: カーネルがパケットを破棄するまでに、キュー内に滞留できる時間。
ルールの適用状況を確認するには、以下のコマンドを使用します:
tc -s qdisc show dev eth0
深掘り:Hierarchical Token Bucket (HTB)
TBF は単純な制限には適していますが、現実世界の QoS には HTB (Hierarchical Token Bucket) が必要です。HTB はトラフィックをツリー構造で管理します。異なるサービスで全体の帯域幅プールを共有しつつ、重要なアプリには最小速度を保証し、ネットワークが空いている時には余剰帯域を「借用」させることができます。
tc の 3 つの柱
- Qdisc (Queueing Discipline): HTB や FQ_CoDel など、キューを管理する高レベルのアルゴリズム。
- クラス (Class): 特定の帯域幅制限を定義する細分化された単位。
- フィルター (Filter): 入力パケットを特定のクラスに割り当てるロジック。
本番環境向けの QoS ツリーの構築
100Mbps の回線があると仮定します。SSH に 5Mbps、ウェブトラフィックに 50Mbps を保証し、残りの 45Mbps をその他すべてで分け合うように設定します。ウェブサーバーがアイドル状態であれば、他のクラスが 100Mbps までバーストできるようにします。
まず、ルートとメインの親クラスを設定します:
# 1. HTB ルートを作成
sudo tc qdisc add dev eth0 root handle 1: htb default 30
# 2. 合計帯域幅を設定 (100mbit)
sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit
次に、優先度別のティアを定義します:
# SSH: ティア 1 (クラス 10) - 5mbit 保証、100mbit までバースト可能
sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit ceil 100mbit prio 1
# HTTP/HTTPS: ティア 2 (クラス 20) - 50mbit 保証
sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 50mbit ceil 100mbit prio 2
# バルク通信: ティア 3 (クラス 30) - 10mbit 保証
sudo tc class add dev eth0 parent 1:1 classid 1:30 htb rate 10mbit ceil 100mbit prio 3
この設定では、rate は「セーフティネット」となる保証帯域です。ceil パラメータは、他のクラスから借用できる絶対的な最大値を定義します。
応用編:iptables によるパケットのマーキング
生の IP アドレスに基づいて tc filters を記述するのは、メンテナンス上の悪夢です。iptables の mangle テーブルを使用してパケットに「タグ」を付ける方がはるかにスマートです。これにより、ポート、状態、さらにはプロセス ID によるマッチングなど、使い慣れた iptables の構文を使用してトラフィックを tc クラスに誘導できます。
ステップ 1: tc を iptables のマークに関連付ける
送信パケットの数値ハンドル(マーク)を確認するように tc に指示します:
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 1 handle 10 fw flowid 1:10
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 2 handle 20 fw flowid 1:20
ステップ 2: トラフィックにタグを付ける
SSH とウェブトラフィックにマークを付け、tc がそれらを識別できるようにします:
# SSH (ポート 22) をハンドル 10 としてタグ付け
sudo iptables -t mangle -A POSTROUTING -p tcp --dport 22 -j MARK --set-mark 10
# ウェブ (80/443) をハンドル 20 としてタグ付け
sudo iptables -t mangle -A POSTROUTING -p tcp -m multiport --dports 80,443 -j MARK --set-mark 20
この関心の分離は非常に強力です。ウェブ (80/443) を非標準ポートに移動した場合でも、tc の階層全体を再構築することなく、ファイアウォールのルールを 1 つ更新するだけで済みます。
現場で役立つ実践的なヒント
1. 送信 (Egress) が鍵
Linux の tc は、送信 (Egress) トラフィック用に設計されています。他人が自分に送ってくるパケットを、到着した後にコントロールするのは容易ではありません。ダウンロードを制限したい場合は、通常、応答 (ACK) パケットを制御します。本番環境において、サーバーから出ていくパケットを制御することは、戦いの 90% を制することを意味します。
2. デフォルトクラスの安全性
HTB ルートには必ず default クラスを定義してください(例:default 30)。フィルターに一致しないパケットはすべてここに分類されます。デフォルトがないと、分類されていないトラフィックが制限を完全に回避し、優先度の高いクラスの帯域を奪ってしまう可能性があります。
3. リアルタイム・モニタリング
状況を見ずに QoS を設定するのは、災害の元です。以下のコマンドを使用して、トラフィックがリアルタイムで各クラスに振り分けられる様子を監視してください:
watch -n 1 tc -s class show dev eth0
“Sent” バイトに注目してください。クラスがその rate に達すると、HTB が制限を維持するためにパケットの送信を遅らせ始め、”Tokens” が減少するのがわかります。
4. ハードウェア・オフロードの罠
現代の NIC は Generic Segmentation Offload (GSO) を使用して、小さなパケットを巨大な「スーパーパケット」にまとめてからドライバに渡します。これが tc の計算を狂わせることがあります。レート制限が不安定に感じる場合は、オフロードを無効にして挙動が安定するか確認してください:
sudo ethtool -K eth0 gso off tso off
5. 設定의 永続化
tc や iptables で適用した設定は、再起動後に消失します。信頼性の高い本番環境を構築するには、これらのコマンドをシェルスクリプトにまとめてください。このスクリプトは systemd サービス経由で実行するか、iptables-persistent を使用して呼び出すことができます。ロジックを 1 つのスクリプトにまとめておくことで、チーム内での監査や修正が容易になります。
tc を使いこなすことで、ネットワークが負荷のかかった状態でどのように振る舞うかを正確に制御できるようになります。これは、スパイク時にクラッシュするサーバーと、ユーザーに対して完璧な応答性を維持し続けるサーバーの決定的な違いとなります。
