Go(Echo) + Vue.js + nginx の環境をDocker Composeで立てる。

# はじめに
goa、ginと触ったので、Echoあたりも触ってみようと思ったのと、自分自身、サーバーサイドもフロントエンドも一括で担当することが多いこともあり、「どうせだ、docker環境でフロントエンドも含めて環境を作ってみっか。」というのが始まりです。

GoコンテナはAPIを配信、Vue.jsでAPIを叩いて、値を画面にレンダリング。
みたいな想定でやっています。また、nginx噛ませてルーティングさせてます。

駄文だったらすいません。
先に言っておくと、長文です。

なお、以下のようにしています。
## Go
- Go Modules
- ホットリロード

を利用してます。

## Vue.js
- vue-cli3
- axios

を利用してます。

# 環境
- Mac OS X 10.14.6（Mojave）
- Docker version 19.03.2
- docker-compose version 1.24.1

※ ベースの環境だけ記述しておきます。GoのバージョンなどはDockerfileに記述してますので。

# 手順
## 初めのファイル構造
こっからスタートします。
nginxは後から設定するので`myapp/etc/nginx`配下は一旦空にしています。

```shell-session
myapp
├── docker
│   ├── go
│   │   └── Dockerfile
│   └── vue
│       └── Dockerfile
├── docker-compose.yml
└── etc
    └── nginx
        └── .
```

## 各Dockerfileの中身
### Go
非常にシンプルです。
Goのバージョンは1.13。
Go Modulesを利用するため、`ENV GO111MODULE=on`を指定。
ホットリロード環境を作りたいので`fresh`を`go get`しています。

```docker:myapp/docker/go/Dockerfile
FROM golang:1.13.1-alpine

ENV GO111MODULE=on

RUN apk update && \
    apk add emacs curl git && \
    go get github.com/pilu/fresh
```

### Vue.js
nodeのバージョンは、~~現時点（2019年10月）で最新の~~`8.16`。
(何をみていたのかよくわからないですね。8.16は全然最新じゃないです。)
vue-cli3を利用するので`@vue/cli`を`npm install`しています。

```docker:mysqpp/docker/vue/Dockerfile
FROM node:8.16-alpine

RUN apk update && \
    npm install -g npm && \
    npm install -g @vue/cli
```

## docker-compose.ymlの中身（初期）
nginxの設定は後で足します。
まずは、GoとVue.jsのコンテナを作るところから始めていきたいので。

```yml:docker-compose.yml
 version: '3'
 services:
   vue:
     build:
       context: ./docker/vue
     container_name: myapp-vue
     ports:
       - 8080:8080
     volumes:
       - .:/app
     working_dir: /app
     tty: true
     # command: sh -c "cd assets && yarn install && yarn serve"
 
   go:
     build:
       context: ./docker/go
     container_name: myapp-go
     ports:
       - 8082:8082
     volumes:
       - .:/go/src
     working_dir: /go/src/app
     tty: true
     # command: fresh
```

ちなみに、`go`、`vue`共にcommandがコメントアウトしているのは、一旦コンテナで作業する必要があるので、コメントアウトしています。
`go`は、Go Modulesの初期設定をしてからホットリロードを設定しないとエラーになります（なりました）。
`vue`は、コンテナ内で`vue create`をしたいので、コメントアウトしています。

## コンテナ立ち上げ

まずはなにはともあれ、ビルドします。

```shell-session
$ docker-compose build
```
※ npm周りでWARNが出る可能性がありますが、ここは無視します。
　

`up -d`でバックグラウンド立ち上げをします(オプション`-d`無くてもいいですが、その場合は以降は別タブで作業してください)。

```shell-session
$ docker-compose up -d
```
　

コンテナが立ち上がっているか確認。

```shell-session
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS                               NAMES
9897f0c5c15c        nginx               "nginx -g 'daemon of…"   9 seconds ago       Up 7 seconds               0.0.0.0:10080->80/tcp               myapp-nginx
9e8c34dacba8        kaikairanban_go     "/bin/sh"                11 seconds ago      Up 9 seconds               0.0.0.0:8082->8082/tcp              myapp-go
7178d2a496dc        kaikairanban_vue    "docker-entrypoint.s…"   40 seconds ago      Up 39 seconds              0.0.0.0:8080->8080/tcp              myapp-vue
```
この時点でappディレクトリがmyappディレクトリ内にできているので、`ls -la`とかで確認してみてください。


