LoginSignup
20
11

More than 3 years have passed since last update.

dockerでgoの開発環境を構築する

Last updated at Posted at 2019-11-06

dockerでgoの開発環境を構築する

本記事は以下の記事からのリンクとして作成されました。
以下の記事も是非ご覧ください!!

本記事について

本記事は、Go言語の開発環境をdockerで構築するための方法を記した記事です。
ホストOSからコンテナ上で動作する実行ファイルをデバッグできることが目標です。

完成したリポジトリは以下です。
https://github.com/fumiya-uehara/go_dev

環境

  • macOS Catalina ver 10.15
  • go 1.13
  • Docker version 18.09.2, build 6247962
  • docker-compose version 1.24.0, build 0aa5906

事前準備

Goのインストール

ホストOSにgoがインストールされていることを確認してください。


❯ go version
go version go1.13 darwin/amd64

もしもインストールされていない場合は Homebrew などのパッケージマネージャを利用してインストールしてください。


❯ brew install go

GOPATHを設定する

Go言語ではGOPATHという環境変数を設定する必要があります。
インストールされたライブラリをソースコード内でimportする際に利用されたり、 go get で外部ライブラリをインストールした際に保存先として指定されたりと様々なケースで使われます。

GOPATHを設定していないとimportで指定したライブラリを読み込めないので注意が必要です。
また、Goの実行環境がDocker上にある場合、Docker上ではインストールされているライブラリがローカルには存在しないため、IDEによる定義ジャンプができないなど不便な面もあるのでDocker上でインストールされたライブラリに関してはローカルにもインストールするなどの考慮が必要です。(なんかいい方法ないのだろうか・・・)

以下が設定方法です。(筆者はshellにzshを利用しているのでzshの設定方法です)

cat << EOL >> ~/.zprofile
then cmdand> export GOPATH="$HOME/work/go/dev"
then cmdand> export PATH="$HOME/work/go/dev/bin:$PATH"
then cmdand> EOL
❯ echo $GOPATH
/Users/sumio/work/go/dev

GOPATHに指定するディレクトリは自由ですので悩まずに適当なところに置けばよいです。
もちろん後から変更もできますが、変更するとこれまでにインストールしたライブラリへのパスが通らないのでimportで呼び出せないことを頭の片隅に入れといてください。(環境がdocker上にある場合はあまり関係ないですが)

realizeのインストール

今回realizeというタスクランナーツールを利用し、ホットリロードを行います。
このツールのみローカル環境へのインストールが必要となるため事前にインストールしておきます。


❯ go get -u github.com/oxequa/realize
❯ which realize
/Users/sumio/work/go/dev/bin/realize

インストール後、realizeへのパスが通っていればOKです。

構築手順

勉強不足で誤った理解で記述している箇所があるかもしれません。その際はご指摘ください。

GoModulesの導入

GoModulesはバージョン管理システムです。
Projectのディレクトリを作成する際にも少し触れましたが、これまでGo言語でProjectを作成するとなると ${GOPATH}/src 配下に置くことが暗黙的なルールとしてあり、このルールに従って開発を進めるとプロジェクト毎にライブラリのバージョンを変えることができないなどの問題がありました。

これまではこの問題に対して各Projectディレクトリ配下にvendorというディレクトリを切り、そこにProjectで利用する固有バージョンのライブラリを置くことで対処してきました。(GoはvendorというディレクトリがProjectディレクトリ配下にある時優先的にそこにあるライブラリを読み込みます。)

しかし、GoModulesの導入によってvendorは廃止となり、容易にライブラリを管理することができるようになりました。仕組みとしては、go.modというファイルでProjectで利用するライブラリのバージョンを管理し、実際のライブラリは${GOPATH}/pkg/mod配下に置かれます。
もしも、必要としているライブラリがインストールされていなければ${GOPATH}/pkg/mod配下に追加しますし、前述したPATHにライブラリが存在しているのであれば、それを利用するといった感じで動作します。(すみません。検証していないので確証はないですがドキュメントなど読むとそうなのかなって思ってます。)

前置きが非常に長くなってしまいました。
以下を実行し、module-aware modeで動作させ、go.modファイルを作りましょう。

go mod init github.com/fumiya-uehara/go_devgithub.com/fumiya-uehara/go_dev は対象プロジェクトのリモートリポジトリを指定することをお勧めします。

