在 Serverless 时代,Google Cloud Run (GCR) 或 AWS Fargate 凭借其“按需计费、自动扩缩容、零运维”的特性,成为了部署轻量级应用的首选。
然而,Serverless 的核心特质是 Ephemeral(临时性)。容器实例随时可能被回收,这意味着本地文件系统的任何修改都会随之灰飞烟灭。对于像 Streamlit 这种依赖本地代码运行,且常使用 SQLite 处理轻量级数据的应用来说,如何实现高效、可靠的“持久化”成了架构设计的头号难题。
为此,我设计了一套“双轨同步方案”,完美解决了这一痛点。
1. 痛点:为什么“挂载云盘”不是最优解?
在处理持久化时,最直观的想法是通过 FUSE 挂载 S3 或云端存储。但在实际工程中,这会带来几个致命问题:
- IO 延迟: S3 挂载的读写延迟远高于本地磁盘,会导致 Streamlit 页面在读取多文件或资源时出现明显的卡顿。
- SQLite 兼容性: SQLite 极其依赖文件锁(File Locking)。在网络文件系统上,多进程争抢锁极易导致数据库损坏或死锁。
- 成本代价: 频繁的小文件 IO 会产生大量的 API 请求费用,这与我们“省钱”的初衷背道而驰。
2. 架构方案:读写分离的“双轨同步”
为了兼顾性能与安全,我将应用数据分为两类,并采用完全不同的同步路径:
轨道 A:代码与静态资源(单向分发)
- 核心工具:
rclone - 同步路径: S3 Bucket → 容器实例 (Google Cloud Run)
- 业务逻辑: 学生在 Web IDE 修改代码并保存后,后台将文件同步至 S3。容器内的 rclone 进程会周期性辣取S3最新代码。对容器内的 Streamlit 进程而言,代码文件是“只读”且位于本地磁盘的,加载速度极快。
轨道 B:SQLite 数据库(实时增量备份)
- 核心工具:
Litestream - 同步路径: 容器实例 ⇄ S3 Bucket
- 业务逻辑: 利用 Litestream 监控 SQLite 的 WAL (Write-Ahead Log)。一旦应用产生写入,Litestream 会将增量日志实时流式传输到 S3。当容器重启时,Litestream 会先从 S3 恢复最新的数据库镜像,确保数据连续性。
3. 技术核心实现
Dockerfile 关键设计
我们需要在一个镜像中集成 rclone、litestream 和 streamlit 运行环境。
FROM python:3.11-slim
# 安装必要工具、rclone 和 litestream
RUN apt-get update && apt-get install -y curl unzip
RUN curl https://rclone.org/install.sh | bash
RUN curl -L https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.deb -o litestream.deb \
&& dpkg -i litestream.deb
WORKDIR /app
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 暴露 Streamlit 默认端口
EXPOSE 8501
ENTRYPOINT ["/entrypoint.sh"]
Entrypoint.sh:多进程编排脚本
这个脚本是整个架构的“大脑”,负责协调初始化、数据恢复、后台同步和应用启动。
#!/bin/bash
set -e
# 环境变量建议从 Cloud Run 配置中注入
DB_PATH="/app/data/codingroo.sqlite3"
REPLICA_URL="s3://your-bucket-name/db-replicas"
CODE_SOURCE="s3://your-bucket-name/student-code"
CODE_DEST="/app/code"
# 1. 启动前置动作:从 S3 恢复最新的数据库状态
if [ -f "$DB_PATH" ]; then
echo "Database exists, checking for updates..."
else
echo "Restoring database from remote replica..."
# 如果 S3 上已有备份,则恢复到本地;否则跳过
litestream restore -if-db-not-exists -if-replica-exists -o $DB_PATH $REPLICA_URL
fi
# 2. 初始代码同步:确保容器启动即拥有最新代码
echo "Initial code sync via rclone..."
rclone copy $CODE_SOURCE $CODE_DEST
# 3. 启动 Litestream 复制进程(后台运行)
# 它会监控本地 SQLite 的 WAL 文件,并实时上传增量到 S3
litestream replicate $DB_PATH $REPLICA_URL &
# 4. 启动 rclone 监听(后台运行)
# 使用 --watch 模式,确保 Web 端修改代码后,容器内能秒级刷新
rclone sync $CODE_SOURCE $CODE_DEST --watch &
# 5. 启动主应用 Streamlit
# 使用 exec 确保 Streamlit 接管 PID 1,以便正确处理容器信号
echo "Launching Streamlit Academy Platform..."
exec streamlit run $CODE_DEST/main.py \
--server.port=8501 \
--server.address=0.0.0.0 \
--server.runOnSave=true
4. 方案优势总结
- 极速响应: Streamlit 直接读取本地磁盘上的
.py文件,避开了网络存储的延迟,体验极佳。 - 毫秒级 RPO: 得益于 Litestream 的 WAL 监听机制,即使容器意外崩溃或被回收,数据丢失也仅在毫秒级之间。
- 显著降低开销: * 弃用了昂贵的托管数据库(如 Cloud SQL),改用极低成本的 S3/GCS。
- 容器在无请求时自动缩减至 0,没有任何计算费用。
- 自主可控: 成功摆脱了对现有付费平台高额订阅费的依赖,且环境高度定制化。
5. 结语
优秀的架构不在于组件的堆砌,而在于对数据流向的精准把控。 通过 rclone 处理下行分发,Litestream 处理上行回传,我们不仅规避了 Serverless 的存储短板,更利用其弹性优势构建了一个高可用、低成本的在线编程平台。这就是工程思维的魅力:用最合适的工具,解决最核心的痛点。