以Vaultwarden为例使用SQLite Litestream实现无数据库服务
Sep 3 2024前言
Vaultwarden是一个非官方实现的Bitwarden Server,用于密码管理,支持Web端、桌面端和移动端。它支持多种数据来存储数据,包括SQLite、PostgreSQL和MySQL等。当我们想把它部署在一个容器服务平台时,如果容器服务本身没有提供持久挂载的卷,那我们就只能使用PostgreSQL或者MySQL外部数据来存储数据。
但是我们并不想额外再买资源来运行一个数据库,那我们就可以使用SQLite来存储数据,并且使用Litestream来实现容灾备份。这样虽然容器服务没有持久存储,但是数据还是安全的。下面以Vaultwarden为例,使用SQLite和Litestream来实现无数据库服务。其它可以使用SQLite的程序也可以使用这种方式来实现无数据库服务。
1. litestream.yml
Litesream是一个开源的SQLite数据库备份工具,它使用Go语言开发。它可以监控一个SQLite数据库文件的变化,并且将变化记录到S3、MinIO等对象存储服务上。它巧妙的使用了SQLite的WAL(Write-Ahead Logging)机制,来记录数据库的变化,并流式地将这些变化记录到对象存储服务上,达到实时备份的目的。在服务启前,Litestream可以从对象存储服务上恢复数据到SQLite数据库文件中。它的实现原理可以参考这篇文章。
在使用Litestream时,我们需要准备一个S3兼容的对象存储,这里以Cloudflare R2为例,我们需要准备一个litestream.yml
的配置文件,内容如下:
dbs:
- path: /data/db.sqlite3
replicas:
- type: s3
endpoint: ${REPLICA_URL}
bucket: sqlite
/data/db.sqlite3
是Vaultwarden的SQLite数据库文件路径,REPLICA_URL
是对象存储服务的地址,比如 https://<cloudflare-account-id>.r2.cloudflarestorage.com
,我们需要在Cloudflare R2上创建一个sqlite
的存储桶。
2. entrypoint.sh
我们还需要准备一个entrypoint.sh
的脚本文件,用于启动Liestream和Vaultwarden服务。内容如下:
#!/bin/bash
set -e
# Set the directory of the database in a variable
DB_PATH=/data/db.sqlite3
# Restore the database if it does not already exist.
if [ -f $DB_PATH ]; then
echo "Database already exists, skipping restore"
else
echo "No database found, restoring from replica if exists"
litestream restore -if-replica-exists -config /etc/litestream.yml $DB_PATH
fi
# Litestream replicate database and start vaultwarden
echo "exec litestream"
exec litestream replicate -exec "/start.sh" -config /etc/litestream.yml
在启动Vaultwarden服务前,我们首先检查SQLite数据库文件是否存在,如果不存在则从对象存储服务上恢复数据。
3. Dockerfile
FROM litestream/litestream:latest AS litestream
FROM vaultwarden/server:latest
COPY --from=litestream /usr/local/bin/litestream /usr/local/bin/litestream
COPY --chmod=755 entrypoint.sh /usr/bin/entrypoint.sh
COPY litestream.yml /etc/litestream.yml
CMD ["/usr/bin/entrypoint.sh"]
这样会打一个新的镜像把Litestream和Vaultwarden服务一起打包,并且使用entrypoint.sh
启动服务。
4. 启动服务
我们还要准备一个.env文件,用于设置环境变量:
LITESTREAM_ACCESS_KEY_ID=<cloudflare-r2-access-key-id>
LITESTREAM_SECRET_ACCESS_KEY=<cloudflare-r2-access-key>
PUSH_ENABLED=true
PUSH_INSTALLATION_ID=<push-installation-id>
PUSH_INSTALLATION_KEY=<push-installation-key>
PUSH_RELAY_BASE_URI=https://push.bitwarden.com
REPLICA_URL=https://<cloudflare-account-id>.r2.cloudflarestorage.com
SIGNUPS_ALLOWED=true
vaulewarden的PUSH配置需要到这里申请。
然后我们就可以使用Docker来启动服务了:
docker run -d \
--name bitwarden \
-p 80:80 \
--restart unless-stopped \
--env-file .env \
vaultwarden/server:latest
这样我们就可以在没有持久存储的容器服务上无数据库的运行Vaultwarden服务了,比如GCP Code Run,我们可以把实例数量scale到0,只有流量进来时才启动服务,真正做到只有使用时才付费。
总结
我们使用Litestream实现了Vaultwarden的无数据库服务,并且使用Cloudflare R2作为对象存储服务。这样即使容器服务没有持久存储卷,数据也是安全的。类似的思路还可以推广到其它使用SQLite的程序上。比如我们自己开发了一个Django的程序,不依赖外部的数据库服务,也可以使用Litestream来实现容灾备份。