cd ${PROJECT_ROO}export GO111MODULE=on
❯ go mod init github.com/fumiya-uehara/go_dev 

export GO111MODULE=on にて module-aware mode に変更しています。デフォルトは auto です。
Go1.13からは常に module-aware mode で動作すると言われていましたが、最終的には auto で落ち着いたようです。
ただ、 auto の場合でも go.mod ファイルがカレントディレクトリもしくは親ディレクトリに存在する場合は、 on になります。
さらに詳しく知りたい方は以下のリリースノートをチェックしてください。

realizeの設定

タスクランナーツールとしてrealizeを導入しています。
開発時にコードを変更する度に go rungo build を行うのはとても不便です。

そのためにコードの変更を検知し自動でbuildしてくれるホットリロードの仕組みが必要となります。
本プロジェクトではホットリロードを実現するためにrealizeを導入しています。

cd ${PROJECT_ROOT}
realize start

上記コマンド実行後、.realize.yamlが作成されればOKです。
realizeがフォアグラウンドで動作し続けるので Ctrl + c を押下してKillしましょう。

settings:
  legacy:
    force: false
    interval: 0s
schema:
  - name: go_dev
    path: .
    commands:
      install:
        status: true
        method: go build -o /app/go_dev
      run:
        status: true
        method: /go/bin/dlv
    args:
      - exec
      - /app/go_dev
      - --headless=true
      - --listen=:3000
      - --api-version=2
      - --accept-multiclient
    watcher:
      extensions:
        - go
      paths:
        - /
      ignore:
        paths:
          - .git
          - .realize
          - vendor

大事な部分のみピックアップして説明します。

commands:
  install:
    status: true
    method: go build -o /app/go_dev
  run:
    status: true
    method: /go/bin/dlv
args:
  - exec
  - /app/go_dev
  - --headless=true
  - --listen=:3000
  - --api-version=2
  - --accept-multiclient

変更を検知すると対象のファイルをbuildし、最新の状態になります。

ただ、リモートデバッグを行っている最中にコードを変更するとbuildは走りますが、一度コンテナとのコネクションを切断し、新たに繋ぎ直さないと新しいbinaryファイルを読み込みません。
当然の動作のように見えますし、そこまで手間ではないですが、良い方法があれば知りたいものです。

Dockerfile(Go実行コンテナ)

multi-stage buildを採用していますが、# Final Stageは利用されていません。
なので、実際にこちらを見ながら構築する方は消しちゃって問題ありません。

# Variable
ARG GO_VERSION=1.13
ARG ALPINE_VERSION=3.10

# Build Stage
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-stage
ENV CGO_ENABLED 0

WORKDIR /go/go_dev
COPY . .
RUN set -ex && \
    apk update && \
    apk add --no-cache git && \
    go get gopkg.in/urfave/cli.v2@master && \
    go get github.com/oxequa/realize && \
    go get github.com/go-delve/delve/cmd/dlv && \
    go build -o /go/bin/dlv github.com/go-delve/delve/cmd/dlv && \
    go build -o /app/go_dev

# Final Stage
FROM alpine:${ALPINE_VERSION}
WORKDIR /app
COPY --from=build-stage /app/go_dev .
EXPOSE 9000
ENTRYPOINT ["/app/go_dev"]

一つだけ説明します。

go get gopkg.in/urfave/cli.v2@master && \
go get github.com/oxequa/realize && \

realizeのインストールに失敗する問題があり、対策で gopkg.in/urfave/cli.v2@master を前段でインストールしています。

realizeの依存関係の問題のようです。以下のissueです。
https://github.com/oxequa/realize/issues/253

Dockerfile(mysql実行コンテナ)

以下を実行し、ディレクトリとファイルを作成します。
PATHについては適宜変更してください。

$ cd ~/work/go/go_dev && \
mkdir -p ./docker/db && \
cd ./docker/db && touch {Dockerfile,my.cnf}

FilePath: ${PROJECT_ROOT}/docker/db/Dockerfile

FROM mysql:8.0.17
COPY my.cnf /etc/my.cnf

FilePath: ${PROJECT_ROOT}/docker/db/my.cnf

[mysqld]
port=3306
socket=/tmp/mysql.sock
key_buffer_size=16M
max_allowed_packet=8M
default_authentication_plugin=mysql_native_password

