概要
MetabaseのコンテナをKubernetesを使って複数台構成にして冗長化できたので、その方法を共有する。
冒頭で言ってしまうと身も蓋もないのだが、同じようなことを下記サイトにあるHelmを使ってもできそうなので、Helmに詳しい方はこちらを参考にしたほうが簡単にできるかも。
https://github.com/helm/charts/tree/master/stable/metabase
Metabaseコンテナの構造
今回、以下のMetabaseコンテナを利用する。
https://www.metabase.com/docs/latest/operations-guide/running-metabase-on-docker.html
Metabaseの中身をざっくりと見たのが下図。コンテナの中にアプリケーション本体と、設定情報(ユーザアカウントやコレクション等)を格納したアプリケーションDBが存在する。アプリケーションDBはJavaプラットフォーム上で動くH2データベース上で起動している。

このままの状態でMetabaseコンテナを複数立ち上げてしまうと、各コンテナの中に別個にアプリケーションDBが出来てしまうので、アプリの設定が適切に反映されなくなってしまう。そこでちょっと工夫が必要。
手順の概要
上記の問題を解決するために、まずはコンテナ内に入っているアプリケーションDBをコンテナ外に予め作成したPostgreSQLのDBに変換する。以下がそのイメージ。
Metabaseのコンテナ起動時にオプションを使うとPostgreSQL形式に変換してくれるので、今回はそれを利用してKubernetesのJobを作成する。

その後、サービス用のMetabaseコンテナを立ち上げる。デフォルトだとH2データベースのアプリDBが作られてしまうので、環境変数でポスグレのアプリDB情報を指定する必要あり。下図が冗長化構成にしたイメージ。

