fluxa · 多商户支付网关后端

fluxa 是运行在 platform 命名空间的多商户支付网关(backend),源自 everyapi-ai/payment 仓库的 backend/,admin-console 的 SPA(web/)被打进同一镜像由后端同源提供。它由同一个镜像 ghcr.io/yldm-tech/fluxa 派生出四个工作负载:server(HTTP API:商户 API 走 HMAC、admin API 走 JWT、provider webhooks、checkout 页面)、worker(后台循环:watcher 链上扫描与入账、settlement 归集对账、notify webhook 投递、billing、autopayout)、mcp-merchant(商户档案的 MCP 工具面,按商户 API key 隔离)、mcp-admin(运营级 MCP 工具面,OAuth 2.1 + admin session JWT 鉴权)。

server 与 worker 都在 Pod 启动时跑同一套 schema migration 并 bootstrap 初始 admin,迁移在事务级 pg_advisory_xact_lock 下串行化,多 Pod 并发安全。下文事实均来自该服务的 manifest 与目录内 README。

部署形态

  • Name
    命名空间
    Description
    platform(kustomization 设置 namespace: platformnamePrefix: platform-,生成资源名 platform-fluxa-*)
  • Name
    工作负载
    Description
    四个 Deployment 共用一个镜像:fluxa-serverfluxa-workerfluxa-mcp-merchantfluxa-mcp-admin,均 revisionHistoryLimit: 2,均标注 reloader.stakater.com/auto: "true"(ConfigMap/Secret 变更时由 Reloader 触发滚动重启)
  • Name
    镜像与版本
    Description
    ghcr.io/yldm-tech/fluxa:1.0.9(kustomization images: 块;tag 由 argocd-image-updater 按 semver 写回,首发为 0.0.1,容器内 image 字段仍写死 0.0.1 由 kustomize 覆盖。一个 entry 同时覆盖所有工作负载,保持版本一致)
  • Name
    副本数
    Description
    server 2、worker 1、mcp-merchant 2、mcp-admin 1(kustomization replicas 与各 Deployment 一致)。注:ArgoCD 忽略 Deployment 的 /spec/replicas,此值只在首次创建生效,在线扩缩用 kubectl scale
  • Name
    调度
    Description
    所有工作负载 nodeSelector: workload=app;server 用 priorityClassName: production-high,worker / mcp-merchant / mcp-admin 用 production-medium;server 与 mcp-merchant 配 podAntiAffinity 软反亲和(weight 100,topologyKey: kubernetes.io/hostname),按 component 分散副本
  • Name
    端口
    Description
    server 容器 8090(name http)、worker 8081(name metrics,健康/指标共用)、mcp-merchant 8091(name http)、mcp-admin 8092(name http)
  • Name
    滚动策略
    Description
    server 与 mcp-merchant 用 rollingUpdate(maxUnavailable: 0 / maxSurge: 1);worker 与 mcp-admin 用 Recreate
  • Name
    资源
    Description
    server requests cpu 100m / mem 128Mi,limits cpu 1 / mem 512Mi;worker requests 50m/64Mi,limits 500m/256Mi;两个 MCP requests 50m/64Mi,limits 500m/256Mi;unseal init 容器统一 requests 20m/32Mi,limits 200m/64Mi
  • Name
    健康探针
    Description
    四个工作负载同一套:startupProbe 给启动最多 5 分钟(failureThreshold: 60 × periodSeconds: 5),原因是 HTTP 监听器要等 app.Build 通过 RPC seed 链上 channel(可达数十秒),否则 liveness 会在启动中误杀导致 crash-loop;之后 livenessProbe GET /healthzreadinessProbe GET /readyz
  • Name
    存储
    Description
    无业务 PVC。每个 Pod 挂一个 medium: Memory 的 emptyDir(tmpfs)卷 vault-unseal,init 容器把解密后的 APP_SECRET 写到这里,主容器只读挂载,明文不落盘
  • Name
    ServiceAccount
    Description
    专用 SA fluxa,作为 Pod 身份向 Vault kubernetes auth(role fluxa)换取短时 transit/decrypt token

reencrypt-job.yaml(Job fluxa-reencrypt)是 APP_SECRET 轮换的 phase 2 手动一次性作业,刻意不在 kustomization 里,ArgoCD 永不应用,只在轮换密钥时手动 apply(默认 dry-run,加 -apply 才真写)。

