自建类 Supabase Serverless 平台:K8s + Knative + PostgreSQL 完全本地化方案
背景
Supabase 是 Firebase 的开源替代,但生产环境中你需要:
- 数据不出机房:所有组件跑在自己的 K8s 集群内
- 零依赖外部服务:不依赖 Supabase 官方托管、不上云
- Serverless 弹缩:函数按请求扩缩容,闲时缩零节省资源
- SQL 即 API:写 PostgreSQL 查询自动暴露 REST 接口
本文基于已部署的 3 节点 K8s 1.34 集群(Ubuntu 24.04 ARM64、containerd、Calico eBPF),实现完全自建的 Serverless 应用平台。
总体架构
用户请求 (HTTPS)
│
▼
Kourier Ingress (Knative 网络层)
│ 域名路由、TLS 终止、自动缩零
▼
Knative Serving (Serverless 运行时)
│ 冷启动、流量灰度、版本管理
▼
┌──────────────────────────┐
│ 自定义函数容器 (Deno) │ ← Serverless 函数(缩零)
└──────────────────────────┘
│ REST API 调用
▼
PostgREST ────▶ PostgreSQL ────▶ MinIO
(SQL→REST) (主数据库) (S3 对象存储)
全部组件运行在 K8s 集群内,不依赖任何外部服务。
组件清单
| 组件 | 角色 | 最小资源 | 容器镜像 |
|---|---|---|---|
| PostgreSQL 17 | 主数据库 | 512MB 内存 | postgres:17-alpine |
| PostgREST 12 | SQL → REST API | 128MB | postgrest/postgrest:v12.2 |
| Knative Serving 1.16 | Serverless 运行时 | 200MB | gcr.io/knative-releases/* |
| Kourier 1.16 | Knative 网络层 | 100MB | gcr.io/knative-releases/* |
| MinIO | S3 兼容对象存储 | 256MB | minio/minio:latest |
| 自定义函数 | 业务逻辑 | 128MB | 自定义 |
总计增量开销:集群已有基础上增加约 1.5GB,现有 3 节点(Master 4GB + 2 Worker 各 2GB)完全够用。
第一步:部署 PostgreSQL(集群内自建)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pg-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Secret
metadata:
name: pg-secret
stringData:
POSTGRES_PASSWORD: "your-strong-password"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
spec:
containers:
- name: postgres
image: postgres:17-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: pg-secret
key: POSTGRES_PASSWORD
- name: POSTGRES_DB
value: appdb
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: pg-data
---
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
ports:
- port: 5432
selector:
app: postgres
初始化 Schema
-- 用户表
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 项目表
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
name TEXT NOT NULL,
data JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now()
);
-- 文件表
CREATE TABLE files (
id SERIAL PRIMARY KEY,
project_id INT REFERENCES projects(id),
filename TEXT NOT NULL,
size BIGINT,
storage_path TEXT,
uploaded_at TIMESTAMPTZ DEFAULT now()
);
关键配置:wal_level = logical
PostgreSQL 需要开启逻辑复制以支持实时订阅:
# 在 Deployment 的 args 中添加
args:
- "-c"
- "wal_level=logical"
- "-c"
- "max_replication_slots=5"
- "-c"
- "max_wal_senders=5"
这不影响普通查询性能,仅增加少量 WAL 写入(约 5-10%)。
第二步:部署 PostgREST(SQL 即 API)
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgrest
spec:
replicas: 2
selector:
matchLabels:
app: postgrest
template:
spec:
containers:
- name: postgrest
image: postgrest/postgrest:v12.2
ports:
- containerPort: 3000
env:
- name: PGRST_DB_URI
value: "postgres://authenticator:password@postgres:5432/appdb"
- name: PGRST_DB_SCHEMAS
value: "public"
- name: PGRST_DB_ANON_ROLE
value: "anonymous"
- name: PGRST_JWT_SECRET
value: "your-jwt-secret-min-32-chars"
- name: PGRST_SERVER_PORT
value: "3000"
---
apiVersion: v1
kind: Service
metadata:
name: postgrest
spec:
ports:
- port: 3000
selector:
app: postgrest
PostgreSQL 角色设置
-- 创建认证角色
CREATE ROLE authenticator NOINHERIT LOGIN PASSWORD 'password';
CREATE ROLE anonymous NOLOGIN;
-- anonymous 角色只读权限
GRANT anonymous TO authenticator;
GRANT USAGE ON SCHEMA public TO anonymous;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO anonymous;
效果
部署后,直接通过 REST 访问数据库:
# 获取所有用户(无需编写任何 API 代码!)
curl http://postgrest:3000/users
# 带过滤的查询
curl "http://postgrest:3000/users?email=eq.admin@example.com"
# 跨表关联查询
curl "http://postgrest:3000/projects?select=id,name,users(email)"
PostgREST 自动将 PostgreSQL 的表/视图/函数暴露为 RESTful API,支持:
- 增删改查 CRUD
- 嵌套资源关联
- 分页、排序、过滤
- JWT 认证
- OpenAPI 自动文档
第三步:部署 Knative(Serverless 基座)
# 1. Knative Serving CRDs
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.16.0/serving-crds.yaml
# 2. Knative Serving 核心
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.16.0/serving-core.yaml
# 3. Kourier 网络层(比 Istio 轻 10 倍)
kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-v1.16.0/kourier.yaml
# 4. 设为默认网络层
kubectl patch configmap/config-network -n knative-serving \
--type merge -p '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'
# 5. 配置 Kourier 为 LoadBalancer(配合 MetalLB 可用 VIP)
kubectl patch svc kourier -n kourier-system \
-p '{"spec":{"type":"NodePort","ports":[{"name":"http2","port":80,"nodePort":30081}]}}'
Knative 核心概念
Knative Service = Deployment + Service + Ingress + Autoscaler 的声明式封装
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello-world
spec:
template:
spec:
containers:
- image: denoland/deno:alpine-2.1.9
command: ["deno", "run", "-A", "--watch", "main.ts"]
一个 YAML 搞定:容器打包、路由、扩缩容、缩零、流量管理。
第四步:编写第一个 Serverless 函数
// main.ts - Deno 函数,查询 PostgreSQL
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";
const PG_URL = "http://postgrest:3000";
serve(async (req: Request) => {
const url = new URL(req.url);
// GET /users → 查询所有用户
if (url.pathname === "/users" && req.method === "GET") {
const res = await fetch(`${PG_URL}/users`, {
headers: { "Accept": "application/json" }
});
return new Response(await res.text(), {
headers: { "Content-Type": "application/json" }
});
}
// POST /users → 创建用户
if (url.pathname === "/users" && req.method === "POST") {
const body = await req.json();
const res = await fetch(`${PG_URL}/users`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Prefer": "return=representation"
},
body: JSON.stringify(body)
});
return new Response(await res.text(), {
status: res.status,
headers: { "Content-Type": "application/json" }
});
}
return new Response("Not Found", { status: 404 });
});
# Dockerfile
FROM denoland/deno:alpine-2.1.9
WORKDIR /app
COPY main.ts .
CMD ["deno", "run", "-A", "main.ts"]
部署为 Knative Service
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: my-api
spec:
template:
spec:
containers:
- image: my-registry/my-api:v1
env:
- name: DENO_ENV
value: production
Knative 自动提供的能力
| 特性 | 零配置 |
|---|---|
| 域名 | my-api.default.10.211.55.6.sslip.io |
| 扩缩容 | 请求量驱动,默认 0 → N |
| 缩零 | 60 秒无请求自动缩到 0 |
| 冷启动 | eBPF 加速,300-800ms |
| 流量灰度 | kubectl apply 新版本自动分流 |
第五步:部署 MinIO(S3 存储)
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio
spec:
replicas: 1
selector:
matchLabels:
app: minio
template:
spec:
containers:
- name: minio
image: minio/minio:latest
args: ["server", "/data", "--console-address", ":9001"]
ports:
- containerPort: 9000
name: api
- containerPort: 9001
name: console
env:
- name: MINIO_ROOT_USER
value: "minioadmin"
- name: MINIO_ROOT_PASSWORD
value: "minioadmin123"
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: minio
spec:
ports:
- port: 9000
name: api
- port: 9001
name: console
selector:
app: minio
第六步:实时订阅(Supabase Realtime 替代)
Supabase 的实时订阅基于 PostgreSQL 逻辑复制。自建方案使用 WAL 监听:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: realtime
spec:
template:
spec:
containers:
- image: my-realtime-server:v1 # 监听 PG WAL ,推送 WebSocket
env:
- name: DATABASE_URL
value: "postgres://appuser:password@postgres:5432/appdb"
核心逻辑(伪代码):
// 1. 连接 PG logical replication slot
const slot = await pg.createReplicationSlot("realtime_slot");
// 2. 监听 WAL 变更
slot.on("change", (change) => {
// 3. 推送到 WebSocket 客户端
wsServer.clients.forEach(client => {
client.send(JSON.stringify({
table: change.table,
action: change.action, // INSERT / UPDATE / DELETE
data: change.new
}));
});
});
完整拓扑
┌─────────────────────────────────────────────────────────────────┐
│ K8s 集群 (3 节点) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Master │ │ Worker1 │ │ Worker2 │ │
│ │ 4GB │ │ 2GB │ │ 2GB │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ └───────────────┼───────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ Calico eBPF Network │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ │ Kourier Ingress :30081 │ │
│ │ Knative Serving │ │
│ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ my-api │ │ realtime │ │ uploader │ │ ← Serverless │
│ │ │ (Deno) │ │ (WS) │ │ (Deno) │ │ Functions │
│ │ └────┬────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────┐ ┌──────────┐ ┌─────────┐ │ │
│ │ │PostgRST│ │PostgreSQL│ │ MinIO │ │ ← 数据层 │
│ │ │ :3000 │ │ :5432 │ │ :9000 │ │ │
│ │ └────────┘ └──────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
外部访问: http://10.211.55.x:30081/my-api
资源预算
| 组件 | 副本 | 内存/副本 | 总内存 |
|---|---|---|---|
| PostgreSQL 17 | 1 | 512MB | 512MB |
| PostgREST 12 | 2 | 128MB | 256MB |
| Knative Serving | 1 | 200MB | 200MB |
| Kourier | 2 | 100MB | 200MB |
| MinIO | 1 | 256MB | 256MB |
| Serverless 函数 | 0-3 | 128MB | 0-384MB |
| 合计(闲时) | 约 1.4GB | ||
| 合计(3 函数活跃) | 约 1.8GB |
现有 Master 4GB + 2 Worker 各 2GB,总可用约 6GB(扣除系统开销),跑这套方案绰绰有余。
与 Supabase 官方对比
| 能力 | Supabase 官方 | 本方案 |
|---|---|---|
| 数据库 | 托管 PG | 自建 PG 17 |
| REST API | PostgREST(同) | PostgREST 12 |
| 认证 | GoTrue | JWT + PostgREST(可选 GoTrue) |
| 实时订阅 | Supabase Realtime | WAL listener + WebSocket |
| 存储 | S3 | MinIO |
| Edge Functions | Deno Deploy | Deno + Knative |
| 缩零 | ✅ | ✅ Knative |
| 冷启动 | ~200ms | ~500ms (eBPF) |
| 数据留存 | 云端 | 本地,完全掌握 |
| 成本 | 按量计费 | 无额外成本 |
| 离线使用 | ❌ | ✅ 完全离线可用 |
优势
- 完全自建:不依赖任何云服务商或外部 API
- 数据主权:所有数据留存在你的 K8s 集群内
- Serverless 原生:函数按需运行,闲时零资源消耗
- SQL 即 API:定义 schema 即有 REST 接口,零样板代码
- eBPF 网络:已部署的 Calico eBPF 直接加速 Knative 冷启动
- 可扩展:每个 Knative Service 独立扩缩,互不影响
快速开始(基于现有 K8s 集群)
# 1. 部署 PostgreSQL
kubectl apply -f postgres.yaml
# 2. 部署 PostgREST
kubectl apply -f postgrest.yaml
# 3. 安装 Knative + Kourier
kubectl apply -f serving-crds.yaml
kubectl apply -f serving-core.yaml
kubectl apply -f kourier.yaml
# 4. 部署 MinIO
kubectl apply -f minio.yaml
# 5. 编写并部署第一个函数
docker build -t my-api:v1 .
kubectl apply -f knative-service.yaml
# 6. 访问
curl http://10.211.55.6:30081/my-api/users
本文是 K8s 1.34 集群部署实录 的进阶篇。集群部署完成后,可直接基于本文构建 Serverless 应用平台。
自建类 Supabase Serverless 平台:K8s + Knative + PostgreSQL 完全本地化方案
https://bmap.xyz/archives/k8s-serverless-post