helloworlds

not a noun, it's a verb

【Go】簡単!使い捨て、GoのDocker開発環境を構築

以前は、macOSでのGoのプロジェクトについて考えました。

o21o21.hatenablog.jp

今回はGo (golang)のDocker開発環境を考えてみます。 拡張性を意識して、docker-composeを利用してみます。

※ ヴァージョンの関係で1発でうまくいかない場合もあります。 分かり次第更新します!(2019/02/22更新、下部記載)

想定として、なる早で環境構築し、GoのWebアプリを開発していこう!みたいなノリです。 ちなみに、Goのフレームワークとして、echoをインストールしていきます。 ゴールは、ブラウザでのHello Worldを目指します。

私の環境は、macOSです。 事前にDocker (macならDocker for macをインストールしましょう!)

さて、順をおってみていきましょう!

プロジェクトの作成

今回、Dockerなので特にホストOS(私ならmac)のGOPATHを意識する必要はありません。 (Docker上でのGOPATHは指定します。) 気になる方は、この記事の冒頭であげた過去の記事を参考にしてみて下さい。

なので、任意のディレクトリに新規プロジェクトを作成しましょう。

必要なファイルを作成

コマンド docker-compose upで起動させるために、必要な設定ファイルを新規作成します。 この記事では必要最低限の記述にします。portの指定などご自身のやりたいことに合わせて設定してみて下さい。 ※ 自分の使用しているdocker及びdocker-composeのversionにも注目!

Dockerfile

FROM golang:latest                                                                                                                                                                                    
  
WORKDIR /go/src/go-app2
COPY . .
    
# "Go" Environment variable
ENV GO111MODULE=on
    
# install Go web framework
RUN go get -u github.com/labstack/echo
   
# install "Fresh" restart tool
RUN go get github.com/pilu/fresh

CMD ["fresh"]

上からみていきます。

goのヴァージョンは最新のものを取得しにいってます。(ヴァージョン指定も勿論可能)

GO111MODULEというGOの環境変数をonに設定します。 これでモジュールを自動的に読み込むことができます。

これをDockerfileに記述しないと、docker-compose upした時に以下のWARNINGが出ました。

go get: warning: modules disabled by GO111MODULE=auto in GOPATH/src;
    ignoring go.mod;
    see 'go help modules'

GO111MODULEを記述した際に、プロジェクトディレクトリに自動的にgo.modというファイルが作成されます。 (= 今回のプロジェクトに依存したgo.modファイルが生成される。)

そして、フレームワークのechoとホットリロードをするためのFreshをgo getでインストールします。 go getを使用するということは、今回作成されるDocker上には、複数のプロジェクトを共存させる目的ではないことを覚えておいて下さい。 仮に、go getをすると、GOPATH配下にインストールされて2つ目以降のパッケージの依存関係を管理しにくくなるからです。 (goから離れていたので、間違ってたらごめんなさい...!!!!)

GO111MODULE

さきほど定義した環境変数GO111MODULEについてです。 こちらヴァージョン1.11から追加された、モジュール管理モード環境変数です。(正式リリースは、1.12かららしい)

on : モジュール対応モード off : GOPATH モード(今まで通り) auto : GOPATH以外のみモジュールモード

今までdepなどを使用していたと思いますが、これは便利! depからの乗り換えも楽みたいですね!

ちなみに、今回のDocker環境で開発中に新しいモジュールを追加すると、freshが検知し自動的にモジュールがインストールされます。 そうすると、go.modに require package/name v1.1x.2x のように追記されていることが確認できました。 また、go.sumファイルも生成されます。これは、モジュールの依存関係を示すファイルのようです。

そして最後にコマンド実行です。

ホットリロードについてですが、どういうことかというと コンパイル不要な言語ならいいのですが、一旦コンパイルしてから動作させる言語において、 よくDockerで起動させられたけど、ファイルの編集をした際にもう一度docker runとかしないといけない...ということが起こります。

かなり面倒なので、タスクランナー的なものが必要です。 厳密に今回使用するFreshはタスクランナーとは書いてないようですが、 要はファイルを監視し変更が検知されると、自動的にrestartしてくれるものです。

なので、Dockerfileでは、最後にfreshというコマンドで起動させます。