配置与依赖

非密配置集中在 ConfigMap fluxa-config,server 与 worker 通过 envFrom 共享:

key值 / 含义
APP_ENVproduction
HTTP_ADDR:8090
APP_SECRET_FILE/vault/secrets/app_secret(读 init 写出的解密文件,而非 APP_SECRET env;应用本身从不连 Vault)
PUBLIC_URLhttps://pay.fluxa.cash(构建 checkout / webhook 回调 / 跳转 URL 的对外基址)
BASE_DOMAINfluxa.cash(每商户子域 <slug>.fluxa.cash 的根域)
TENANT_DNS_ENABLED / TENANT_DNS_PROVIDERtrue / cloudflare(商户设 slug 时自动开 proxied CNAME 指向 account-B Cloudflare Tunnel)
CLOUDFLARE_ZONE_ID / TENANT_DNS_TARGET58eba1cdf91cc00216979e2125b49258 / 2b822c22-21be-4d1e-98d4-c02a83f1cace.cfargotunnel.com
DB_DRIVER / DB_MAX_CONNSpostgres / 20(worker 在自身 Deployment 覆盖为 10,两个 MCP 各覆盖为 10 / 5)
CORS_ALLOWED_ORIGINShttps://pay.fluxa.cash
WORKER_HEALTH_ADDR:8081
KYC_ENFORCEDfalse
ALERT_SMTP_*Lark SMTP:host smtp.larksuite.com、port 587、user/from no-reply@fluxa.cash(密码走 ExternalSecret)
STRIPE_ENABLED / CRYPTO_GATEWAY_ENABLED / AUTO_PAYOUT_ENABLED全部 false(渠道默认关闭,按上线范围逐个开启)
CONFIRMATION_BUFFER2(链上确认数之外的 reorg 安全余量)

密钥经 ExternalSecret fluxa-secrets(ClusterSecretStore vault-backend,refreshInterval: 1h)从 Vault KV 路径 yldm/production/fluxa 拉取,目标 Secret 名固定为 fluxa-secrets(非 namePrefix),各工作负载经 envFrom.secretRef 注入。映射的 key:APP_SECRET(app_secret,32+ 字节随机,AES-256-GCM 静态加密主密钥)、VAULT_TOKEN(vault_transit_token,仅 transit/decrypt/fluxa 的受限 token)、JWT_SECRET(jwt_secret)、ADMIN_PASSWORD(admin_password,prod guard 拒绝 admin12345/空)、DATABASE_URL(database_url)、REDIS_URL(redis_url)、CLOUDFLARE_API_TOKEN(cloudflare_api_token,需 Zone:DNS:Edit)、ALERT_SMTP_PASSWORD(smtp_password)。渠道 / 对象存储 / 告警等额外密钥仅在开启对应功能时再加进 ExternalSecret——Vault 里不存在的 key 会让整次 sync 失败。

依赖:共享集群 Postgres(经 pgbouncer.postgres.svc.cluster.local:5432,专用 fluxa 库,DSN 用 sslmode=disable,fluxa 自管并迁移自己的 schema)、Redis(redis.redis.svc.cluster.local:6379,分布式限流器)、Vault(vault.vault.svc.cluster.local:8200,Transit mount transit、key fluxa,用于 unseal init 解封 APP_SECRET)。

每个工作负载都带一个 unseal init 容器:若 APP_SECRET 是 Vault Transit 密文,则以 k8s-auth 向 Vault 解密并把明文写入 tmpfs;在 APP_SECRET 为明文时该容器 INERT(透传,不调 Vault),因此可在密钥激活前安全部署。

访问与监控

四个 Service:fluxa(server,80→8090,ingress 后端 + 指标抓取)、fluxa-worker(headless clusterIP: None,仅暴露 8081 给 ServiceMonitor,不收 ingress)、fluxa-mcp-merchant(80→8091)、fluxa-mcp-admin(80→8092)。

Ingress 全部走 ingressClassName: traefik,TLS 在 Cloudflare edge 终止(account-B Cloudflare Tunnel → Traefik :80,proxied),因此没有 cert-manager 证书 / tls 块;DNS 由 Cloudflare API 带外创建,external-dns 不托管 fluxa.cash 区。三条 Ingress:

