【Go言語】ホットリロード可能なHTTPサーバのDocker環境構築手順

インフラ

先日、Go言語のホットリロードツール『Air』でコードの修正を即時反映させるでAirを利用したホットリロード可能なHTTPサーバの紹介をしました。

Go言語のホットリロードツール『Air』でコードの修正を即時反映させる

2021年5月3日

今回は『Air + Go言語』のDocker環境を構築する手順について紹介します。

今回利用するサンプルコード

localhost:3000にアクセスするとレスポンスが返ってくるHTTPサーバをサンプルとして利用します。

main.go

package main

import (
    "log"
    "net/http"
)

func rootHander(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Header().Set("Content-Type", "text/html; charset=utf8")
    w.Write([]byte("こんにちは"))
}

func main() {
    http.HandleFunc("/", rootHander)
    log.Fatal(http.ListenAndServe(":3000", nil))
}

『Air + Go言語』のDocker環境の構築手順

Docker環境の構築手順について紹介します。

下準備: 作業ディレクトリの用意

作業ディレクトリとgo.modファイルの作成をします。

$ mkdir go-docker-example && cd $_
$ go mod init `basename $PWD`

各種ファイルの作成

Dockerfileファイルは以下の通りです。

Dockerfile

FROM golang:1.16.3-buster

# コンテナの作業ディレクトリにローカルのファイルをコピー
WORKDIR /app
COPY . /app

# 必要なパッケージをインストール
RUN go mod tidy

# Airをインストール
RUN go install github.com/cosmtrek/air@v1.27.3

# airコマンドでGoファイルを起動
CMD ["air"]

docker-compose.ymlは以下の通りです。

HTTPサーバがlistenしているポートと、開放するコンテナのポートは一緒にする必要があります。(今回でいうところの3000番)

docker-compose.yml

version: '3'
services:
  app:
    build: .
    ports:
      - '3030:3000' # ローカルの3030番ポートでコンテナの3000番ポートに接続
    volumes:
      - .:/app # ローカルとコンテナのディレクトリをバインドマウント(同期)
      - go_path:/go # パッケージやバイナリファイルのインストール先($GOPATH)を永続化
volumes:
  go_path:

動作確認

コンテナ起動後、ローカルからcurlを実行してレスポンスが返ってくればOKです。

### 作業ディレクトリへ移動
$ cd go-example

### バックグラウンドで起動
$ dokcer-compose up -d

### 接続の確認
$ curl localhost:3030

こんにちは

参考: 依存パッケージ不足でコンテナ起動が失敗する問題を解決する

上記で紹介したDocerfiledocker-compose.ymlでは、go.modに不備があるとコンテナ起動に失敗します。

たとえば、以下のようにコードを修正したとします。

package main

import (
    "log"
    "net/http"
+   "rsc.io/quote"
)

func rootHander(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Header().Set("Content-Type", "text/html; charset=utf8")
-   w.Write([]byte("こんにちは"))
+   w.Write([]byte(quote.Hello())
}

func main() {
    http.HandleFunc("/", rootHander)
    log.Fatal(http.ListenAndServe(":3000", nil))
}

Goファイル修正後go mod tidyを実行せずにコンテナを起動すると、rsc.io/quotego.modに追加されていないため失敗します。

$ docker-compose up

Creating air_app_1 ... done
Attaching to air_app_1
app_1  |
app_1  |   __    _   ___
app_1  |  / /\  | | | |_)
app_1  | /_/--\ |_| |_| \_ , built with Go
app_1  |
app_1  | watching .
app_1  | !exclude tmp
app_1  | building...
app_1  | main.go:7:2: no required module provides package rsc.io/quote; to add it:
app_1  |    go get rsc.io/quote
app_1  | failed to build, error: exit status 1

go mod tidyをコンテナ起動時、つまりairコマンドの直前に実行することで、この問題が解決できます。

start.sh

#!/bin/bash -eu
go mod tidy
air

start.shを利用してコンテナを起動するようにdocker-compose.ymlを修正します。

docker-compose.yml

version: '3'
services:
  app:
    build: .
    ports:
      - '3030:3000'
    volumes:
      - .:/app
      - go_path:/go
+   command: ["./start.sh"]
volumes:
  go_path:

airコマンドの直前にgo mod tidyが実行されるようになったため、問題なくコンテナが起動できます。

$ docker-compose up

Creating air_app_1 ... done
Attaching to air_app_1

app_1  | go: finding module for package rsc.io/quote
app_1  | go: downloading rsc.io/quote v1.5.2
app_1  | go: found rsc.io/quote in rsc.io/quote v1.5.2
app_1  | go: downloading rsc.io/sampler v1.3.0
app_1  | go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
app_1  |
app_1  |   __    _   ___
app_1  |  / /\  | | | |_)
app_1  | /_/--\ |_| |_| \_ , built with Go
app_1  |
app_1  | watching .
app_1  | !exclude tmp
app_1  | building...
app_1  | running...

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!