😎

dockerを使ってgoの環境構築

2020/10/14に公開

これからデータベースやストレージなどGo以外も使って開発していくので、そのまま自分のPC上にこれらの環境を構築していくのはいいものではありません。そこでDockerを使ってコンテナ上で開発をしていこうと思います。

今回のデータはgithubの07go_dockerにあります。

Dockerとは

コンテナという技術を使って仮想環境を構築できるツールです。例えば私が使っているMacOS上にWindowsのソフトが使えるような場所を作成します。これが仮想環境です。
仮想環境といえばVMWareやVirtualBoxなどがありますが、それらと異なりゲストOSを挟まないので軽量に動作する利点があります。
私もここら辺は全然詳しくないのですが、Dockerは開発環境を持つコンテナを簡単に立ち上げたり潰したりできるので重宝しています。またいろいろなものがコンテナ内で閉じているので自分のPC環境を汚すことがありません(これが今回Dockerを使う1番の目的でした)。
他にもコンテナ作成用のファイルを共有するだけでDockerを使えるPCならどれでも同じ環境が作れます。チームで開発する時にも重宝されるツールです。

Dockerのインストール等は各々に任せます。とても話題になったツールなので調べればいくらでもインストール方法は出てきます。また基本的な使い方も本筋から逸れるのでここでは説明しません。しかしマネするだけでも同じ環境が作れると思います。

コンテナの作成

ファイルの作成

開発環境であるコンテナを作成するためにはコンテナの元になるイメージファイルが必要です。
イメージファイルを1から作るのは素人の私には無理なのでDocker Hubから取得します。DockerHubにはいろいろな種類のイメージが管理されているので我々がやろうと思うことに使用するイメージは大体あるのでまずはDockerHubをみてみることをオススメします。

まずは07go_dockerというディレクトリを作成し、以下のように2つのファイルを作成してください。

$ tree
.
├── Dockerfile
└── docker-compose.yml

それぞれのファイルを説明します。

  • Dockerfile
    DockerHubから取得してくるイメージの種類や自分たちの開発に合わせた設定を行います。
    # 2020/10/14最新versionを取得
    FROM golang:1.15.2-alpine
    # アップデートとgitのインストール!!
    RUN apk update && apk add git
    # appディレクトリの作成
    RUN mkdir /go/src/app
    # ワーキングディレクトリの設定
    WORKDIR /go/src/app
    # ホストのファイルをコンテナの作業ディレクトリに移行
    ADD . /go/src/app
    
    今回はLinuxベースOSであるalpine上にgoの最新verをインストールしたイメージを使います。alpineを使う理由は容量が他と比べて小さいからです。
    そのあとはコメントアウトに書いているようにワーキングディレクトを設定します。
  • docker-compose.yml
    単体のコンテナだけを使う場合は上のDockerfileだけで良いのですが、今後データベースやストレージのコンテナなど複数のコンテナを立てる場合は、このdocker-composeファイルを書いておくと便利です。これでコンテナどうしの接続などの設定をすることが出来ます。
    version: "3" # composeファイルのバージョン
    services: 
        app: # サービス名
            build: . # ①ビルドに使うDockerfileの場所
            tty: true # ②コンテナの永続化
            volumes:
                - ./app:/go/src/app # ③マウントディレクトリ
    
    1. 書き方は上のような感じです。buildでビルドに使うDockerfileを指定します。
    2. コンテナは基本的に立ち上がって所定の処理が終わったら自動で閉じます。コンテナの永続化が必要な場合はtty:trueを入れてください。
    3. コンテナ内の状態は変更を加えてもコンテナを閉じると元に戻ってしまいます。そこでワーキングディレクトリなど頻繁に変更を加えるディレクトリなどはホストOS上のディレクトリをマウントするのが一般的です。ここでは/appディレクトリをコンテナ内のワーキングディレクトリにマウントするようにしています。

ファイルを実行しコンテナを作成

