簡単にDockerのネットワーク帯域制限を実現する



# 結論を先に言うと

[tcconfig](https://github.com/thombashi/tcconfig)を使おう。
これはまじで神ツールで、散々tc芸を覚えたあとでこれを見つけて笑いすぎて崩れ落ちた（精神不安定）。

```{sh:pip}
pip install tcconfig
```
して、

```{sh:outbound制限}
tcset <device> --rate 1Mbps
```
とすると、あら不思議

```
root@tc-test:/# speedtest-cli
Retrieving speedtest.net configuration...
Testing from Google Cloud (34.85.93.218)...
Retrieving speedtest.net server list...
Selecting best server based on ping...
Hosted by Rakuten Mobile, Inc (Tokyo) [5.66 km]: 5.484 ms
Testing download speed................................................................................
Download: 1347.63 Mbit/s
Testing upload speed......................................................................................................
Upload: 994.95 Mbit/s
```
だったのが

```
root@tc-test:/# tcset ens4 --rate 1Mbps
root@tc-test:/# speedtest-cli
Retrieving speedtest.net configuration...
Testing from Google Cloud (34.85.93.218)...
Retrieving speedtest.net server list...
Selecting best server based on ping...
Hosted by fdcservers.net (Tokyo) [5.66 km]: 4.518 ms
Testing download speed................................................................................
Download: 216.79 Mbit/s
Testing upload speed......................................................................................................
Upload: 1.14 Mbit/s
```

Uploadが1.14Mbit/sに！なるほどそうなるか。

Downloadも遅くなっているように見える。しかし、1Mbpsにはなっていない。これはinbound帯域制限をやってないから。
やる方法は後述の部分を読んで欲しい。

# 帯域制限を実現する方法

簡単に言えばtcコマンドを使えば事実上は帯域制限をかけることができる。
tcコマンドは、こちらの記事：[tcコマンドの使い方](https://qiita.com/hana_shin/items/d9ba818b49aca87b2314)や、
他さまざまな文献でも取り上げられており、その複雑なtc芸を見ていると本当の意味で芸に見えてくる。

正直あまりtcコマンドに詳しくなってもしょうがないが、我慢して色々勉強した結果、以下のことがわかってきた。

* tcで実現する帯域制限は、htbと呼ばれるものを使えば実現できる。
* htb設定でrateとceil(つまり帯域上限)を設定できる。
* htb設定はclassを使って設定できる
* deviceとhtbをtc芸でaddしてつなぐと、outboundはできる。

これだけやってもoutboundだけしかできない。うーん、難しいtcコマンド。
ちなみに、ここの部分についてはこちらの記事：[よくわかるLinux帯域制限](https://labs.gree.jp/blog/2014/10/11266/)がわかりやすい

# Download帯域制限を実現するには？

結論から言うとifbモジュールと呼ばれるものを使って転送をエミュレートするとできる。
ifbとは、[Intermediate Functional Block device](https://wiki.linuxfoundation.org/networking/ifb)の略で、google訳すると、「中間機能ブロックデバイス」らしいのだが、これを利用するとエミュレートが可能なのだとか。

まぁ、普通のLinuxカーネルを持ってるリモートサーバなら、大体持っているのだが、手元にあるMacでは無かったので注意。

で、肝心のtc芸だが、これもtcconfig先生にtc芸をやってもらうと、

```{sh:tcconfig}
tcset <device> --rate 1Mbps --direction incoming
```
こうすればDownload方向へのパケット制限が1Mbpsになる。

...はずだった

# Dockerで動かない問題

普通のDocker runでやってみる。

```{Dockerfile:Dockerfile}
FROM python:3.8.8-buster

RUN apt update && apt install -y \
    kmod \
    net-tools \
    iperf \
    iproute2 \
    iptables \
    iputils-ping \
 && pip install speedtest-cli tcconfig

CMD ["/bin/bash"]
```

```{sh:docker build}
docker build -t tc:latest .
```

```{sh:docker run}
docker run -it tc
```

```{sh:}
root@1b8cadc820e2:/# modprobe ifb
modprobe: ERROR: ../libkmod/libkmod.c:586 kmod_search_moddep() could not open moddep file '/lib/modules/4.19.0-14-cloud-amd64/modules.dep.bin'
modprobe: FATAL: Module ifb not found in directory /lib/modules/4.19.0-14-cloud-amd64
```

お？なんだなんだ？ 読めない。。。

# 解決策

以下のようにdocker runを行う

```{sh: docker run}
sudo docker run --rm -it --cap-add NET_ADMIN --net=host -v /lib/modules:/lib/modules:ro tc 
```
すると

```
root@tc-test:/# tcset ens4 --rate 1Mbps --direction incoming
root@tc-test:/# speedtest-cli
Retrieving speedtest.net configuration...
Testing from Google Cloud (34.85.93.218)...
Retrieving speedtest.net server list...
Selecting best server based on ping...
Hosted by fdcservers.net (Tokyo) [5.66 km]: 3.635 ms
Testing download speed................................................................................
Download: 0.85 Mbit/s
Testing upload speed......................................................................................................
Upload: 56.54 Mbit/s
```

見事に、Downloadが1Mbpsになっている。

# 副作用

これは、Dockerにいわば特権を与えて実現しているので、当然同じサーバで動いている別のDockerバッチにも影響を与えることは考慮しなければならない。
ちなみに、Dockerコンテナごとに帯域制限をかける方法は未だ実装されていないことを確認している。