不安定な network を生き抜くシステム開発のくふう
こんにちは、ホシイです 👋
Internet 越しの API 呼び出しがより一般的になったことや、何より public cloud での運用が増えた昨今、network の遅延や packet loss は日常的なものとして認識しておく必要があります。server application の開発でも、普段からそのような環境での動作で問題が発生しないか確認しておくことが重要でしょう。
Docker を使って低品質 network 環境を模倣する
今回は、network 遅延のような状況を検証しやすい環境を Docker を利用して構築してみます。
こちら を参考にさせていただきました。
上の記事でもすでに Docker Compose で実行できるようになっていますが、VS Code の Dev Container で動作すると network 通信を含むシステム開発に便利そうだなと思ったので、いくらか更新・最適化をしてみました。
また、今回の内容は環境に左右される度合いが大きいようなのでご注意ください。以下の環境で動作を確認しています。
- macOS Sonoma + Docker Desktop
- Windows 11 Enterprise + Docker Desktop (WSL を使用しない・補足を参照)
構成と必要なフォルダ構成
Dev Container を利用するにあたり、以下のフォルダ構成をつくります。(VS Code + Dev Containers extension の導入を前提としています)
ゼロからやるとすこしめんどうですが、つくるファイルは 3 つだけです。
workspace
├─ .devcontainer/
│ └─ client/
│ └─ Dockerfile
│ ├─ compose.yml
│ └─ devcontainer.json
まずは .devcontainer/client/Dockerfile
から。Ubuntu をベースに、iproute2
と iputils-ping
を入れているだけです。
FROM ubuntu:24.04
WORKDIR /work
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
iproute2 iputils-ping && \
echo "done."
次に、Docker Compose を使用するための .devcontainer/compose.yml
です。ふたつの service を定義していて、両方でおなじ image を使用するようにしています。後でも触れますが、これをスタート地点にして、用途に応じて付け足したりしながら使っていくとよさそうです。
services:
client:
container_name: client
build: ./client
tty: true
cap_add:
- NET_ADMIN
volumes:
- ..:/workspace:cached
server:
container_name: server
build: ./client
tty: true
cap_add:
- NET_ADMIN
最後に、.devcontainer/devcontainer.json
です。先の Docker Compose 定義で container を起動するように設定します。
{
"name": "lowq-net",
"dockerComposeFile": "compose.yml",
"service": "client",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
}
"service": "client"
を入れておくことで、指定した container で Dev Container server が起動し、workspace folder を mount して VS Code の Explorer に見えるようにしてくれます。
Feature docker-outside-of-docker
を入れているのは、VS Code Terminal から隣の server
container を操作できると便利だからです。これも後で触れますが、そういう使い方をしないのなら不要です。
ファイル構造ができたら、その folder を VS Code で開き、 Dev Containers: Reopen in Container
などの command で Dev Container で開き直します。
試す
さて、Dev Container で起動できたでしょうか。
VS Code の Terminal を開くと client
container に接続されます。以下、そちらで作業していきます。
以下のようにして、遅延を追加することができます。
# ping server
PING server (172.22.0.2) 56(84) bytes of data.
64 bytes from server.lowq-net-app_devcontainer_default (172.22.0.2): icmp_seq=1 ttl=64 time=0.674 ms
64 bytes from server.lowq-net-app_devcontainer_default (172.22.0.2): icmp_seq=2 ttl=64 time=0.124 ms
...
# tc qdisc add dev eth0 root netem delay 100ms
# ping server
PING server (172.22.0.2) 56(84) bytes of data.
64 bytes from server.lowq-net-app_devcontainer_default (172.22.0.2): icmp_seq=1 ttl=64 time=104 ms
64 bytes from server.lowq-net-app_devcontainer_default (172.22.0.2): icmp_seq=2 ttl=64 time=100 ms
...
通常の ping では time
値が極めて小さいですが、 tc qdisc ...
を実行すると、それに応じて遅延が追加されたのがわかります。
より複雑な制御を入れてみます。
# tc qdisc add dev eth0 root handle 1: netem delay 200ms 20ms distribution normal loss 10%
# ping server
PING server (172.22.0.2) 56(84) bytes of data.
64 bytes from server.lowq-net-app_devcontainer_default (172.22.0.2): icmp_seq=1 ttl=64 time=194 ms
64 bytes from server.lowq-net-app_devcontainer_default (172.22.0.2): icmp_seq=2 ttl=64 time=216 ms
...
^C
--- server ping statistics ---
23 packets transmitted, 21 received, 8.69565% packet loss, time 22173ms
rtt min/avg/max/mdev = 137.806/195.585/240.116/23.413 ms
200ms 前後のゆれ付きの遅延と、10% 前後の packet loss が観測できます。
戻すときは、それぞれ実行した tc コマンドの add
を del
に変えて実行すれば良いようです。
また、以下のようにすると現在の設定が確認できます。
# tc qdisc show
qdisc noqueue 0: dev lo root refcnt 2
qdisc netem 1: dev eth0 root refcnt 9 limit 1000 delay 200ms 20ms loss 10%
Dev Container Feature で docker-outside-of-docker
を入れてあるので、(都度 server
container に入ることなく) client
container から server
container 側も操作できます。
# docker exec server tc qdisc add dev eth0 root netem loss 90%
この状態で client から ping してみます。
# ping server
PING server (172.22.0.2) 56(84) bytes of data.
From 846f51297346 (172.22.0.3) icmp_seq=1 Destination Host Unreachable
...
From 846f51297346 (172.22.0.3) icmp_seq=9 Destination Host Unreachable
ping: sendmsg: No route to host
From 846f51297346 (172.22.0.3) icmp_seq=10 Destination Host Unreachable
...
From 846f51297346 (172.22.0.3) icmp_seq=15 Destination Host Unreachable
64 bytes from server.lowq-net-app_devcontainer_default (172.22.0.2): icmp_seq=16 ttl=64 time=0.332 ms
^C
--- server ping statistics ---
30 packets transmitted, 1 received, +13 errors, 96.6667% packet loss, time 29683ms
rtt min/avg/max/mdev = 0.332/0.332/0.332/0.000 ms, pipe 4
このように、なかなか見ないくらい反応が悪い server を用意することもできます。
今回の例では client
server
どちらの container にも開発対象にあたるものは入れていません。目的に応じて、たとえば開発対象が client library なら client
container にソースと開発環境を入れ、 server
container にはダミーサーバーを何かしら入れるなどするとよいでしょう。
また DB container も追加して、server からのデータ操作で不整合が出ないことを確認するといったことも効果が高そうです。
しくみについての補足
- Linux の tc (Traffic Control) コマンドを使用しています。
- qdisc (queueing discipline) の制御により遅延の追加や packet 破棄ができます。
- qdisc は outbound traffic にのみ影響します。(なので帰りの通信遅延は相手 container 側で設定します)
- netem は Network Emulator の略のようです。
- Docker によって network interface が container ごとに生成されているので、tc で interface を指定していれば、host はもちろん他の container への影響もありません。(iptables などで操作すると影響することがある)
tc に関しては仕組みも含めてかなり奥が深く、いろんなことができそうでした。末尾にいくつか参考リンクをつけておきます。
まとめ
Docker Compose を使用し、とてもかんたんに低品質な network 環境を用意することができました。クラウドで動作する application をつくることも増えました。設備などの品質をある程度自前で担保できるオンプレ環境と違い、クラウド環境では思いも寄らないタイミングで通信遅延が起こるといったことはよくあります。毎日のよい睡眠のために、普段から準備をしておきましょう。
補足: WSL について
(この項の内容は WSL、Docker、VS Code 等の version が進むことで不要になるかもしれません。ご参考まで)
Windows で Docker Desktop を使用する際、最近の default 設定では WSL 2 を使用するようになっていると思います。ただ、今回の記事の内容はそれでは動作しませんでした。具体的には、tc qdisc add ...
等を実行した時に以下のようなエラーが発生します。
Error: Specified qdisc kind is unknown.
Windows で実行する場合には、Docker Desktop の設定で WSL 2 を使用しない設定にしてください。
設定項目の名称は version によって変わりそうですが、こちら などを参照すると “Use the WSL 2 based engine” となっています。
また、WSL 2 を使用しない設定で devcontainer を実行しようとすると以下のようなエラーが発生します。
Error response from daemon: \\wsl.localhost\Ubuntu\mnt\wslg\runtime-dir\wayland-0%!(EXTRA string=is not a valid Windows path)
WSLg のためのもののようです。今回は使用しないので、VS Code の設定で以下の項目をオフにすると回避ができます。(参考: https://github.com/microsoft/vscode-remote-release/issues/9002)
- Dev > Containers: Mount Wayland Socket
いずれも、用が済みましたら必要に応じて設定を戻しておきましょう。