1. 项目概述:一个由AI驱动的全栈开发平台

最近在折腾一个挺有意思的开源项目,叫Fulling。简单来说,它想干的事儿,是让开发者能真正“专注写代码”,把那些繁琐的配置、部署、环境搭建都交给AI去处理。这听起来有点像“云IDE”或者“在线开发环境”的升级版,但它的核心玩法更激进: 配置驱动开发

想象一下这个场景:你想给你的Next.js应用加个Stripe支付,或者集成GitHub OAuth登录。传统做法是,你得去官网看文档,装SDK,配环境变量,写集成代码,调试……一套流程下来,半天就没了。在Fulling里,你只需要在项目设置里,把Stripe的API Key或者GitHub OAuth的Client ID填进去。然后,平台内置的AI编程助手(目前是Claude Code)会“读懂”你的配置,自动帮你生成、修改代码,实现对应的功能。你只需要审核、微调,或者直接告诉AI“这里改一下”。

这背后的技术栈相当“全栈”:前端是Next.js 16 + React + TypeScript + Shadcn/ui这套现代组合拳,后端跑Node.js,数据层用PostgreSQL配Prisma ORM,而整个平台的运行时环境则构建在Kubernetes之上。每个用户的项目(他们称之为“沙盒”)都是一个独立的Kubernetes命名空间,里面跑着一个定制化的Docker镜像,集成了Web终端、文件管理器、应用运行时和Claude Code CLI。

这个项目目前处于v2的开发阶段,正在向“智能体化”(Agentic)架构重构,所以文档里会提示有破坏性更新。但对于我们这些喜欢折腾前沿技术的开发者来说,这正是深入理解其设计思路和实现细节的好时机。接下来,我会结合官方文档和我的实际探索,拆解一下Fulling的核心设计、实操要点以及我踩过的一些坑。

2. 核心架构与设计思路拆解

2.1 为什么是“配置驱动”与“AI编程”的结合?

Fulling的核心理念,我认为是在解决一个老生常谈但始终没被完美解决的问题: 开发者的认知负荷与上下文切换

我们花在写业务逻辑上的时间,可能远少于花在:查某个库的API文档、调试构建配置、处理数据库迁移、配置CI/CD流水线、设置反向代理和SSL证书……这些“支撑性”工作上。Fulling的思路是,把这些工作抽象成“配置”。你需要什么服务,就在UI里填上必要的密钥或配置项。然后,由一个足够聪明的“智能体”(AI)来理解这些配置的意图,并自动执行对应的代码生成和环境适配工作。

这比单纯的“低代码”平台更灵活,因为你依然拥有完整的代码控制权,AI只是你的超级助手;也比传统的云IDE更“主动”,因为它不止提供环境,还试图理解你的项目并主动提供实现。

技术选型背后的考量:

  • Next.js (App Router) :选择最新的App Router而非Pages Router,显然是押注未来。App Router对服务端组件、流式渲染、嵌套布局的原生支持,更适合构建复杂的、数据驱动的管理平台。Fulling的管理界面需要实时反映沙盒状态(创建中、运行中、错误),App Router的架构能更优雅地处理这些异步状态。
  • Kubernetes + 定制Runtime :用Kubernetes来隔离和管理每个用户的沙盒环境,是资源调度和隔离的“标准答案”。但关键在于他们构建的 fullstack-web-runtime 镜像。这个镜像不是一个简单的Node环境,它预装了:
    1. ttyd :提供浏览器内完整的Linux终端体验。
    2. FileBrowser :一个轻量级的Web文件管理器,支持上传下载和在线编辑。
    3. Claude Code CLI :这是AI编程能力的核心接入点。
    4. 你的应用代码和依赖。这相当于把一个完整的、带辅助工具的云端开发机,打包成了一个Pod。
  • PostgreSQL + Prisma :用关系型数据库来管理平台自身的元数据(用户、项目、配置)很合适。Prisma的强类型和直观的数据模型,能很好地与TypeScript后端配合,减少数据层的心智负担。值得注意的是,每个用户沙盒里的 应用数据库 ,也是通过KubeBlocks动态创建的PostgreSQL实例,与平台元数据库是物理隔离的,安全性更好。

