Macでdockerを使う際にlocalhostでcontainerに繋げない問題の調査

# DockerでFailed to connect to localhost問題

Web系のソフトウェア開発をするとき、手元のmacにdocker daemonを立てて動作確認などを行うことがあると思います。
その際、ポートフォワードの設定が正しく設定できていれば、
`curl http://localhost:8080/` のような形で、localhost(または127.0.0.1など)でWebアプリケーションにつなぎに行きますが、ここで次のようなエラーが返ってくる環境があります。

```
$ curl http://localhost:8080/
curl: (7) Failed to connect to localhost port 8080: Connection refused
```

どうもWebサーバが `http://localhost:8080` に建っていないような挙動です。
原因として考えられることがいくつかあるのですが、この記事ではそのうちの一つについて説明します。

## TL;DR

MacOS用の古い実装であるDocker Toolboxでは無邪気にVMを立ててその中でdocker daemonを起動します。
ですので、containerに接続したい場合はMacのlocalhostではなく、VMのアドレスにアクセスする必要があります。

MacでDocker Toolboxを使うのは古い方法です。
特に理由が無ければDocker Desktop for Macを利用するのが推奨です。(ただし要アカウント登録)

## 環境

Docker Toolboxを利用
https://docs.docker.com/toolbox/

```
$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.1
BuildVersion:   18B75
$ docker --version
Docker version 18.06.1-ce, build e68fc7a
$ docker-machine --version
docker-machine version 0.15.0, build b48dc28d
```

## 問題の再現

