本文档介绍 AINS 的三级部署方案:本地开发、Docker Compose 中小型部署、Kubernetes 高可用部署(含 CloudNativePG 读写分离集群)。
| 方案 | 适用场景 | DB 拓扑 | 应用副本 | 优势 |
|---|---|---|---|---|
| 本地开发 | 个人开发 | 单实例 PG + Redis | 1 | 快速启动,热重载 |
| Docker Compose | 中小型/测试 | 单实例 PG + Redis | 1 | 一键部署,可复现 |
| K8s 单 PG | 开发/预发布 | 单实例 PG StatefulSet | 3 | 应用层高可用 |
| K8s CNP 集群 | 生产环境 | 1 主 2 从 + 自动故障转移 | 3 | 全链路高可用 |
- Rust 1.92+
- Docker 24.0+(用于启动 PostgreSQL 和 Redis)
- Dioxus CLI 0.7.9+(前端开发,可选)
cargo binstall dioxus-cli --version 0.7.9 -y# 1. Docker 网络 & 数据库
docker network create ains-net 2>/dev/null || true
docker run -d \
--name ains-postgres-dev \
--network ains-net \
-e POSTGRES_DB=ains \
-e POSTGRES_USER=ains \
-e POSTGRES_PASSWORD="${AINS_POSTGRES_PASSWORD:-CHANGE_ME_POSTGRES_PASSWORD}" \
-p 5432:5432 \
-v ains-postgres-data:/var/lib/postgresql/data \
--restart unless-stopped \
postgres:16-alpine
# Redis(用于缓存、会话和分布式限流)
docker run -d \
--name ains-redis-dev \
--network ains-net \
-p 6379:6379 \
-v ains-redis-data:/data \
--restart unless-stopped \
redis:7-alpine \
redis-server \
--requirepass "${AINS_REDIS_PASSWORD:-CHANGE_ME_REDIS_PASSWORD}" \
--maxmemory 256mb \
--maxmemory-policy allkeys-lru
# 2. 配置
cp -n config.toml.example config.toml
# config.toml already has CHANGE_ME_POSTGRES_PASSWORD and CHANGE_ME_REDIS_PASSWORD as defaults
# Set custom passwords here if needed, or override via AINS_DATABASE_URL / AINS_REDIS_URL
# 3. 启动(默认 Axum 引擎)
cargo run --package ains-server -- --env development --log-level debug
# 4. 切换 Salvo 引擎(可选)
cargo run --package ains-server --no-default-features --features ains-salvo \
-- --env development --log-level debugcurl http://127.0.0.1:3000/api/health
# → {"status":"ok","version":"0.1.0"}
# 可选的 Dioxus 前端
cd app/web && dx serve --package web --platform web --hot-reload true如需一键启动本地环境,可将上方命令合并为 start-dev.sh;更简单的全栈启动方式见
Docker Compose 部署——docker compose up -d 一条命令拉起
postgres / redis / ains-server / ains-web。
Docker Compose 完整编排以下服务:
| 服务 | 容器 | 端口 | 依赖 |
|---|---|---|---|
| ains-server | 后端 (Axum) | 3000 | postgres, redis |
| ains-web | Nginx + WASM | 80 | ains-server |
| postgres | PostgreSQL 16 | 5432 | - |
| redis | Redis 7 | 6379 | - |
# 生成安全密钥
JWT_SECRET=$(openssl rand -base64 64 | tr -d '\n')
DB_PASS=$(openssl rand -hex 32)
REDIS_PASS=$(openssl rand -hex 32)
cat > .env << EOF
AINS_ENV=production
AINS_JWT_SECRET=${JWT_SECRET}
AINS_POSTGRES_PASSWORD=${DB_PASS}
AINS_REDIS_PASSWORD=${REDIS_PASS}
EOF
cp config.toml.example config.toml
docker compose up -d
# 验证
curl http://127.0.0.1:3000/api/health
curl http://127.0.0.1/nginx-health用户 → nginx:80 (ains-web)
├── ains-server:3000 → postgres:5432
└── → redis:6379
所有容器在 ains-net 网络内通过服务名互访。
# 备份 PG
docker compose exec -T postgres pg_dump -U postgres -d ains > backup.sql
# 恢复
cat backup.sql | docker compose exec -T postgres psql -U postgres -d ains
# Redis 持久化(AOF 模式需在 command 中添加 --appendonly yes)
docker compose exec redis redis-cli BGSAVEgit pull && docker compose build && docker compose up -dK8s 部署提供两种数据库拓扑,按环境选择。
| 配置文件 | DB 拓扑 | 用途 |
|---|---|---|
k8s/postgres.yml |
单实例 StatefulSet + PVC | 开发/预发布 |
k8s/postgres-cluster.yml |
CNP 1 主 2 从 | 生产高可用 |
- Kubernetes 1.20+ 集群
- kubectl 已配置
- CloudNativePG Operator(使用 CNP 集群时需要)
kubectl apply -f k8s/namespace.yml
JWT_SECRET=$(openssl rand -base64 64 | tr -d '\n')
POSTGRES_PASS=$(openssl rand -hex 32)
REDIS_PASS=$(openssl rand -hex 32)
# 单实例 PG 的 URL
DATABASE_URL="postgres://postgres:${POSTGRES_PASS}@postgres-service.ains.svc.cluster.local:5432/ains"
# CNP 集群的 URL(需要同时配置读库地址)
DATABASE_RO_URL="postgres://postgres:${POSTGRES_PASS}@postgres-cluster-ro.ains.svc.cluster.local:5432/ains"
REDIS_URL="redis://:${REDIS_PASS}@redis-service.ains.svc.cluster.local:6379"
cat > k8s/secret.yml << YAMLEOF
apiVersion: v1
kind: Secret
metadata:
name: ains-secrets
namespace: ains
type: Opaque
data:
jwt_secret: $(echo -n "$JWT_SECRET" | base64 | tr -d '\n')
postgres_password: $(echo -n "$POSTGRES_PASS" | base64 | tr -d '\n')
redis_password: $(echo -n "$REDIS_PASS" | base64 | tr -d '\n')
database_url: $(echo -n "$DATABASE_URL" | base64 | tr -d '\n')
redis_url: $(echo -n "$REDIS_URL" | base64 | tr -d '\n')
YAMLEOF
kubectl apply -f k8s/secret.ymleval $(minikube docker-env)
docker build -t ains-server:latest -f Dockerfile.server .
docker build -t ains-web:latest -f Dockerfile.web .
minikube image load ains-server:latest ains-web:latestkubectl apply -f k8s/redis.yml
kubectl apply -f k8s/configmap.yml
kubectl wait --for=condition=ready pod -l app=redis -n ains --timeout=120s适用于开发/预发布环境。使用 k8s/postgres.yml 部署单实例 PostgreSQL StatefulSet。
kubectl apply -f k8s/postgres.yml
kubectl wait --for=condition=ready pod -l app=postgres -n ains --timeout=300s此时数据库只有单一端点 postgres-service:5432,无需配置 database_read_urls。
# 方式一:直接安装最新版本
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.1.yaml
# 方式二:Helm
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm upgrade --install cnpg cnpg/cloudnative-pg --namespace cnpg-system --create-namespace
# 验证 Operator 就绪
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=cloudnative-pg \
-n cnpg-system --timeout=120skubectl apply -f k8s/postgres-cluster.yml
# 自定义存储类(如 Local SSD):
# kubectl apply -f k8s/postgres-cluster.yml --overwrite=false
kubectl wait --for=condition=ready cluster -n ains postgres-cluster --timeout=300s集群拓扑(定义于 k8s/postgres-cluster.yml):
spec:
instances: 3 # 1 primary + 2 replicas
storage:
size: 1Gi
postgresql:
parameters:
max_connections: "100"
shared_buffers: "256MB"
hot_standby_feedback: "on" # 防止主库 vacuum 清理读库正在使用的元组CNP Operator 自动创建三个 Service 端点:
| Service | 用途 | 对应 AutoRouter 配置 |
|---|---|---|
postgres-cluster-rw |
读写(主库) | database_url |
postgres-cluster-ro |
只读(从库) | database_read_urls |
postgres-cluster-r |
随机读写 | 内部使用 |
故障转移:主库宕机时,Operator 自动提升一个从库为新主,-rw Service 自动指向新主。
DATABASE_URL="postgres://postgres:${POSTGRES_PASS}@postgres-cluster-rw.ains.svc.cluster.local:5432/ains"
# 注意:database_read_urls 在 ConfigMap 中配置,非 Secret
kubectl create secret generic ains-secrets --dry-run=client -o yaml \
--from-literal=jwt_secret="$JWT_SECRET" \
--from-literal=postgres_password="$POSTGRES_PASS" \
--from-literal=redis_password="$REDIS_PASS" \
--from-literal=database_url="$DATABASE_URL" \
--from-literal=redis_url="$REDIS_URL" \
-n ains | kubectl apply -f -# 3 副本后端应用
kubectl apply -f k8s/ains.yml
kubectl rollout status deployment/ains -n ains --timeout=300s
# 前端 Nginx(可选)
kubectl apply -f k8s/ains-web.yml
# Ingress
kubectl apply -f k8s/ingress.yml关键配置(k8s/ains.yml):
spec:
replicas: 3
template:
spec:
containers:
- env:
- name: AINS_DATABASE_URL
valueFrom:
secretKeyRef:
name: ains-secrets
key: database_url
- name: AINS_REDIS_URL
valueFrom:
secretKeyRef:
name: ains-secrets
key: redis_url
# 使用 CNP 集群时,ConfigMap 中注入 database_read_urls
envFrom:
- configMapRef:
name: ains-configIngress → ains-web:80 (2 replicas)
→ ains-server:3000 (3 replicas)
→ postgres-service:5432 (1 primary)
→ redis-service:6379
Ingress → ains-web:80 (2 replicas)
→ ains-server:3000 (3 replicas)
→ postgres-cluster-rw:5432 (writes)
→ postgres-cluster-ro:5432 (reads, L4 LB)
→ redis-service:6379
# 手动扩展
kubectl scale deployment ains --replicas=5 -n ains
# HPA 自动伸缩
kubectl autoscale deployment ains --min=3 --max=10 --cpu-percent=70 -n ains
# 滚动更新
kubectl set image deployment/ains ains=your-registry/ains-server:v2 -n ains
kubectl rollout status deployment/ains -n ains
# 金丝雀发布(需要服务网格)
kubectl create deployment ains-canary --image=your-registry/ains-server:v2 -n ains当配置了读库地址后,AutoRouter 自动启用读写分离。业务代码零改动。
# 单 URL → 无读写分离,所有操作走同一个连接
database_url = "postgres://user:pass@primary:5432/ains"
# 多 URL → 启用 AutoRouter
database_url = "postgres://user:pass@primary:5432/ains"
database_read_urls = [
"postgres://user:pass@replica1:5432/ains",
"postgres://user:pass@replica2:5432/ains",
]
# 读写分离行为(可选,以下为默认值)
[database_routing]
strategy = "round_robin" # round_robin | random | weighted
retry_attempts = 2 # 读库失败重试次数
circuit_break_ms = 30000 # 熔断持续时间(毫秒)
fallback_to_write = true # 所有读库熔断时降级回写库
health_check_interval_secs = 15 # 熔断恢复探测间隔# docker-compose.yml 中只有一个 PG 实例,不启用读写分离
# 如需在本地测试读写分离,添加第二个 PG 容器:
docker compose up -d postgres-replica# ConfigMap 中配置读库端点
kubectl patch configmap ains-config -n ains -p \
'{"data":{"AINS_DATABASE_READ_URLS":"postgres://postgres:${POSTGRES_PASS}@postgres-cluster-ro.ains.svc.cluster.local:5432/ains"}}'
kubectl rollout restart deployment/ains -n ains| 状态 | 行为 |
|---|---|
| 读库连接失败 | 标记熔断 down_until(默认 30s) |
| 后台健康检查 | 每 15s 探测,成功自动恢复 |
| 所有读库熔断 | fallback_to_write=true → 降级写库 |
fallback_to_write=false |
读操作返回错误 |
- 代码默认值 → 2. config.toml → 3. K8s Secret → 4. 环境变量(最高)
# 数据库
AINS_DATABASE_URL=postgres://user:pass@host:port/dbname
AINS_DATABASE_READ_URLS=postgres://user:pass@replica1:5432/dbname # 逗号分隔或数组 JSON
# 数据库路由(JSON 编码)
AINS_DATABASE_ROUTING={"strategy":"round_robin","retry_attempts":2,"circuit_break_ms":30000}
# Redis
AINS_REDIS_URL=redis://:password@host:6379
# JWT
AINS_JWT_SECRET=your-secret-key # >= 32 chars
AINS_JWT_EXPIRY_SECONDS=3600
# 服务器
AINS_SERVER__HOST=0.0.0.0
AINS_SERVER__PORT=3000
AINS_SERVER__ALLOWED_ORIGINS=https://domain1.com,https://domain2.com
# 日志
RUST_LOG=info|debug|trace
# 环境
AINS_ENV=development|production# 生成安全密钥
JWT_SECRET=$(openssl rand -base64 64) # JWT 签名密钥
DB_PASS=$(openssl rand -hex 32) # 数据库密码
REDIS_PASS=$(openssl rand -hex 32) # Redis 密码(hex 保证 URL 安全)
# 不要将 .env / secret.yml / config.toml 提交到 Git
echo ".env" >> .gitignore
echo "k8s/secret.yml" >> .gitignore- 最小 3 副本:
k8s/ains.yml默认replicas: 3,容忍单节点故障 - Pod 反亲和:避免同一节点多个副本
- 优雅关闭:
SIGTERM处理,完成进行中的请求后退出
- 连接池调优:生产环境
max_connections=50, min_connections=10 - Redis AOF:在 redis 命令中添加
--appendonly yes --appendfsync everysec - PG 参数:CNP 集群中调优
shared_buffers/work_mem
- HTTPS:在 Ingress 配置 Let's Encrypt(cert-manager)
- 非 root 运行:Dockerfile 使用
USER ains - NetworkPolicy:限制 ains 命名空间内流量
引入套餐(Plan)功能后,/api/ai/response 对 user 角色强制校验有效套餐
(admin/system 豁免)。存量部署升级后,未持有套餐的存量用户调用该接口
会收到 403 no_active_plan。升级前请二选一:
- 通过管理后台(或
POST /api/users/{id}/plans)为存量用户批量授予套餐; - 在公告/发布说明中告知用户需先购买套餐后方可继续调用。
kubectl exec -it pod/ains-xxx -n ains -- \
psql -h postgres-service -U postgres -d ains# 检查集群状态
kubectl cnpg status postgres-cluster -n ains
# 检查 Service 端点
kubectl get endpoints -n ains | grep postgres-cluster
# 获取主库 Pod 名称
kubectl get pod -n ains -l postgresql=postgres-cluster \
-o jsonpath='{.items[?(@.metadata.labels.role=="primary")].metadata.name}'
# 手动测试连接
kubectl exec -it pod/ains-xxx -n ains -- \
sh -c 'apt-get update && apt-get install -y postgresql-client && \
psql "$AINS_DATABASE_URL" -c "SELECT 1"'
kubectl exec -it pod/ains-xxx -n ains -- \
sh -c 'psql -h postgres-cluster-ro -U postgres -d ains -c "SELECT 1"'kubectl exec -it pod/redis-0 -n ains -- \
sh -c 'redis-cli -a "$REDIS_PASSWORD" ping'# 检查 ConfigMap 是否包含 database_read_urls
kubectl get configmap ains-config -n ains -o yaml
# 检查应用日志是否显示 AutoRouter 初始化
kubectl logs deployment/ains -n ains | grep -i "router\|read\|replica"kubectl describe pod ains-xxx -n ains | grep -A10 Events
kubectl logs pod/ains-xxx -n ains --previous| 组件 | 最低版本 | 推荐 |
|---|---|---|
| Rust | 1.92 | 最新 stable |
| PostgreSQL | 16 | 16-alpine(容器) |
| Redis | 7 | 7-alpine(容器) |
| Docker | 24.0 | 最新 |
| Kubernetes | 1.20 | 1.28+ |
| CloudNativePG | 1.24 | 1.24+ |