[mysqld_safe]
pid-file=/var/run/mysqld/mysqld.pid

ログの設定を行いたい場合はご自由にmy.cnfへ追記してください。
コンテナ内のログ出力先とホストOSの任意のディレクトリをマッピングするとコンテナに入らずともログを確認できると思うので、docker-composeを書くときに意識するといいかもしれません。

docker-compose

version: '3.7'
services:
  gin:
    build:
      context: .
      target: build-stage
    depends_on:
      - db
    volumes:
      - ./:/go/go_dev
    command: realize start --run
    ports:
      - "19001:9000"
      - "3000:3000"
    security_opt:
      - seccomp:unconfined
    cap_add:
      - SYS_PTRACE
  db:
    build: ./docker/db
    ports:
      - "13306:3306"
    volumes:
      - ./docker/db/mysql:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: "example"
      MYSQL_DATABASE: "test"

全てを説明していては時間が足りないので、大事なところだけ抜粋します。

build:
  context: .
  target: build-stage

Dockerfile内ではmulti-stage build構成を採用していますが、ローカルの開発環境ではFinal-Stageの利用はありません。
そのため、targetにはbuild-stageが指定されています。

command: realize start --run

上記コマンドを実行することで変更を自動検知しbuildを行い、buildされたbinaryファイルを対象にデバッガを起動します。

security_opt:
  - seccomp:unconfined
cap_add:
  - SYS_PTRACE

Docker上で動作するプロセスをホストOSにアタッチするための設定です。
リモートデバッグを行うのに利用します。

main.go

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    r := gin.Default()

    r.GET("/structJSON_test", func(context *gin.Context) {
        var msg struct {
            Name    string `json:"user"`
            Message string
            Number  int
        }
        msg.Name = "foo"
        msg.Message = "これは構造体をJSONで返すためのテストです。"
        msg.Number = 1111
        context.JSON(http.StatusOK, msg)
    })
    _ = r.Run(":9000")
}

ginを利用しており、エンドポイントとして/structJSON_testを用意しました。
これは構造体をJSON形式で返すAPIです。
用途としてはテスト用です。

動作確認

以下を実行します。

cd ${PROJECT_ROOT}
❯ docker-compose build
❯ docker-compose up -d

もしも、起動に失敗している場合は最初に貼ったリポジトリと見比べて誤りがないか確認してください。
それでも解決することが難しい場合はコメントください。(筆者がこれまで記述した手順に誤りがある可能性も十分にあるので・・・)

問題なく起動に成功したらデバッグの設定を行います。
筆者はIDEにGoLandを利用していますのでそちらの設定を紹介します。

以下の Edit Configurations... をクリックします。
スクリーンショット 2019-11-03 14.09.36.png

左上の『+』マークをクリックし、「Go Remote」をクリックします。
スクリーンショット 2019-11-03 14.10.24.png

Portを「3000」に設定し、右下のAPPLYをクリックします。
スクリーンショット 2019-11-03 14.11.30.png

虫のマークをクリックします。
スクリーンショット 2019-11-03 14.12.11.png

下図のように緑の点が出てくれば接続できています。
スクリーンショット 2019-11-03 14.12.31.png

ブレークポイントをおきましょう。
スクリーンショット 2019-11-03 14.13.44.png

以下のコマンドを実行してください

❯ curl localhost:19001/structJSON_test

すると、以下のように止まると思います。
スクリーンショット 2019-11-03 14.14.57.png

最後に以下のような結果が返ってくれば完了です。

{"user":"foo","Message":"これは構造体をJSONで返すためのテストです。","Number":1111}

最後に

拙い文章で読み辛い箇所がたくさんあったと思いますが、ここまで読んでいただきありがとうございます。

Goの環境を構築する上で大変だったことは、$GOPATHGoModulesなどの独自概念を理解することでした。色々な記事を読み、実際に動かして検証してやっと今の理解まで至りました。(まだまだ初歩ですが)
こればっかやってた2ヶ月でコードはほとんど書いてなかったりします。笑
やっと「書ける!」って思っていたのですが、次はディレクトリ構成で悩んでおり、コードを書ける日はまだ遠そうです。

参考

realize & delve

multi stage build

GoModules

20
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
11