多年来,我一直是一名 Smalltalk 程序员,这段经历给了我一个不同的视角来观察编程世界的想法。例如,源代码应该存储在文本文件中的想法就让我花了一些时间才适应。
我们程序员经常区分“开发”和“部署”,特别是我们使用与部署软件后的地点和工具不同的工具在一个地方开发软件的想法。在 Smalltalk 世界中,没有这样的区别。
Smalltalk 构建于虚拟机的概念之上,该虚拟机包含您的开发环境(IDE、调试器、文本编辑器、版本控制等等),如果您需要修改任何代码,您将修改内存中运行的副本。如果您想快照您的运行机器,您可以这样做,如果您想分发您的代码,您可以将您的运行机器镜像(包括 IDE、调试器、文本编辑器、版本控制等等)的副本发送给用户。这就是 90 年代(我们中的一些人)软件开发的工作方式。
如今,部署环境与开发环境截然不同。首先,您不期望您的任何开发工具都在那里。一旦部署,就没有版本控制,没有调试,没有开发环境。有日志记录和监控,这是我们在开发环境中没有的,还有一个“构建管道”,它将我们的软件从我们开发的格式转换为我们部署的格式。例如,Docker 容器试图重新捕捉 1990 年代 Smalltalk 程序员部署体验的某些简单性,而没有渴望在开发体验方面也这样做。
我想,如果 Smalltalk 世界是我唯一没有区分开发和部署环境的编程隐喻体验,我可能会回顾它,认为这是一个侥幸。但在我成为 Smalltalk 程序员之前,我是一名 APL 程序员,那也是一个可破解的虚拟机镜像世界,其中开发和部署是无法区分的。因此,我相当确信,当前人们编辑单独的源代码文件,然后运行构建管道来创建在编辑代码时不存在的部署工件,然后将这些工件部署给用户的世界是一个侥幸。我们不知何故将这种软件开发反模式制度化了,不断发展的软件环境的需求正在迫使我们找到一种方法,回到 1990 年代更有效的技术。因此,Docker 取得了成功。因此,需要我接下来要提出的建议。
我有两个建议:我们在运行时系统中实现(并使用)版本控制,以及我们通过更改正在运行的系统来开发软件,而不是用新的运行系统替换它们。这两个想法是相关的。为了安全地更改正在运行的系统,我们需要一些版本控制功能来支持“撤消”功能。也许可以公平地说,我只提出一个建议。让我用一个例子来说明。
让我们从想象一个静态网站开始。您想更改一些 HTML 文件。这应该如何工作?如果您像大多数开发人员一样,您将有两个,也许是三个站点——一个用于开发,一个用于 QA(或暂存),一个用于生产。您将直接编辑开发实例中的文件。当您准备好时,您会将更改“部署”到暂存实例。在用户验收测试之后,您将再次部署,这次是部署到生产环境。
使用奥卡姆剃刀原理,让我们避免不必要地创建实体。我们需要多少台机器?我们可以使用一台计算机。我们需要多少台 Web 服务器?我们可以使用一台具有多个虚拟主机的 Web 服务器。我们可以使用单个虚拟主机而不是多个虚拟主机吗?那么我们将需要多个目录,并且需要使用 URL 的顶级路径来区分不同的版本,而不是虚拟主机名。但是我们为什么需要多个目录?因为 Web 服务器将从文件系统提供静态资源。我们遇到的问题是有三个不同版本的目录,而我们的解决方案是制作三个不同目录的副本。这难道不是像 Subversion 和 Git 这样的版本控制系统旨在解决的问题吗?制作目录的多个副本以存储多个版本的策略可以追溯到 CVS 之前的时代。为什么不使用,例如,一个裸 Git 仓库来存储文件呢?因为为了做到这一点,Web 服务器需要能够从 git 仓库中读取文件(参见 mod_git)。
这将是一个支持版本控制的运行时系统。
使用这样的 Web 服务器,正在服务的版本将通过例如 cookie 来标识它。通过这种方式,任何人都可以推送到一个仓库,用户将继续看到他们在启动会话时分配的版本。版本控制系统具有不可变的提交;一旦会话开始,开发人员可以随意推送更改,而不会影响正在运行的用户。开发人员可以重置他们的会话以跟踪他们的新提交,因此开发人员或测试人员可以在与普通用户相同的 URL 和相同的服务器上查看开发中或测试中的版本。作为一个偶然的副作用,A/B 测试只是将不同的用户分配给不同的提交。所有用于管理多个版本的 git 功能都被引入到运行环境中。当然,git reset 为我们提供了我们之前提到的“撤消”功能。
为什么不是每个人都这样做呢?
一种可能性是,像版本控制系统这样的工具并非设计用于生产环境。例如,不可能给予某人推送到测试分支但不推送到生产分支的权限。对此方案最常见的反对意见是,如果发现漏洞,您会希望将某些提交标记为不可访问。这将是更细粒度的权限的另一种情况;开发人员将拥有对所有提交的读取权限,但外部用户则不会。我们可能需要对现有工具进行一些额外的工程设计来支持这种范式,但这些功能很容易理解,并且已被工程设计到其他软件工件中。例如,Linux(或 PostgreSQL)实现了针对不同用户的细粒度权限的概念。
随着云环境变得越来越普遍,这些想法变得更加相关:云始终在运行。例如,我们可以看到,AWS 相当于“文件系统”(S3)实现了版本控制,因此您可能会有这种想法的变体,其中涉及从 S3 提供资源的 Web 服务器,并使用会话信息来选择这些资源的不同版本。重要的想法不是哪个实现是最好的,而是支持运行时版本控制的愿望。
已部署的软件环境应“版本感知”的原则扩展到除提供静态资源的 Web 服务器之外的其他工具。在未来的文章中,我将反思库、数据库和应用服务器的版本控制方法。
在 Robert Lefkowitz 在 2017 年澳大利亚 LinuxConf (#lca2017) 霍巴特举行的演讲中了解更多信息:保持 Linux 的伟大。
2 条评论