2.2 事件驱动与调和(Reconciliation)模式

在翻阅其架构文档时,我发现Fulling v2的一个重要设计是采用了 事件驱动架构 调和模式 。这通常是Kubernetes控制器和Infrastructure-as-Code工具(如Terraform)的核心思想,用在这样一个应用平台里很巧妙。

它是怎么工作的?

  1. 期望状态(Desired State) :当你在前端点击“创建项目”或“添加Stripe配置”时,平台会在数据库中记录下你期望的项目状态(比如,需要运行一个Next.js应用,并连接一个PostgreSQL数据库,且集成了Stripe)。
  2. 生成事件 :这个状态变更会触发一个事件(如 ProjectCreated ServiceConfigured ),进入事件队列。
  3. 调和循环(Reconciliation Loop) :后台的“调和器”(Reconciler)会持续监听这些事件。它的职责是检查 当前实际状态 (通过查询Kubernetes API:Pod存在吗?Service暴露了吗?数据库实例创建了吗?)是否与 期望状态 一致。
  4. 执行差异(Drift Correction) :如果不一致,调和器就会调用对应的管理器(如 SandboxManager , DatabaseManager )去执行操作,让实际状态向期望状态靠拢。比如创建缺失的Pod,或者更新应用的ConfigMap以注入新的环境变量。
  5. 状态更新 :操作完成后,更新数据库中的状态标记,可能再触发新的事件(如 SandboxReady )。

这样设计的好处:

  • 容错性强 :如果某个创建Pod的操作失败了,调和器下次循环时会再次尝试,直到成功。
  • 状态清晰 :平台在任何时刻都清楚地知道每个项目“应该是什么样”和“实际是什么样”。
  • 易于扩展 :要支持新的服务(比如加入Redis或Elasticsearch),只需要定义新的“期望状态”模型,并编写对应的调和逻辑与管理器即可。

注意 :实现一个健壮的调和器并不简单,需要考虑事件幂等性(同一事件处理多次结果要一致)、错误重试策略、状态锁(防止并发调和导致混乱)等问题。Fulling的代码里可以看到对 @prisma/client 事务和 bull 队列的运用来处理这些挑战。

3. 核心服务模块深度解析

3.1 SandboxManager:沙盒生命周期管家

SandboxManager 是平台最核心的组件之一,位于 lib/k8s/sandbox-manager.ts 。它负责与Kubernetes API交互,管理用户沙盒(StatefulSet)的整个生命周期。

关键操作解析:

  • 创建沙盒 :不仅仅是 kubectl create 一个StatefulSet。它需要:
    1. 根据用户选择的模板(如Next.js, Node.js),决定使用哪个Docker镜像。
    2. 生成唯一的沙盒标识符(通常与Kubernetes Namespace名称绑定)。
    3. 创建专属的Namespace(实现资源隔离)。
    4. 创建PVC(PersistentVolumeClaim)用于持久化存储用户的代码和 node_modules ,这样重启Pod不会丢失数据。
    5. 创建ConfigMap,里面包含应用运行所需的环境变量,比如数据库连接字符串(从 DatabaseManager 获取)、第三方API密钥等。
    6. 最后,创建StatefulSet,并挂载上述PVC和ConfigMap。
  • 暴露服务 :创建对应的Kubernetes Service和Ingress。Fulling使用了类似 [sandbox-id].fulling.example.com 的域名模式,通过Ingress Controller(如Nginx Ingress或Traefik)提供HTTPS访问,并配置WebSocket以支持终端功能。
  • 销毁沙盒 :当用户删除项目时, SandboxManager 需要按顺序清理资源:删除Ingress -> 删除Service -> 删除StatefulSet -> 删除PVC -> 删除Namespace。 这里有个坑 :PVC的删除策略默认可能是 Retain ,如果不显式删除,会导致存储卷残留,占用集群资源。Fulling的代码里应该会处理这个清理逻辑。

资源限制的考量: 在StatefulSet的定义中,可以看到CPU和内存的 requests limits requests 是调度保证, limits 是硬性上限。为沙盒设置 limits 至关重要,防止某个用户的错误代码(如内存泄漏或死循环)拖垮整个Kubernetes节点,影响其他用户。这也是云平台多租户隔离的基本要求。

