WSL on Dockerでbridgeネットワークを使うと通信速度がやけに劣化する

Windows Subsystem for Linux に apt 経由で docker をインストール*1したものの、どうにもこうにも処理が遅い。 元々のきっかけは docker をビルドに利用する別のツールを利用していたのだけれど、1ビルドに数十分かかるのでいったいなぜだと調べ始めたこと。

結論から言うと、根本原因までは調べ切れてないですが暫定解決策はあるので、1ケースとして書き留めておきます。

TL;DR

  • WSL on Docker を使っている場合に Docker コンテナ内部からの通信がものすごく遅い事象に遭遇した
  • docker run なら --net=host, docker build なら --network host を使うことで暫定解決はできる

状況

Client:
Version: 17.03.2-ce
API version: 1.27
Go version: go1.6.2
Git commit: f5ec1e2
Built: Thu Jul 5 23:07:48 2018
OS/Arch: linux/amd64

Server:
Version: 17.03.2-ce
API version: 1.27 (minimum version 1.12)
Go version: go1.6.2
Git commit: f5ec1e2
Built: Thu Jul 5 23:07:48 2018
OS/Arch: linux/amd64
Experimental: false

検証の流れ

元々はdocker上でのビルドに数十分かかっておかしいと考えて調査していたら、コンテナ上でGitからのダウンロード速度(git clone)が25KiBとかしか出ていないことに気づいた。
どうやらネットワーク周りがかなりボトルネックになっているっぽいことまでを把握。

そこでまずはDocker内部に必要なツールをセットアップしてからあたろうと思って以下の通りのDockerfileを書く。

$ cat Dockerfile
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y curl iputils-ping traceroute dnsutils

# Image Build
sudo docker build .

で、この時点で既に問題が発生してしまう。 なんと apt-get update が10分ぐらいたっても成功しない。

Sending build context to Docker daemon 61.76 MB
Step 1/3 : FROM ubuntu:latest
---> 74f8760a2a8b
Step 2/3 : RUN apt-get update
---> Running in 479fe1cc6fe7
Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB]
... (中略) ...
Get:9 http://archive.ubuntu.com/ubuntu bionic/universe Sources [11.5 MB]
Get:9 http://archive.ubuntu.com/ubuntu bionic/universe Sources [11.5 MB]
Get:9 http://archive.ubuntu.com/ubuntu bionic/universe Sources [11.5 MB]
(以下繰り返し)

回避策から述べると、この時点で --network host を指定することでビルド時のネットワーク問題を一時的に回避する。

# ビルド時のネットワークとしてhostを利用する / 指定しない場合はデフォルトの bridge が利用される
$ sudo docker build . --network host
... (中略) ...
$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              a88f6a8a4fa0        21 seconds ago      186 MB

あとはこのImageを使って中から外部向けの通信を行ってみる。
format.txt は curl を利用したネットワーク計測手段の内容をそのまま利用している。

$ sudo docker run --rm -it a88f
# 以下、コンテナ内部からの処理
$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=120 time=9.14 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=120 time=8.94 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=120 time=9.22 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=120 time=9.52 ms

# 単なるWebページの検索
$ curl -q -o /dev/null -w @format.txt https://www.google.co.jp/
url_effective           : https://www.google.co.jp/
http_code               : 200
http_connect            : 000
time_total              : 5.718374
time_namelookup         : 5.520191
time_connect            : 5.574668
time_appconnect         : 5.646445
time_pretransfer                : 5.647841
time_redirect           : 0.000000
time_starttransfer              : 5.715033
size_download           : 10830
size_upload             : 0
size_header             : 715
size_request            : 78
speed_download          : 1894.000
speed_upload            : 0.000

# 約30MBのファイルを落としてきた場合
curl -o a.exe -w @format.txt https://home.jeita.or.jp/page_file/JB1_0.exe
url_effective           : https://home.jeita.or.jp/page_file/JB1_0.exe
http_code               : 200
http_connect            : 000
time_total              : 291.270568
time_namelookup         : 5.518307
time_connect            : 5.554633
time_appconnect         : 5.606024
time_pretransfer                : 5.606144
time_redirect           : 0.000000
time_starttransfer              : 5.648392
size_download           : 30870046
size_upload             : 0
size_header             : 275
size_request            : 99
speed_download          : 105984.000
speed_upload            : 0.000