ファイルを作成したらそのディレクトリで以下を実行します。
まずはコンテナを作成します。以下のコマンドでイメージの取得します。

# コンテナイメージのビルド
$ docker-compose build
Building app
Step 1/4 : FROM golang:1.15.2-alpine
1.15.2-alpine: Pulling from library/golang
df20fa9351a1: Already exists
ed8968b2872e: Pull complete                                                                 
a92cc7c5fd73: Pull complete
e871e8e8d7a9: Pull complete                                                                 
e73272ec9a57: Pull complete
Digest: sha256:4d8abd16b03209b30b48f69a2e10347aacf7ce65d8f9f685e8c3e20a512234d9
Status: Downloaded newer image for golang:1.15.2-alpine
 ---> b3bc898ad092
Step 2/4 : RUN mkdir /go/src/app
 ---> Running in 2825baa5ff8b
Removing intermediate container 2825baa5ff8b
 ---> 1d9cfa183605
Step 3/4 : WORKDIR /go/src/app
 ---> Running in 6634eaf2d95d
Removing intermediate container 6634eaf2d95d
 ---> 45a9a0733c47
Step 4/4 : ADD . /go/src/app
 ---> ce52e2af08cd

Successfully built ce52e2af08cd
Successfully tagged 07go_docker_app:latest

次にきちんとイメージが出来ているか以下のコマンドで確認します。

# イメージの確認
$ docker images
REPOSITORY                                        TAG                 IMAGE ID            CREATED             SIZE
07go_docker_app                                   latest              e61c3be1f5be        31 seconds ago      321MB
07go_db_app                                       latest              5cf69e7bd132        14 hours ago        321MB
<none>                                            <none>              1022eb1f93f7        14 hours ago       
略

イメージが出来ていたらマウントするためのappディレクトリとファイルを作成します。

$ touch app/main.go

$ tree
.
├── Dockerfile
├── app
│   └── main.go
└── docker-compose.yml

main.goには以下を記述します。コンテナから実行出来たことを確認するためにメッセージを出力するだけのものです。

package main

import "fmt"

func main() {
	fmt.Println("Hello golang from docker!")
}

コンテナを立ち上げます。-dオプションをつけるとバックグラウンドでの起動になります。

# コンテナの立ち上げ
$ docker-compose up -d
Removing 07go_docker_app_1
Recreating c8e4321b21d2_07go_docker_app_1 ... done

コンテナからgo run main.goコマンドを実行します。メッセージが返ってきたので上手くいっています。

# 作成したファイルの実行
$ docker-compose exec app go run main.go
Hello golang from docker!

最後はコンテナを閉じることを忘れないようにしてください。

$ docker-compose down
Stopping 07go_docker_app_1 ... done
Removing 07go_docker_app_1 ... done
Removing network 07go_docker_default

コンテナのポートをホストOSのポートにつないでブラウザで閲覧

次はこれまでに作ったウェブサーバをDockerコンテナ上に作成しましょう。以前作成した04templateを07go_docker/appにコピーします。

$ tree
.
├── Dockerfile
├── app
│   ├── assets
│   │   ├── img
│   │   │   └── biplane.png
│   │   ├── js
│   │   │   └── script.js
│   │   └── style
│   │       └── style.css
│   ├── main.go
│   └── root
│       ├── index.html
│       └── template
│           ├── footer.html
│           └── header.html
└── docker-compose.yml

これで実行します。

# go-docker-1
$ docker-compose up -d
Creating network "07go_docker_default" with the default driver
Creating 07go_docker_app_1 ... done

$ docker-compose exec app go run main.go
2020/10/15 02:10:14 Server listening on http://localhost:8080/

これでhttp://localhost:8080/にアクセスしても何も表示されないと思います。これはコンテナ内のlocalhostにウェブサーバをつなげているだけで自分たちが使用しているホストOSには繋がっていないので表示されません。コンテナの8080ポートとホストOSの8080ポートをつないであげる必要があります。docker-compose.ymlを少し書き換えます。

