kamify · 双 Next.js 应用(公开站点 + 管理后台)
kamify 由两个独立的 Next.js(standalone 模式)Deployment 组成,同处 app 命名空间:kamify-frontend 对外提供公开站点 kamify.store,kamify-console 提供管理后台 admin.kamify.store。两者共用同一份非密配置 ConfigMap(kamify-config)和同一份 Vault 密钥(kamify-secrets),数据层是 Supabase 托管的 Postgres(通过连接池 pooler 访问),认证走 Supabase Auth。
部署形态
kamify 拆成前台与后台两个 Deployment,镜像版本由 argocd-image-updater 按 semver 写回 kustomization,不手动改。
- Name
- 命名空间
- Description
- app(kustomization
namespace: app+namePrefix: app-,资源名形如app-kamify-frontend)
- Name
- 工作负载
- Description
- 两个 Deployment:
kamify-frontend、kamify-console
- Name
- 镜像 / 版本
- Description
ghcr.io/kamify-ai/kamify-frontend与ghcr.io/kamify-ai/kamify-console,kustomizationimages:中 newTag 均为0.1.5(deployment.yaml 内联写的是占位0.1.0,以 kustomization 为准);拉取用ghcr-secret
- Name
- 副本数
- Description
- 各 2(kustomization
replicas:显式设为 2)
- Name
- 调度
- Description
nodeSelector: workload=app;priorityClassName: production-medium;各自带podAntiAffinity(preferred,topologyKeykubernetes.io/hostname),且用matchLabelKeys: pod-template-hash只统计同版本 Pod,避免滚动更新时旧 ReplicaSet 的 Pod 把新副本挤到同一节点
- Name
- 端口
- Description
- 容器
containerPort: 3000(name http),Service 暴露port: 80 → targetPort 3000
- Name
- 存储
- Description
- 无 PVC,无状态(数据落在外部 Supabase Postgres)
- Name
- 自动重载
- Description
- 两个 Deployment 都带
reloader.stakater.com/auto: "true",ConfigMap/Secret 变更时由 reloader 触发滚动重启
资源请求/限制(两个 Deployment 相同):requests cpu 50m / memory 256Mi,limits cpu 500m / memory 512Mi,revisionHistoryLimit: 2。
探针上有差异:kamify-frontend 用 HTTP 探针打 /api/health:3000;kamify-console 用 TCP tcpSocket:3000。
配置与依赖
非密配置在 ConfigMap kamify-config(前台、后台都 envFrom 引入),密钥走 ExternalSecret kamify-secrets 从 Vault 拉取。
ConfigMap kamify-config 关键项:
| 键 | 值 | 说明 |
|---|---|---|
NODE_ENV | production | |
PORT / HOSTNAME | 3000 / 0.0.0.0 | Next.js standalone 监听 |
NEXT_PUBLIC_SUPABASE_URL | https://wmpfvtqzzpfktruylioi.supabase.co | Supabase 项目(project ref wmpfvtqzzpfktruylioi,区域 ap-northeast-2) |
NEXT_PUBLIC_SITE_URL | https://kamify.store | |
NEXT_PUBLIC_APP_URL | https://kamify.store | OAuth 回调基址;缺它会让 Google 登录回跳到 0.0.0.0:3000 |
NEXT_PUBLIC_FRONTEND_URL | https://kamify.store | 后台用它链回公开站点 |
ExternalSecret kamify-secrets 经 ClusterSecretStore vault-backend(KV v2)从 yldm/production/kamify 拉取,refreshInterval: 1h,secretKey 即应用读取的环境变量名:
| 环境变量 | Vault property | 用途 |
|---|---|---|
DATABASE_URL | database_url | Supabase transaction pooler DSN(端口 6543,?pgbouncer=true) |
APP_DATABASE_URL | app_database_url | session-mode pooler(端口 5432),用于迁移 / RLS role |
ENCRYPTION_KEY | encryption_key | 32+ 字节 AES 密钥(静态加密) |
SUPABASE_SERVICE_ROLE_KEY | supabase_service_role_key | |
NEXT_PUBLIC_SUPABASE_ANON_KEY | supabase_anon_key | 公开 anon key,与其余 Supabase 配置一并放 Vault |
SUPABASE_MANAGEMENT_TOKEN | supabase_management_token |
依赖:数据层为 Supabase Postgres(@kamify/db 经 pooler 访问),认证为 Supabase Auth。出站还会触达 Stripe 与 Resend。Stripe 相关密钥(stripe_secret_key / stripe_webhook_secret / stripe_connect_webhook_secret)目前尚未进 Vault,因此 ExternalSecret 中暂未挂载这些键;启用支付功能时需先写入 yldm/production/kamify 再补回。
访问与监控
两个 Service 均为默认 ClusterIP(port 80 → 3000),对外经 Ingress + Cloudflare Tunnel 暴露。
- Name
- 公开站点 Ingress
- Description
kamify-frontend,ingressClassName: traefik,hostkamify.store与www.kamify.store,注解nginx.ingress.kubernetes.io/proxy-body-size: 5m
- Name
- 后台 Ingress
- Description
kamify-console,ingressClassName: traefik,hostadmin.kamify.store
- Name
- 入口链路
- Description
- account-B Cloudflare Tunnel(k3s-yldm,
2b822c22)→ Traefik :80;TLS 在 Cloudflare 边缘终止(proxied),无 cert-manager 证书 / tls 块
- Name
- DNS
- Description
kamify.store/www/admin→ tunnel(proxied),经 Cloudflare API 带外创建;该 zone 不由 external-dns 托管,故无 external-dns 注解
- Name
- NetworkPolicy
- Description
allow-kamify-ingress:app 命名空间默认 default-deny-ingress,此策略放行来自kube-system(Traefik)到app=kamify的:3000;不放则请求返回 502。egress 不受限,应用可直达 Supabase pooler / Stripe / Resend
- Name
- PDB
- Description
kamify-frontend与kamify-console各一个,maxUnavailable: 1
- Name
- 监控
- Description
- manifest 中无 ServiceMonitor / PrometheusRule / HPA / VPA
注意事项
镜像 tag 由 argocd-image-updater 以 semver 策略写回 kustomization images::build-image 在 vX.Y.Z git tag 上推送 :X.Y.Z,updater 跟踪最高 semver 并写回 newTag。不要手工 pin 版本与之对冲。
OAuth 回调依赖 NEXT_PUBLIC_APP_URL:应用读的是它(不是 NEXT_PUBLIC_SITE_URL)作为 OAuth 重定向基址,缺失会让 signInWithOAuth 回退到 HOSTNAME:PORT(0.0.0.0:3000),Google 登录因此回跳到错误地址。
ExternalSecret 是全或无:yldm/production/kamify 下任一被引用的 property 缺失,整个 ExternalSecret 同步失败,Secret 不生成,Pod 起不来。新增 Stripe 等密钥时务必先在 Vault 写好再在 manifest 中引用。
Cloudflare Tunnel 路由是前置条件:applications/networking/cloudflare-tunnel-yldm 的 tunnel 配置必须把 kamify.store / *.kamify.store 路由到 Traefik,本服务才能收到流量。
返回 app 服务总览