Go
Heroku
docker

Goで書いたサーバーをHerokuにDocker Deployする

Goで書いたサーバーをデプロイする先を探していて、Google App Engine(SE)Arukasなどを試してきたが、ふと気になって調べてみたらRailsアプリのホスティングで馴染みのあるHerokuもDocker Deployに対応していたので、試してみた。割と良さげなので、手順を残しておく。

前提: 必要なもの

Heroku CLI ログイン

heroku login
heroku container:login

HerokuへのデプロイはCLIを使ってシェルから行います。heroku loginをして、CLIからログインして起きましょう。Docker Deployの場合はheroku container:loginContainer Registryへのログインも必要となります。

プログラムの準備

まずはHerokuにDeployする、簡単なHTTPサーバーをGoで書いて、ローカル(+ローカルのDocker)で動作確認をします。

シンプルなHTTPサーバーをGoで書く

main.go
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello world\n")
}

func main() {
    port, _ := strconv.Atoi(os.Args[1])
    fmt.Printf("Starting server at Port %d", port)
    http.HandleFunc("/", handler)
    http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

HTTPリクエストがきたらHello world\nを返す、シンプルなHTTPサーバーです。

重要なのは、Listenに使うポート番号をコマンドライン引数(os.Args[1])で実行時に指定できるようにしていることです。これは、HerokuのDocker DeployでHTTPサーバーを動かしたい場合、Herokuから指定されたポート番号($PORT)でListenをする必要があるためです。

参考: Dockerfile commands and runtime

The web process must listen for HTTP traffic on $PORT, which is set by Heroku.

ローカルで実行して動作確認しましょう。

go run main.go 3000

curllocalhost:3000にアクセスして、Hello world\nが返ってきたらOKです。

Dockerfileを定義する

FROM golang:latest as builder

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
WORKDIR /go/src/github.com/yokoe/go-server-example
COPY . .
RUN go build main.go

# runtime image
FROM alpine
COPY --from=builder /go/src/github.com/yokoe/go-server-example /app

CMD /app/main $PORT

最新のgolangでビルド、容量の小さいAlpine Linux上でサーバーを動かすDocker imageを作ります。HerokuではEXPOSEコマンドは無視されるので、記述する必要はありません。前述の通り、サーバーはHerokuから指定されるポート番号($PORT)でListenする必要があるので、起動時の引数に渡しています。

docker build -t herokuexp .
docker run -e "PORT=3000" -p 3000:3000 -t herokuexp

ローカルでビルドして試してみましょう。先ほどと同じくHello world\nが返ってきたらOKです。

Herokuにデプロイする

gitレポジトリの作成

git init

gitレポジトリを作成するのは必須ではありませんが、gitで管理しておくと以降のherokuコマンドでデプロイなどの作業をする際に--appnameを指定しなくてよくなるので楽です。

herokuアプリの作成

heroku create

新しいherokuアプリを作成します。

pushする

heroku container:push web

デプロイの作業はpushreleaseの二段階に分かれています。2018年5月まではpushをするだけでデプロイが完了していましたが、今は明示的にreleaseをするまでデプロイが完了しないので注意しましょう。

参考: https://devcenter.heroku.com/changelog-items/1426

まずは、Container RegistryにDocker imageをPushします。pushを実行するとローカルでDocker imageが作成され、Container Registryに登録されます。最後の引数はプロセスタイプです。今回はHTTPサーバーなのでwebを指定します。

なお、gitレポジトリを使用していない場合(gitのremoteにherokuが登録されていない状態)の場合は、コマンドを叩く際にappnameを明示的に指定してあげる必要があります。

releaseする

heroku container:release web

Container Registryにイメージをpushできたら、それをreleaseします。webプロセス1個のみを使う場合はpushreleaseを分けて叩くメリットがわからないかもしれませんが、複数のプロセスタイプのイメージを同時にデプロイしたい場合などに便利なようです。

pushにはDocker Image作成のために多少時間がかかりますが、releaseは一瞬で完了します。二つの作業合わせても、Google App Engineのデプロイに比べると圧倒的に速いので快適ですね。

pushreleaseをわける必要がない場合は、Taskで下記のようなタスクを追加すると楽です。

Taskfile.yml
version: '2'

tasks:
  release:
    cmds:
      - heroku container:push web
      - heroku container:release web
      - osascript -e 'display notification "Deploy Done" with title "Heroku Deploy" subtitle "My Go Server" sound name "Submarine"'

蛇足ですが、Macの場合は最後にosascriptでNotificationを出すようにしておくと、作業完了までの間他の作業に集中しやすいのでおすすめです。

動作確認

heroku open

デプロイに成功したら、サーバーにアクセスして確認します。サーバーのURLはheroku createした際などに表示されるので、それをcurlで叩いても良いですし、heroku openコマンドを叩くとそのURLをブラウザで起動してくれるので楽です。(APIサーバーの場合はcurlで確認したいですが)

まとめ

GoアプリのHerokuへのデプロイはかなり簡単で速い上、値段も明瞭なのでおすすめです。CircleCIと連携させればGitレポジトリへPushしたタイミングで自動デプロイすることも可能なので、気が向いたら追記or新しいポストで投稿しようと思います。

参考リンク