Goで書いたサーバーアプリケーションをdockerで動かす

来年1月からアサイン予定のプロジェクトにて、dockerを用いた開発を行なうので、ここ最近勉強したdockerに関する知識をまとめたいと思います。


# 課題
この記事ではgo言語を用いて簡単なサーバーアプリケーションを作成し、dockerで動かします。後半はさらにmulti-stage-bulidという機能を用いてより軽量なdocker imageを作成するまでを目標にします。

[dockerのインストール](https://hub.docker.com/editions/community/docker-ce-desktop-mac)は各自でやっておいてねw

# サーバーアプリケーション
ここでは静的なHTMLを返してくれるアプリケーションを作成します。
`/`にアクセスすると、Hello!!を返してくれるものとし、のちほどherokuにデプロイする事を想定してポートは実行時に指定できるようにしましょう。(デフォルトでは:8080ポートを使用します。)

`$GOPATH/src`以下にパッケージ名`docker_demo`とし以下のコードを配置してください。

```go:main.go

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
)

func main() {
	port := flag.String("port", "8080", "port number")
	flag.Parse()
	http.HandleFunc("/", indexHandler)
	fmt.Println("server start!")
	if err := http.ListenAndServe(fmt.Sprintf(":%s", *port), nil); err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "<h1>Hello!!</h1>")
}

```

## コンパイル&実行
```
$ go install
$ $GOPATH/bin/docker_demo -9000　#9000ポートを使用
server start!
```

```

$ curl http://localhost:9000
<h1>Hello!!</h1>
```
goで本格的なwebアプリケーションを書いてみたいと思った方は[こちら](https://astaxie.gitbooks.io/build-web-application-with-golang/ja/)が大変参考になります。

# dockerを用いて起動する
下準備これくらいにしてdockerコンテナ内で先ほど作成したアプリケーションを起動してみましょう。

## docker imageの作成
Dockerfileを下記のように定義してbulidします。

```Dockerfile:Dockerfile
FROM golang:1.13-alpine
WORKDIR /go/src/docker_demo
COPY . .
CMD go run .
```

```

$ docker image build -t docker_demo:latest .
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker_demo         latest              126666514643        1 minutes ago       359MB
```

## コンテナ内でアプリケーションを立ち上げる

それでは今回のメインディッシュdocker container内で先ほど作成したサーバーアプリを動かしてみましょう。

ポートフォワードを使用する事で、ホスト側のポートとコンテナ側のポートを繋ぐ事ができます。今回動かすプログラムはデフォルトで8080ポートを使用します。ホスト側9000ポートと接続してみましょう。

```
# -d バックグラウンドで起動、-p ポートフォワードを使用(ホスト:コンテナ)
$ docker container run -d -p 9000:8080 docker_demo
```
バックグラウンドでコンテナが起動しました。
http requestを送って確認してみましょう。

```
$ curl http://localhost:9000
<h1>Hello!!</h1>
```

# より軽量なdocker imageを作成する

先ほど作成したdocker imageは問題なくコンテナを作成し、動かす事ができました。
しかしこのimageにはプログラムを実行するのに余分なものが含まれています。
goはコンパイラ言語なので実行にはコンパイルした実行ファイルと、最低限のosの機能さえあれば十分です。
したがってこの余分なものを削ぎ落とす事でイメージのサイズを小さくする事ができます。

## multi-stage-build

`$ docker image ls`で先ほどビルドしたイメージのサイズを確認してみると359MBもあります... 余計なお肉は削ぎ落としてスリムな体型にしてあげたいものです。
こんな時に便利なmulti-stage-buildという機能が存在します。

その名の通りイメージのbuildを複数段階に分けてbuildできる機能ですが、これを使用する事でアプリケーションをビルドする環境と、実行環境を分離できるというわけです。

では早速Dockerfileを下記のように変更します。

```Dockerfile:Dockerfile

FROM golang:1.13-alpine AS build
WORKDIR /go/src/docker_demo
COPY . .
RUN go build
RUN GOOS=linux GOARCH=amd64 go install  

FROM alpine AS docker_demo
RUN apk add ca-certificates 
COPY --from=build /go/bin/docker_demo /bin/docker_demo
CMD ./bin/docker_demo -port=9000
```

ビルド環境イメージにASを用いてエイリアスを貼ることで、実行環境イメージをビルドする際に参照できるようになっています。

実行環境のベースイメージには必要最低限の機能をもつAlpine Linuxを使用し、ビルド環境で出来上がった実行ファイルのみを渡してあげる事で、最終的なイメージを最小にする事ができます。

docker imageをビルドしてサイズを確認してみましょう。

```

$ docker image build -t docker_demo
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker_demo         latest              947f14bfd6ad        1 minutes ago      15.2MB
```
なんという事でしょう！15MBまで小さくする事ができました！

# まとめ
今回はgoアプリケーションをコンテナで動かしてみるという簡単な内容でした。multi-stage-buildを使用してビルドと実行の環境を分けるという手法は、docker初心者の僕としては感動ものです。

今回作成したアプリケーションをherokuにデプロイする工程も記事にする予定なので、参考にしていただけると幸いです。