はじめに
どうも、共同編集できるWikiが大好きなinajobです。
今回はHackMDのオープンソース版のHedgedocのデータをLiteStreamを使いS3にレプリケーションして永続化する方法について紹介します。
(LiteStreamを使いたかっただけです。)
材料
共同編集ができるWikiを動かす時に必要なのは、アプリケーションをホストするサーバと、データを蓄えるストレージです。
愚直にやるならサーバにアプリケーションと、データベースサーバをインストールして、設定ファイルをいい感じに書く、というのをやればこれを用意できます。
しかし、昨今はXaaS(なんとか あず あ サービス)の時代。愚直にサーバにセットアップするより、それぞれを得意とするサービスを組み合わせてサービスを構築することで、より柔軟なシステムを作ることができます。
アプリケーションを動かすプラットフォーム、 データを保存するプラットフォームは様々あり、それぞれが特徴を持っていますが、今回はアプリケーションを動かすプラットフォームとしては、Kubernetes(これは社内にあるものを使います)、 S3互換のサービスとしてはTebi.ioを選択しました(これも、特にこだわりはなくS3互換であれば何でも良いです)。
Hedgedocはコンテンツの保存にはMySQLやPostgreSQL、SQLiteなどを想定しており、S3互換のサービスは普通は利用できません。しかし今回紹介するLiteStreamというソフトを使うことで、SQLiteのデータをS3互換のサービスにレプリケーションできます。
構成
詳細
淡々とKubernetesのManifestファイルを紹介します
deploymentのManifest
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: hedgedoc-sqlite
name: hedgedoc-sqlite
spec:
replicas: 1
selector:
matchLabels:
app: hedgedoc-sqlite
template:
metadata:
labels:
app: hedgedoc-sqlite
spec:
initContainers:
- image: litestream/litestream # すでにあるバックアップから復元する処理
name: litestream-init
command: ["/bin/sh", '-c']
args:
- |
/usr/local/bin/litestream restore -config /opt/litestream/litestream.yaml /data/hedgedoc.sqlite && chmod 777 /data/hedgedoc.sqlite || true
volumeMounts:
- name: data
mountPath: /data
- name: litestream-secret
mountPath: /opt/litestream/litestream.yaml
subPath: litestream.yaml
containers:
- image: quay.io/hedgedoc/hedgedoc # Hedgedoc本体
name: hedgedoc
env:
- name: CMD_DOMAIN
value: <ingressなどで公開するドメインに合わせる>
- name: CMD_DB_DIALECT
value: sqlite
volumeMounts:
- name: data
mountPath: /data
- name: hedgedoc-config
mountPath: /hedgedoc/config.json
subPath: config.json
- image: litestream/litestream # hedgedoc動作中にレプリケーションするサイドカー
name: litestream
command: ["/bin/sh", '-c']
args:
- |
/usr/local/bin/litestream replicate -config /opt/litestream/litestream.yaml
volumeMounts:
- name: data
mountPath: /data
- name: litestream-secret
mountPath: /opt/litestream/litestream.yaml
subPath: litestream.yaml
volumes:
- name: hedgedoc-config
configMap:
name: hedgedoc-sqlite
items:
- key: config.json
path: config.json
- name: litestream-secret
secret:
secretName: litestream
items:
- key: litestream.yaml
path: litestream.yaml
- name: data # コンテナ間でデータを共用するためのボリューム
emptyDir: {}
設定ファイルのManifest
dbs:
- path: /data/hedgedoc.sqlite
replicas:
- type: s3
endpoint: https://s3.tebi.io
name: hedgedoc.sqlite
bucket: hedgedoc-backup
path: hedgedoc.sqlite
forcePathStyle: true
sync-interval: 1s
access-key-id: <アクセスキー>
secret-access-key: <シークレットアクセスキー>
以下のコマンドで上記をSecretリソースとしてデプロイします
$ kubectl create secret generic litestream --from-file=litestream.yaml
{
"production": {
"db": {
"dialect": "sqlite",
"storage": "/data/hedgedoc.sqlite"
}
}
}
以下のコマンドで上記をConfigMapリソースとしてデプロイします
$ kubectl create configmap hedgedoc-sqlite --from-file=config.json
外部からのアクセスのためのマニフェスト
メインのmanifestはここまでで、以下はクラスタ外からアクセスできるようにするためのServiceとIngressリソースです。
クラスタの構成によってはServiceのtype=Loadbalanerを使ったり、別途DNSの設定が必要なこともあります。
apiVersion: v1
kind: Service
metadata:
labels:
app: hedgedoc-sqlite
name: hedgedoc-sqlite
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 3000
selector:
app: hedgedoc-sqlite
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hedgedoc-sqlite
spec:
rules:
- host: <外部に公開するドメイン名>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hedgedoc-sqlite
port:
number: 3000
感想
ここまでのマニフェストをデプロイすると、普通にHedgedocがデプロイでき、1s間隔でデータベースの変更がS3にレプリケーションされるようになります。
Kubernetes上のPodが再生成されても、データは復元されます。
もちろんこの構成にはパッと思いつくだけでも以下のようなたくさんの制限があります
- 複数のPodを作ろうとするとデータが不整合になる
- スケールアウトできない
- 無停止でのバージョンアップができない
- 同期タイミングより早くアプリケーションが落ちるとその間のデータは失われる
- データが多くなると再デプロイ時の復元でネットワーク帯域を消費する
- データの変更が多い使い方をするとネットワークの帯域を消費する
- 書き込みのオーバーヘッドが生じる
しかし、この制限を補ってあまりあるほどの簡潔さがこの仕組みにはあるように感じました。
ちょっと個人でデータを永続化するサービスを設計する際はもちろん、大規模なサービスを構成するパターンの1つとして、エッジで動くSQLiteと永続化のためのS3という構成は役立つ場面が多いのではと思います。
参考
正直ほとんどこの記事をなぞっただけです。
https://zenn.dev/mattn/articles/fef682a8b204ac