Goで書いたサーバーをデプロイする先を探していて、Google App Engine(SE)やArukasなどを試してきたが、ふと気になって調べてみたらRailsアプリのホスティングで馴染みのあるHerokuもDocker Deployに対応していたので、試してみた。割と良さげなので、手順を残しておく。
前提: 必要なもの
- Herokuアカウント
-
Heroku CLI
- CLIのバージョンが古いと
container:release
でエラーが発生するので注意!
- CLIのバージョンが古いと
- Docker
Heroku CLI ログイン
heroku login
heroku container:login
HerokuへのデプロイはCLIを使ってシェルから行います。heroku login
をして、CLIからログインして起きましょう。Docker Deployの場合はheroku container:login
でContainer Registryへのログインも必要となります。
プログラムの準備
まずはHerokuにDeployする、簡単なHTTPサーバーをGoで書いて、ローカル(+ローカルのDocker)で動作確認をします。
シンプルなHTTPサーバーを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
curl
でlocalhost: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
デプロイの作業はpush
とrelease
の二段階に分かれています。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個のみを使う場合はpush
とrelease
を分けて叩くメリットがわからないかもしれませんが、複数のプロセスタイプのイメージを同時にデプロイしたい場合などに便利なようです。
push
にはDocker Image作成のために多少時間がかかりますが、release
は一瞬で完了します。二つの作業合わせても、Google App Engineのデプロイに比べると圧倒的に速いので快適ですね。
push
とrelease
をわける必要がない場合は、Taskで下記のようなタスクを追加すると楽です。
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新しいポストで投稿しようと思います。