1.0 概述
Codenvy 是一个云 IDE,大约有 10 万开发者在使用该 IDE 编码、构建和测试应用程序。
本文解释了 Codenvy 的各种技术和网站的架构。
(点击图像放大)
我们的架构是由打算为用户提供的不同内容驱动的:
- Codenvy.com ——一个托管的云 IDE,支持服务协议定义(SLA)和硬件。
- Codenvy Enterprise——允许组织在他们自己的服务器上编码、构建、测试和部署应用程序。
- Codenvy ISV ——使用推动工厂(Promoted Factories)、收费插件(Monetizable Plug-ins)和 IDElets 驱动并度量已发布的 SDK 和 API 的技术约定。一个工厂是一种通过策略启动一个临时的编码、构建、测试和调试工作空间的方法。一个 IDElet 是一个能够插入到其他产品中的可嵌入的编码、构建、测试、调试工作流。
- Codenvy Platform——一个云 IDE 引擎,为开发者提供了一种方法让其能够开发、测试、运行工具插件和应用程序。
Codenvy Platform 是用于交付 Codenvy.com、Codenvy Enterprise 和 Codenvy ISV 的引擎。我们还能使用它创建任何其他有品牌化实现需要的 IDE。该 SDK 的结构和 Eclipse Platform 相似,但是它是针对云环境设计的。该 IDE 还支持插件开发,开发人员能够为构建、运行、测试和调试工作流开发插件,而这些插件通常在 IDE 本身之外运维。
架构讨论将会分为两个部分:1)驱动 Codenvy Platform 的 SDK,2)对 SDK 进行扩展从而创建 Codenvy.com 的组件。
2.0 云 IDE 和桌面 IDE 之间的区别
这两种 IDE 之间的主要技术区别是——典型区别——对于桌面 IDE 而言,IDE 提供商的期望是在工具本身之外安装和管理打包、构建、测试和运行环境。当然并非总是如此,因为确实有一些先进的 IDE 能够将这些额外的组件安装到主机上,但是这并不是很常见。
对于云 IDE 而言,它完全存在于云端;所以除了执行 IDE 之外,云 IDE 提供商通常还必须在一个托管的位置提供构建和运行时环境。
这个区别带来的不仅仅是希望,还有挑战。云 IDE 带来的希望是,开发者能够完全在云端开发,云 IDE 提供商能够提供更多的组件、做的更快并且潜在地降低由配置而引发的错误。挑战是管理一个工作空间的额外成本,现在它不仅包含了一个 IDE,还包含了项目、代码、构建器和运行器。
2.1 云 IDE 工作空间的管理方法
管理云端工作空间通常有两种方法可以采用:
- 为用户分配一个专有的虚拟机,他们在该虚拟机上拥有特权,可以安装那些能够被编辑、构建或者执行的文件以及其他的软件。
- 用户共享合并的均匀规整的资源、IDE 功能(例如重构)命令,构建功能、运行功能分布在不同的集群上并针对每一个功能做了针对性的优化。开发环境越规整,对于操作员而言系统的操作越密集。负面影响便是这些系统不够灵活,也不太可能成为开发者工作上的一个完美的匹配方案。
Codenvy.com 为用户提供了这两种配置。
对于资金计划充裕的用户而言,我们会运行第一个配置,为用户分配一个支持 SSH 选项的专有虚拟机。用户将能够在 2013 年的第 4 季度使用该计划。他们可以在虚拟机上安装不同的构建和运行时软件。然后可以重新配置运行在 Codenvy 系统内部的 IDE,调整构建、运行和调试命令的指向让它们执行驻留在专有虚拟机中的进程。
对于想使用免费计划的用户而言,我们运营了一组集中式服务器,它们被分为了三个集群:一个用于 IDE,一个用于构建,一个用于运行。每一个集群之间存在一个队列系统,它们控制着活动在集群间的前进和后退。每一个集群能够独立地扩展,并且支持不同用户的高密度操作。IDE 集群对它们的 I/O 和内存瓶颈进行扩展,构建集群对技术能力扩展,运行集群对内存扩展。
3.0 CODENVY 平台的 SDK 架构
因为不同的开发者使用的开发工作流可能是不同的,所以我们需要设计一个引擎,该引擎允许创建不同的云 IDE,它们能够改变系统的行为和体验。有一些良好定义的方式能够组织过去这些年由 Eclipse 和 JetBrains 产生的那些插件,同时这些接口能够被扩展到一个多租户的云环境。
SDK 包括:
- 一个云客户端运行时,负责发现、注册、加载和管理插件。该运行时还负责管理一组连接到参与工具工作流系统的多租户入站和出站外部连接。
- 一个云客户端 SDK,能够开发多租户、云客户端插件。该 SDK 为资源、事件和用户界面的使用提供了一个通用的模型。插件能够按照一种可扩展的、良好定义的格式进行集成或分层。
- 一组标准的插件,它们提供与核心开发工具相关的功能,并且在必要的时候可以从运行时中分离出来。目前的插件集合包括:git、Java、CSS、Python、HTML、Ruby、XML、PHP、JavaScript、Browser Shell 和 maven。
- 一个默认的 IDE,它包含了一个结构良好的工作台用于组织代码仓库、项目、文件、构建集成、运行时 / 调试器集成以及部署 / 发布工作流。该 IDE 是一个插件集成包,它交付了一组默认的工具。用户能够通过浏览器或者一组 REST 风格的 Web 服务(表示每个工具的功能)访问该 IDE。
3.1 插件架构
构建一个 IDE 的基础是能够创建、打包、部署并更新一个插件。插件能够按照适合自己的方式进行扩展,并且能够以下面的方式进行分层:一个插件能够调用并扩展另一个插件。
SDK 是一个两层应用程序,它包含 Web 应用程序和服务器端应用程序服务。这两层都是可扩展的,能够由第三方修改。
插件使用 Java、GWT 和 CDI 实现。接口系统自始至终都使用了注入和面向方面的编程,它们是插件扩展、将插件连接到一起并将插件链接进 IDE 本身所使用的技术。通过注入和 CDI 接口系统能够在插件前面创建一个非常干净简单的接口。同时还使用了 GWT,因为它对生成能够在多种浏览器上运行的高性能的标准 JavaScript 代码功能做了一些优化。
下面是一个空白插件,它向 IDE 中添加了一个菜单项,该菜单项的状态在被选中时会发生变化。该插件能够在它自己的工作流中进行编译、测试和验证。为了扩展 Java 类的构造函数使用了注入技术,这些注入能够添加额外的会被传入扩展本身的参数,
package com.codenvy.ide.extension.demo; import com.codenvy.ide.api.editor.EditorAgent; import com.codenvy.ide.api.extension.Extension; import com.codenvy.ide.api.ui.action.ActionManager; import com.codenvy.ide.api.ui.workspace.WorkspaceAgent; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Extension used to demonstrate the IDE 2.0 SDK fetures * * @author <a href="mailto:nzamosenchuk@exoplatform.com">Nikolay Zamosenchuk</a> */ @Singleton @Extension(title = "Demo extension", version = "3.0.0") public class DemoExtension { @Inject public DemoExtension(final WorkspaceAgent workspace, ActionManager actionManager, EditorAgent editorAgent) { menu.addMenuItem("Project/Some Project Operation", new ExtendedCommand() { @Override public Expression inContext() { return projectOpenedExpression; } @Override public ImageResource getIcon() { return null; } @Override public void execute() { Window.alert("This is test item. The item changes enable/disable state when something happend."); } @Override public Expression canExecute() { return null; } @Override public String getToolTip() { return null; } }); } }
3.2 插件服务和 API
插件有各种各样的服务和 API 可以使用。它提供的服务分为三类。
3.2.1 IDE 客户端 API
这些 API 是开发者期望一个平台具有的典型 API,开发者能够借助它们在 IDE 的客户端部分创建可视化扩展。该 API 包括用于首选项(preferences)、菜单(menus)、视图(views)、帮助(helpers)、向导(wizards)、工具栏(toolbars)、选择(selections)、编辑器(editors)、热键(hotkeys)等内容的包。还包括包含 UI 组件和其他实用功能的通用类库的集合。
默认 IDE 还提供了一组顶层的、标准化的视图。这些视图安置了各种视角和面板,能够直接通过插件访问。它包含的视图有 console、editor、project explorer、menus、forms、shell 和 wizards。
3.2.2 IDE 服务器端服务
为了让插件能够访问运行在云环境中的工作空间,有一个标准的 REST 风格的 API 集合,插件可以通过这些 API 与云开发环境交互。云负责管理构建集群、运行集群、存储所有文件的虚拟文件系统、Codenvy 和外部 / 第三方服务之间的连接池、管理用户证书的身份管理服务器、账单 / 订阅的接口以及其他最好在云端而不是在浏览器中进行处理的高计算量的功能。
Codenvy 平台和大部分云 IDE 不同。开发者的工作空间是虚拟化的,它横跨了多种为不同的 IDE 功能服务的物理资源。依赖管理、构建、运行和代码助手能够在不同的物理节点集群上执行。为了在所有的这些资源上进行合适的虚拟化,我们需要实现一个不仅能够支撑服务和物理资源,还能够理解原生 IDE 行为的虚拟文件系统(VFS)。
卸载服务的一个例子便是重构。因为一个重构命令可能需要修改许多文件的内容,包括重命名某些文件、删除某些文件,所以将重构作为一个云服务而不是在浏览器端进行处理将更加有效。这些能力是作为一组 REST 风格的服务暴露的,插件能够直接访问它们。
3.3 插件的生命周期
插件是命令服务器端和客户端按照某种方式运行的代码的组合。因为客户端是 GWT,所以核心 IDE 也是使用 GWT 实现的,加载到系统中的插件每次都必须和 IDE 的剩余部分进行编译从而生成一个新的 IDE。核心 IDE GWT 代码和 GWT 扩展(插件提供商实现)一起创建了一个已编译的二进制代码集,该代码集会生成一套集成的、优化的 JavaScript UI。插件本身是作为一个单独的 JAR 文件打包的,该 JAR 在创建期间会和 IDE WAR 合二为一。
SDK 运行时能够和一个加载的插件集一起运行,然后能够通过配置文件激活或者取消激活应该向用户积极显示的插件。用户能够借助于启动时的插件配置项从整体出发对 IDE 的内存和 CPU 印记进行一些适当的微调控制。
ISV 和其他第三方开发者需要构建并测试他们开发的插件。有三种插件部署模型:
- 独立部署。在一个专用的机器上创建、编译插件,然后使用一个自定义的 Codenvy 运行时(是一个 WAR 文件)启动。这通常是在桌面上完成的,但是也能够作为托管服务实现。在可下载版本的 Codenvy SDK 中有一个标准的插件项目模板和一组相关的 maven 构建命令支持这种打包。
- 寄宿在 SDK 中。位于 codenvy-sdk.com,这是运行 Codenvy 平台的一个影子网站。它提供了一个基础的 IDE,插件能够在这里创建、编译和执行。执行的插件会被加载到一个实例化的 IDE 中。所有这一切都发生在浏览器中,实例化的 IDE 运行在一个不同的 Tomcat 服务器进程中。本质上,开发者是通过一个影子 Codenvy 去创建更多的加载了一个插件的 Codenvy 实例进而进行行为和功能的测试。每一个插件的“运行”都会启动另外一个使用不同 IDE 加载配置的 JVM。
- Codenvy.com。我们将被认可的插件合并进 Codenvy.com 的产品构建中,构建一组 Selenium 测试自动化接口的测试,然后激活它们从而让社区能够使用。
目前,所有加载的插件都被激活了。我们最终将会创建一种机制,允许 Codenvy.com 任命的用户确定哪些插件需要在他们命名的工作空间中活动。
4.0 CODENVY.COM 架构
我们使用让平台具有创建 Codenvy.com 能力的引擎,一个弹性的、托管的、支持的环境。我们将 Codenvy.com 想象为一个持续生存的实体,它由各种各样的不同角色访问。因为这个愿景,Codenvy.com 首先是一个拥有不同接口的系统。开发者使用浏览器利用该系统。Devops 和 ISV 能够通过编程接口访问工作流,按需创建 IDE;同时内部的观众能够通过配置接口查看分析数据,管理用户 / 工作空间,并指示在无人值守的时候系统该如何运行。
(点击图像放大)
4.1 客户端和服务器之间的通信
浏览器通过 WebSocket 和 HTTP REST 连接与 Codenvy 交互。这些连接在工作空间会话创建的时候建立。浏览器和服务器之间的通信是有限的,并且仅限于需要服务器访问的功能。服务器访问功能指访问新文件、定期自动保存、构建服务、事件日志和运行服务。没有心跳,协议也已经被优化为最小化网络流量。
当我们需要一个持续的协作讨论集合的时候我们会使用 WebSocket 通信。在多个用户同时进行编辑和聊天的协作会话期间这是非常常见的。因为 WebSockets 会消耗服务器上的大量资源,所以对于单向的或者即发即弃(fire-and-forget)的所有命令我们会使用 HTTP REST 连接,例如一个用户开始一个构建请求、一个重构请求、提交一个 PAAS 部署或者提交一个日志事件。
4.2 临时工作空间 VS 命名工作空间
Codenvy 有临时工作空间的概念。临时工作空间指的是拥有一个项目、代码库以及一些代码、构建、测试、调试服务,但是却构建在一个隔离的区域中、相关工作无法长久持久化的地方。另外,如果一个临时工作空间在一段时间内保持闲置或者关闭了浏览器,那么它将会被销毁。在一个临时工作空间中有一些所有操作都必须重复的行为,例如对一个外部提供者进行身份验证,这是因为系统并不会持久化任何证书。一个永久的工作空间会和一个用户或者一个组织绑定,同时它里面的项目会持久化。此外,系统会持久化账号信息,外部服务的证书(例如连接 GitHub 的 SSH 密钥),同时允许公共 / 私有项目功能。
对于未认证和已认证的用户而言临时工作空间的表现是相似的。对于仅想使用临时工作空间的开发者来说,所有可分解的项目都开始于一个临时工作空间。对于拥有注册账户的开发者来说,他们能够将项目复制到自己的持久化工作空间中。而那些没有注册账号的开发者则能够执行身份认证流程,在身份认证确认之后就能够复制项目了。
临时工作空间和命名工作空间之间的架构区别在于:是否会使用持久性存储(LDAP)保存相关的账号信息。临时工作空间拥有完整的用户概要,但是因为信息是全部保存在内存中的,所以租户自己的任何破坏都将影响整个项目空间。我们还将所有的临时租户放入了虚拟文件系统中的一个隔离区域从而能够对这些文件运行批量清除和报表算法。
4.3 用户管理和认证服务
用户能够通过基于表单的登录或者基于 OAuth 进行身份验证。我们使用 Java 身份验证和授权服务作为处理身份验证的核心技术。无论何时,如果一个用户试图访问受保护的资源(URL 引用)那么就会进行身份验证。有一些 URL 引用是不受保护的(公共的),另外一些是受保护的(私有的)。在身份验证发生之后,系统会尝试着决定用户应该访问哪些工作空间和项目。如果被引用的 URL 并没有引用一个明确的工作空间,那么用户就会被重新路由到一个不同的工作空间和租户选择算法。
(点击图像放大)
4.4 匿名用户 VS. 命名用户
命名用户指的是拥有关联账号同时被授予了配置的权限能够访问受保护资源的用户。匿名用户指的是那些能够访问系统但是没有可识别的凭证能够关联到账号的用户。匿名用户能够在下面两个场景中出现:
- 没有 cookie 的用户启动一个工厂操作,并将它们放入一个临时工作空间。
- 用户访问一个公共的项目 URL 并且通过该 URL 在只读模式下进入了该产品。
匿名用户和命名用户之间的系统行为是不同的。对于匿名用户而言,管理员能够通过配置文件指定一系列的配置参数从而决定匿名用户可以访问产品的哪些功能。为了重新激活这些功能匿名用户必须创建一个账号或者进行认证(或者在产品外部或者在其内部)。匿名用户还必须将他们的所有流量路由到一个 IDE 集群,该集群与其他处理命名用户流量的 IDE 集群隔离。该路由和隔离使用 HAProxy 和我们已经创建的云管理 IP。
从体系结构上看,匿名用户有一个自动生成的用户名,但不会持久化进 LDAP,仅会存在于内存中。匿名用户和命名用户都有一个关联的权限集合,都是组成员。匿名用户的关联组和权限是预定义的并且不能修改。
4.5 公共项目 VS. 私有项目
Codenvy 支持公共项目和私有项目的概念。对于公共项目,任何能够访问相关 URL 的人都可以访问该项目的项目空间和所有文件。我们最终会在 Web 网站上通过一个导航目录暴露所有的这些公共项目,但是现在需要显式地指定 URL 才能访问。
我们使用 ACL 控制针对工作空间、项目和文件的行为。工作空间、每个项目和子目录中关联的文件集合的 ACL 设置在虚拟文件系统上。我们能够通过根的任意子目录传播 ACL 属性。对于被指定为完全公共的工作空间(例如我们的社区计划),我们在虚拟文件系统的根目录上创建了一个标准的 ACL 集合,并且将其设置为不可变的。对于私有项目,我们会让这些 ACL 可修改。组和个人都能够拥有 ACL 集合和修改。
4.6 邀请 VS. 工厂(Factory)
当一个用户被邀请进入另一个用户的工作空间时,他们必须有一个合适的密钥才能访问。对于已有的被邀请进入工作空间的 Codenvy 用户,我们会自动地将邀请密钥和进入邀请者工作空间的受邀用户进行关联。对于没有关联 Codenvy 账号的受邀 email 地址,我们会生成一个临时密钥,并将它和用户的 email 存储到我们的 LDAP 仓库中,然后向用户发送一封带有链接和密钥的 email。当用户单击链接的时候会应用临时密钥,然后用户就能够访问他们被邀请进入的账号。
对于访问公共工作空间的用户而言,密钥并不是必须的。我们会创建一个自动的访问密钥授予匿名用户或者命名用户访问工作空间的某些权限。这些一般是读文件权限,以及数量有限的项目构建和运行功能。
对于单击工厂的用户,我们会创建一个单独的临时工作空间。用户并不会被邀请使用一个工厂。任何可访问工厂 URL 的用户都能够利用该工厂。如果访问一个工厂的用户已经认证通过,那么我们就能够识别出他们并允许他们将内容复制到他们已有的工作空间。如果用户未认证,那么我们会在临时工作空间会话期间创建一个匿名用户账号。
4.7 协作模式
当一个文件被打开的时候,我们既可以通过标准编辑器又可以通过协作编辑器处理。
(点击图像放大)
一个标准的编辑器仅在一个单独的JVM 中运行,它对其他JVM 中发生的事情毫不知情。这意味着该编辑器仅对明确打开它的用户可用,并且它不适合真正的协作模式。在有弱连接或者网络ping 时间很长以致于会影响WebSocket 性能的情况下会创建标准编辑器。使用标准编辑器消耗的内存空间是受控制的,用户的保存完全是人工的,有保证。
协作编辑器是Codenvy 平台上的默认编辑器类型。为了让多个用户能够同时编辑文件我们扩展了Google 的collide 开源项目。协作编辑器控制虚拟文件系统中文件的锁,然后实时地调整每一个用户对文件做出的修改。Collide 编辑器能够定期地异步保存文件的变化,同时保存事件会被传播回最终用户的屏幕让他们知道持久性改变。另外,协作编辑器会同步同时在同一个编辑器中处理相同文件的各个用户之间的事件。
4.8 多租户(Multi-Tenancy)
IDE 集群、构建器和运行器有不同的租户模型。
- IDE 集群。在该集群中,每一个节点运行一个单独的 JVM,它会占用节点上的所有可用内存和计算资源。在 JVM 中我们会使用一些内部 IP 从而在 JVM 内启用多租户。每一个工作空间有它自己的线程、内存和虚拟文件空间。当一个工作空间和它自己的 IDE 在一个单独的 JVM 中实例化的时候,我们会配置路由器将 HTTP 请求和 WebSocket 连接映射到正确的 IDE。同时 IDE 本身会被映射到合适的目录。一般来说,在 HAProxy 必须创建另一个 IDE 服务器之前,在 AWS 中等实例上运行的一个单独的 JVM 能够运行大约 300 个 IDE 租户。
- 构建集群。在构建集群的前面有一个队列集合。在队列的前面有一个 BuildManager 服务,你能够通过 REST 风格的 Web 服务访问它。BuildManager 处理消息集合、排序和处理。构建消息会基于进入的客户端上下文被路由到一个队列,例如付费 / 免费账号。还有一个管理控制台,它指定了应该为一个队列分配多少个节点。我们为社区层(免费)运行了一个处理队列,然后每一个 Premium 订阅(付费)都有一个专用的队列。使用这个模型我们能够为一个单独的工作空间分配多个硬件节点,同时队列管理者能够在不同的构建节点之间负载平衡工作空间请求。客户端 IDE 定期调查分配给它的流程的构建器从而收集输出和日志文件并在浏览器中显示。
- 运行器集群。类似于构建集群,在构建集群和运行集群之间也有一个队列系统。不仅构建集群可以将订单放到运行集群上,IDE 也可以直接将命令发送到运行集群。运行集群上的每一个节点都运行了一个多租户部署的 CloudFoundry。CloudFoundry 黑盒用于决定启动服务器的环境配置
在本文的第二部分我们将会介绍下面的 __Codenvy__ 主题:虚拟文件系统的使用,日志和分析是如何实现的,托管 __API_,集群管理、整体情况、发布模型和开发使用的 __SCRUM__ 流程。_
关于作者
Tyler Jewell 是 Codenvy 的 CEO,Toba Capital 的投资合伙人,在这里他关注于开发者投资。他是 Sauce Labs、WSO2、Exo Platform 和 Codenvy 的董事会成员,同时投资于 Cloudant、ZeroTurnaround、InfoQ 和 AppHarbo。
查看英文原文: Codenvy’s Architecture, Part 1
评论