Freshについては、以下の公式を参照してみて下さい。 Fresh自体の設定ファイルも作成できるようです。

github.com

docker-compose.yml

続いてdocker-compose.ymlですが、そんなに記述がないので説明を割愛します。

   version: '3'                                                                                                                                                                                        
   services:
     app:
       build: .
       container_name: <projecrt_name>
       volumes:
         - ./:/go/src/<project_name>
       ports:
         - "8080:8080"
      environment:
          GO111MODULE: "on"

ここまで作成できれば、ほぼ完成です。 Dockerfileを基に、1つのコンテナと1つのイメージが作成されるはずです。(golangのイメージ含めるなら2つかな?)

あとは適当にmain.goを作成します。

goファイル

  • main.go
package main

import (
    "log"
    "net/http"
    "path/filepath"
    "sync"
    "text/template"
)

type templateHandler struct {
    once     sync.Once
    filename string
    templ    *template.Template
}

func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t.once.Do(func() {
        t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
    })
    t.templ.Execute(w, nil)
}

func main() {
    http.Handle("/", &templateHandler{filename: "top.html"})

    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal("ListenAndServe", err)
    }
}
  • templates/top.html
<p>Hellooooooooooooooooo World!!!!!!!!!!!!!!</p>

実行

いざ実行です!

$ docker-compose up
Building app
Step 1/7 : FROM golang:latest
 ---> d817ad5b9beb
Step 2/7 : WORKDIR /go/src/go-app2
Removing intermediate container 816a33af9a01
 ---> bb3d6012e557
Step 3/7 : COPY . .
 ---> e38c75d754d3
Step 4/7 : ENV GO111MODULE=on
 ---> Running in 57345b25fb4f
Removing intermediate container 57345b25fb4f
 ---> d9500cf1f5cd
Step 5/7 : RUN go get -u github.com/labstack/echo
 ---> Running in 01dc8d6ebd6c
go: finding github.com/labstack/echo v3.2.2+incompatible
go: downloading github.com/labstack/echo v3.2.2+incompatible
go: finding github.com/labstack/gommon/log latest
go: finding github.com/labstack/gommon/color latest

... 略 

Creating goapp2_app_1 ... done
Attaching to goapp2_app_1
app_1  | 11:2:58 runner      | InitFolders
app_1  | 11:2:58 runner      | mkdir ./tmp
app_1  | 11:2:58 runner      | mkdir ./tmp: file exists
app_1  | 11:2:58 watcher     | Watching .
app_1  | 11:2:58 watcher     | Watching templates
app_1  | 11:2:58 main        | Waiting (loop 1)...
app_1  | 11:2:58 main        | receiving first event /
app_1  | 11:2:58 main        | sleeping for 600 milliseconds
app_1  | 11:2:59 main        | flushing events
app_1  | 11:2:59 main        | Started! (8 Goroutines)
app_1  | 11:2:59 main        | remove tmp/runner-build-errors.log: no such file or directory
app_1  | 11:2:59 build       | Building...
app_1  | 11:3:00 runner      | Running...

あとは、ブラウザでアクセス。

http://localhost:8080/

htmlの内容が表示されていたら成功です。

また、docker-composeを起動したまま編集してみましょう! リロードし、変更が反映されていたら大丈夫です。

Dockerコンテナの中へ

以下のコマンドでDockerコンテナの中に入って、ローカルとのファイルのシンクがされているか確認できます。

$ docker exec -i -t <container_name> bash

今回の例だと、ディレクト/go/src/<project_name>が存在し、 $GOPATHは、/goになっていることが確認できます。

まとめ

いつもホットリロードに悩まされていましたが、Freshは楽ですね! 他にもGoのプロジェクトで使用されているタスクランナーがあります。

github.com

github.com

少しdockerで個人的に気になることがあるので、解決したら更新します!

トラブルシューティング

私が試したことしか記載できませんが、以下で1発でブラウザ確認までいけました。

  1. go.modファイル作成(空でおk)

  2. goのヴァージョンを確認

私の場合、1.12系で大丈夫でした。他のヴァージョンは試してないのでわかりません。。

FROM golang:1.12-rc   

go.modファイル作成の理由としては、GO111MODULE=onにしているとgo.modファイルが設置されているディレクトリでのみgo getコマンドが実行できないためです。

以上.