SupabaseをKubernetesでセルフホスティングする方法について

本記事は、[Supabase Advent Calendar 2023](https://adventar.org/calendars/9077) 18日目の記事です。

## 概要

FirebaseとSupabaseに同じ名前が付けられている**Hosting**があります。

https://firebase.google.com/docs/hosting?hl=ja

https://supabase.com/docs/guides/self-hosting

これらは、全く別物でFirebaseはインフラ準備無しで静的コンテンツをホストしてくれるサービスで、Supabaseは公式のBaaSを使用せずに自前で用意したインフラに簡単にSupabaseを構築ができてサービス運用できるサービスになります。

今回は、**Supabaseが提供しているKubernetesのSelf-Hostingでインフラを自前で準備する詳細やどのくらいコストがかかるのかなどをまとめ**たいと思います。


## [Kubernetes](https://kubernetes.io/ja/)

k8sは、Dockerなどでコンテナ化してまとめたアプリケーションをサービス提供する際に活用する管理ツール。
初めて知る方は、以下のリンクなどを参考にk8sで何ができるのかインプットしておきましょう。

https://learn.microsoft.com/ja-jp/training/modules/intro-to-kubernetes/

https://knowledge.sakura.ad.jp/3681/

### Kubernetes on Mac

https://jp.ubuntu.com/blog/kubernetes-on-mac-how-to-set-up-jp


### Kubernetes on Ubuntu

https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/


### Kubernetes on Windows

https://learn.microsoft.com/ja-jp/virtualization/windowscontainers/kubernetes/getting-started-kubernetes-windows


### ディストリビューションツール

標準のk8s機能に加えて追加機能やさまざまなサポートを提供するカスタマイズされたツール。
Minikube・MicroK8s・K3sなどがあり、今回はMinikubeを使用します。


#### [Minikube](https://minikube.sigs.k8s.io/docs/start/)

ローカル上に単一ノードのk8sクラスターを作成し、開発やテストなどの目的に使用するツール。

https://knowledge.sakura.ad.jp/15320/

https://github.com/kubernetes/minikube?tab=readme-ov-file


#### [MicroK8s](https://github.com/canonical/microk8s?tab=readme-ov-file)

Ubuntuによって提供される、軽量で簡単にセットアップ可能なk8sディストリビューション。
デスクトップ、サーバー、IoTデバイスなど、幅広い環境で使用できる。

https://sysdig.jp/learn-cloud-native/kubernetes-101/what-is-microk8s/


#### [K3s](https://github.com/k3s-io/k3s/)

軽量でシンプルなk8sディストリビューション。リソースが限られた環境やエッジコンピューティング用に設計されています。

https://www.linode.com/ja/blog/kubernetes/lightweight-kubernetes-evaluating-k8s-vs-k3s/


### その他のツール

ディストリビューションの他にも開発をサポートしてくれるk8sツールがあり、今回はHelmを使用します。

#### [Helm](https://helm.sh/ja/docs/)

k8sアプリケーションの定義、インストール、アップデートを簡単に作業できるパッケージマネージャーツール。
Helmを使うことでk8sにデプロイされるアプリケーションの管理がより効率的で、再現性が高くなります。

https://circleci.com/ja/blog/what-is-helm/


#### [Skaffold](https://github.com/GoogleContainerTools/skaffold)

Googleによって開発されたk8sの継続的開発ツール。
ローカル開発とCI/CDパイプラインの両方で、ソースコードの変更をk8sクラスタに自動的にデプロイするために使われます。

https://knowledge.sakura.ad.jp/15321/


#### [Kustomize](https://github.com/kubernetes-sigs/kustomize/)

k8sリソースの宣言的な管理を支援し、Helmとは異なるアプローチで
Kustomizeはk8sの一部として直接統合されており、kubectlとともに使用されることが多いです。

YAMLファイルをカスタマイズし、宣言的にk8sリソースを管理するためのk8sの構成管理ツール。

https://cloud.google.com/anthos-config-management/docs/concepts/kustomize?hl=ja


## Self-Hosting with Kubernetes

https://github.com/supabase-community/supabase-kubernetes

Supabaseのk8sは、GitHubで展開されており概要や使用方法などは、[supabase-community/supabase-kubernetes/charts/supabase/README.md](https://github.com/supabase-community/supabase-kubernetes/blob/main/charts/supabase/README.md)にまとまっています。

[supabase-kubernetes](https://github.com/supabase-community/supabase-kubernetes/)は、Helmが活用されており、使用方法をMinikubeで解説されています。


## [Self-Hosting with Docker](https://supabase.com/docs/guides/self-hosting/docker)

k8sはコンテナを管理するツールなので、もちろんDockerのSelf-Hostingもサポートされています。
本番・検証は、公式のSupabaseまたはk8sで自前で用意した環境を利用して、開発はDockerでローカルにSupabase環境を構築して各開発を並行して進めることもできます。


## k8sを構築してSupabaseを起動してみる

### 環境

- MacBook Pro 14インチ
- macOS 14.1.2
- Apple M2 Max
- メモリ 96 GB

### セットアップ

Minikubeは、Dockerコンテナとして起動するため、以下のようにk8sの設定が必要です。

![スクリーンショット 2023-12-22 1.45.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/a8de191b-8f10-ad47-0d34-06c7ab1d3fa6.png)

次にMinikubeをインストールして起動します。

```bash
$ brew install minikube
Running `brew update --auto-update`...

...省略...

==> minikube
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions


$ minikube version 
minikube version: v1.32.0
commit: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


$ minikube start
😄  Darwin 14.1.2 (arm64) 上の minikube v1.32.0
✨  docker ドライバーが自動的に選択されました
📌  root 権限を持つ Docker Desktop ドライバーを使用
👍  minikube クラスター中のコントロールプレーンの minikube ノードを起動しています
🚜  ベースイメージを取得しています...
💾  ロード済み Kubernetes v1.28.3 をダウンロードしています...
    > preloaded-images-k8s-v18-v1...:  341.16 MiB / 341.16 MiB  100.00% 30.21 M
    > gcr.io/k8s-minikube/kicbase...:  410.58 MiB / 410.58 MiB  100.00% 22.59 M
🔥  Creating docker container (CPUs=2, Memory=11908MB) ...
🐳  Docker 24.0.7 で Kubernetes v1.28.3 を準備しています...
    ▪ 証明書と鍵を作成しています...
    ▪ コントロールプレーンを起動しています...
    ▪ RBAC のルールを設定中です...
🔗  bridge CNI (コンテナーネットワークインターフェース) を設定中です...
🔎  Kubernetes コンポーネントを検証しています...
    ▪ gcr.io/k8s-minikube/storage-provisioner:v5 イメージを使用しています
🌟  有効なアドオン: storage-provisioner, default-storageclass
🏄  終了しました！kubectl がデフォルトで「minikube」クラスターと「default」ネームスペースを使用するよう設定されました


$ minikube addons list
|-----------------------------|----------|--------------|--------------------------------|
|         ADDON NAME          | PROFILE  |    STATUS    |           MAINTAINER           |
|-----------------------------|----------|--------------|--------------------------------|
| ambassador                  | minikube | disabled     | 3rd party (Ambassador)         |
| auto-pause                  | minikube | disabled     | minikube                       |
| cloud-spanner               | minikube | disabled     | Google                         |
| csi-hostpath-driver         | minikube | disabled     | Kubernetes                     |
| dashboard                   | minikube | disabled     | Kubernetes                     |
| default-storageclass        | minikube | enabled ✅   | Kubernetes                     |
| efk                         | minikube | disabled     | 3rd party (Elastic)            |
| freshpod                    | minikube | disabled     | Google                         |
| gcp-auth                    | minikube | disabled     | Google                         |
| gvisor                      | minikube | disabled     | minikube                       |
| headlamp                    | minikube | disabled     | 3rd party (kinvolk.io)         |
| helm-tiller                 | minikube | disabled     | 3rd party (Helm)               |
| inaccel                     | minikube | disabled     | 3rd party (InAccel             |
|                             |          |              | [info@inaccel.com])            |
| ingress                     | minikube | disabled     | Kubernetes                     |
| ingress-dns                 | minikube | disabled     | minikube                       |
| inspektor-gadget            | minikube | disabled     | 3rd party                      |
|                             |          |              | (inspektor-gadget.io)          |
| istio                       | minikube | disabled     | 3rd party (Istio)              |
| istio-provisioner           | minikube | disabled     | 3rd party (Istio)              |
| kong                        | minikube | disabled     | 3rd party (Kong HQ)            |
| kubeflow                    | minikube | disabled     | 3rd party                      |
| kubevirt                    | minikube | disabled     | 3rd party (KubeVirt)           |
| logviewer                   | minikube | disabled     | 3rd party (unknown)            |
| metallb                     | minikube | disabled     | 3rd party (MetalLB)            |
| metrics-server              | minikube | disabled     | Kubernetes                     |
| nvidia-device-plugin        | minikube | disabled     | 3rd party (NVIDIA)             |
| nvidia-driver-installer     | minikube | disabled     | 3rd party (Nvidia)             |
| nvidia-gpu-device-plugin    | minikube | disabled     | 3rd party (Nvidia)             |
| olm                         | minikube | disabled     | 3rd party (Operator Framework) |
| pod-security-policy         | minikube | disabled     | 3rd party (unknown)            |
| portainer                   | minikube | disabled     | 3rd party (Portainer.io)       |
| registry                    | minikube | disabled     | minikube                       |
| registry-aliases            | minikube | disabled     | 3rd party (unknown)            |
| registry-creds              | minikube | disabled     | 3rd party (UPMC Enterprises)   |
| storage-provisioner         | minikube | enabled ✅   | minikube                       |
| storage-provisioner-gluster | minikube | disabled     | 3rd party (Gluster)            |
| storage-provisioner-rancher | minikube | disabled     | 3rd party (Rancher)            |
| volumesnapshots             | minikube | disabled     | Kubernetes                     |
|-----------------------------|----------|--------------|--------------------------------|


$ minikube dashboard
🔌  ダッシュボードを有効化しています...
    ▪ docker.io/kubernetesui/dashboard:v2.7.0 イメージを使用しています
    ▪ docker.io/kubernetesui/metrics-scraper:v1.0.8 イメージを使用しています
💡  いくつかのダッシュボード機能は metrics-server アドオンを必要とします。全機能を有効にするためには、次のコマンドを実行します:

	minikube addons enable metrics-server	


🤔  ダッシュボードの状態を検証しています...
🚀  プロキシーを起動しています...
🤔  プロキシーの状態を検証しています...
🎉  デフォルトブラウザーで http://127.0.0.1:57050/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ を開いています...
```

`minikube dashboard`を実行すると以下のようにk8sのダッシュボードがブラウザで表示されます。

![スクリーンショット 2023-12-22 2.36.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/8d0ea8d1-bd8a-55b1-32b8-7c96208a2b55.png)


### ローカルでSupabaseの起動確認

次にSupabaseの起動を行います。
READMEの解説に従って進めますが、Helmをまだインストールしていない場合は、brewで簡単にインストールできます。

```bash
$ git clone https://github.com/supabase-community/supabase-kubernetes
Cloning into 'supabase-kubernetes'...
remote: Enumerating objects: 561, done.
remote: Counting objects: 100% (333/333), done.
remote: Compressing objects: 100% (98/98), done.
remote: Total 561 (delta 274), reused 235 (delta 235), pack-reused 228
Receiving objects: 100% (561/561), 409.25 KiB | 4.31 MiB/s, done.
Resolving deltas: 100% (355/355), done.

$ cd supabase-kubernetes 
$ kubectl -n default create secret generic demo-supabase-jwt \
  --from-literal=anonKey='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInJvbGUiOiAiYW5vbiIsCiAgICAiaXNzIjogInN1cGFiYXNlIiwKICAgICJpYXQiOiAxNjc1NDAwNDAwLAogICAgImV4cCI6IDE4MzMxNjY4MDAKfQ.ztuiBzjaVoFHmoljUXWmnuDN6QU2WgJICeqwyzyZO88' \
  --from-literal=serviceKey='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInJvbGUiOiAic2VydmljZV9yb2xlIiwKICAgICJpc3MiOiAic3VwYWJhc2UiLAogICAgImlhdCI6IDE2NzU0MDA0MDAsCiAgICAiZXhwIjogMTgzMzE2NjgwMAp9.qNsmXzz4tG7eqJPh1Y58DbtIlJBauwpqx39UF-MwM8k' \
  --from-literal=secret='abcdefghijklmnopqrstuvwxyz123456'
secret/demo-supabase-jwt created

$ kubectl -n default create secret generic demo-supabase-smtp \
  --from-literal=username='your-mail@example.com' \
  --from-literal=password='example123456'
secret/demo-supabase-smtp created

$ kubectl -n default create secret generic demo-supabase-db \
  --from-literal=username='postgres' \
  --from-literal=password='example123456'
secret/demo-supabase-db created

$ helm -n default install demo -f values.example.yaml .
zsh: command not found: helm

$ brew install helm
==> Downloading https://formulae.brew.sh/api/formula.jws.json
#################################################################################################################### 100.0%
==> Downloading https://formulae.brew.sh/api/cask.jws.json
#################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/helm/manifests/3.13.3
#################################################################################################################### 100.0%
==> Fetching helm
==> Downloading https://ghcr.io/v2/homebrew/core/helm/blobs/sha256:1a6fd49305a1d9aa469785bb6d02cdbaf618a31db1752ccfce5015ce
#################################################################################################################### 100.0%
==> Pouring helm--3.13.3.arm64_sonoma.bottle.tar.gz
==> Caveats
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions
==> Summary
🍺  /opt/homebrew/Cellar/helm/3.13.3: 65 files, 53.7MB
==> Running `brew cleanup helm`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

$ cd supabase-kubernetes/charts/supabase 
$ helm -n default install demo -f values.example.yaml .
NAME: demo
LAST DEPLOYED: Fri Dec 22 02:21:54 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

$ kubectl -n default get pod 
NAME                                     READY   STATUS             RESTARTS   AGE
demo-supabase-auth-6d7686db6-gfhbm       0/1     ImagePullBackOff   0          2m20s
demo-supabase-db-54bbdcbf66-xc5q6        1/1     Running            0          2m20s
demo-supabase-kong-78696ccf55-p8mfr      1/1     Running            0          2m20s
demo-supabase-meta-6d74588dff-2rvt6      1/1     Running            0          2m20s
demo-supabase-realtime-9f45bdfc6-t7rcc   1/1     Running            0          2m20s
demo-supabase-rest-8454676765-6vgcs      1/1     Running            0          2m20s
demo-supabase-storage-74cfb8fc5c-l42lj   1/1     Running            0          2m20s
demo-supabase-studio-6b7db68f86-btlw5    1/1     Running            0          2m20s

$ minikube addons enable ingress
💡  ingress is an addon maintained by Kubernetes. For any concerns contact minikube on GitHub.
You can view the list of minikube maintainers at: https://github.com/kubernetes/minikube/blob/master/OWNERS
💡  アドオンを有効にした後、「minikube tunnel」を実行することで、ingress リソースが「127.0.0.1」で利用可能になります
    ▪ registry.k8s.io/ingress-nginx/controller:v1.9.4 イメージを使用しています
    ▪ registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0 イメージを使用しています
    ▪ registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0 イメージを使用しています
🔎  ingress アドオンを検証しています...
🌟  'ingress' アドオンが有効です

$ minikube tunnel
✅  トンネルが無事開始しました

📌  注意: トンネルにアクセスするにはこのプロセスが存続しなければならないため、このターミナルはクローズしないでください ...

❗  demo-supabase-kong service/ingress は次の公開用特権ポートを要求します:  [80 443]
🔑  sudo permission will be asked for it.
❗  demo-supabase-studio service/ingress は次の公開用特権ポートを要求します:  [80 443]
🔑  sudo permission will be asked for it.
🏃  demo-supabase-kong サービス用のトンネルを起動しています。
🏃  demo-supabase-studio サービス用のトンネルを起動しています。
Password:
```

上記のように全て正常に起動できると`http://api.localhost`と`http://studio.localhost`にそれぞれアクセスすると以下のように表示されます。

![スクリーンショット 2023-12-22 2.34.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/92fce2ef-5241-3afb-0ecb-e77732eb5985.png)

![スクリーンショット 2023-12-22 2.34.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/cd18c071-f292-074a-f751-f916bb813bfc.png)

`minikube tunnel`を停止したい場合は、`controlキー & cキー`を入力して、k8sも使用しなければ`minikube stop`をお忘れなく。


## まとめ

筆者は、初めてk8s・Minikube・Helmを触りましたが、とても簡単にSupabase環境を構築することができました。
まだ、ローカル構築でクラウドサーバーにk8sを載せてSupabaseを構築しておらず、その環境での開発・運用もやったことないですが、諸々のコストが気になるのでセルフホスティングを採用するプランはなかなかないかも？

ただ先日、[構築済みKubernetes環境を提供する『cloud tap』が大幅料金改定](https://prtimes.jp/main/html/rd/p/000000079.000010358.html)というニュースがあったので、インフラが安く済むなら[SupabaseのProプラン](https://supabase.com/pricing)が必要な開発であれば検討アリなのかも？？

筆者的には、公式のBaaSを利用する方が楽だからセルフホスティングはやりたくない所管で、個人的にk8sを入門したかったので良い題材になりましたw