KubernetesでCloud Spanner Emulatorを用いたローカル開発環境を構築する

この投稿はグレンジ [Advent Calendar 2021][advent-calendar]の21日目の記事です。

[Cloud Spanner Emulator][cloud-spanner-emulator]を利用して、Kubernetes上でローカル開発環境を構築します。

appコンテナからspannerコンテナへ接続するサンプルを作ってみます。
環境は[Helm][helm]を使って構築していきます。

## イメージ作成

Go、gcloud、[spanner-cli][spanner-cli]を利用できるようにします。

```dockerfile:build/app/Dockerfile
FROM golang:1.17.5-buster

# https://cloud.google.com/sdk/docs/quickstart-debian-ubuntu?hl=ja
RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | \
        tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
        apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \
    apt-get update -y && \
    apt-get install google-cloud-sdk -y

# https://github.com/cloudspannerecosystem/spanner-cli/releases
RUN go install github.com/cloudspannerecosystem/spanner-cli@v0.9.9
```

```
$ docker build -t app:1.0.0 build/app
```

## Helmで環境作成

### values.yaml

```yaml:deployments/helm/local/values.yaml
gcloud:
  config:
    name: local

app:
  image:
    repository: app
    tag: 1.0.0

spanner:
  image:
    repository: gcr.io/cloud-spanner-emulator/emulator
    tag: 1.4.0
  ports:
    grpc:
      port: 9010
      targetPort: 9010
      protocol: TCP
    rest:
      port: 9020
      targetPort: 9020
      protocol: TCP
  instance:
    id: sample-instance
  database:
    id: sample-database
```
### spanner

```yaml:deployments/helm/local/templates/spanner-service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: spanner-service
  labels:
    app: spanner
spec:
  type: NodePort
  ports:
  {{- range $key, $value := .Values.spanner.ports }}
  - name: {{ $key }}
    port: {{ $value.port }}
    targetPort: {{ $value.targetPort }}
    protocol: {{ $value.protocol }}
  {{- end }}
  selector:
    app: spanner
```

```yaml:deployments/helm/local/templates/spanner-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spanner
spec:
  selector:
    matchLabels:
      app: spanner
  replicas: 1
  template:
    metadata:
      labels:
        app: spanner
    spec:
      containers:
        - name: spanner
          image: "{{ .Values.spanner.image.repository }}:{{ .Values.spanner.image.tag }}"
          ports:
            - containerPort: {{ .Values.spanner.ports.grpc.port }}
            - containerPort: {{ .Values.spanner.ports.rest.port }}
```

### app

```yaml:deployments/helm/local/templates/app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  selector:
    matchLabels:
      app: app
  replicas: 1
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
        - name: app
          image: "{{ .Values.app.image.repository }}:{{ .Values.app.image.tag }}"
          workingDir: /app
          env:
            - name: SPANNER_EMULATOR_HOST
              value: "spanner-service:{{ .Values.spanner.ports.grpc.port }}"
            - name: GCLOUD_PROJECT
              value: {{ .Values.gcloud.config.name }}
            - name: SPANNER_PROJECT_ID
              value: {{ .Values.gcloud.config.name }}
            - name: SPANNER_INSTANCE_ID
              value: {{ .Values.spanner.instance.id }}
            - name: SPANNER_DATABASE_ID
              value: {{ .Values.spanner.database.id }}
          command:
            - sleep
            - infinity
          lifecycle:
            postStart:
              exec:
                command:
                  - "sh"
                  - "-c"
                  - |
                    gcloud config configurations create {{ .Values.gcloud.config.name }}
                    gcloud config set auth/disable_credentials true
                    gcloud config set project {{ .Values.gcloud.config.name }}
                    gcloud config set api_endpoint_overrides/spanner http://spanner-service:{{ .Values.spanner.ports.rest.port }}/
```

Spannerで利用する各種環境変数の設定とgcloudの設定コマンドを実行するようにしています。

### インストール

```
$ helm install local deployments/helm/local
```

## 初期設定

appコンテナに接続します。

```
$ kubectl exec -it deploy/app -- bash
```

インスタンスを作成します。

```
# gcloud spanner instances create ${SPANNER_INSTANCE_ID} --config spanner-emulator --description "${SPANNER_INSTANCE_ID}" --nodes=1
Creating instance...done.                                                                                                                                                                                                                                    
```

データベースを作成します。

```
# gcloud spanner databases create ${SPANNER_DATABASE_ID} --instance ${SPANNER_INSTANCE_ID}
Creating database...done.                                                                                                                                                                                                                                    
```

## 確認

spanner-cliで接続します。

```
# spanner-cli
Connected.
```

テーブルを作成します。

```
spanner> CREATE TABLE Singers (
      ->     SingerId   INT64 NOT NULL,
      ->     FirstName  STRING(1024),
      ->     LastName   STRING(1024),
      ->     SingerInfo BYTES(MAX)
      -> ) PRIMARY KEY (SingerId);
Query OK, 0 rows affected (0.00 sec)

spanner> CREATE TABLE Albums (
      ->     SingerId     INT64 NOT NULL,
      ->     AlbumId      INT64 NOT NULL,
      ->     AlbumTitle   STRING(MAX)
      -> ) PRIMARY KEY (SingerId, AlbumId),
      -> INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
Query OK, 0 rows affected (0.00 sec)
```

Goクライアントで接続します。

[spanner_snippets][spanner_snippets]を使って書き込み、読み込みをします。

```
# git clone https://github.com/GoogleCloudPlatform/golang-samples.git
# cd golang-samples/spanner/spanner_snippets/
```

テーブルに書き込みをします。

```
# go run snippet.go write projects/${SPANNER_PROJECT_ID}/instances/${SPANNER_INSTANCE_ID}/databases/${SPANNER_DATABASE_ID}
```

テーブルから読み込みをします。

```
# go run snippet.go query projects/${SPANNER_PROJECT_ID}/instances/${SPANNER_INSTANCE_ID}/databases/${SPANNER_DATABASE_ID}
1 2 Go, Go, Go
2 2 Forever Hold Your Peace
1 1 Total Junk
2 1 Green
2 3 Terrified
```

Kubernetes環境にて、Cloud Spanner Emulatorにspanner-cliとGoクライアントで接続できることが確認できました。

[//]:#(参考Start)
## 参考
- Cloud Spanner Emulator
  - [Cloud Spanner Emulator][cloud-spanner-emulator]
  - [Cloud Spanner エミュレータの使用][spanner/docs/emulator]
  - [Go で Cloud Spanner を使ってみる][spanner/docs/getting-started/go]
  - [spanner_snippets][spanner_snippets]
  - [spanner-cli][spanner-cli]
- Cloud SDK
  - [Debian と Ubuntu 用のクイックスタート][quickstart-debian-ubuntu]
[//]:#(参考End)

[//]:#(RefUrlStart)
[cloud-spanner-emulator]: https://github.com/GoogleCloudPlatform/cloud-spanner-emulator
[spanner/docs/emulator]: https://cloud.google.com/spanner/docs/emulator
[spanner/docs/getting-started/go]: https://cloud.google.com/spanner/docs/getting-started/go
[spanner_snippets]: https://github.com/GoogleCloudPlatform/golang-samples/tree/main/spanner/spanner_snippets
[spanner-cli]: https://github.com/cloudspannerecosystem/spanner-cli

[helm]: https://helm.sh/

[quickstart-debian-ubuntu]: https://cloud.google.com/sdk/docs/quickstart-debian-ubuntu

[advent-calendar]: https://qiita.com/advent-calendar/2021/grenge
[//]:#(RefUrlEnd)