谈古论金 發表於 2025-7-14 10:12:00

支撑 1300 万月活的幕后真相:Figma 是怎么把核心服务搬上 EKS 的?

<blockquote>
<p>本文由 CloudPilot AI 编译,转载请联系marketing@cloudpilot.ai</p>
</blockquote>
<p>近日,设计软件新贵 Figma 正式递交 IPO 申请,有望成为 2025 年规模最大的科技上市案。</p>
<p>2024 年,Figma 实现营收 7.49 亿美元,同比增长 48%;2025 年 Q1 收入达 2.28 亿美元,延续 46% 的高速增长。其平台被广泛应用于 Google Maps、Uber 等知名产品,<strong>月活用户超过 1300 万</strong>,其中 70% 的收入来自企业级大客户。</p>
<p>作为近年来全球增长最快的 SaaS 公司之一,Figma 在短短<strong>四年内营收增长近百倍</strong>。凭借在线协同设计的创新模式,它曾吸引 Adobe 抛出&nbsp;<strong>200 亿美元</strong>的收购报价。</p>
<p>本文由&nbsp;Figma&nbsp;的基础设施团队撰写,解析他们是<strong>如何进行技术选型与架构迁移,从而支撑千万级用户访问,保持平台的高性能与高可用性的。</strong></p>
<h2 id="引言"><strong>引言</strong></h2>
<p>在一家高速成长的公司里,资源显得尤为宝贵。</p>
<p>在 Figma,无论是面向用户的功能还是后端基础设施,我们必须确保所做的每一个决策都能让平台比原先变得更好。</p>
<p>工作流越大、资源需求越高,我们就越需要有信心确保这项工作能够<strong>在合理的时间内完成</strong>,并且不会对用户<strong>造成停机影响。</strong></p>
<p>因此,经过深思熟虑,我们决定将核心服务迁移到 Kubernetes 。以下是我们整个评估、定义范围并执行迁移过程的回顾。</p>
<h2 id="关于-figma-的计算平台"><strong>关于 Figma 的计算平台</strong></h2>
<p>到 2023 年初,我们已经完成了将所有服务容器化的艰巨工作。</p>
<p>当时我们使用的是 AWS 的 Elastic Container Service(ECS)作为容器编排平台——这是一种开箱即用、能快速启动容器化工作负载的优秀编排平台。</p>
<p>为了扎实地建设这块关键基础设施,我们还组建了专门的团队,并开始系统性地思考如何规划接下来的技术路线,为长期发展打下基础。</p>
<p>也正是在这个阶段,我们开始重新思考下一代“计算平台”的构建方向——也就是支撑 Figma 各团队拥有并运维自己服务的那套底层系统。</p>
<p>我们考虑过在 ECS 的基础上构建新系统,但那样做会让我们难以实现许多长期目标中的关键功能。于是我们开始反思:<strong>我们是向一个“局部最优”的路径迭代,还是应该追求“全局最优”?</strong></p>
<p>从更宏观的角度上说,Figma 并不是一家以微服务为核心架构的公司,也无意成为那样的公司。</p>
<p>虽然在某些场景下,比如为了性能隔离或独立部署的需要,我们确实会引入新的服务(比如构建服务用于管理对多个 AI 推理引擎的调用),但整体而言,我们拥有一套强大的核心服务体系,这些服务天然具备模块化与流量隔离的能力。</p>
<p>这意味着我们通常可以通过在现有服务中添加逻辑来支持新产品,而不必创建新的服务。</p>
<p>正因为如此,迁移到 Kubernetes 的想法也变得更加可行:<strong>我们并不需要迁移数以千计的微服务,而是在一套相对稳定、可控的核心服务体系上完成架构升级。</strong></p>
<h3 id="ecs-缺失的-k8s-功能"><strong>ECS 缺失的 K8s 功能</strong></h3>
<p>那么,ECS 有哪些局限性呢?</p>
<p>首先,随着服务复杂度的提升,各个服务负责团队不得不投入越来越多的工程时间,去绕过 ECS 平台本身的一些能力瓶颈。</p>
<p>其中最典型的例子,就是我们<strong>试图在 ECS 上运行 etcd (一种强一致性的分布式共识数据存储)。</strong></p>
<p>由于 ECS 不支持 Kubernetes 中的 StatefulSets(允许 Pod 拥有持久标识的一种控制器),我们不得不通过在 etcd 的启动流程中嵌入自定义代码,用以动态更新集群成员信息。</p>
<p>这个方案不仅复杂,而且极易出错,维护成本也非常高。</p>
<p>而在 Kubernetes 中运行 etcd,就可以直接使用 StatefulSet 提供的固定网络标识机制,方式更稳定、社区支持也更成熟。</p>
<p>另一个限制是,<strong>ECS 无法很好地支持通过 Helm chart 启动一组服务。</strong></p>
<blockquote>
<p>Helm 是 Kubernetes 社区中广泛使用的软件打包与分发工具,特别适用于安装开源软件(OSS)。</p>
</blockquote>
<p>Figma 内部越来越多的团队希望部署如 Temporal(一个专为现代分布式系统设计的工作流编排平台)这类开源服务,但在 ECS 上安装和运维这些服务会非常困难。</p>
<p>因为我们不得不将每一个 Helm Chart 服务手动转写迁移成 Terraform 模块,用基础设施即代码的方式重写部署逻辑,这在实践中是非常繁琐低效的。</p>
<p>我们还遇到许多令人头疼的小问题,比如:在 ECS on EC2 模式下,当某个 EC2 实例出现问题时,我们很难优雅地移除该节点。</p>
<p>而在 Amazon 的 Elastic Kubernetes Service(EKS)中,这个过程就简单得多:你只需将该节点标记为不可调度(cordon),API 服务器就会将 Pod 安全地迁移至其他机器,同时确保 Pod 的关闭流程被妥善执行。</p>
<h3 id="接入-cncf-开源生态"><strong>接入 CNCF 开源生态</strong></h3>
<p>除了上述的功能缺失,运行在 ECS 上也让我们错失了云原生计算基金会(Cloud Native Computing Foundation,CNCF )中丰富的开源技术资源。</p>
<p>当我们开始关注下一代计算平台时,<strong>弹性伸缩</strong>成为我们优先关注的能力。</p>
<p>那时,我们的容器化服务几乎都没有启用弹性伸缩机制,为了应对高峰期的流量,即使是在夜间或周末流量较低的时候,我们仍然保持所有服务的满负载配置,从而产生了大量不必要的资源浪费和成本开销。</p>
<p>虽然 ECS 提供了一定的自动伸缩支持,但相比之下,Kubernetes 生态拥有更成熟的开源方案,例如 KEDA(Kubernetes Event-driven Autoscaler)。</p>
<p>KEDA 不仅支持基于 CPU 利用率等简单触发条件,还能基于 <strong>AWS SQS 队列长度</strong>,甚至是来自 <strong>Datadog 的自定义指标</strong>来触发伸缩,灵活性远远优于 ECS。</p>
<p>除了自动伸缩,我们还预见未来可能会引入某种形式的<strong>服务网格(Service Mesh)</strong> 。</p>
<p>当时我们是通过 AWS 的应用型负载均衡器 ALB 和网络型负载均衡器 NLB 实现服务之间流量路由的。</p>
<p>这些方式存在一些痛点,例如在 NLB 上注册新目标或移除旧目标可能需要数分钟时间,这会显著拖慢我们应急部署的速度,进而拉高故障修复的平均时间(MTTR)。</p>
<p>相比之下,<strong>Envoy</strong>&nbsp;更灵活、更可定制,支持运行自定义过滤器来实现精细化流量控制。</p>
<p>我们其实已经为某个核心服务搭建了一组独立的 Envoy 实例,作为前置代理,用于在故障期间根据定制逻辑进行流量熔断(load shedding)。</p>
<p>因此我们推测,从长远来看,我们将更倾向于在整个平台范围内全面引入 Envoy 构建服务网格。</p>
<p>在 EKS 的世界里,这样的需求并不陌生,开源社区早已有了丰富的解决方案,比如 Istio 等。</p>
<p>而在 ECS 上,我们若想实现同样的功能,就只能“自己动手,丰衣足食”,不仅过程繁琐,还容易埋下技术债。</p>
<p>除了上述自动伸缩和服务网格这两个典型例子,CNCF 生态仍在不断演进和完善。</p>
<p>我们相信,无论是 Kubernetes 本身,还是其周边工具与平台生态,在未来几年内所获得的关注与投资,将远远超过 AWS 对 ECS 的投入。</p>
<p>这背后的根本原因在于:<strong>Kubernetes 拥有广泛、开放、去中心化的用户基础。</strong></p>
<h3 id="使用主流平台的优势"><strong>使用主流平台的优势</strong></h3>
<p>我们一向尽量避免成为任何服务或软件的“最大用户”。因为这样的用户,往往最先遇到隐藏的 bug、架构瓶颈以及各种扩展性挑战。</p>
<p>而在使用 Kubernetes 这件事上,我们距离“最大用户”还远着呢。</p>
<p>实际上,很多大型公司都在 Kubernetes 上运行着庞大的计算平台,这让我们更有信心:<strong>他们已经帮整个社区踩过不少坑,替我们降低了平台风险,尤其是对于像我们这样运行相对中等规模集群的公司来说。</strong></p>
<p>此外,运行在 Kubernetes 上还能有效避免<strong>供应商锁定</strong>(vendor lock-in)。</p>
<p>从长期来看,如果你的计算平台足够灵活,具备跨云迁移能力,那么你就能始终为自己争取到更好的供应商选择和更优的价格方案。</p>
<p>Amazon EKS 在这方面提供了一个不错的折中方案:我们既能享受到 AWS 提供的托管控制面带来的便利,又由于我们的服务全部以标准 Kubernetes 接口开发,未来无论是迁移到其他云厂商的 Kubernetes 平台,还是搭建自托管集群,<strong>迁移成本都不会太高。</strong></p>
<p>最后,虽然我们相信自己的团队可以快速上手任何技术栈,但事实证明,<strong>具备 Kubernetes 实战经验的工程师更加容易找到。</strong></p>
<p>这使他们能够在入职后迅速进入状态,为我们带来成熟的技术背景和决策参考,帮助我们少走弯路。</p>
<p>总之,这不仅是一次技术选型,更是一场“降低未来复杂度、提升团队能力”的战略投资。</p>
<h2 id="确定迁移范围"><strong>确定迁移范围</strong></h2>
<p>我们坚信,如果操作得当,这次迁移将显著提升 Figma 的计算平台能力。接下来,我们需要验证的是——<strong>能否在一个合理的时间范围内完成这次迁移。</strong></p>
<p>这其中的关键,是对迁移范围的定义要非常谨慎。</p>
<p>对于一次大型迁移来说,最稳妥的做法是:<strong>仅替换核心底座系统,并尽可能保持平台用户侧的抽象不变。</strong></p>
<p>也就是说,所有服务仍然以原有方式运行、部署、交互,只是它们的运行环境从 ECS 转为 EKS。</p>
<p>之所以要如此严格,是因为哪怕是看似“非功能性”的小改动,通常也会引发连锁反应,而这些连锁效应,往往才是拖慢整个大迁移进度的元凶。</p>
<p>当然,这条规则也有两个例外:</p>
<ol>
<li>
<p><strong>如果为了让新系统具备与旧系统相同的表现或功能,需要投入额外的精力去搭建和适配,可能得不偿失。</strong></p>
<p>在这种情况下,与其耗费资源伪装旧系统,不如接受一些必要的变化,直接迭代更新。</p>
<p>幸运的是,我们这次并未遇到太多这类问题,因为 EKS 的功能在很大程度上是 ECS 的超集,原有的大部分功能都能自然承接,无需太多重写或兼容层。</p>
</li>
<li>
<p><strong>有些决策属于一锤定音的“单向门”,一旦选定,未来修改将代价极高。</strong> 这类情况值得我们在一开始就引入新的设计,而不是严格照搬旧系统。</p>
</li>
</ol>
<p>即便我们已经将迁移范围控制得足够精简,这项工作依然需要投入大量时间与资源。</p>
<p>因此,在真正启动迁移之前,我们先明确了一些能够通过此次迁移实现的关键收益,确保整个项目在投入与产出之间具备足够的合理性。</p>
<h3 id="纳入迁移范围的优化事项"><strong>纳入迁移范围的优化事项</strong></h3>
<h4 id="开发者体验"><strong>🟢开发者体验</strong></h4>
<p>在过去的 ECS 工作流中,开发者通常需要通过 Terraform 来创建或修改服务定义。</p>
<p>执行 Terraform 后,它会生成一个包含 0 实例数的 ECS 任务集,作为服务的“模板”,它并不会真正启动任何容器。</p>
<p>随后,开发者需要手动部署服务:克隆这个模板任务集、替换成正确的镜像 hash,并将这个新的任务集部署到 ECS 上,然后设置实例数量(不能为0),才能让服务真正运行起来。</p>
<p>这个流程意味着,哪怕只是简单地新增一个环境变量,也必须先写 Terraform 配置并应用,再进行部署。</p>
<p>而且,<strong>整个流程必须严格按顺序执行</strong>,否则代码可能在变量尚未生效时就被调用,导致一系列 bug。我们中的很多人都曾因为忘记这点而踩坑。</p>
<p>在 EKS 上,我们决定让服务定义集中在一个位置,并支持一键部署。</p>
<p>因此我们开发了一个内部方案:通过 <strong>Bazel</strong> <strong>配置文件</strong>定义服务,CI 系统则会根据该配置自动生成服务定义所需的 YAML 配置,包括服务本身的定义、Ingress 等其他相关资源,并通过我们的自建部署系统进行应用。</p>
<p>我们之所以优先推动这项改动,是因为它符合我们前面提到的两个例外情形:</p>
<ul>
<li>如果继续维持现状(使用旧的 Terraform 流程生成服务定义),将需要投入大量维护成本;</li>
<li>一旦我们未来想要脱离该流程,迁移的代价将会非常高,甚至可能成为架构演进的阻碍。</li>
</ul>
<h4 id="更强的可靠性"><strong>🟢更强的可靠性</strong></h4>
<p>我们通过部署三个独立的 EKS 集群,让每个服务的 Pod 在这三个集群中同时运行并接受流量,从而提升了服务的可靠性。</p>
<p>如果某个操作或故障只影响其中一个集群,最多也只会影响整个服务三分之一的容量。尤其对于支持请求重试或异步运行的服务,这种方式往往能将对用户的影响降到最低。</p>
<p>在实际运行中,我们已经多次见证这种架构的价值——一些 bug 或操作失误本可以导致全量故障,但得益于这种架构,故障波及范围明显缩小。</p>
<p>不过,这种模式在构建和维护上都更复杂,也增加了部署流程的复杂度。但我们还是决定在迁移中直接采用它,而不是事后再添加。</p>
<p>这也契合了 Figma 基础设施团队的主要目标之一:<strong>提升可靠性。</strong></p>
<h4 id="成本优化"><strong>🟢成本优化</strong></h4>
<p>我们并不打算在迁移过程中过度追求复杂的成本优化,唯一的例外是:我们决定从一开始就支持节点自动伸缩。</p>
<p>此前在 ECS on EC2 上,我们都是过度预留大量资源以保证部署期间有足够的容量应对突增,这种方式成本高昂。</p>
<p>因此,我们决定在迁移时引入开源 CNCF 项目&nbsp;<strong>Karpenter</strong>,根据需求动态地帮我们自动弹性伸缩节点,从而以较小代价换来大量成本节省。</p>
<h3 id="不纳入迁移范围的工作"><strong>不纳入迁移范围的工作</strong></h3>
<p>在迁移前,我们的日志处理流程过于复杂,先是将所有日志写入 CloudWatch,然后通过一个 Lambda 函数读取日志,执行脱敏特定内容、添加标签等转换操作,最后再写入 Datadog 和 Snowflake。</p>
<p>CloudWatch 作为中间存储的成本越来越高。</p>
<p>我们计划引入 CNCF 项目 Vector,支持以 sidecar 形式运行,能在 EKS 环境中对日志进行实时处理和转发。</p>
<p>虽然有机会替换现有的日志转发方案,但我们认为迁移时把这个纳入范围代价太大,尤其担心把所有日志转发逻辑迁移到 Vector 配置里可能带来的二次风险,决定留待后续再解决。</p>
<p>另一个例子是:我们在迁移过程中并<strong>没有引入 Pod 层级的自动扩缩容机制</strong>,尽管我们知道这对提升资源利用率有帮助。</p>
<p>主要原因是它会显著增加迁移复杂度,而这部分能力同样可以在后续再补充上线。</p>
<p>这两个功能后来都成为了我们在迁移完成后紧跟上线的 “fast-follow” 项目。即使在继续迁移其他服务到 EKS 的同时,也能逐步推进这些优化。</p>
<p>这样分批推进工作,既能<strong>持续为用户和公司带来收益</strong>,又<strong>避免了迁移过程中过度扩展范围</strong>,同时也让我们<strong>降低了初期迁移的风险</strong>,让初期的迁移工作尽可能快速、安全地落地。</p>
<p><img src="https://img2024.cnblogs.com/blog/3543217/202507/3543217-20250714100814881-1784430605.png"></p>
<h2 id="如何安全地迁移"><strong>如何安全地迁移</strong></h2>
<p>一旦我们确定了一个切实可行的迁移功能范围,接下来就得考虑如何安全地执行这么大规模的迁移。</p>
<p>我们的 ECS 体系相对稳定,所以必须确保迁移过程本身,以及那些未经充分验证的新系统,至少要做到同样可靠。</p>
<p>我们坚持了许多行之有效的工程原则,这些原则值得深入分享,因为它们帮助帮助我们几乎不影响用户地完成了整个迁移。</p>
<p>下面是我们总结的经验准则,供未来执行类似迁移的团队参考:</p>
<h3 id="投入时间做负载测试"><strong>投入时间做负载测试</strong></h3>
<p>这是我们理解新集群在大规模运行下行为表现的关键。</p>
<p>我们做了个“Hello, World”服务,把它扩容到跟 Figma 最大服务一样多的 Pod 数量。通过这个测试,我们发现必须调优很多核心计算服务的规模和伸缩策略,才能支撑起整个大平台。</p>
<p>拿 Kyverno 举例,它是个做集群安全策略校验的工具,如果给它的资源分配不够,就会拖慢新 Pod 启动速度,影响整体效率。</p>
<h3 id="实现渐进式发布机制"><strong>实现渐进式发布机制</strong></h3>
<p>我们使用<strong>加权 DNS 记录来逐步将流量从 ECS 服务迁移到其 EKS 替代服务</strong>,这种细粒度的流量控制能力对于安全迁移至关重要。</p>
<p>我们知道,在迁移过程中肯定会遭遇各种意想不到的问题,而且这些问题往往出现在一些难以预判的临界点。</p>
<p>如果缺乏良好的发布机制,一旦出问题,影响范围会迅速扩大。我们需要一个手段,在最小影响范围内隔离问题并快速回滚。</p>
<p>而加权 DNS 的好处在于:<strong>一旦发现异常,我们可以迅速回滚流量,将影响控制在最小范围内</strong>,大大降低了系统稳定性风险。</p>
<h3 id="尽早在预演环境中跑真实服务"><strong>尽早在预演环境中跑真实服务</strong></h3>
<p>仅靠预演环境的测试无法发现所有问题,只有把真实业务放进去,才能发现更多细节。</p>
<p>我们甚至在预演环境还没完全搭建好之前,就先迁移了一个服务过去,事实证明这样做很有价值。它帮助我们快速降低了整体风险,验证了端到端的运行能力,同时也提前发现了瓶颈和问题,避免了后续更多麻烦。</p>
<h3 id="隐藏原始-yaml简化使用体验"><strong>隐藏原始 YAML,简化使用体验</strong></h3>
<p>如果直接让用户编写 Kubernetes YAML,体验可能会非常混乱。</p>
<p>我们选择为用户定义一条“黄金路径”,同时为特殊情况预留自定义能力。通过明确用户可以/应该自定义的部分,并对其他部分进行默认约束,既节省了用户时间,也简化了未来的维护和系统变更。</p>
<h3 id="与服务负责人密切协作"><strong>与服务负责人密切协作</strong></h3>
<p>我们负责搭建新的服务配置,但在监控和告警的更新上,跟服务负责人保持了密切协作,因为他们最了解自己服务的健康状况。</p>
<p>迁移开始前,我们花了大量时间和负责人一起讨论各种方案并权衡不同方案带来的利弊。尽早获得他们的支持,不仅验证了我们的判断,也让他们意识到参与这项复杂工作的价值。</p>
<h3 id="合理配置团队人员"><strong>合理配置团队人员</strong></h3>
<p>如此规模的迁移,必然伴随许多预料之外的挑战,也需要大量团队精力投入。</p>
<p>我们清楚新平台必然会出现问题、复杂的交互以及各种 Bug,这些都需要深厚的技术功底和调试能力。因此,我们确保组建了一支能够应对这些挑战的专业团队。</p>
<h2 id="我们的迁移过程"><strong>我们的迁移过程</strong></h2>
<ul>
<li><strong>2023 年第一季度:</strong> 我们制定好了上述计划,并获得了执行迁移的批准。</li>
<li><strong>第二季度:</strong> 搭建了预演环境,并成功迁移了第一个服务到该环境。</li>
<li><strong>第三季度:</strong> 集中精力实现生产化,进行负载测试,并准备更多服务进行迁移。</li>
<li><strong>第四季度以及2024年一月初:</strong> 逐步将流量切换到这些服务上。</li>
</ul>
<p>截至 2024 年 1 月,我们已经将大多数优先级最高的服务迁移到了新的 EKS 集群中。</p>
<p>这包括承载核心业务逻辑的“单体应用”、处理 Figma 文件多人编辑复杂场景的服务,以及组成全新 Livegraph 100x 实时更新推送系统的一系列服务。</p>
<p>我们收获了诸多益处:</p>
<ul>
<li><strong>通过避免部署时过度预留节省了成本</strong></li>
<li><strong>依托三集群架构提升了可靠性</strong></li>
<li><strong>开发体验的大幅改善</strong></li>
</ul>
<p>随着平台扩展,我们也对未来服务优化充满期待。</p>
<p>整个迁移过程仅出现了一些轻微事故,几乎没有客户受到影响。其中部分抗风险能力可归功于三集群架构。</p>
<p>比如,我们曾遇到一个操作人员意外销毁并重建了某个生产集群中的 CoreDNS 服务。以往类似事故会导致服务完全中断,但因为有三个集群,影响仅限于三分之一的请求。大多数下游服务通过重试,最终都成功恢复了请求。</p>
<h3 id="上线后的迭代优化"><strong>上线后的迭代优化</strong></h3>
<p>我们为服务负责人开发了调试工具,方便他们查看集群中运行的实例数量、进入容器终端,以及执行紧急操作如扩缩容等。</p>
<p>但上线后不久,我们收到反馈说这些工具对用户来说不够友好。</p>
<p>深入分析后,我们发现主要有两个原因导致工具变复杂:</p>
<ul>
<li>三集群架构要求用户在多个集群间执行命令,且每条命令都要带上集群名称;</li>
<li>迁移过程中与安全团队合作,引入了 Kubernetes 的细粒度 RBAC 权限管理,提升了安全性并遵循最小权限原则,但用户因此需要了解自己拥有的角色和完成特定任务所需的角色。</li>
</ul>
<p>幸运的是,这两个问题都能轻松解决。于是我们立刻暂停了其他工作,更新工具,实现自动推断用户当前应操作的集群和所需角色。</p>
<p>这样一来,用户就不用再花时间去找对权限,尤其是在半夜处理紧急问题时,效率提升非常明显。</p>
<h2 id="下一步计划"><strong>下一步计划</strong></h2>
<p>除了这些上线后的优化,我们现在也在并行推进多个新平台的改进项目,同时继续迁移剩余服务。</p>
<p>目前的重点包括:</p>
<ul>
<li>简化日志处理流水线的设计</li>
<li>通过 Keda 支持 Pod 水平自动伸缩(HPA)</li>
<li>将 Figma 成本最高的服务迁移到 Graviton 处理器以节省费用并为后续服务铺路</li>
</ul>
<p>我们也对尚未深入探索的领域充满期待。</p>
<p>我们认为在网络层面,特别是服务网格方向,还有很大的提升空间,无论是在<strong>可靠性</strong>还是<strong>可观测性</strong>方面。</p>
<p>同时,我们也想进一步简化并统一整个技术栈,比如将更多资源从 Terraform 迁移至 AWS Controllers for Kubernetes(ACK)。</p>
<p>最后,我们计划与开发者体验团队合作,统一开发环境与生产环境的服务运行方式。</p><br><br>
来源:https://www.cnblogs.com/cloudpilot-ai/p/18983470/figma-k8s-migration
頁: [1]
查看完整版本: 支撑 1300 万月活的幕后真相:Figma 是怎么把核心服务搬上 EKS 的?