簡単な静的ページと簡単なAPIサーバーをGoogle Cloud Runで動かす

# なにこれ
社内勉強会で[dockerの概論的なものとマルチステージビルドについてのお話](https://speakerdeck.com/mochisuna/docker-lt-multi-stage-build-fc896676-b033-4cef-8c67-627d728a93f1)をしました。
単純にマルチステージビルドするサンプルだけだと、ちょっと内容としては寂しかったので、せっかくだから精神で[Cloud Run](https://cloud.google.com/run/?hl=ja)にイメージをデプロイして走らせてみます。

# 利用するプロジェクト
https://github.com/mochisuna/vue-go-sample

## 構成
```bash
$ tree -L 1
.
├── LICENSE
├── Makefile // docker-compose周りのビルド処理
├── README.md
├── app      // 雑な静的ページのプロジェクトディレクトリ
├── docker-compose.yml
└── server   // 雑なAPIサーバーのプロジェクトディレクトリ
```

## 静的ページ
```bash
.
├── Dockerfile
├── babel.config.js
├── nginx
│   └── default.conf // nginx定義ファイル（本番用）
├── package-lock.json
├── package.json
├── src              // ソースディレクトリ
├── vue.config.js
└── yarn.lock
```
- 現状Cloud Runのポートが `8080` で固定されているのでnginx側で8080ポートを使うように `default.conf` を再定義
- productionSourceMapはfalse
- `.env` を作成してAPIサーバーのOriginを指定
- サンプル自体は[基礎から学ぶ Vue.js](https://cr-vue.mio3io.com/)の応用。いい本だからみんな買おう。

Dockerfileは以下のような形

```dockerfile
# --- local ---
FROM node:8.15.0-alpine as build-vue
WORKDIR /app
COPY . .
RUN apk update && \
  npm install -g npm && \
  npm install -g yarn && \
  yarn install && \
  yarn run build

# --- production ---
FROM nginx:1.15.12-alpine as production-vue
COPY --from=build-vue /app/dist /usr/share/nginx/html
COPY --from=build-vue /app/nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
```
- ローカルではnodeのalpineで開発し、本番はnginxのalpineにbuildしたものを入れる
- 現状だと[nginxとCloud Runの相性がちょっと良くない](https://medium.com/google-cloud/google-cloud-run-or-how-to-run-your-static-website-in-5-minutes-and-much-more-dbe8f2804395)ので `default.conf` も上書き

## APIサーバー
```bash
.
├── Dockerfile
├── Makefile   // go用各種ビルド処理
├── go.mod
├── go.sum
├── list.json  // 最強のクソ雑要素
└── main.go
```
- ビルド周りの設定は全てMakefileで定義
- realizeでホットリロード
- 入力データを `list.json` に任せる雑設計

Dockerfileは以下のような形

```dockerfile
# --- local ---
FROM golang:1.12-alpine as build-go
WORKDIR /go/server
COPY . .
RUN apk add --no-cache git make  && \
  go get github.com/oxequa/realize && \
  make build

# --- production ---
FROM alpine as production-go
WORKDIR /app
COPY --from=build-go /go/server/bin/server .
COPY --from=build-go /go/server/list.json .
RUN addgroup go \
  && adduser -D -G go go \
  && chown -R go:go /app/server
EXPOSE 8080
CMD ["./server"]
```
- ローカルではgolangのalpineで開発、本番はただのalpineにバイナリファイルを上げる
- ビルド周りはMakefileで定義
- `list.json` で入力を定義しちゃっているのでこれも追加


## docker-compose
フロントエンドとバックエンドを同じプロジェクトで管理するという~~取り回しにくい~~構成になっているので、docker-compoes側をちょっとひねります。

```yml
version: '3.5'
services:
  vue-app:
    stdin_open: true
    tty: true
    build:
      context: ./app    # フロント側のディレクトリ（Dockerfileのあるとこ）を渡してあげる
      target: build-vue # ローカルステージをターゲットに（as で指定した名前）
    command: yarn run serve
    volumes:
      - ./app:/app      # ホットリロードかけたいからbuild contextと同じ所を指定
    ports:
      - 39000:8080      # ポートは適当。被んなければいいっしょ精神
    depends_on:
      - go-app

  go-app:
    build:
      context: ./server # バックエンド側のディレクトリ（Dockerfileのあるとこ）を渡してあげる
      target: build-go  # ローカルステージ。名前もっと考えればよかった
    command: realize start --run
    volumes:
      - ./server:/go/server # ホットリロードかけたいからvolumeを指定
    ports:
      - 38080:8080      # ポートは適当。8080ついてればいいっしょ精神
```

重要な点としては

1. `build:`でDockerfileと連携を取る
    * `context` にDockerfileのあるディレクトリを指定する
    * `target` に、Dockerfileで `From <image名> as <stage名>`のas以下の値を入れる 
2. volumesを指定してあげる
    * これがないと`docker-compose up`した後にホストマシンでコードを書き換えてもリロードされない
    * イメージに直にsshして値を書き換えるとホットリロードしてくれるけど・・・
3. 8080を叩くようにポートを指定
    * 現状Cloud Runのポートは8080
    * これのせいでnginxもdefault.confの書き換えが必要

---

# Cloud Runにデプロイ
先にサービスの一覧を確認します。

```bash
$ gcloud beta run services list
WARNING: No target platform specified. This will be a required flag in a future version of the gcloud command-line tool. Pass the `--platform` flag or set the [run/platform] property to satisfy this warning.
Available platforms:
- gke: Cloud Run on Google Kubernetes Engine. Use with the `--cluster` and `--cluster-location` flags or set the [run/cluster] and [run/cluster_location] properties to specify a cluster in a given zone.
- managed: Fully managed version of Cloud Run. Use with the `--region` flag or set the [run/region] property to specify a Cloud Run region.

Listed 0 items.
```

`Listed 0 items.` なので現状何もサービスが動いていないことが確認できます。

<img width="1364" alt="スクリーンショット 2019-08-07 11.26.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287363/857aca9a-73d7-8325-2457-0729ae4a96a6.png">

## デプロイ処理
例えば静的ページを以下のイメージ名でビルドした場合を考えます。

```bash
docker build -t gcr.io/sugoi-cloud-run-test/super-sugoi-sample-app app/
```

まず最初にGoogle Cloud Buildにイメージをアップロードします。

```bash
# 静的ページ側
$ gcloud builds submit --project sugoi-cloud-run-test --tag gcr.io/sugoi-cloud-run-test/super-sugoi-sample-app app/.

# API側
$ gcloud builds submit --project sugoi-cloud-run-test --tag gcr.io/sugoi-cloud-run-test/super-sugoi-sample-server server/.
```

<img width="766" alt="スクリーンショット 2019-08-07 12.51.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287363/46ac1b9b-c087-2e5a-fe2e-824b7b797931.png">
すごそう。

イメージが上がったら、デプロイを実行します。

```bash
$ gcloud beta run deploy super-sugoi-sample-app --image gcr.io/sugoi-cloud-run-test/super-sugoi-sample-app:latest --region asia-northeast1
```

実行するとWARNINGとかがモチャモチャ出た後、未認証の呼び出しを許可するかどうかを聞かれるので、`y`を入れておきます。

```bash
WARNING: No target platform specified. This will be a required flag in a future version of the gcloud command-line tool. Pass the `--platform` flag or set the [run/platform] property to satisfy this warning.
Available platforms:
- gke: Cloud Run on Google Kubernetes Engine. Use with the `--cluster` and `--cluster-location` flags or set the [run/cluster] and [run/cluster_location] properties to specify a cluster in a given zone.
- managed: Fully managed version of Cloud Run. Use with the `--region` flag or set the [run/region] property to specify a Cloud Run region.

Allow unauthenticated invocations to [super-sugoi-sample-app]
(y/N)?  y

Deploying container to Cloud Run service [super-sugoi-sample-app] in project [sugoi-cloud-run-test] region [asia-northeast1]
✓ Deploying new service... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [super-sugoi-sample-app] revision [super-sugoi-sample-app-***] has been deployed and is serving traffic at https://super-sugoi-sample-app-***.a.run.app
```


ここまで行った段階で、もう一度listコマンドを試します。

```bash
$ gcloud beta run services list --region asia-northeast1

   SERVICE                    REGION           LATEST REVISION                SERVING REVISION               LAST DEPLOYED BY  LAST DEPLOYED AT
✔  super-sugoi-sample-app     asia-northeast1  super-sugoi-sample-app-***     super-sugoi-sample-app-***     ***               2019-08-07T03:54:55.770Z
✔  super-sugoi-sample-server  asia-northeast1  super-sugoi-sample-server-***  super-sugoi-sample-server-***  ***               2019-08-07T03:59:15.436Z
```

<img width="843" alt="スクリーンショット 2019-08-07 13.05.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287363/8494e020-13d8-fc59-f3d7-908492e91c38.png">

ちゃんと入っているのでデプロイOKです。

デプロイコマンドの詳細については、公式も確認してください
FYI: https://cloud.google.com/sdk/gcloud/reference/beta/run/deploy

## 動作確認
デプロイできたので動作確認をします。
デプロイコマンド実行時に`serving traffic at ~`出てきたURLにアクセスしてみます。

### API側
```bash

$ curl https://super-sugoi-sample-server-***.a.run.app/monsters | jq .

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   147  100   147    0     0   1732      0 --:--:-- --:--:-- --:--:--  1750
[
  {
    "id": 1,
    "name": "スライム",
    "max": 10,
    "hp": 10
  },
  {
    "id": 2,
    "name": "ゴブリン",
    "max": 50,
    "hp": 50
  },
  {
    "id": 3,
    "name": "ドラゴン",
    "max": 500,
    "hp": 500
  }
]
```

### フロント側
```
$ open https://super-sugoi-sample-app-***.a.run.app/
```

<img width="450" alt="スクリーンショット 2019-08-07 13.19.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287363/9664f60b-abd9-fa0e-25f5-de7c997af3c8.png">

初期データが入っているので、ちゃんとAPI側を呼んだ結果が取れているのがわかりますね。

# お片付け
遊んだものはちゃんと片付けましょう。

```bash
$ gcloud beta run services delete super-sugoi-sample-service --region asia-northeast1
$ gcloud beta run services delete super-sugoi-sample-app --region asia-northeast1
```

# まとめ
簡単な静的ページとAPIサーバーをCloud Runにデプロイして挙動を試しました。

beta版ということで、8080ポート縛りなどがあるので、最初nginxのデプロイで困りましたし（`docker run -p 8080:80 sugoi-sample-app:latest`は動くのになんでや！ってなりました）、こちらの設計で言えばフロントとサーバーを同じプロジェクトに突っ込んで一つのdocker-composeで管理するというのも初めての体験だったので、ホットリロード動かない問題の原因とかbuild contextをどう置くかなど、色々学びができました（CI/CD考えると取り回しが悪いのでこのパターンは二度とやらないでしょうね）。

それでも、料金体系と開発の楽さを考えるとかなり可能性を感じます。
これからもちょくちょく使っていこうと思います。