Excalidraw 地区部署:子目录与子域名的架构权衡
在分布式团队成为常态的今天,一个能“随手画两笔”的白板工具,往往比复杂的流程图软件更能激发协作灵感。Excalidraw 正是这样一款让人眼前一亮的开源手绘风格白板工具——它不追求像素级精准,却用潦草线条还原了真实会议中的思维流动。随着越来越多企业尝试将其纳入内部协作体系,一个问题逐渐浮现:当团队遍布全球时,如何部署 Excalidraw 才能让印度的工程师和德国的产品经理都能流畅协作?
表面上看,这只是个 URL 设计问题:我们该让用户访问company.com/excalidraw/eu还是eu.whiteboard.company.com?但深入下去,你会发现这背后牵扯出一套完整的架构决策链——从 DNS 解析到反向代理,从证书管理到 Cookie 作用域,每一个选择都在悄悄影响着系统的可维护性、性能表现甚至安全边界。
路由的本质:路径还是主机名?
我们先抛开 Excalidraw 本身,思考一个更根本的问题:服务该如何被寻址?
在 Web 架构中,有两种主流方式来区分不同实例:
- 基于路径(Path-based):通过 URL 路径前缀划分逻辑区域,如
/excalidraw/us - 基于主机名(Host-based):通过子域名划分独立上下文,如
us.excalidraw.example.com
这两种方式看似只是写法差异,实则代表了两种截然不同的系统设计理念。
子目录方案:统一入口下的逻辑隔离
想象你是一家初创公司的运维,老板说:“下周上线白板功能,集成进现有门户就行。”这时候,子目录几乎是唯一合理的选择。
它的核心优势在于“收敛”:
- 单一域名意味着一张 SSL 证书搞定全站 HTTPS;
- 所有流量经过同一个 Nginx 或 Traefik 实例,日志、认证、监控可以集中处理;
- 用户无需记住新地址,直接从主站导航进入即可。
实现起来也很直观。以下是一个典型的 Nginx 配置片段:
location /excalidraw/us/ { proxy_pass http://excalidraw-us-backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location /excalidraw/eu/ { proxy_pass http://excalidraw-eu-backend/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }这段配置完成了三件事:
1. 按路径前缀将请求路由到对应后端;
2. 保留客户端原始信息用于审计;
3. 支持 WebSocket 升级,确保实时协作正常。
但别忘了,前端构建也得配合调整。比如使用 React 或 Vue 时,必须设置PUBLIC_URL=/excalidraw/us,否则资源会加载失败。SPA 路由还需要 fallback 到index.html,否则刷新页面会出现 404。
这种模式下,所有区域共享同一套安全策略。CORS、Content-Security-Policy、Cookie 设置都是全局生效的。好处是简单,坏处是灵活性差——如果你某天想给欧洲实例启用更严格的 GDPR 合规策略,就会发现很难单独控制。
子域名方案:真正的多区域独立运行
当你开始面对跨国客户,尤其是金融、医疗这类对延迟敏感且合规要求高的行业时,子域名就成了更自然的选择。
它的本质是“解耦”:每个地理区域拥有独立的身份标识。
这意味着你可以为us.excalidraw.company.com和eu.excalidraw.company.com分别配置:
- 不同的 TLS 证书(支持通配符或 SAN)
- 独立的 CDN 缓存策略
- 基于 GeoDNS 的智能解析
- 区域专属的安全组和 WAF 规则
在 Kubernetes 环境中,这通常表现为一组 Ingress 规则:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: excalidraw-ingress annotations: nginx.ingress.kubernetes.io/proxy-body-size: "10m" cert-manager.io/cluster-issuer: letsencrypt-prod spec: tls: - hosts: - us.excalidraw.example.com secretName: us-excalidraw-tls - hosts: - eu.excalidraw.example.com secretName: eu-excalidraw-tls rules: - host: us.excalidraw.example.com http: paths: - path: / pathType: Prefix backend: service: name: excalidraw-us-service port: number: 80 - host: eu.excalidraw.example.com http: paths: - path: / pathType: Prefix backend: service: name: excalidraw-eu-service port: number: 80这套配置的价值不仅在于路由本身,更在于它打开了自动化的大门。结合 cert-manager,证书签发完全透明;配合 GitOps 流水线,新增一个亚太区域只需提交一条 PR,CI 自动创建 DNS 记录 + Ingress + 监控面板。
更重要的是,DNS 层就能完成地理路由。用户访问ap.excalidraw.company.com时,Cloudflare 或 Route 53 可以直接返回最近边缘节点的 IP,首跳延迟显著低于子目录方案(后者仍需先抵达主站再由 L7 路由转发)。
当然,代价也随之而来:
- 每个子域名都需要备案(在中国等地区尤为麻烦);
- 若需跨子域名共享登录状态,Cookie Domain 必须设为
.excalidraw.company.com; - CORS 策略需要显式允许多个来源;
- 成本上升——CDN 按域名计费时可能翻倍。
架构对比:不只是技术选型,更是组织能力的映射
我们可以把两种方案放在几个关键维度上做一次横向拆解:
| 维度 | 子目录 | 子域名 |
|---|---|---|
| 部署复杂度 | 低。修改反向代理即可 | 中高。需协调 DNS、证书、Ingress |
| 扩展性 | 新增区域需改配置,易出错 | 新增区域仅需加记录,适合自动化 |
| 性能优化空间 | 依赖 CDN 路径缓存 | 支持 DNS 级调度 + 独立 CDN 加速 |
| 安全性 | 全局策略,难以差异化 | 可为每个区域定制防火墙/WAF/CSP |
| 运维成本 | 低。集中管理 | 高。需批量管理证书与监控 |
| 用户体验 | URL 稍长,但结构清晰 | 更简洁,感知更快(心理延迟降低) |
有意思的是,这个表格其实反映了一个更深层的事实:你的部署方式往往是团队成熟度的一面镜子。
一个五人小团队,靠手动改 Nginx 配置活着很正常;但当你有专门的 SRE 团队、使用 ArgoCD 做 GitOps、用 Prometheus 实现全域监控时,子域名带来的结构化优势才真正释放出来。
实际落地中的那些“坑”
无论选择哪条路,总有些细节会在深夜把你叫醒。
子目录常见陷阱
前端 base href 忘记设置
- 表现:页面空白,控制台报错大量 404
- 原因:JS/CSS 资源试图从根路径加载
- 解法:构建时注入PUBLIC_URL或动态读取<meta>WebSocket 路径未重写
- 表现:连接建立失败,提示Invalid frame header
- 原因:代理未正确传递 Upgrade 头
- 解法:确保proxy_set_header Connection "upgrade"存在SPA 路由刷新 404
- 表现:直接访问/excalidraw/us/editor/abc返回 404
- 原因:服务器不知道这是前端路由
- 解法:添加 fallback 规则,非 API 请求全部指向 index.html
子域名典型挑战
证书泛滥管理困难
- 解法:使用 ACME 客户端(如 cert-manager)自动签发;优先申请通配符证书(*.excalidraw.company.com)Cookie 跨域失效
- 场景:用户在app.company.com登录后,希望自动登录白板
- 解法:SSO Token 存储在.company.com域下,并通过 OAuth2 授权码流传递DNS 解析不一致
- 表现:部分用户被错误导向远距离节点
- 原因:本地 DNS 缓存或递归解析器未支持 EDNS Client Subnet
- 解法:启用 Anycast IP + CDN 边缘计算兜底(如 Cloudflare Workers 根据 CF-Connecting-IP 重定向)
如何选择?一套实用决策框架
别急着下结论,先问自己这几个问题:
1. 你们有多少个活跃区域?
- ≤2 个:子目录足够应付
- ≥3 个且持续增长:考虑子域名,避免路径越来越深(
/excalidraw/na/us-west这种噩梦)
2. 是否已有成熟的 DevOps 流水线?
- 有 CI/CD + GitOps + 自动化证书管理 → 子域名可行
- 手动部署为主 → 先用子目录稳住局面
3. 对延迟是否极度敏感?
- 内部工具,偶尔卡顿可接受 → 路径路由没问题
- 面向外部客户,SLA 要求 <200ms P95 → 子域名 + GeoDNS 是标配
4. 是否需要独立合规策略?
- 欧盟实例需满足 GDPR 日志脱敏
- 印度实例需数据本地化存储
→ 这些都要求更强的隔离能力,子域名更适合
5. 团队是否有权限操作 DNS?
- 没有独立域名管理权(如受制于集团 IT)→ 子目录是唯一出路
最佳实践建议
不管你最终选择哪种方式,以下几点值得坚持:
✅ 统一身份体系
不要让每个区域都有自己的用户数据库。使用中央 OAuth2 提供商(如 Keycloak、Auth0 或自建),通过 OIDC 实现单点登录。JWT 中携带 region hint,首次访问时自动跳转最优节点。
✅ WebSocket 高可用设计
Excalidraw 的灵魂在于实时协作。每个区域至少部署两个 WebSocket 实例,前面挂负载均衡。使用 Redis Pub/Sub 或 NATS 做房间消息广播,防止单机故障导致会话中断。
✅ 数据持久化策略
- 文件上传走对象存储(S3/GCS),并按区域复制
- 白板元数据可共享数据库(PostgreSQL)+ 读写分离
- 敏感数据分库分表,遵守数据驻留法规
✅ 监控不可少
为每个区域配置独立的指标采集:
- 请求延迟(按路径/主机名标签区分)
- WebSocket 连接数
- 错误率(特别是 5xx 和连接超时)
- CDN 命中率
Prometheus + Grafana 是标配,告警规则要覆盖“跨区域误访”场景(例如 EU 用户连到了 US 实例)。
✅ 渐进式演进路径
不必一开始就追求完美。推荐路线:
阶段一:单实例 + 子目录(快速验证) ↓ 阶段二:多区域 + 子目录(初步扩展) ↓ 阶段三:子域名 + 自动化(成熟架构)很多团队就是在第一阶段跑通 MVP 后,才意识到“原来我们可以做得更好”。
结语
Excalidraw 的魅力,在于它用最简单的线条表达了最复杂的想法。而我们在部署它时所做的每一个技术决策,本质上也是在绘制一幅看不见的架构草图。
选择子目录还是子域名,从来不是一个非黑即白的问题。它更像是在“敏捷交付”与“长期可维护性”之间寻找平衡点的过程。对于大多数团队来说,答案或许不是“永远用哪一个”,而是“什么时候切换到另一个”。
重要的是,无论你站在哪个阶段,都要清楚当前选择背后的 trade-off 是什么。因为真正的工程智慧,不在于掌握多少高级技术,而在于知道何时该克制,何时该突破。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考