川獺の外部記憶

なんでも残しておく闇鍋みたいな備忘録

tcコマンドを使用した帯域制限を試す

Linuxには、ネットワーク帯域やレイテンシを制限できるtcコマンドというものがあり、これが便利そうということで試してみた。

調べてみたら色々出てくる領域なので、深掘りするよりはサラッと設定例をメモするぐらい。

環境はUbuntu 22上。制御対象のNICeth0 の場合で記載。

選択的な帯域制限を試す

tcコマンドには、filterというサブコマンドを使うことでパケットの送信元や宛先などの情報を元に、制限をかける機能がある。
この機能を使った例として、「すべてのパケットの帯域を広くとっておき、特定のネットワークやサーバとの間だけ帯域幅を狭める」という用途が一般的であり、この用途についてはググってもよく出てくる。
一方で、 「すべてのパケットの帯域を絞っておき、特定のネットワークやサーバとの間だけ帯域幅を広げる」 という用途は例があまりなく、試してみた。

結論からになるが、広げる例であっても問題なく制限できた。

以下に、実験例で使用した制限条件と、当該の条件での設定コマンド例を記載する。

  • プライベートネットワーク 192.168.0.0/16 に出入りするパケットは、帯域幅制限なし(10Gbit/sで制限)
  • 上記以外に出入りするパケットは、帯域幅制限(1Mbit/sで制限)
# 内向きフィルタ用の仮想デバイスの準備
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にする上でのベストプラクティスを模索する

帯域制限を本番環境に常設したい場合、考えるべきことが以下のようにいくつかありそう。

  • 環境を再起動したあとの持続性
    • 上記コマンドだけだと、 modplobeip linktc も環境が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については特筆できるところは多くないが、設計は次のような感じ。

  1. modprobe モジュールでは、 persistentpresent にすることで、 /etc/modules-load.d/ に永続化を書き込んでいる。
  2. sysvinit モジュールでは 選択的な帯域制限を試すmodplobe 以外の部分をOS起動時に実行するよう仕込んでいる。この際、 state を定義してこの場で起動しようとしてしまうと、Playbook実行時に必ず changed フラグが立ってしまう。今回は state 指定はせず、次のOS起動で制限がかかるようにした。

  1. 参考ページ: テスト用に回線速度をあえて下げる方法 - クイックノート
  2. dockerインストール直後の標準状態でdocker runした場合のみ検証。