コンテナ間通信にレイテンシを設定する

## はじめに
パフォーマンスチューニングといえば、DBのインデックスやN+1クエリの解消を思い浮かべることが多いですが、ネットワークレイテンシについても考慮する必要があります。
特にローカル環境での開発ではネットワークレイテンシがほとんど発生しないため、本番環境と比べるとパフォーマンスが大幅に向上している傾向があります。

開発環境ではdocker composeを使って単一ホストでコンテナ間通信をさせることが多いと思いますが、この通信に対してネットワークレイテンシ等の設定をする方法が気になったので調べます。

## tc
Linuxだとtc(Traffic Control)コマンドを使うことによって、トラフィックを制御することができます。
今回の目的であるネットワークレイテンシの設定だけでなく、パケットロス割合の設定などもできます。

https://man7.org/linux/man-pages/man8/tc.8.html

tcコマンドはkernelとNetwork Interceの間に特定のキューを設定することで、トラフィックを制御します。
このキューは`qdisc(queueing discipline)`と呼ばれています。

`process -[socket]-> kernel -[device driver]-> NIC`

↓

`process -[socket]-> kernel - qdisc -> -[device driver]-> NIC`

## 環境
2コンテナ間での通信に対してネットワークレイテンシの設定をします。
`compose.yaml`ファイルは以下のような設定です。

```compose.yaml
services:
  host-1:
    image: ubuntu:latest
    container_name: host-1
    tty: true
    cap_add:
      - NET_ADMIN
  host-2:
    image: ubuntu:latest
    container_name: host-2
    tty: true
    cap_add:
      - NET_ADMIN
```

ポイントとしては、コンテナに`NET_ADMIN`のcapabilityを追加しているところです。
デフォルトでは`CAP_NET_ADMIN`のcapabilityが抑えられた状態でコンテナを起動するので、tcコマンドが実行できないためです。

`CAP_NET_ADMIN`を付与することによって以下の操作が可能になります。
https://man7.org/linux/man-pages/man7/capabilities.7.html

> #### CAP_NET_ADMIN
> Perform various network-related operations:
> - interface configuration;
> - administration of IP firewall, masquerading, and accounting;
> - modify routing tables;
> - bind to any address for transparent proxying;
> - set type-of-service (TOS);
> - clear driver statistics;
> - set promiscuous mode;
> - enabling multicasting;
> - use setsockopt(2) to set the following socket options: SO_DEBUG, SO_MARK, SO_PRIORITY (for a priority outside the range 0 to 6), SO_RCVBUFFORCE, and SO_SNDBUFFORCE.

今回の検証に必要なpackageを入れていきます。

- `iproute2`
    - tc
- `iputils-ping`
    - ping

```sh
$ docker compose exec host-1 bash
$ apt update -y && apt install -y iproute2 iputils-ping
```

## レイテンシの設定
ネットワークレイテンシの設定をせずに、初期状態でhost-1からhost-2に対してpingを実行すると以下のような結果になります。
```sh
$ ping -c 5 host-2
PING host-2 (172.18.0.2) 56(84) bytes of data.
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=1 ttl=64 time=0.197 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=2 ttl=64 time=0.116 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=3 ttl=64 time=0.131 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=4 ttl=64 time=0.138 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=5 ttl=64 time=0.113 ms

--- host-2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4106ms
rtt min/avg/max/mdev = 0.113/0.139/0.197/0.030 ms
```

`qdisc`はNetwork Interfaceごとに作ることができるので、`host-1`, `host-2`それぞれで使われている仮想Network Interfaceの`eth0`に対してqdiscを作成します。

500msのネットワークレイテンシを設定する場合以下のようになります。

- `tc`
- `qdisc add`
- `dev eth0`
    - ネットワークデバイスの`eth0`に対して追加
- `root`
    - 指定したdeviceのrootにattachする
    - > In the absence of classful qdiscs, classless qdiscs can only be attached at the root of a device.
    - https://man7.org/linux/man-pages/man8/tc.8.html
- `netem`
    - Network Emulatorとしてのqdiscを追加
    - > Network Emulator is an enhancement of the Linux traffic control facilities that allow one to add delay, packet loss, duplication and more other characteristics to packets outgoing from a selected network interface.
- `delay 500ms`
    - 500msの遅延を設定
    - https://man7.org/linux/man-pages/man8/tc-netem.8.html

```sh
$ tc qdisc add dev eth0 root netem delay 500ms
```

ネットワークレイテンシの設定をしてから再度pingを実行すると、ネットワークレイテンシが追加されていることを確認できます。
```sh
root@f6459af85b82:/# ping -c 5 host-2
PING host-2 (172.18.0.2) 56(84) bytes of data.
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=1 ttl=64 time=506 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=2 ttl=64 time=506 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=3 ttl=64 time=506 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=4 ttl=64 time=506 ms
64 bytes from host-2.sandbox_default (172.18.0.2): icmp_seq=5 ttl=64 time=501 ms

--- host-2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4024ms
rtt min/avg/max/mdev = 501.273/504.945/506.072/1.846 ms
```

## 参考
https://www.oreilly.co.jp/books/9784814400072/

https://man7.org/linux/man-pages/man8/tc.8.html

https://docs.redhat.com/ja/documentation/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/linux-traffic-control_configuring-and-managing-networking#linux-traffic-control_configuring-and-managing-networking

https://stackoverflow.com/questions/27708376/why-am-i-getting-an-rtnetlink-operation-not-permitted-when-using-pipework-with-d