3.2 DatabaseManager:动态数据库即服务

DatabaseManager ( lib/k8s/database-manager.ts ) 的职责是为每个沙盒动态提供数据库实例。Fulling选择了 KubeBlocks 而不是直接创建PostgreSQL Pod,这是一个更生产级的做法。

为什么用KubeBlocks? KubeBlocks是一个Kubernetes上的数据库运维引擎,它可以管理数据库的生命周期(安装、配置、备份、恢复、监控),并提供了统一的自定义资源(CRD)接口。对Fulling来说:

  1. 声明式API :只需要创建一个 Cluster 自定义资源,KubeBlocks的控制器就会自动帮你拉起一个高可用的PostgreSQL集群(根据配置可能是单实例或一主多从)。
  2. 运维简化 :备份、扩缩容、版本升级这些头疼的事,可以交给KubeBlocks或通过其API简化。
  3. 凭证管理 :KubeBlocks在创建集群时会自动生成管理员密码,并存入一个Kubernetes Secret中。 DatabaseManager 只需要读取这个Secret,将连接信息组装成连接字符串,再注入到沙盒的ConfigMap里即可。用户和平台都无需手动管理数据库密码。

连接安全 :最佳实践是让数据库实例和沙盒Pod在同一个Kubernetes集群内,通过Service名称进行内部网络通信,而不是暴露到公网。这既减少了攻击面,也降低了网络延迟。

3.3 认证与终端安全

认证模块 ( lib/auth.ts ) 支持多提供商OAuth(GitHub、密码、Sealos)。这里重点说一下 终端安全 ,因为给用户开放一个Web Shell是风险很高的操作。

Fulling的方案是:

  1. 身份验证 :用户必须先通过平台主认证登录。
  2. 动态令牌 :当用户点击进入某个沙盒的终端时,后端会为该会话生成一个一次性的、有时效性的令牌(Token)。
  3. 代理与注入 :终端服务(ttyd)本身配置了HTTP Basic认证。前端在请求终端WebSocket连接时,平台会作为一个代理,将动态令牌作为查询参数注入到指向ttyd服务的内部请求URL中。ttyd服务被配置为信任这个令牌。
  4. 命名空间隔离 :每个沙盒的ttyd服务只运行在其专属的Namespace中,用户即使通过终端获得了容器内的shell权限,也被限制在自己的Namespace内,无法访问平台或其他用户的资源。

这个设计在便利性和安全性之间取得了不错的平衡,但实施时需要仔细检查网络策略(Network Policies),确保Pod间默认的网络隔离是生效的。

4. 本地开发与部署实操指南

4.1 环境准备与踩坑记录

按照官方README操作前,你需要一个比较“重”的环境:

  1. Node.js 22.12.0+ :建议用nvm管理,确保版本匹配。
  2. PostgreSQL :用于平台自身的元数据库。本地开发可以用Docker跑一个: docker run --name fulling-db -e POSTGRES_PASSWORD=yourpassword -p 5432:5432 -d postgres:14
  3. Kubernetes集群 :这是最大的门槛。本地可以用 minikube kind Docker Desktop 内置的Kubernetes。 个人强烈推荐kind ,因为它轻量且创建集群快。你需要确保 kubectl 能连接到你的集群。
  4. KubeBlocks :需要在你的K8s集群里安装。可以按照其官方文档,通常就是一条 helm install 命令。
  5. GitHub OAuth App :因为平台集成了GitHub登录,你需要去GitHub Developer Settings创建一个OAuth App,获取 CLIENT_ID CLIENT_SECRET 。回调URL(Callback URL)设为 http://localhost:3000/api/auth/callback/github

实操心得与避坑:

  • 依赖安装 :项目用 pnpm ,记得全局安装 ( npm i -g pnpm )。如果 pnpm install 失败,可能是Node版本或网络问题,可以尝试删除 node_modules pnpm-lock.yaml 重试,或使用 --force 标志。
  • 环境变量 .env.local 文件里的 DATABASE_URL 要指向你刚启动的PostgreSQL实例。 KUBECONFIG 环境变量或默认的 ~/.kube/config 文件必须包含对你K8s集群的有效配置。 这里最容易出错 ,务必用 kubectl get nodes 确认连接成功。
  • 数据库初始化 npx prisma generate 会根据 prisma/schema.prisma 生成TypeScript客户端代码。 npx prisma db push 会将数据模型推送到数据库,创建表。如果失败,检查 DATABASE_URL 和网络连通性。
  • KubeBlocks安装后 :用 kubectl get pods -n kb-system 查看KubeBlocks相关Pod是否都运行正常。有时需要等待几分钟所有组件才就绪。