version: "3" # composeファイルのバージョン
services: 
    app: # サービス名
        build: . # ビルドに使うDockerfileの場所
        tty: true # コンテナの永続化
        ports: # ホストOSのポートとコンテナのポートをつなげる 
            - "8080:8080"
        volumes:
            - ./app:/go/src/app # マウントディレクトリ

これでお互いのポートがつながります。
これでもう一度go-doccker-1を実行してみてください。次は上手くいくはずです。

ホットリロード機能をつける

ここまでで開発をすることは可能ですが、ファイルを書き換える度にdocker-compose exec app go run main.goコマンドを打たなければいけないので少し面倒です。ここではrealizeを使用してファイルを保存するとgoファイルを立ち上げ直さなくても変更が反映されるホットリロードを実現したいと思います。
ここら辺は素人すぎたのでrealize + dockerでGolangのホットリロード付き開発環境を構築するを参考にさせてもらっています。
Dockerfileの最後に以下を追加します。

RUN go get -u github.com/oxequa/realize 
CMD ["realize", "start"]

これでrealizeをインストールし、実行します。

$ docker-compose build
$ docker-compose up -d

これで/appに.realize.yamlというファイルが出来ます(もしかしたらbuildの時点で作成されるかもしれません)。これに以下を書き加えます。

settings:
  legacy:
    force: false
    interval: 0s
schema:
- name: app
  path: .
  commands: {}
#   ここから
  commands:
    run:
      status: true
#   ここまでを追加する
  watcher:
    extensions:
    - go
    paths:
    - /
    ignored_paths:
    - .git
    - .realize
    - vendor

今回はルートにmain.goがあるシンプルな構成なので設定はほとんど必要ありませんでしたが、もしファイル名をserver.goにしたりmain関数ファイルの場所が異なる場合はさらに設定が必要です。上で紹介した参考記事をみてください。

念のため一度コンテナを停止して、再度起動し実行します。

$ docker-compose down
$ docker-compose up -d
$ docker-compose exec app go run main.go
2020/10/15 03:47:08 Server listening on http://localhost:8080/
2020/10/15 03:47:08 listen tcp :8080: bind: address already in use

例えばhandleFuncの第1引数(パス)を変えるなど変更を加えて保存をすると自動的に変更が反映されていると思います。

これでホットリロードができるようになったので毎回実行を止める必要はなくなりました。開発に専念することが出来ます。

追記
以下のようにgo run main.goではなくrealize startでサーバを立ち上げるとログが出ます。

$ docker-compose up -d
$ docker-compose exec app realize start
[01:34:05][APP] : Watching 1 file/s 7 folder/s
[01:34:05][APP] : Install started
[01:34:06][APP] : Install completed in 0.273 s
[01:34:06][APP] : Running..
[01:34:06][APP] : 2020/10/18 01:34:06 &[] &[] &[]
[01:34:06][APP] : 2020/10/18 01:34:06 map[1:{1 gophar 5555}]
[01:34:06][APP] : 2020/10/18 01:34:06 map[1:{1 gophar 5555} 2:{2 octcat 0000}]
[01:34:06][APP] : 2020/10/18 01:34:06 map[1:{1 gophar 5555} 2:{2 octcat 0000} 3:{3 gophar 5555}]
[01:34:06][APP] : 2020/10/18 01:34:06 Server listening on http://localhost:8080/
[01:35:06][APP] : GO changed /go/src/app/main.go
[01:35:06][APP] : Install started
[01:35:08][APP] : Install completed in 1.700 s
[01:35:08][APP] : Running..
[01:35:08][APP] : 2020/10/18 01:35:08 &[] &[] &[]
[01:35:08][APP] : 2020/10/18 01:35:08 map[1:{1 gophar 5555} 2:{2 octcat 0000} 3:{3 gophar 5555}]
[01:35:08][APP] : 2020/10/18 01:35:08 Server listening on http://localhost:8080/

Discussion