golang
DockerDay 11

golangでdockerをはじめる ~ goのwebサーバーをdockerでたててみた ~

最近並列的なバッチ処理でgolangを使いたくなりました。しかも大量のデータを並列で回したいので、リソースを増やしやすいdockerを使いたくなりました。
golangをdockerで立ち上げて開発するだけならそんなに難しいこともないのですが、dockerでdaemonを立ち上げようと思ったらdockerコマンドを色々駆使しなければならなかったので、dockerアドベントカレンダーにてほそぼそと公開させていただきます。

1. golangの入ったdockerを準備

a. コミュニティに管理されているdocker imageがあるのでもってくる

$ docker pull golang

b. docker内で開発するために、vimをインストールする

$ docker run -it golang
(dockerコンテナ内)
# apt-get update && apt-get upgrade
# apt-get vim

c. dockerをcommitしてpush

$ docker commit [container ID] [dockerhub username]/golang
$ docker push [container ID]

d. 新しく作ったイメージからコンテナをもう一度立ち上げる

$ docker run -it [dockerhub username]/golang

2. golangでサーバースクリプトを書く

docker run -it でdockerに入ると、rootユーザーの /go に移動すると思います。その直下に以下のような server.go ファイルを作成しました。

/go/server.go
package main
import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Your url path is %s", r.URL.Path[1:])
}

3. サーバーを起動する

2の状態からコンテナをstopしたり、attachしたりしながらportマッピングを追加できないかとやってみましたが、imageを一度コミットして、再度新しいコンテナを立ち上げる方法しか見つかりませんでした。

a. port mappingするために再びimageをcommit

まずはコンテナから抜ける
# exit

commit (pushしてもよい)
$ docker commit [container ID] [dockerhub username]/goserver

b. 新しく作成したイメージからport mappingを記載した上でdaemonモードで起動

$ docker run -p 8080:8080 -td [dockerhub username]/goserver

c. 起動したコンテナに入る

$ docker exec -it [container ID] /bin/bash

d. サーバースクリプトを実行する

# go run server.go

e. http://localhost:8080 にアクセスする

4. Dockerfileを使ってサーバーを立てる

3までで「goのwebサーバーをdockerでたてる」のはできました。
がしかし、2点問題があります
1. vimが入ってしまっている
2. srcファイルが入ってしまっている(goはコンパイル言語なので実行ファイルだけでいいはず)

dockerのimageは不要なものはいれず、できるだけシンプルに保ちたいものです。でないとdockerの起動時間や複数コンテナを立ち上げた時のリソースを無駄に消費してしまいます。

4.1 vimをdockerのimageから除く

vimを除きたいですが、何かしらエディタがないと開発はできません。
今回は、localで作成したserver.goファイルをdockerに配置するという形で組んでみました

a. Dockerfileの用意

Dockerfile
FROM golang
MAINTAINER vankobe
ADD . /go/src/
EXPOSE 8080
CMD ["/usr/local/go/bin/go", "run", "/go/src/server.go"]

b. build実行

# ビルドを実行するフォルダの中
$ ls
Dockerfile server.go

$ docker build .
...
(実行ログ)
...
Successfully built a176df9f6c3e

c. buildしたimageを使ってコンテナを起動
(この時立ち上がらなかったら、CMDの引数などが間違ってる可能性がある。その場合は docker logs で見ると原因がわかるケースもある)

$ docker run -p 8080:8080 -td a176df9f6c3e 

d. http://localhost:8080 にアクセスする

4.2 ソースファイルをdockerのimageから除く

goはコンパイル言語なので、コンパイルした後の実行ファイルさえあればいいはずです。
今回は、golang imageを使って作った実行ファイルをalpineのimageにcopyして実行するようにしてみました

a. ファイル名を変える
先ほどは、/go/srcserver.go をおいてましたが、/go/src/servermain.go を配置します。
まずは server.gomain.go にリネームします

$ mv server.go main.go
$ ls
Dockerfile main.go

b. Dockerfileを用意する

Dockerfile
FROM golang
MAINTAINER vankobe
ADD . /go/src/server
RUN go install server

FROM alpine
COPY --from=0 /go/bin/server .
ENV PORT 8080
CMD ["./server"]

c. build実行

$ docker build .
...
(実行ログ)
...
Successfully built 19ace85b6019

d. buildしたimageを使ってコンテナを起動

$ docker run -p 8080:8080 -td 19ace85b6019 

e. http://localhost:8080 にアクセスする

立ち上げたコンテナに入ると実行ファイル以外のファイルはコピーされていないことがわかります。

最後に

aws, gcpともにコンテナをサポートする機能が充実してきました。
gcpのcontainer-engine-samplesに色んな例があるので試して見ると思います。
(書いてる途中で今回の例は上記サンプル内のhello-appとほぼ同じことに気付きました。がーん)

enjoy life with docker

Appendix

ADDをカレントディレクトリで行うとDockerfileもコピーされてしまったので.dockerignoreを用意しておくといいと思います。

.dockerignore
Dockerfile