## Goのコンテナを設定していく
appディレクトリに移動して、`main.go`ファイルを作ります。

```shell-session
$ cd app && emacs main.go
```
※ emacsの部分は、vimでもなんでもいいです。筆者はemacsを使っているのでこうなります。
　

`main.go`の中身は一旦こんな感じにしておきます。

```go:main.go
package main

import (
       "fmt"
)

func main () {
     fmt.Println("Hello World !!")
}
```
　
Goのコンテナに入ります。

```shell-session
$ docker exec -it myapp-go /bin/sh
/go/src/app #
　
# コンテナ内で ls -la で確認してみるとこんな感じになっているはず。
/go/src/app # ls -la
total 4
drwxr-xr-x    4 root     root           128 Oct  3 08:35 .
drwxr-xr-x    5 root     root           160 Oct  3 08:25 ..
-rw-r--r--    1 root     root            90 Oct  3 08:35 main.go
drwxr-xr-x    2 root     root            64 Oct  3 08:25 tmp
```
　
コンテナ内でGo Modulesの初期設定をします。

```shell-session
/go/src/app # go mod init
go: creating new go.mod: module app

/go/src/app # ls -la
total 8
drwxr-xr-x    5 root     root           160 Oct  3 08:41 .
drwxr-xr-x    5 root     root           160 Oct  3 08:25 ..
-rw-r--r--    1 root     root            20 Oct  3 08:41 go.mod
-rw-r--r--    1 root     root            90 Oct  3 08:35 main.go
drwxr-xr-x    2 root     root            64 Oct  3 08:25 tmp
```

go.modができましたね。
　
`Ctr + d`とかでコンテナの外に出て、`go.mog`ファイルの中身を確認すると以下のうようになっています。

