CloudRun + Litestream で Rails アプリを激安で公開する

# はじめに

少し前に、この記事を読んで激しく共感しました。

https://laiso.hatenablog.com/entry/nope-sql

自分も様々な目的で簡単なWebアプリケーションを開発することがあるのですが、DBが欲しくなるタイミングでいつも技術選定に悩んでいました。
大抵の場合は記事にある「1. SQLあきらめる」や「3. DB自分で立てる」を選ぶのですが、不慣れな NoSQL に対する抵抗感やメンテナンスコストが辛くなってきます。

そんな折に以下の Tweet を見つけて SQLite + Litestream という解決策を知りました。

https://twitter.com/voluntas/status/1522073753538088960

Litestream は SQLite にレプリケーション機能を提供するオープンソースソフトウェアです。
レプリケーション先には Amazon S3 や Google Cloud Storage を利用でき、安価に利用できることが特徴です。

https://litestream.io/


本記事では、Rails + SQLite3 で作られたWebアプリケーションを Google Cloud Run で稼働させ、Google Cloud Strageにレプリケーションすることを目指します。


# 前提条件

本記事ではRailsのチュートリアルで作成したアプリケーションを対象に、Cloud Runへのデプロイと Litestream でのレプリケーションの実現方法を扱います。
https://railsguides.jp/getting_started.html

チュートリアルのアプリケーションの実装は完了している前提です。


また、本記事の内容は以下の環境で動作を確認しています。

```shell
$ ruby --version
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]

$ docker --version
Docker version 20.10.14, build a224086

$ sqlite3 --version
3.31.1 2020-01-27 19:55:54 3bfa9cc97da10598521b342961df8f5f68c7388fa117345eeb516eaa837balt1

$ gcloud --version
Google Cloud SDK 390.0.0
bq 2.0.75
core 2022.06.10
gsutil 5.10
Updates are available for some Google Cloud CLI components.  To install them,
please run:
  $ gcloud components update
```


# Dockerfileを作成する

まずはDockerfileを作成します。
Railsアプリを動かす一般的なDockerfileとの違いは

- Litestream のインストール
- Litestream の設定ファイル（litestream.yml）をコピーする

の2点です。

```dockerfile:Dockerfile
FROM ruby:3.0.2
EXPOSE 8080

RUN apt-get update -qq \
  && apt-get install -y sqlite3 nodejs npm \
  && rm -rf /var/lib/apt/lists/* \
  && npm install --global yarn

# Download the static build of Litestream directly into the path & make it executable.
# This is done in the builder and copied as the chmod doubles the size.
ADD https://github.com/benbjohnson/litestream/releases/download/v0.3.8/litestream-v0.3.8-linux-amd64-static.tar.gz /tmp/litestream.tar.gz
RUN tar -C /usr/local/bin -xzf /tmp/litestream.tar.gz

WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install

COPY . /app
COPY ./litestream.yml /etc/litestream.yml
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
```

Litestreamの設定内容は以下の通りです。
`development.sqlite3` というファイルを GCS の `blog-litestream` というバケットにレプリケーションしています。

```yaml:litestream.yml
dbs:
  - path: ./db/development.sqlite3
    replicas:
      - url: gcs://blog-litestream
```

最後に `entrypoint.sh` です。
最初にrestoreして、その後 `litestream replicate -exec "rails server -p 8080 -b 0.0.0.0"` にて litestream のレプリケーションを実行しながら Railsアプリを起動します。

```shell:entrypoint.sh
#!/bin/bash
set -e

if [ -f ./db/development.sqlite3 ]; then
  rm ./db/development*
fi

# Restore the database
litestream restore -v -if-replica-exists -o ./db/development.sqlite3 gcs://blog-litestream

# Run litestream with your app as the subprocess.
exec litestream replicate -exec "rails server -p 8080 -b 0.0.0.0"
```


# Cloud Runにデプロイする

Cloud Run のデプロイは以下のリンクを参考に、 Cloud Build を使用して行います。
https://cloud.google.com/build/docs/deploying-builds/deploy-cloud-run

この時の注意点ですが、以下の記事にある通り第二世代の実行環境で動作させる必要があります。
https://zenn.dev/walf443/articles/4059d4a3787cf3

そのため、「Deploy container image to Cloud Run」の部分だけコマンドを少し修正しています。

```yaml:cloudbuild.yaml
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/<PROJECT_ID>/blog', '.']
# Push the container image to Container Registry
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'gcr.io/<PROJECT_ID>/blog']
# Deploy container image to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  entrypoint: gcloud
  args: ['beta', 'run', 'deploy', 'blog', '--image', 'gcr.io/<PROJECT_ID>/blog', '--region', 'asia-east1', '--execution-environment', 'gen2']
images:
- gcr.io/<PROJECT_ID>/blog
```

cloudbuild.yamlが書けたら、あとは手順通りにデプロイするだけです。

```sh
$ gcloud builds submit --region=asia-east1
```

Cloud Runの権限設定などは必要に応じて適宜行う必要があります。


# まとめ

以上の手順で、Cloud Run上でRailsアプリを動作させることが出来ました。
コールドスタートしてもGCSからリストアして動くのでストレージを永続化しておく必要もなく、確かにSQLを諦めることなく個人開発のコストを削減することが出来そうです。