Edited at

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を明示的に指定してあげる必要があります。


unauthorized: authentication requiredが出る場合

Container Registryにログインしていない可能性があります。heroku container:loginでContainer Registryにログインしてから試しましょう。


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新しいポストで投稿しようと思います。


参考リンク