Docker＋GolangをVisualStudioCodeでリモートデバッグする

#はじめに
DockerにGolangの環境を閉じ込めたときデバッグどうするの？と思ってなかなか情報を集められなかったのでまとめます。

##検証環境
##### ホスト側
- Windows10 + Boot2Docker + VisualStudioCode
- Sierra + Docker for Mac + VisualStudioCode

##### コンテナ側
- golang:onbuildを使用（執筆時点ではgolang 1.7）

#Golangのデバッガについて
VisualStudioCodeでは[Delve](https://github.com/derekparker/delve)を使うようなので、Delveを使います。
ドキュメントや[issuse](https://github.com/Microsoft/vscode-go/issues/304)見てるとリモートデバッグもサポートしていて、もしかしてDockerもいけるかもってところが発端。

#Dockerのリモートデバッグについて
リモートデバッグが可能なようで、こちらをベースにGolang用に書き換えます。
 [Live Debugging With Docker](https://blog.docker.com/2016/07/live-debugging-docker/)


#用意したファイル
$GOPATH/src/github.com/user
|--.vscode
|　　launch.json
|--docker-compose.yml
|--Dockerfile
|--main.go

```go:main.go
// main.go
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/garyburd/redigo/redis"
)

func main() {
	redi, err := redis.Dial("tcp", "redis:6379")
	if err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		res, err := redi.Do("incr", "counter")
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte(err.Error()))
			return
		}

		if res, ok := res.(int64); ok {
			w.Write([]byte(fmt.Sprintf("counter: %d", res)))
		} else {
			w.WriteHeader(500)
			w.Write([]byte("unexpected value"))
		}
	})
	log.Fatal(http.ListenAndServe(":5000", nil))
}
```

```docker:Dockerfile
# Dockerfile
FROM golang:onbuild
RUN go get github.com/derekparker/delve/cmd/dlv
```

```docker:docker-compose.yml
# docker-compose.yml
app:
  build: .
  ports:
    - "5000:5000" #golang webserver用のポート
    - "5050:5050" #delve用のポート
  links:
    - redis
  volumes:
    - ".:/go/src/app"
  privileged: true # Delveで必要なので付与します。セキュリティ面から本番環境では使うべきではありません。 
  command: dlv debug --headless --listen=:5050 --log 

redis:
  image: redis
```

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
         {
            "name": "Remote",
            "type": "go",
            "request": "launch",
            "mode": "remote",
            "remotePath": "/go/src/app", //コンテナ内のパス
            "port": 5050, // dlv debugのポートを指定（WEBのポートではない）
            "host": "192.168.99.100", //左の設定はToolBoxを使った場合のDocker MachineのIP. for Macの場合はlocalhostでよかった.
            "program": "${workspaceRoot}", //${workspaceRoot} =　$GOPATH/src/github.com/user
            "env": {},
            "args": [],
            "showLog": true
        }
    ]
}
```

main.goはこちらの記事を参考にしています。
redisを使って数字をインクリメントして表示するシンプルな処理。
参考）[Dockerを使ったGolang開発環境](http://unknownplace.org/archives/golang-development-enviroment-with-docker.html)

#リモートデバッグを行う
##コンテナを起動する
main.goのカレントでコンテナをビルドして、起動

```bash
> docker-compose build
redis uses an image, skipping
Building app
Step 1 : FROM golang:onbuild
# Executing 3 build triggers...
Step 1 : COPY . /go/src/app
Step 1 : RUN go-wrapper download
 ---> Running in ff0ae2dae819
+ exec go get -v -d
github.com/garyburd/redigo (download)
Step 1 : RUN go-wrapper install
 ---> Running in d778a728a336
+ exec go install -v
github.com/garyburd/redigo/internal
github.com/garyburd/redigo/redis
app
 ---> 0b0ffd816054
Removing intermediate container ff0ae2dae819
Removing intermediate container d778a728a336
Removing intermediate container 677062574a96
Step 2 : RUN go get github.com/pilu/fresh
 ---> Running in 06b38d625b20
 ---> 3bc412d615be
Removing intermediate container 06b38d625b20
Step 3 : RUN go get github.com/derekparker/delve/cmd/dlv
 ---> Running in 10b51b638607
 ---> 206e4fb5f2c0
Removing intermediate container 10b51b638607
Successfully built 206e4fb5f2c0
```

```bash
> docker-compose up -d
> docker-compose ps
      Name                    Command               State                       Ports

----------------------------------------------------------------------------------------------------------
user_app_1     dlv debug --headless --lis ...   Up      0.0.0.0:5000->5000/tcp, 0.0.0.0:5050->5050/tcp
user_redis_1   docker-entrypoint.sh redis ...   Up      6379/tcp
````

コンテナが二つ立ち上がってればオッケーです。

##VisualStudioCodeでデバッグを開始する
ミソはVisualStudioCodeからコンテナ内のコードを起動することらしい。
まずはVisualStudioCodeのデバッグビューを開いて、ブレークしたいコードにブレークポイントを置いておきます。

![image](https://qiita-image-store.s3.amazonaws.com/0/119359/b8621db8-59c1-7f1a-87f4-c203b9f2870e.png)

そんで左上のデバッグを```Remote```にして実行。
※うまくいくと下のバーがオレンジになる。

![image](https://qiita-image-store.s3.amazonaws.com/0/119359/ea88d7d7-757c-c421-d63c-f01ccaf60844.png)


この状態になれば、コンテナ内のmain.goが起動されているので、ブラウザでIPを叩く。(docker-machineのIPかlocalhostでいけるはず)
ブレークポイントを置いた場所にブレークするようになります。

![image](https://qiita-image-store.s3.amazonaws.com/0/119359/17572130-30d6-e6b1-cfc9-6bea40231165.png)

あとは通常のデバッグと同じく、好きなようにデバッグできますね。


#解決できていない問題点
リモートデバッグはできるようになったものの以下の問題点があって、まだ使いづらさあります。

1. 実行中にブレークポイントの付け替えができない
2. リモートデバッグを停止したのち再度実行するとデバッガがハングアップする
3. DockerのprivilegedをTrueにする必要があってよろしくない

1.に関しては再起動やホットデプロイを仕込めばいけそうですが、
2.はVSCode上で停止しても新しくリモートデバッグが開始できず、結局コンテナの再起動が必要になっています。（コンテナ内にプロセスが残りっぱなしになるのがだめっぽい。killもできない。）
3. はDelveで必要な権限がわからず、全権限渡してますが、セキュリティ的によくないですね。開発環境だけにするとかの対処が必要そうです。

何かいい方法はないでしょうか・・・

#さいごに
VisualStudioCodeとGolangの相性はとてもよく、エディタでありながら、IDEのように使えるし、とても使いやすいです。
Dockerを使った開発手法を確立させて、効率を上げていきたいですね。