それでは具体的な方法を記載する。
データベースサーバーの用意
アプリケーションデータを格納するためのDBサーバ(今回はPostgreSQL)を用意する。
- DBサーバーは各環境に合わせて用意してもらえればいいが、本例ではKubernetesで作成(永続化はしていないので注意)
- コンテナイメージは下記Docker Hubのpostgresを利用
マニフェストは以下
# PostgreSQLのServiceリソースに関する定義
# このリソースがあることで、同じKubernetesクラスター内の他のPodから
# 「metadata.name」で指定したホスト名(=postgres)でアクセス可能となる
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
type: ClusterIP
selector:
app: postgres
ports:
- protocol: TCP
port: 5432
targetPort: 5432
---
# パラメータの定義。後でMetabaseのマニフェストを書く際にも利用する。
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-vars
data:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: pgpasswd
---
# postgresサーバーのデプロイ方法に関する定義
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: postgres
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
# コンテナ本体の定義
containers:
- image: postgres
name: postgres
env:
envFrom:
- configMapRef:
name: postgres-vars
- リソース作成
$ kubectl apply -f postgres.yml
- 各リソースが出来ていることを確認
$ kubectl get pod -l app=postgres
NAME READY STATUS RESTARTS AGE
postgres-ff5bfdccf-2wgvl 1/1 Running 0 14m
$ kubectl get deployments -l app=postgres
NAME READY UP-TO-DATE AVAILABLE AGE
postgres 1/1 1 1 14m
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 76d
postgres ClusterIP 10.107.54.211 <none> 5432/TCP 16m
H2データベースをPostgreSQLに変換
手順の概要で述べたとおり、Metabaseの設定情報(ユーザアカウントやコレクション等)はデフォルトではH2データベースに格納されていため、先ほど作成したPostgreSQLのDBに移行する。
以下サイトにMetabaseのDockerコンテナを使ってH2データベースをPostgreSQLに変換する方法が記載されており、これを参考に「H2データベース→PostgreSQL変換」のJobを実行するマニフェストを書く。
apiVersion: v1
kind: ConfigMap
metadata:
name: metabase-load-vars
# Metabaseの設定ファイルをPostgreSQLに変換するためのパラメータを定義
data:
MB_DB_FILE: /metabase.db
MB_DB_TYPE: postgres
MB_DB_PORT: "5432"
MB_DB_HOST: postgres
---
apiVersion: batch/v1
kind: Job
metadata:
name: load-from-h2
spec:
completions: 1
template:
spec:
restartPolicy: Never
containers:
- image: metabase/metabase # ★自作でイメージを作った場合はここを変更
name: load-from-h2
args: ["load-from-h2"]
envFrom:
- configMapRef:
name: metabase-load-vars
# DB名、DBユーザー名、パスワードはpostgresの
# コンテナと同じパラメータを利用
env:
- name: MB_DB_DBNAME
valueFrom:
configMapKeyRef:
name: postgres-vars
key: POSTGRES_DB
- name: MB_DB_USER
valueFrom:
configMapKeyRef:
name: postgres-vars
key: POSTGRES_USER
- name: MB_DB_PASS
valueFrom:
configMapKeyRef:
name: postgres-vars
key: POSTGRES_PASSWORD
# 以下はMetabaseのH2データベース形式の設定ファイル
# (metabase.db.mv.db,metabase.db.trace.db)を
# PersistentVolumeを経由してマウントする例
# Metabaseを一から新しく作る場合や、コンテナ内にカスタマイズ済みの
# 設定ファイルが既に格納されている場合は、以後の記載は不要
volumeMounts:
- name: settings-vol
mountPath: /metabase.db
volumes:
# ここには記載しないが、persistentVolumeと
# persistentVolumeClaimを別途作る必要あり
- name: settings-vol
persistentVolumeClaim:
claimName: metabase-pvc
このマニフェストでは、Metabaseの設定ファイルをPersistentVolumeを使って反映している。この手順が面倒な場合は、予め設定ファイルをコンテナ内にコピーしたイメージをDockerfile等で作っておき、自作イメージをコンテナ化するマニフェストを書いても差し支えない。(上記マニフェストの# ★自作でイメージを
とコメントした部分のコンテナ名を書き換える)
- 以下コマンドで、変換Jobを実行
$ kubectl apply -f load-from-h2.yml
- Jobが完了していることの確認(筆者の環境では1分ほどかかった)
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
load-from-h2 1/1 75s 76s
Metabaseのマニフェストを書いてデプロイ
以下がMetabaseのサービス化用マニフェスト
apiVersion: v1
kind: Service
metadata:
name: metabase
spec:
type: ClusterIP
selector:
app: metabase
ports:
- protocol: TCP
port: 3000 # 公開するポート
targetPort: 3000
# 環境に応じて適宜設定。
# サービス公開するポート番号が任意でよければ、
# NodePortを使ってexternalIPsは書かなくても問題ない
externalIPs:
- <ホストOSのIP>
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: metabase
name: metabase
spec:
replicas: 2 # デプロイするPodの数を記載
selector:
matchLabels:
app: metabase
template:
metadata:
labels:
app: metabase
spec:
containers:
- image: metabase/metabase
name: metabase
ports:
- containerPort: 3000
protocol: TCP
# ポスグレに格納したアプリデータにアクセスするための設定
# load-from-h2のJobで設定した環境変数と全く同じ
envFrom:
- configMapRef:
name: metabase-load-vars
env:
- name: MB_DB_DBNAME
valueFrom:
configMapKeyRef:
name: postgres-vars
key: POSTGRES_DB
- name: MB_DB_USER
valueFrom:
configMapKeyRef:
name: postgres-vars
key: POSTGRES_USER
- name: MB_DB_PASS
valueFrom:
configMapKeyRef:
name: postgres-vars
key: POSTGRES_PASSWORD
- Metabaseのリソース作成と稼働状況の確認
$ kubectl apply -f deploy.yml
service/metabase created
deployment.apps/metabase created
$ kubectl get all -l app=metabase
NAME READY STATUS RESTARTS AGE
pod/metabase-6c8b6d84bb-gb47j 1/1 Running 0 106s
pod/metabase-6c8b6d84bb-8m8rf 1/1 Running 0 106s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/metabase 2/2 2 2 106s
NAME DESIRED CURRENT READY AGE
replicaset.apps/metabase-6c8b6d84bb 2 2 2 106s
- http://hostname:3000 でアクセスできることを確認
おまけ
最初はアプリDBをポスグレに移行せずに、下図のようにH2データベースが格納されたボリュームを別途作って、各コンテナのH2データベースがここを共通にアクセスすることを試みていたのだが、上手く行かなかった。

具体的には、一方のコンテナから以下のようなエラーが出力。どうやらデータには一つのH2データベースからしかアクセスできない模様。
2020-11-07 07:45:18,797 ERROR driver.util :: Database connection error
org.h2.jdbc.JdbcSQLException: Database may be already in use: null. Possible solutions: close all other connection(s); use the server mode [90020-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357) ~[metabase.jar:?]
複数のH2データベースからアクセス可能する方法を調べたら、下記サイトで「H2データベースのパス指定でAUTO_SERVER=trueを追加すればOK」という情報を見つける。だがMetabaseのコンテナ起動時のオプションではそのような指定は不可能。(自分で新たにDockerfileから作ればいけるかもしれないが…)
そもそも一つのデータを複数のDBサーバから共有して使おうとすること自体間違っていたのかも。