tcコマンドを使用した帯域制限を試す
Linuxには、ネットワーク帯域やレイテンシを制限できるtcコマンドというものがあり、これが便利そうということで試してみた。
調べてみたら色々出てくる領域なので、深掘りするよりはサラッと設定例をメモするぐらい。
環境はUbuntu 22上。制御対象のNICは eth0
の場合で記載。
選択的な帯域制限を試す
tcコマンドには、filterというサブコマンドを使うことでパケットの送信元や宛先などの情報を元に、制限をかける機能がある。
この機能を使った例として、「すべてのパケットの帯域を広くとっておき、特定のネットワークやサーバとの間だけ帯域幅を狭める」という用途が一般的であり、この用途についてはググってもよく出てくる。
一方で、 「すべてのパケットの帯域を絞っておき、特定のネットワークやサーバとの間だけ帯域幅を広げる」 という用途は例があまりなく、試してみた。
結論からになるが、広げる例であっても問題なく制限できた。
以下に、実験例で使用した制限条件と、当該の条件での設定コマンド例を記載する。
# 内向きフィルタ用の仮想デバイスの準備 modprobe ifb modprobe act_mirred ip link set dev ifb0 up # 内向きをフィルタするための準備(仮想デバイスへ内向きパケットを流す) tc qdisc add dev eth0 ingress handle ffff: tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ifb0 # 内向きパケットをフィルタする tc qdisc add dev ifb0 root handle 1:0 htb default 20 tc class add dev ifb0 parent 1:0 classid 1:10 htb rate 10Gbit tc class add dev ifb0 parent 1:0 classid 1:20 htb rate 1Mbit tc filter add dev ifb0 protocol ip parent 1:0 prio 1 u32 match ip src 192.168.0.0/16 flowid 1:10 # 外向きパケットをフィルタする tc qdisc add dev eth0 root handle 1:0 htb default 20 tc class add dev eth0 parent 1:0 classid 1:10 htb rate 10Gbit tc class add dev eth0 parent 1:0 classid 1:20 htb rate 1Mbit tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 192.168.0.0/16 flowid 1:10
内向きパケットのフィルタについては、tcコマンドで内向きパケットを制御する原理1の都合上、複雑になっている。この点への言及は省略するが、その他の部分については以下のような設計とした。
Docker Networkとの併用を試す
iptableなどの低層のネットワーク周りに絡む技術は、Docker Networkと競合して動かないものが稀にある。本技術に関しても、念のためにDocker Networkとの併用が可能か確認する。
結果のみとなるが、少なくとも上記フィルタについては、tcコマンドによる制限はDocker Network内のコンテナに対しても効くことが確認できた。2
Ansible playbookにする上でのベストプラクティスを模索する
帯域制限を本番環境に常設したい場合、考えるべきことが以下のようにいくつかありそう。
- 環境を再起動したあとの持続性
- 上記コマンドだけだと、
modplobe
もip link
もtc
も環境がrebootすると設定が飛んでしまう。
- 上記コマンドだけだと、
- 帯域制限の構成を確認・再現できるよう管理する
上記をまとめて解決できるよう、Ansibleのplaybookにしておくと便利。
playbook.yml
- name: Add my-traffic-control to /etc/init.d and enable it hosts: all tasks: - name: Load ifb module modprobe: name: ifb state: present persistent: present become: yes - name: Load act_mirred module modprobe: name: act_mirred state: present persistent: present become: yes - name: Add my-traffic-control to /etc/init.d/my-traffic-control template: src: my-traffic-control.j2 dest: /etc/init.d/my-traffic-control owner: root group: root mode: '0755' become: yes - name: Make sure my-traffic-control is starts on boot sysvinit: name: my-traffic-control enabled: yes runlevels: - 2 - 3 - 4 - 5 become: yes # 手動再起動を前提としている。自動再起動を行う場合は以下を有効化する # - name: Reboot # reboot: # reboot_timeout: 300 # become: yes
my-traffic-control.j2
#!/bin/sh ### BEGIN INIT INFO # Provides: my-traffic-control # Required-Start: $all # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: # Short-Description: My traffic control script ### END INIT INFO # 準備 ip link set dev ifb0 up # 内向き(準備) tc qdisc add dev eth0 ingress handle ffff: tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ifb0 # 内向き tc qdisc add dev ifb0 root handle 1:0 htb default 20 tc class add dev ifb0 parent 1:0 classid 1:10 htb rate 10Gbit tc class add dev ifb0 parent 1:0 classid 1:20 htb rate 1Mbit tc filter add dev ifb0 protocol ip parent 1:0 prio 1 u32 match ip src 192.168.0.0/16 flowid 1:10 # 外向き tc qdisc add dev eth0 root handle 1:0 htb default 20 tc class add dev eth0 parent 1:0 classid 1:10 htb rate 10Gbit tc class add dev eth0 parent 1:0 classid 1:20 htb rate 1Mbit tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 192.168.0.0/16 flowid 1:10
Playbookについては特筆できるところは多くないが、設計は次のような感じ。
modprobe
モジュールでは、persistent
をpresent
にすることで、/etc/modules-load.d/
に永続化を書き込んでいる。sysvinit
モジュールでは 選択的な帯域制限を試す のmodplobe
以外の部分をOS起動時に実行するよう仕込んでいる。この際、state
を定義してこの場で起動しようとしてしまうと、Playbook実行時に必ず changed フラグが立ってしまう。今回はstate
指定はせず、次のOS起動で制限がかかるようにした。
- 参考ページ: テスト用に回線速度をあえて下げる方法 - クイックノート↩
- dockerインストール直後の標準状態でdocker runした場合のみ検証。↩