今回の問題を再現させるために、dockerがinstallされたMacに外部から通信するcontainerを建てます。
imageは何でも良いのですが、ここではnginxの静的webサーバで確認します。
dockerのnginx imageについては[こちら](https://hub.docker.com/_/nginx#exposing-external-port)を参照します。

### nginx containerをdocker hubからdownloadして起動

```bash
$ docker run --name some-nginx -p 8080:80 -d nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
5e6ec7f28fb7: Pull complete
ab804f9bbcbe: Pull complete
052b395f16bc: Pull complete
Digest: sha256:4c47bf32cd72eaa779f051e3a077d3ed160e18b8e0dbaf1f705027d8ea6be5c8
Status: Downloaded newer image for nginx:latest
1708ec26c07dd4ae51bcb96744f9c83a5e08b13e608c65e8135c26893a2a6656
```

### 起動していることを確認

`docker ps`して、nginx containerのSTATUS列がUPであることを確認します。
また、PORTS列より、8080 -> 80 のポートフォワードの設定が入っていることが確認できます。

```bash
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
1708ec26c07d        nginx               "nginx -g 'daemon of…"   22 seconds ago      Up 22 seconds       0.0.0.0:8080->80/tcp   some-nginx
```


### nginxへのアクセス確認

`http://localhost:8080` へアクセスし、nginxサーバのWelcome Pageを表示します。
ブラウザでもいいのですが、ここではcurlでの結果を載せます。

```
$ curl http://localhost:8080/
curl: (7) Failed to connect to localhost port 8080: Connection refused
```

welcome pageのhtmlが出力されることを期待しましたが、Connection refuseされてしまいました。

## 調査

### ポートフォワード設定の確認(再掲)

アクセスしているポートを間違えていて、開いていないポート番号に接続しようとすると `connection refused` となる場合があるので、まずはポートフォワード設定を確認します。

```bash
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                  NAMES
1708ec26c07d        nginx               "nginx -g 'daemon of…"   About a minute ago   Up About a minute   0.0.0.0:8080->80/tcp   some-nginx
```

`0.0.0.0:8080->80/tcp` となっていることから、8080番で間違っていなさそうです。

### nginxのログの確認

外から見てcontainerは起動はしていますが、中のnginxでなにかエラーが起きているかもしれません。
念の為containerのログを確認します。

```bash
$ docker logs some-nginx
```

何も表示されないので、異常は起きていなさそうです

### container内部の正常性確認

containerの中から自身への疎通を確認し、まずはnginxが正常に建っているかどうかを占います。
これを実施することで、原因はnginxではないところ(Dockerかmacのfirewall設定など)に切り分けることができます。

#### containerのshellに入る

```bash
$ docker exec -it some-nginx sh
#
```

#### 確認のためのツールをインストール

ここでは、curlをインストールします。
nginxのイメージはdebianベースのようなので`apt`が使えます。

```bash
# apt update
Ign:1 http://cdn-fastly.deb.debian.org/debian stretch InRelease
Get:2 http://cdn-fastly.deb.debian.org/debian stretch-updates InRelease [91.0 kB]
Get:4 http://security-cdn.debian.org/debian-security stretch/updates InRelease [94.3 kB]
Get:3 http://cdn-fastly.deb.debian.org/debian stretch Release [118 kB]
Get:5 http://cdn-fastly.deb.debian.org/debian stretch Release.gpg [2434 B]
Get:6 http://cdn-fastly.deb.debian.org/debian stretch-updates/main amd64 Packages [5152 B]
Get:7 http://security-cdn.debian.org/debian-security stretch/updates/main amd64 Packages [469 kB]
Get:8 http://cdn-fastly.deb.debian.org/debian stretch/main amd64 Packages [7090 kB]
Fetched 7869 kB in 4s (1960 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
1 package can be upgraded. Run 'apt list --upgradable' to see it.
# apt install curl
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  ca-certificates krb5-locales libcurl3 libffi6 libgmp10 libgnutls30 libgssapi-krb5-2 libhogweed4 libidn11 libidn2-0 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2
  libldap-common libnettle6 libnghttp2-14 libp11-kit0 libpsl5 librtmp1 libsasl2-2 libsasl2-modules libsasl2-modules-db libssh2-1 libssl1.0.2 libtasn1-6 libunistring0 openssl publicsuffix
Suggested packages:
  gnutls-bin krb5-doc krb5-user libsasl2-modules-gssapi-mit | libsasl2-modules-gssapi-heimdal libsasl2-modules-ldap libsasl2-modules-otp libsasl2-modules-sql
The following NEW packages will be installed:
  ca-certificates curl krb5-locales libcurl3 libffi6 libgmp10 libgnutls30 libgssapi-krb5-2 libhogweed4 libidn11 libidn2-0 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2
  libldap-common libnettle6 libnghttp2-14 libp11-kit0 libpsl5 librtmp1 libsasl2-2 libsasl2-modules libsasl2-modules-db libssh2-1 libssl1.0.2 libtasn1-6 libunistring0 openssl publicsuffix
0 upgraded, 31 newly installed, 0 to remove and 1 not upgraded.
Need to get 6616 kB of archives.
After this operation, 16.9 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
(省略)
```

#### nginxの正常性確認

自身の80番ポートにアクセスし、nginxが建っていることを確認します

```
# curl http://localhost:80/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

上記の結果より、nginxのcontainerとnginxサーバ自体は正しく建っていることがわかります。
Mac側でなにか問題があるようです。

### Docker環境の確認

ここで、Mac上でDockerの情報を調べていると、 `docker-machine`　というコマンドがあることに気づきます。
例えば次のようにすると、Dockerが動いているmachineの情報を確認できます。

```
$ docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
default   *        virtualbox   Running   tcp://192.168.99.100:2376           v18.06.1-ce
$ docker-machine config default
--tlsverify
--tlscacert="/Users/yuma/.docker/machine/machines/default/ca.pem"
--tlscert="/Users/yuma/.docker/machine/machines/default/cert.pem"
--tlskey="/Users/yuma/.docker/machine/machines/default/key.pem"
-H=tcp://192.168.99.100:2376
$ env | grep DOCKER
DOCKER_MACHINE_NAME=default
DOCKER_CERT_PATH=/Users/yuma/.docker/machine/machines/default
DOCKER_TLS_VERIFY=1
DOCKER_HOST=tcp://192.168.99.100:2376
```

`192.168.99.100` というIPアドレスが怪しいので確認のためHTTPリクエストしてみると次のような結果になります。

```
$ curl http://192.168.99.100:8080/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

nginxのWelcome Pageです。
どうもMacから見てnginxは `localhost` ではなく、 `192.168.99.100` に存在するようです。

## 結論

結論としては、このアドレス`192.168.99.100`は、MacにインストールしたDocker ToolboxがVirtualbox上に建てたVMのアドレスです。MacでVirtualboxを開いてVM一覧を確認するとこのVMが起動していることがわかります。
Docker Toolboxでは、Macの上にVMを構築して、その中にdocker daemonをインストールします。そのため、dockerへのアクセスはlocalhostではなくVMのIPアドレスを使うことになります。
Docker Toolboxについて詳しくは[公式ドキュメント](https://docs.docker.com/docker-for-mac/docker-toolbox/)を参照してください。

Docker Toolboxは昔にMacでDockerを利用する際に使われたツールです。
現在はDocker Desktop for Macを使うのが良いようです。

## 対処

原因がわかり、特に不具合でもなんでもなかったので別にこのままでも良いのですが、せっかくなのでDocker Desktop for Macへの移行をしておきます。

### Docker Toolboxのuninstall

こちらを参考にしてDocker Toolboxをuninstallします。
https://docs.docker.com/toolbox/toolbox_install_mac/#how-to-uninstall-toolbox

### machine(VM)を削除する

```
$ docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
default   *        virtualbox   Running   tcp://192.168.99.100:2376           v18.06.1-ce
$  docker-machine rm default
About to remove default
WARNING: This action will delete both local reference and remote instance.
Are you sure? (y/n): y
Successfully removed default
```

### Macの設定等削除

> In your “Applications” folder, remove the “Docker” directory, which contains “Docker Quickstart Terminal” and “Kitematic”.

<img width="378" alt="2019-01-25-00-05-22.png" src="https://qiita-image-store.s3.amazonaws.com/0/266939/a8597bd6-d037-9d95-acc7-9e3db8bfc3a6.png">
消します(パスワードが求められます)

```
$ rm -fr ~/Library/Application\ Support/Kitematic
```

```
$ rm -f /usr/local/bin/docker
yuma@UG-30-maki-mac - 00:06:41 ~/playground/article/docker_toolbox $
$ rm -f /usr/local/bin/docker-compose
yuma@UG-30-maki-mac - 00:06:45 ~/playground/article/docker_toolbox $
$ rm -f /usr/local/bin/docker-machine
$ ls ~/.docker/machine/
cache    certs    machines
$ rm -rf ~/.docker/machine
```

使わないならOracle Virtualboxをuninstallします

### Docker Desktop for Macをinstall

こちらの手順を参考にしてDocker Desktop for Macをinstallします。
https://docs.docker.com/docker-for-mac/install/

ここからイメージをダウンロードしてインストールします。Docker IDが必要なので登録がまだの場合は登録します。
また、途中でOSの特権パスワードを求められます。
https://hub.docker.com/editions/community/docker-ce-desktop-mac

<img width="250" alt="2019-01-25-03-04-02.png" src="https://qiita-image-store.s3.amazonaws.com/0/266939/4b89fe94-fddc-f395-659a-514ebe2b9e38.png">

起動成功

## 確認

もし docker versionしたときに`could not read CA certificate`が出たら、Toolboxの環境変数が残っていることが原因のため、シェルを開き直す。

### 再度環境の確認

```
$ docker version
Client: Docker Engine - Community
 Version:           18.09.1
 API version:       1.39
 Go version:        go1.10.6
 Git commit:        4c52b90
 Built:             Wed Jan  9 19:33:12 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.1
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.6
  Git commit:       4c52b90
  Built:            Wed Jan  9 19:41:49 2019
  OS/Arch:          linux/amd64
  Experimental:     false
```

### conatinerの確認

installしたばかりなのでなにもない

```
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

### nginxを起動

```
$ docker run --name some-nginx -p 8080:80 -d nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
5e6ec7f28fb7: Pull complete
ab804f9bbcbe: Pull complete
052b395f16bc: Pull complete
Digest: sha256:4c47bf32cd72eaa779f051e3a077d3ed160e18b8e0dbaf1f705027d8ea6be5c8
Status: Downloaded newer image for nginx:latest
1ac2cc1c3d0d92fa01dad9998673dca330b915033819d9b0305261327de15207
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
1ac2cc1c3d0d        nginx               "nginx -g 'daemon of…"   27 seconds ago      Up 26 seconds       0.0.0.0:8080->80/tcp   some-nginx
```

### localhostで接続確認

```
$ curl http://localhost:8080/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

成功しました。

# もしかして: 別問題

(2019/08/31 追記)

この記事のケースは稀で、大半の人は次のケースだと思います。

localhostでdockerのcontainerに繋げない人のうち、エラーが次のいずれかの場合は[こちら](https://qiita.com/amuyikam/items/01a8c16e3ddbcc734a46)
- ERR_EMPTY_RESPONSE
- curl: (52) Empty reply from server
- curl: (56) Recv failure: Connection reset by peer