Dockerコンテナ上で動くGinサーバーにアクセスできないエラーの解決法

## 概要
GolangのWebフレームワークである[Gin](https://github.com/gin-gonic/gin)を使ったAPIサーバーを、Dockerコンテナ上にデプロイして動かそうとしたところ、APIにアクセスできずかなり長い時間悩まされました。

結果的には、Ginサーバーのコードの書き方の問題だったことが分かったのですが、解決方法を念のためここにメモしておきます。

ちなみに、このエラーはWindows10及びAWS上のUbuntuサーバー(t2.small)で起こりました（尤も、実行環境はこのエラーの発生にあまり関係ないようでしたが）。
### 状況
Ginを用いたAPIサーバーを立てようとしていました。まだ環境構築の段階なので、コードは以下のようなモックのものになっています。

```go
package main

import (
	"log"
	"os"
	"github.com/gin-gonic/gin"
)

func main() {
	logConfig()

	r := gin.Default()
	r.GET("/accounting-api", func(c *gin.Context) {
		log.Println("GET")
		c.JSON(200, gin.H{
			"state": "success",
		})
	})
	r.DELETE("/accounting-api", func(c *gin.Context) {
		log.Println("DELETE")
		c.JSON(200, gin.H{
			"state": "success",
		})
	})
	log.Println("Start Server")
	r.Run()
}

func logConfig() {
	logFile, _ := os.OpenFile("log/log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	log.SetOutput(logFile)
	log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile)
	log.SetPrefix("[LOG] ")
}
```
単純に、`GET`か`DELETE`メソッドで特定のパスへのリクエストが来たら、`{"state": "success"}`というjsonを返すだけのサーバーです。

そして、このサーバーを動かすためのDockerfileが以下です。

```dockerfile
FROM golang:alpine
RUN apk update && apk add --no-cache git
RUN go get -u github.com/gin-gonic/gin && mkdir /usr/src && mkdir /usr/src/api
COPY ./api /usr/src/api
WORKDIR /usr/src/api
CMD ["go","run","main.go"]
```
ホスト上の`api`というディレクトリには上記のGoファイル等があるため、それをコンテナ上にコピーして、サーバーを立ち上げます。このDockerfileを`api`という名前でビルドして、それを以下のコマンドで立ち上げました。

```bash
docker run -p 8083:8080 api
```
ホスト上のポート`8083`をコンテナ上のポート`8080`にマッピングしています。上記のコマンドを実行すると、以下のような出力がなされ、Ginサーバーが立ち上がっていることが確認できます。

```
[GIN-debug] GET    /accounting-api           --> main.main.func1 (3 handlers)
[GIN-debug] DELETE /accounting-api           --> main.main.func4 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on localhost:8080
```
ポートを指定しなかったので、デフォルトでポート`8080`で動いています。このように一見ちゃんと動いているようにも見えますが、ホスト側で`http:localhost:8083`にcurlでアクセスしてみても、以下のようにエラーが出てしまいました。

```bash
curl: (52) Empty reply from server
```
## エラー解消のため試みたこと
### 1. コンテナの中に入って、APIにアクセスできるか確認
まず最初に、コンテナ上で本当にGoのプログラムがちゃんと動いているのかを確認します。そのために立ち上げたdockerコンテナの中に入ってみます。

```bash
# apiサーバーの動くコンテナの中に入る
docker exec -it api /bin/ash
```
このコンテナのベースとなっているAlpineには`/bin/bash`がなかったので、`/bin/ash`を使います。そして以下のコマンドを打って、ちゃんとプログラムが動いているのか確かめます。

```bash
# そもそもcurlが入っていないので、インストールする
apk add --no-cache curl
# 念のためプロキシを無効にして、curlでapiサーバーにアクセスする
curl -x "" http://localhost:8080/accounting-api
```
curlの実行結果がこちら。

```bash
{"state": "success"}
```
ちゃんと結果が取れています。なので、コンテナ上ではちゃんとGinサーバーのプログラムが動いているようです。
### 2. DockerfileでポートをEXPOSE
コンテナ上ではプログラムは動いているようなので、次はポートマッピングの部分がうまくいっていないのではないかと疑ってみます。調べてみるとDockerfileには`EXPOSE`というコマンドが書けるようなので、追加してみます。

```dockerfile
EXPOSE 8080
```
これをやっても、解決しませんでした。

そもそも[公式ドキュメント](https://docs.docker.com/engine/reference/builder/)によると、`EXPOSE`コマンドは実際は何の働きもせず、特定のポートを開放する旨を開発者に知らせるための、ドキュメントのような役割しかもっていないようです。なので、`EXPOSE`コマンドをつけただけで問題が解決するはずがありませんでした。
### 3. Windowsのファイアウォールの設定の確認
開発は基本的にWindows上のDockerで行っていたため、Windowsのファイアウォールの設定を見直してみましたが、これも意味がありませんでした。

そもそも、このDockerfileをUbuntu上でビルドして立ち上げてみても、同様にAPIサーバーにアクセスできなかったため、最初からなんとなくWindowsのファイアウォールのせいではないことが分かっていましたが。
### 4. (これで解決)Ginサーバー側でポートの指定
Goのプログラムの中で、GInサーバーを立ち上げる部分でポートを指定するようにしたところ、上手くアクセスできるようになりました。具体的には、以下の部分です。

```go
r := gin.Default()
r.Run(":8080")
```
何も指定しなくてもデフォルトで`8080`で立ち上がるため気にしていなかったのですが、しっかりと指定しないとどうやらダメなようです。

なぜポートはデフォルトではダメで、明示的に記さなければならないかは、よくわかりません。分かったら追記します。