Ingresshost / path后端
fluxaapi.fluxa.cash*.fluxa.cash,path /(/api /admin /webhooks /pay /docs 全到 server,<slug>.fluxa.cash 由应用按 Host 解析租户)fluxa:80
fluxa-mcpmcp.fluxa.cash,path /mcp(router priority 1000 压过 * 通配)fluxa-mcp-merchant:80
fluxa-mcp-adminmcp-admin.fluxa.cash,path /mcp/.well-known/oauth-protected-resource(OAuth 2.1,RFC 9728 发现)fluxa-mcp-admin:80

server ingress 上有 nginx 注解 proxy-body-size: 2m 及 connect/send/read 超时,作为 provider webhook / checkout POST 的外层兜底(应用自身另有每端点限流)。

监控:四个 ServiceMonitor(fluxa / fluxa-worker / fluxa-mcp-merchant / fluxa-mcp-admin),均 interval: 30spath: /metrics,relabel 出 pod / namespace / service;MCP 的 /metrics/mcp 同端口但 ingress 只放 /mcp,故 Prometheus 是唯一读者。PrometheusRule fluxa(group fluxa.rules)告警:FluxaServerDown(critical,2m)、FluxaWorkerDown(critical,3m)、FluxaPodRestarting(warning,5m)、FluxaWebhookDeliveryFailing(>20% 失败,warning,10m)、FluxaExternalErrors(RPC/provider/KYT >30% 错误,warning,10m)、FluxaHighMemory(>85% limit,warning,10m)。

弹性:HPA fluxa-server(min 2 / max 4,CPU 70% + memory 80%);VPA 覆盖 fluxa-serverfluxa-worker,均 updateMode: "Off"(仅给建议,不自动改),server 上限 cpu 1/mem 512Mi,worker 上限 cpu 500m/mem 256Mi。无 PDB。

NetworkPolicy allow-fluxa-ingress:platform 命名空间默认 default-deny-ingress,共享策略只放 app=gateway,所以该策略为 app=fluxa 开两类来源——kube-system(Traefik)到 8090/8091/8092,prometheus 到 8090/8081/8091/8092;Pod 不监听的端口该规则对其为 no-op。

注意事项

以下转述自该服务目录内 README,围绕首次同步、密钥与渠道:

  • 首次 sync 前置:镜像由 Release workflow 自动发布(按 conventional commits 推导 semver);需先在共享 Postgres 建专用 fluxa 库与 role;并在 Vault yldm/production/fluxa 写入 app_secret / jwt_secret / admin_password / database_url / redis_url 等。首发后不要再手改 newTag,argocd-image-updater 是 tag 的 source of truth。
  • APP_SECRET 轮换:应用支持零停机轮换(解密 key ring + 再加密步骤)。流程为 Vault kv patchapp_secret+app_secret_previous → 在 ExternalSecret 加 APP_SECRET_PREVIOUS(property: app_secret_previous)并确认两 key 都落地 → rollout restart server 与 worker → 手动跑 reencrypt-job(先 dry-run 期望 undecryptable=0,再 -apply)→ 从 Vault 与 ExternalSecret 移除 app_secret_previous 再 roll 一次。绝不可原地替换 app_secret,否则既有密文不可解。
  • 渠道默认全关,按上线范围在 ConfigMap 翻 toggle 并把渠道密钥加进 Vault + ExternalSecret。自建链上渠道不走 env,channel 配置以 DB 为权威(channel_configs 表,在 admin console 管理),运行时只读 DB;启用但字段不全的链上渠道会被 fail-fast 拒绝。EVM 路径已生产可用(USDT@BSC live)。
  • 不要启用 TRON 链(KIND=tron):上游 watcher 会把 base58 地址小写化,永远检测不到 TRON 充值;EVM 不受影响。
  • 无 PreSync migrate Job:应用 Pod 启动自迁移已安全(事务级 advisory lock),单独的 PreSync Job 既冗余又会因先于主 wave 创建 ConfigMap/Secret 而卡在 CreateContainerConfigError 阻塞整次 sync。
  • 其他已知缺口:database_urlsslmode=disable 对集群内 pgbouncer 流量可接受但非 TLS;渠道 tolerance 保持 0(否则平台吸收少付差额);disputes 仅追踪、无资金回拨。

返回 platform 服务总览

评论