見た感じでは、ping自体は問題なさそうですが、DNS引くのにものすごく時間がかかってはいます。単なるWSLの上でダウンロードしてきた場合の数値は以下の通りなので、ダウンロード速度自体も1/30ぐらいになっていることが分かります。

$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=121 time=11.7 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=121 time=9.23 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=121 time=9.48 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=121 time=9.05 ms

$ curl -q -o /dev/null -w @format.txt https://www.google.co.jp/
url_effective           : https://www.google.co.jp/
http_code               : 200
http_connect            : 000
time_total              : 0.391
time_namelookup         : 0.073
time_connect            : 0.082
time_appconnect         : 0.313
time_pretransfer                : 0.313
time_redirect           : 0.000
time_starttransfer              : 0.387
size_download           : 10943
size_upload             : 0
size_header             : 747
size_request            : 80
speed_download          : 27995.000
speed_upload            : 0.000

$ curl -o b.exe -w @format.txt https://home.jeita.or.jp/page_file/JB1_0.exe
url_effective           : https://home.jeita.or.jp/page_file/JB1_0.exe
http_code               : 200
http_connect            : 000
time_total              : 9.976
time_namelookup         : 0.137
time_connect            : 0.152
time_appconnect         : 0.367
time_pretransfer                : 0.367
time_redirect           : 0.000
time_starttransfer              : 0.385
size_download           : 30870046
size_upload             : 0
size_header             : 275
size_request            : 99
speed_download          : 3094353.000
speed_upload            : 0.000

以上の内容から、

  • WSLそのものは悪いわけではない (十分早い)
  • コンテナ -> WSL -> 外部 という経路を通る場合に非常に遅くなる

という事象まではつかめました。

対策

http://docs.docker.jp/engine/userguide/networking/dockernetworks.html

Dockerの内部ネットワークとして、基本的に docker0 の bridge が使われる。
先に考察した通り、WSLからの通信は十分に早いため network に bridge を使わずに直接 host に割り当ててしまえば問題がさらに絞れる、ということで実際にやってみる。

$ sudo docker run --rm -it --net=host a88f
$ curl -q -o /dev/null -w @format.txt https://www.google.co.jp/
url_effective           : https://www.google.co.jp/
http_code               : 200
http_connect            : 000
time_total              : 0.284695
time_namelookup         : 0.128500
time_connect            : 0.138440
time_appconnect         : 0.214466
time_pretransfer                : 0.215841
time_redirect           : 0.000000
time_starttransfer              : 0.279488
size_download           : 10885
size_upload             : 0
size_header             : 715
size_request            : 78
speed_download          : 38327.000
speed_upload            : 0.000

$ curl -o a.exe -w @format.txt https://home.jeita.or.jp/page_file/JB1_0.exe
url_effective           : https://home.jeita.or.jp/page_file/JB1_0.exe
http_code               : 200
http_connect            : 000
time_total              : 7.957994
time_namelookup         : 0.256907
time_connect            : 0.270424
time_appconnect         : 0.326524
time_pretransfer                : 0.326726
time_redirect           : 0.000000
time_starttransfer              : 0.341902
size_download           : 30870046
size_upload             : 0
size_header             : 275
size_request            : 99
speed_download          : 3879608.000
speed_upload            : 0.000

あっけないぐらい普通に動いた。 ので、WSL周りのbridge設定がどうにもよろしくないらしい、ということまでは分かった。

結論

WSL上で本番を動かすわけでもなく、単に開発時検証ビルドのみであれば複数のコンテナを同時に立てる必要はないので、一応オプションを指定することで問題は解決できそう。

ただ、複数のコンテナを組み合わせて動かすような場合はちゃんとDocker内部のネットワークを構成しないといけないから、そういった場合には多分使えない。

そもそも WSL on Docker を使うという選択肢が棘の道な気はするので、どうしようか迷うなあ...

*1:Docker for Windowsではなく、apt-get install docker.io でインストール