```:go.mod
module app

go 1.13
```
　
では、Echoを導入していきます。
コンテナの外からで良いので、以下のように`main.go`を編集します。
なんてことはない、[Echo](https://echo.labstack.com/guide)のクイックスタート（ほぼ）そのままです。

```go:main.go
package main

import (
       "net/http"
       "github.com/labstack/echo"
)

func main () {
     e := echo.New()
     e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World!")
     })
     e.Logger.Fatal(e.Start(":8082"))
     // e.Startの中はdocker-composeのgoコンテナで設定したportsを指定してください。
}
```
　
main.goが編集できたら、`docker-compose.yml`のgoコンテナの記述部分のcommandのコメントアウトを外します。

```yml:docler-compose.yml
# docker-compose.ymlの一部抜粋
go:
  build:
    context: ./docker/go
  container_name: myapp-go
  ports:
    - 8082:8082
  volumes:
    - .:/go/src
  working_dir: /go/src/app
  tty: true
  command: fresh # ここのコメントアウトを外す。
```

保存したら、`docker-compose up`で更新します(一旦、一つ前のコンテナ内作業後、`docker-compose down`でコンテナを停止しておいても良いです)。

以下のような状態になったら、Echo導入完了＆ホットリロードもOnになりました。

```shell-session
$ docker-compose up
myapp-vue is up-to-date
Recreating myapp-go ... done
Recreating myapp-nginx ... done
Attaching to myapp-vue, myapp-go, myapp-nginx
myapp-go | 8:53:07 runner      | InitFolders
myapp-go | 8:53:07 runner      | mkdir ./tmp
myapp-go | 8:53:07 runner      | mkdir ./tmp: file exists
myapp-go | 8:53:07 watcher     | Watching .
myapp-go | 8:53:07 main        | Waiting (loop 1)...
myapp-go | 8:53:07 main        | receiving first event /
myapp-go | 8:53:07 main        | sleeping for 600 milliseconds
myapp-go | 8:53:08 main        | flushing events
myapp-go | 8:53:08 main        | Started! (5 Goroutines)
myapp-go | 8:53:08 main        | remove tmp/runner-build-errors.log: no such file or directory
myapp-go | 8:53:08 build       | Building...
myapp-go | 8:53:14 runner      | Running...
myapp-go | 8:53:14 main        | --------------------
myapp-go | 8:53:14 main        | Waiting (loop 2)...
myapp-go | 8:53:14 app         |
myapp-go |    ____    __
myapp-go |   / __/___/ /  ___
myapp-go |  / _// __/ _ \/ _ \
myapp-go | /___/\__/_//_/\___/ v3.3.10-dev
myapp-go | High performance, minimalist Go web framework
myapp-go | https://echo.labstack.com
myapp-go | ____________________________________O/_______
myapp-go |                                     O\
myapp-go | 8:53:14 app         | ⇨ http server started on [::]:8082
```

上記の状態で、[localhost:8082](http://localhost:8082)にアクセスすると、以下のような画面が表示されます。
<img width="483" alt="スクリーンショット 2019-10-03 12.21.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/e9233027-3178-c777-4e7e-7a4f09583e8c.png">

見事に世界に挨拶ができましたね。


ちなみに、`go.mod`はこんな感じになっています。

```:go.mod
module app

go 1.13

require (
        github.com/labstack/echo v3.3.10+incompatible
        github.com/labstack/gommon v0.3.0 // indirect
        golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc // indirect
)
```

## Vueのコンテナを設定していく
コンテナが立ち上がっている状態で、vueのコンテナに入ります。

```shell-session
$ docker exec -it myapp-vue /bin/sh
/app #
```

vue-cliでvue環境を設定していきます。

```shell-session
/app # vue create assets
```

上記コマンドを打つと、質問形式で色々聞かれます。

```
?  Your connection to the default yarn registry seems to be slow.
   Use https://registry.npm.taobao.org for faster installation? (Y/n) Y ←Yesにします。
```
と聞かれた後、Vueの設定に入っていきます。
お好みで設定すれば良いと思いますが、今回は以下のようにします。

![スクリーンショット 2019-10-03 12.41.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/a1853b57-9506-53d4-432d-cc43308579cb.png)
![スクリーンショット 2019-10-03 12.45.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/726a64a4-688f-5cb0-6349-c8fd511a0963.png)

Enterを無心で押していくと、以下のような感じで設定が始まります。
![スクリーンショット 2019-10-03 12.45.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/596acc15-d34c-e5bd-5ce0-7b3f32791c4a.png)

サクセスすると、以下のような表示になるかと思います。
![スクリーンショット 2019-10-03 18.10.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/13d6e4d4-5d5f-dba5-5716-bceb6f49ff27.png)
　
ここまできたら、`Ctr + d`でコンテナを抜けます。
`docker-compose down`で一旦コンテナを落としておきます。
そして、`docker-compose.yml`のvueコンテナの部分でコメントアウトにしていた部分のコメントアウトを外します。

``` yml
vue:
  build:
    context: ./docker/vue
  container_name: myapp-vue
  ports:
    - 8080:8080
  volumes:
    - .:/app
  working_dir: /app
  tty: true
  command: sh -c "cd assets && yarn install && yarn serve" # ここのコメントアウトを外す。
```
　
バックグラウンドでコンテナを立ち上げた後、vueコンテナのlogだけを表示させてみます。

```shell-session
$ docker-compose up -d
$ docker-compose logs -f vue
```
　
以下のような感じでvueコンテナが立ち上がればOKです。
![スクリーンショット 2019-10-03 18.17.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/5fbd3116-8810-db7f-50cc-6b27c8fb0a93.png)
　
上記画面が出た状態で、[localhost:8080](http://localhost:8080)にアクセスすると、以下のような画面が表示されます。
<img width="1000" alt="スクリーンショット 2019-10-03 18.20.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/72fc51d5-1df2-d18d-459b-533d06ab6ee5.png">
Vue.js Appの世界に無事、迎え入れられてもらうことができました。

## nginxのコンテナを作る
一旦、`docker-compose down`で、コンテナを落としておきます。
そして、`docker-compose.yml`に追記＆`nginx.conf`を作ります。

```yml:docker-compose.yml
version: '3'
services:
  vue:
    build:
      context: ./docker/vue
    container_name: myapp-vue
    ports:
      - 8080:8080
    volumes:
      - .:/app
    working_dir: /app
    tty: true
    command: sh -c "cd assets && yarn install && yarn serve"

  go:
    build:
      context: ./docker/go
    container_name: myapp-go
    ports:
      - 8082:8082
    volumes:
      - .:/go/src
    working_dir: /go/src/app
    tty: true
    command: fresh
  # こっから下を追加
  nginx:
    image: nginx
    depends_on:
      - go
    container_name: myapp-nginx
      ports:
      - 80:80
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - ./etc/nginx/nginx.conf:/etc/nginx/nginx.conf
```

```conf:nginx.conf
worker_processes auto;

events {
  worker_connections 1024;
}

http {
  server {
    listen 80;

    location /api/ {
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_set_header Host $host;
      proxy_pass http://go:8082/;
    }

    location / {
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_set_header Host $host;
      proxy_pass http://vue:8080/;
    }
  }
}
```
　
準備ができたら、`docker-compose up -d`でコンテナを立ち上げます。
`docker-compose logs -f`などで、go、Vueどちらのコンテナも立ち上がって、準備ができたのを確認できたら、http://localhost でアクセスしてみましょう。
<img width="1000" alt="スクリーンショット 2019-10-03 18.20.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/72fc51d5-1df2-d18d-459b-533d06ab6ee5.png">
この画面になったらOKです。

## Vue側からGo側へリクエストを送り、値を得る。
`axios`を使って、GoのAPIを叩き値を取ってくることを想定して、もう少しファイルを編集していきます。

### axiosを使えるようにする。
axiosを使えるようにするため、`package.json`(assetsディレクトリ内にあるはずです)を編集します。
`dependencies`の部分にaxiosを追加します(バージョンは、2019年10月最新のものにしてます)。
併せて、`console.log()`が入っているときにWaringが出ないようにするため、`esLintConfg`の`rules`に、
`"no-console": "off"`を追加します。

```json:package.json
{
  ...
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10",
    "axios": "^0.19.0"
  },
  ...
  ...
  "eslintConfig": {
    ...
    "rules": {
      "no-console": "off"
    },
    ...
  },
  ...
}
```
　
グローバルにaxiosを使えるように、main.jsに登録をします。

```js:main.js
import Vue from 'vue'
import App from './App.vue'

// ここから
import axios from 'axios'
Vue.prototype.$axios = axios
// ここまでを追加

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
```

ファイルが編集できたら、`docker-compose restart vue`で、設定を更新します。

### axiosで、GoのAPIを叩く。
今回は超簡易的に、

1. ブラウザで http://localhost にアクセス。
2. APIが叩かれる(GET)。
3. Go側からレスポンスを返す。
4. `console.log()`を使って、ブラウザのコンソール画面でレスポンスの中身を確認する。


ということを行います。

では、`App.vue`を編集していきます。
Vueのcreatedフックのなかで、axiosを使うように記述します。
（変更箇所のあるscriptタグのブロックだけ以下に表示させてます。）

```vue:App.vue
// <template> のブロックがある

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'app',
  components: {
    HelloWorld
  },
  // createdの中でaxiosを使います。get()の中のURLは、nginx.confで設定してるので、 /api/ になっています。
  created () {
    this.$axios.get('http://localhost/api/')
      .then(response => {
        console.log(response)
      })
  }
}
</script>

// <style> のブロックがある
```

できたら、ブラウザの開発者ツールでコンソールタブを開いて、 http://localhost にアクセスしてみましょう。
（もしくは、リロードしてみましょう。）

<img width="864" alt="スクリーンショット 2019-10-04 12.51.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232071/13288b49-fedd-a2c6-a778-b2bdabdd645b.png">

画像のように、レスポンスが返ってきているのが確認できたらOKです。
あとは、返ってきた値を上手く調理するなどして、画面にレンダリングするとかしてみてみるといいんじゃないでしょうか。
（その辺は、余裕あったら続きを書こうと思います。記録としても残しておく意味で。）

以上。