4.2 运行与初步探索

运行 pnpm run dev 启动开发服务器后,访问 http://localhost:3000

  1. 首次登录 :你会被引导到登录页,选择GitHub登录。输入你刚创建的OAuth App的客户端信息。登录成功后,你应该能看到平台的主界面。
  2. 创建第一个沙盒 :点击“New Project”,你可以选择“Import from GitHub”或“Start from Template”。为了快速体验,选一个模板(比如Next.js)。给项目起个名,点击创建。
  3. 观察后台 :此时,打开你的终端,观察应用日志。同时,打开另一个终端,使用 kubectl get ns,all,ingress -w 命令来实时观察Kubernetes资源的变化。你会看到一个新的Namespace被创建,接着PVC、ConfigMap、StatefulSet、Service、Ingress等资源依次出现。这个过程可能需要1-2分钟,取决于镜像拉取速度。
  4. 进入沙盒 :创建成功后,在项目列表点击进入。你会看到一个类似IDE的界面,左侧是文件树,中间是代码编辑器(或终端、文件管理器),右侧可能是预览窗口。尝试在终端里输入 ls -la npm run dev ,感受一下。

4.3 体验“配置驱动开发”

这是Fulling的精髓。在项目设置(Settings)里,找到“Services”或“Integrations”选项卡。

  1. 模拟添加服务 :假设你想加一个环境变量 NEXT_PUBLIC_API_URL=https://api.example.com 。在配置界面添加这个键值对,保存。
  2. 观察变化 :保存后,平台后端会触发一个“配置更新”事件。调和器会捕获这个事件,然后调用 SandboxManager 去更新对应沙盒的ConfigMap,并滚动重启(Rolling Update)StatefulSet中的Pod,使新的环境变量生效。
  3. AI编程体验 :在代码编辑器中,打开一个页面文件(如 app/page.tsx )。在侧边栏或某个命令面板中,激活Claude Code。你可以用自然语言描述需求,比如:“在这个页面上添加一个按钮,点击后调用 /api/hello 端点,并把结果显示在下面。” Claude Code会尝试理解你的项目上下文(通过读取当前文件和相关文件),然后生成或修改代码。 注意 :这需要你正确配置Claude Code的API密钥(在平台设置中),并且可能需要付费额度。

5. 生产环境部署考量与问题排查

5.1 从开发到生产的挑战

把Fulling部署到生产环境,供真实团队使用,会面临一系列新问题:

  1. 资源成本与配额 :每个沙盒都是一个完整的K8s Pod,消耗CPU、内存和存储。你需要:
    • 在Kubernetes集群层面设置ResourceQuota,限制每个命名空间(即团队或用户)的总资源使用量。
    • 在Fulling平台层面,设计合理的计费或配额模型,防止资源滥用。
    • 考虑使用集群自动伸缩(Cluster Autoscaler)来应对弹性需求。
  2. 网络与域名 :生产环境需要真实的域名和SSL证书。你需要:
    • 配置一个通配符域名证书(如 *.apps.yourcompany.com ),并配置Ingress Controller使用它。
    • 或者,使用Let‘s Encrypt的cert-manager为每个沙盒子域名自动签发证书。
    • 考虑网络策略,确保沙盒只能访问必要的内部服务(如数据库)和外部互联网(用于安装npm包),但不能访问其他沙盒或平台核心组件。
  3. 数据持久化与备份 :用户的代码和数据库数据是无价的。
    • PVC的StorageClass应使用可靠的、支持快照的云存储(如AWS EBS、GCP PD)。
    • 需要实现定期备份策略,备份PVC数据和应用数据库(利用KubeBlocks的备份功能)。
    • 设计项目导出/导入功能,让用户能随时拿走自己的代码和数据。
  4. 安全性加固
    • 镜像安全 :定期扫描和更新基础镜像以及 fullstack-web-runtime 镜像中的漏洞。
    • Pod安全策略 :使用PodSecurityPolicy或更新的Pod Security Standards来限制容器的权限(如禁止特权模式、限制能力Capabilities)。
    • 网络策略 :如前所述,实施严格的网络策略。
    • 审计日志 :记录所有用户操作(登录、创建项目、修改配置)和平台管理操作(调和事件、资源创建删除),便于事后审计和故障排查。

