kamify · 双 Next.js 应用(公开站点 + 管理后台)

kamify 由两个独立的 Next.js(standalone 模式)Deployment 组成,同处 app 命名空间:kamify-frontend 对外提供公开站点 kamify.storekamify-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-frontendkamify-console
  • Name
    镜像 / 版本
    Description
    ghcr.io/kamify-ai/kamify-frontendghcr.io/kamify-ai/kamify-console,kustomization images: 中 newTag 均为 0.1.5(deployment.yaml 内联写的是占位 0.1.0,以 kustomization 为准);拉取用 ghcr-secret
  • Name
    副本数
    Description
    各 2(kustomization replicas: 显式设为 2)
  • Name
    调度
    Description
    nodeSelector: workload=apppriorityClassName: production-medium;各自带 podAntiAffinity(preferred,topologyKey kubernetes.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 512MirevisionHistoryLimit: 2

探针上有差异:kamify-frontend 用 HTTP 探针打 /api/health:3000kamify-console 用 TCP tcpSocket:3000

配置与依赖

非密配置在 ConfigMap kamify-config(前台、后台都 envFrom 引入),密钥走 ExternalSecret kamify-secrets 从 Vault 拉取。

ConfigMap kamify-config 关键项:

说明
NODE_ENVproduction
PORT / HOSTNAME3000 / 0.0.0.0Next.js standalone 监听
NEXT_PUBLIC_SUPABASE_URLhttps://wmpfvtqzzpfktruylioi.supabase.coSupabase 项目(project ref wmpfvtqzzpfktruylioi,区域 ap-northeast-2)
NEXT_PUBLIC_SITE_URLhttps://kamify.store
NEXT_PUBLIC_APP_URLhttps://kamify.storeOAuth 回调基址;缺它会让 Google 登录回跳到 0.0.0.0:3000
NEXT_PUBLIC_FRONTEND_URLhttps://kamify.store后台用它链回公开站点

ExternalSecret kamify-secrets 经 ClusterSecretStore vault-backend(KV v2)从 yldm/production/kamify 拉取,refreshInterval: 1h,secretKey 即应用读取的环境变量名:

环境变量Vault property用途
DATABASE_URLdatabase_urlSupabase transaction pooler DSN(端口 6543,?pgbouncer=true
APP_DATABASE_URLapp_database_urlsession-mode pooler(端口 5432),用于迁移 / RLS role
ENCRYPTION_KEYencryption_key32+ 字节 AES 密钥(静态加密)
SUPABASE_SERVICE_ROLE_KEYsupabase_service_role_key
NEXT_PUBLIC_SUPABASE_ANON_KEYsupabase_anon_key公开 anon key,与其余 Supabase 配置一并放 Vault
SUPABASE_MANAGEMENT_TOKENsupabase_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-frontendingressClassName: traefik,host kamify.storewww.kamify.store,注解 nginx.ingress.kubernetes.io/proxy-body-size: 5m
  • Name
    后台 Ingress
    Description
    kamify-consoleingressClassName: traefik,host admin.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-frontendkamify-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:PORT0.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 服务总览

评论