DockerでFirebase Local Emulator Suiteを起動する。

## はじめに
Firebase Local Emulator Suiteはローカル環境でFirestoreやCloud FunctionsといったFirebaseプロジェクトのコンポーネントをエミュレートできる環境です。
https://firebase.google.com/docs/emulator-suite?hl=ja

本記事ではDocker及びDocker Composeを利用して上記の環境を構築したいと思います。

## 開発環境

- macOS Catalina 10.15.6
- Docker Desktop stable 2.3.0.4

なお、本記事で使用したコードは[こちら](https://github.com/kyhei/firebase_emu_sample)です。

## 実際に作ってみる。

はじめにFirebaseのプロジェクトを作成しておきましょう。
https://console.firebase.google.com/
後述するEmulator Suite UI（GUI上でエミュレータを管理できるコンソール）を利用するために必要となります。
使用するプロジェクトのIDは.envファイルに控えておきます。

```sh
$ mkdir empty_dir && cd empty_dir
$ touch .env
```

```:.env
GCP_PROJECT_ID=YOUR_PROJECT_ID
```

それでは早速Dockerfileから作成していきます。
エミュレータの動作要件を満たすようイメージを作成します。
https://firebase.google.com/docs/emulator-suite/install_and_configure?hl=ja

```sh
$ mkdir docker
$ touch ./docker/firebase.dockerfile
```

```dockerfile:docker/firebase.dockerfile
FROM ubuntu:20.04

RUN apt-get update -y

RUN apt-get install -y curl openjdk-11-jre-headless

RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - \
    && apt-get install -y nodejs

RUN npm install -g firebase-tools
```

つづいてDocker Composeの構成ファイルを作成します。
ボリュームのマウントも行うため適宜ディレクトリも作成しておきます。

```sh
$ mkdir firebase
$ touch docker-compose.yaml
```

```yaml:docker-compose.yaml
version: "3"

networks:
    backend:
    
services:
    workspace:
        image: golang:1.15
        environment:
            FIRESTORE_EMULATOR_HOST: "firebase:8080"
            GCP_PROJECT_ID: "${GCP_PROJECT_ID}"
        volumes:
            - ./gopath/:/go:cached
            - ./workspace/:/opt/workspace:cached
        ports:
            - 8000:8000
        working_dir: /opt/workspace
        networks:
            - backend
        command: bash
        tty: true
    firebase:
        build:
            context: ./docker
            dockerfile: firebase.dockerfile
        volumes:
            - ./firebase/emulators/:/opt/workspace:cached
            - ./firebase/bin/:/root/.cache:cached
            - ./firebase/config/:/root/.config:cached
        ports:
            - 4000:4000 # Emulator Suite UI
            - 5000:5000 # Firebase Hosting
            - 5001:5001 # Clound Functions
            - 9000:9000 # Realtime Database
            - 8080:8080 # Cloud Firestore
            - 8085:8085 # Cloud Pub/Sub
        working_dir: /opt/workspace
        networks:
            - backend
        command: bash
        tty: true
```

今回はGo言語からFirestoreのAPIを叩いてみたいと思います。

```go:./workspace/go.mod
module github.com/kyhei/firebase_emu_sample

go 1.15

require (
	cloud.google.com/go/firestore v1.3.0
	golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
	golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect
	golang.org/x/tools v0.0.0-20200904185747-39188db58858 // indirect
	google.golang.org/api v0.31.0 // indirect
	google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
)
```

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

import (
	"context"
	"log"
	"os"

	"cloud.google.com/go/firestore"
)

func main() {
	ctx := context.Background()
	client, err := firestore.NewClient(ctx, os.Getenv("GCP_PROJECT_ID"))
	defer client.Close()
	if err != nil {
		log.Fatal(err.Error())
	}

	doc, _, err := client.Collection("users").Add(ctx, map[string]interface{}{
		"name": "kyhei",
		"age":  123,
	})
	if err != nil {
		log.Fatal(err.Error())
	}

	log.Println(doc.ID)

	q := client.Collection("users").Select("name", "age")
	docs, err := q.Documents(ctx).GetAll()
	if err != nil {
		log.Fatal(err.Error())
	}

	for _, doc := range docs {
		log.Println(doc.Data())
	}

}
```

ここまでの内容でDocker Composeを実行してみましょう。

```sh
$ docker-compose up -d
$ docker-compose ps
```

２つのコンテナが起動していればOKです。

## エミュレータの起動と設定

firebaseコンテナにログインし、Firebaseプロジェクトの初期化を行います。

```sh
$ docker-compose exec firebase bash
root@da35c7925947:/opt/workspace# firebase login --no-localhost
root@da35c7925947:/opt/workspace# firebase init
```

`firebase init`では、プロジェクトを既存のものを選択し、
利用サービスの選択肢では、Emulatorsを選びます。（他はなんでもOKです。）

`Emulators Setup`では全てのエミュレータにチェックを入れましょう！
その後それぞれに関する設定が聞かれますが全てデフォルトで大丈夫です。

初期化が完了したら、`firebase.json`を編集します。

```json:./firebase/emulators/firebase.json
{
  "emulators": {
    "functions": {
      "host": "0.0.0.0",
      "port": 5001
    },
    "firestore": {
      "host": "0.0.0.0",
      "port": 8080
    },
    "database": {
      "host": "0.0.0.0",
      "port": 9000
    },
    "hosting": {
      "host": "0.0.0.0",
      "port": 5000
    },
    "pubsub": {
      "host": "0.0.0.0",
      "port": 8085
    },
    "ui": {
      "enabled": true,
      "host": "0.0.0.0",
      "port": 4000
    }
  }
}
```

それでは次のコマンドで実際にエミュレータを起動しましょう。

```sh
root@da35c7925947:/opt/workspace# firebase emulators:start
```

初回は実行ファイルがダウンロードされます。
エミュレータが立ち上がったら[http://localhost:4000](http://localhost:4000)でコンソールにアクセスできればOKです！

![スクリーンショット 2020-09-09 16.11.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94505/db76aac3-1954-c20e-065f-d7ef229cc04e.png)

## Firestoreエミュレータの確認

前項で作成したスクリプトを実行して、Firestoreへの接続を確認してみましょう。

```sh
$ docker-compose exec workspace bash
root@c64fed9c2890:/opt/workspace# go run main.go
2020/09/09 07:17:08 6WQVScx2NGm4oli8f7iw
2020/09/09 07:17:08 map[age:123 name:kyhei]
```

コンソールを確認すると無事ドキュメントが追加されているのが確認できます。

![スクリーンショット 2020-09-09 18.58.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94505/c48b7d07-ceca-7c8d-a833-05e3af1f47cc.png)

お疲れ様でした！

Cloud FunctionsやHostingを起動するためにはそれぞれの初期化が必要となります。
エミュレータ起動時のログから初期化のコマンド等が指示されますので、そちらを実行していけばOKです。