5.2 常见问题排查实录

在搭建和测试过程中,我遇到了不少问题,这里总结一下:

问题1:沙盒创建失败,一直处于“Pending”或“ContainerCreating”状态。

  • 排查思路
    1. kubectl describe pod <pod-name> -n <sandbox-namespace> 查看Pod事件。最常见的原因是 镜像拉取失败 (ImagePullBackOff)。检查 fullstack-web-runtime 镜像是否存在于你配置的容器镜像仓库(如Docker Hub、私有仓库),以及集群节点是否有拉取权限。
    2. 如果事件显示“Insufficient cpu/memory”,说明集群资源不足,需要扩容节点或调整沙盒的资源 requests
    3. 如果是“PVC pending”,检查StorageClass配置是否正确,以及持久卷(PV)是否充足。
  • 解决 :确保镜像可访问,检查资源配额,确认存储配置。

问题2:能创建沙盒,但无法通过浏览器访问(HTTPS/域名问题)。

  • 排查思路
    1. kubectl get ingress -n <sandbox-namespace> 查看Ingress资源状态。ADDRESS字段是否为空?如果为空,说明Ingress Controller没有为其分配外部IP或主机名。
    2. 检查Ingress Controller的日志。如果是Nginx Ingress,可以 kubectl logs -n ingress-nginx <ingress-controller-pod>
    3. 本地开发时,你可能需要修改hosts文件,将沙盒子域名(如 project123.localhost )指向 127.0.0.1 。生产环境则需要配置DNS。
  • 解决 :确认Ingress Controller运行正常,检查Ingress配置中的主机名和TLS部分,排查DNS或网络策略。

问题3:终端(ttyd)无法连接,或连接后立即断开。

  • 排查思路
    1. 浏览器开发者工具(F12)的Network标签页,查看WebSocket连接(ws://或wss://)的状态码。如果是403,可能是认证令牌问题;如果是101 Switching Protocols失败,可能是网络策略或Ingress对WebSocket的支持未开启。
    2. 检查ttyd Pod的日志: kubectl logs -n <sandbox-namespace> <ttyd-pod-name>
    3. Ingress注解需要支持WebSocket。对于Nginx Ingress,需要添加注解 nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" ,并确保 nginx.ingress.kubernetes.io/websocket-services: "ttyd-service"
  • 解决 :检查Ingress的WebSocket配置,验证认证令牌的生成和验证逻辑,查看ttyd服务本身是否正常启动。

问题4:AI编程(Claude Code)没有反应或报错。

  • 排查思路
    1. 首先确认你在平台设置中正确配置了Claude Code(或其他LLM)的API密钥,并且额度充足。
    2. 查看浏览器控制台和后端服务器日志,看AI相关的API请求是否成功发出,以及返回了什么错误信息。
    3. 可能是沙盒容器内Claude Code CLI的版本或配置问题。进入沙盒终端,尝试手动运行一条Claude Code命令,看是否报错。
    4. 网络问题:沙盒容器是否能访问外部的AI API端点(如api.anthropic.com)?检查集群的网络策略或出口(Egress)规则。
  • 解决 :检查API密钥和配置,测试网络连通性,查看具体错误日志。

这个项目把很多云原生和AI辅助开发的前沿想法做了集成验证,虽然目前还处于快速迭代的开发阶段,但其中的设计模式和技术选型,比如用Kubernetes Operator思维管理应用生命周期、事件驱动调和、以及将AI深度融入开发工作流,都非常有借鉴意义。对于想深入理解现代云平台架构,或者探索下一代开发工具形态的工程师来说,Fulling是一个值得仔细研究的代码库。

更多推荐