<audio id="audio" title="11 | 研发环境：Facebook怎样让开发人员不再操心环境？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a0/4f/a0604718d5322013784241950e93144f.mp3"></audio>

你好，我是葛俊。今天，我来和你聊聊研发环境的问题，也就是如何才能让开发人员少操心环境，甚至不操心环境。从这篇文章开始，我们就一起进入工程方法模块了。

在[第5篇文章](https://time.geekbang.org/column/article/129857)关于“持续开发”的讨论中，我与你介绍了获取开发环境是开发工作的核心步骤之一，对提高研发效能来说是非常值得优化的环节。当时，我与你提到了开发环境服务系统以及沙盒环境，解决了开发环境的准备工作。

而这里的“开发环境”只是研发环境的一部分，特指开发机器环境，包括开发机器的获取、网络配置、基本工具以及代码的获取和配置。今天，我们就来看看整体的研发环境的配置问题，从全局上切实解决开发人员因为操心环境而导致的效能低下。

在此之前，你可以先回忆下是否对以下问题似曾相识呢？

- 开发人员使用的电脑配置太低，运行太慢，编译一次要10分钟；
- 测试环境不够，上线时熬夜排队等环境；
- 工具零散，不成系统，很多步骤需要手动操作，开发思路常常因为流程不连贯而被打断；
- 团队成员的环境设置参差不齐，有个别开发人员环境配置得比较好，效率比较高，但没有固化下来，其他团队成员也用不上。

这些问题，实际上都可以归结为研发环境不够高效。就像低劣的空气质量和食物质量会影响我们的身体健康一样，不理想的研发环境会严重降低研发效能。

具体来说，按照开发流程，软件研发需要以下几种环境：

- 开发机器；
- IDE；
- 开发过程中使用的各种工具、数据和配置；
- 本地环境、联调环境；
- 测试环境、类生产环境。

接下来，我会按照这个顺序，分别与你介绍这5套环境如何配置。

因为Facebook在环境配置上做得非常好，比如说，他们可以保证开发人员在5分钟之内拿到一套干净的开发机器环境。所以，我会与你着重分享Facebook的实践，然后根据这些实践，总结出一套提供高效研发环境的原则。同时，我也会与你介绍一些我在其他公司的落地实践，帮助你掌握与环境配置相关的内容。

## 如何配置高效的研发环境？

### 开发机器

Facebook从不吝啬在开发机器上的投资。每个开发人员除了有一台笔记本外，还在远程数据中心有一台开发机器。数据中心的机器用于后端以及网站的开发，为方便描述，我称之为后端开发机。而笔记本有两个用处：一是用来做移动端App的开发；二是作为终端，连接到后端开发机做后端和网站开发。

两台机器的配置都非常强劲。后端开发机一开始是实体机器，后来为了便于管理和提高资源利用率，逐步转为了虚拟机，但不变的是配置依然强大。在2015年的时候，绝大部分机器配置都能达到16核CPU 、144G内存。笔记本有两种选择，一种是苹果MacBook Pro，另一种是联想ThinkPad，都是当时市场上顶配或者顶配下一级的配置。

开发机器的获取和释放，则是通过共享机器池以服务化自助化的方式完成的。更多细节，你可以再复习下[第5篇文章](https://time.geekbang.org/column/article/129857)的相关内容。

### IDE

IDE，指的是集成开发环境。Facebook持续在IDE上投入，以完善其功能、一致性及一体化体验。接下来，我会以后端和网站开发为例来说明。

因为代码存放并且运行在后端开发机上，所以在2012年以前，绝大部分开发人员都是使用SSH远程登录到后端开发机，在那里使用VIM/Emacs这类能在命令行里运行的编辑器进行开发工作。也有人尝试在笔记本上运行GUI的IDE，远程挂载后端开发机上的文件系统，但总是有卡顿，效果不好。

在这种情况下，公司首先采用的办法是，尽量提高命令行编辑器的体验，把VIM/Emacs配置成类似IDE的工具，将最常见的功能，比如代码搜索、代码跳转、Debugger等集成进去，效果还算可以。

不过，与GUI形式的IDE相比，这种命令行的工作方式还是有一些局限性，比如，和其他工具服务集成不方便，需要记忆大量快捷键，在显示形式上不够丰富导致用户体验不够好，等等。所以，Facebook持续投入研究GUI形式的IDE。

第一个成型的GUI IDE是一个Web IDE，也就是在数据中心运行一些Web IDE服务。这些IDE服务连接到开发者的后端开发机上获取代码，同时提供网页界面，供开发人员用浏览器登录，进行开发工作。这种专门的IDE服务解决了远程文件夹挂载的卡顿问题，同时跟其他服务集成起来也很方便。

比如，它可以与Phabricator的代码审查功能集成在一起，在代码中用inline的方式显示其他人对你的代码的comments，也就是说，在两行代码之间显示一个额外区域，用于展示comments。类似的，它还可以方便地与代码搜索服务、代码跳转服务、Debugger集成，甚至还可以跟CI/CD工具链的发布工具集成，所以非常方便。

到2014年的时候，Facebook的大部分开发人员都逐渐转移到这个Web IDE上去了。如果你想深入了解Web IDE，可以参考下[Apache Che](https://www.eclipse.org/che/)项目。

这个Web IDE已经很方便了，但基于网页的IDE在易用性和安全性还是有一些局限，所以Facebook继续加大在GUI形式的IDE方面的投入，最后使用Electron框架实现了一个原生的IDE，也就是[Nuclide](https://nuclide.io/docs/quick-start/getting-started/)。

Nuclide的工作原理和Web IDE基本一致，都是在数据中心运行IDE服务，IDE的前端则运行在本地笔记本上，通过与IDE服务联通实现代码编辑等功能。只不过Nuclide的前端，是运行在开发者笔记本上的一个原生应用，而Web IDE的前端是运行在笔记本上的网页浏览器而已。Nuclide的功能比Web IDE更强大，易用性也更好，同时因为没有浏览器的依赖，安全性也更好一些。

### 本地环境、联调环境

关于Facebook的本地环境，我已经在[第5篇文章](https://time.geekbang.org/column/article/129857)中介绍过了，主要就是加快本地构建，使用生产环境的数据，从而使得本地环境更加快捷、方便。

而至于联调环境，我曾在[第6篇文章](https://time.geekbang.org/column/article/131673)中简单提过。在代码提交到Phabricator上进行审查的时候，Phabricator会调用一个沙盒系统创造出一个沙盒环境，运行正在被审查的代码。这个系统也是用机器池实现的，同时也是一个自助式服务，也就是说开发人员可以不通过Phabricator，直接调用API来生成沙盒环境。

本地环境和联调环境是开发中最高频使用的环境，对持续开发很重要。接下来，我再与你分享**两个我在其他公司的实施案例**吧。

**第一个例子是，我在Stand公司搭建本地环境**。Stand的业务规模小，因此并没有使用微服务，主要的服务只有单体的网站后端服务、数据库服务MySQL、缓存服务Redis，以及一些数据监控服务，相对来说比较简单。

我们的做法是，把这些依赖服务尽量在本地开发机器（也就是笔记本）上都运行一个实例。实在不能在本地运行的服务，要么在本地环境运行时不调用它，要么就在调用它的时候传递额外的参数，表明这个调用来自开发环境，而被调用的服务则针对这样的调用进行特殊处理，从而达到不污染线上环境的效果。

这是一个很常见的办法，简单有效。不过，它的缺点是本地环境数据跟线上环境有区别。

如果你的系统采用的是微服务，则可以采用以下3种常见办法。

- 第一种方法是，直接在自己的机器上把所有的依赖服务都跑起来。这种方法的优点是方便，但要求开发机器配置要求高。
- 第二种方法是，团队维护一个环境，让大家的开发环境接入。也就是，开发者自己的机器上只运行自己开发的服务，调用其他服务时就使用这个共享环境中运行的服务实例。这种方式对开发机器要求不高，但需要团队维护一个环境，并且一般来说大家需要经常更新这个环境中的服务，整个环境容易不稳定。
- 第三种方法是，使用服务虚拟化工具，来模拟依赖的服务，比如[Mountbank](https://github.com/bbyars/mountebank)、[WireMock](https://github.com/tomakehurst/wiremock)。你可以参考[这](https://juejin.im/entry/5a54a432f265da3e591e27ee)[篇文章](https://juejin.im/entry/5a54a432f265da3e591e27ee)来了解WireMock的使用方法。

**第二个例子是，我为一个云产品团队提供联调环境**。这个云产品结构非常复杂，有十多个服务，至少需要3台服务器；不但有软件，还有数据、组网等复杂的设置，部署很困难；更严重的问题是，这个环境一旦损坏就很难修复，需要从头再来，所以开发人员自己配置基本不可能，运维人员也是忙于维护，应接不暇。

针对这个问题，我们也是使用了机器池的办法。不过，这个机器池里面的单元不再是单个机器，而是由3台机器组成的服务。这个服务自助化提供给开发者使用，使用之后自动回收销毁。

同时，确保机器池中有两套空闲环境。不够就补充，多了就删除，以保证获取环境时可以马上得到，同时又不会因为有太多空闲环境而造成资源浪费。

另外，每次有了新的稳定版本，运维人员都会更新脚本，并重新安装和配置系统，保证开发人员能够在稳定版本上进行联调。

这样一来，就解决了团队开发人员的环境问题，将获取环境的时间由2~4个小时缩短到了几分钟。

### 开发过程中使用的各种工具、数据和配置

除了IDE，开发过程中还会用到其他工具，比如代码搜索、发布部署，以及日志查看工具等。这些工具的组合，可能与你平时理解的开发环境不大一样，但事实上它也是一种广义的开发环境，对开发效率影响很大。

这部分环境的优化，主要是使用工具之间的网状互联来提高效率。关于工具的网状互联，我在[第4篇文章](https://time.geekbang.org/column/article/128867)中有介绍。这里，我想强调Facebook的另一个重要实践：**重视开发体验，将开发流程中常用步骤的自动化做到极致**。

我用一个具体的例子来说明。我们都知道，Git 的Commit Message（代码提交描述）是提供信息的重要渠道。但它有一个局限，就是只能存储文本，而图片在描述问题时常常比文字有效得多，也就是“A picture is worth a thousand words”，翻译为中文就是我们常说的“一图胜千言”。

为解决这个问题，Facebook采用了以下方式：

1. 提供了一个图片存储服务，并为上传的图片提供永久URL；
1. 开发了一个端测的截屏工具，截屏之后自动上传到图片存储服务，而且，在上传成功之后自动把图片URL保存到本地笔记本的系统剪贴板中；
1. 提供了一个内部使用的URL缩短工具，避免URL太长占用太多文字空间。

这三个工具集成起来，一个具体的使用场景是这样的：我在写Commit Message的时候，如果要截屏描述修改效果，就使用一个快捷键（比如Cmd+Alt+4）激活截屏工具，随后拖动鼠标截屏，然后使用Cmd+v就可以直接把图片的URL粘贴到Commit Message里面了。

这个工作流非常顺畅、高效，不仅被大量用于Commit Message的书写中，也经常被用在聊天工具中。后来，我到了其他公司，都会先配置这样一套工具流程。

### 测试环境、类生产环境

在测试环境、类生产环境的管理上，Facebook使用一个叫作[TupperWare](https://engineering.fb.com/data-center-engineering/tupperware/)的内部系统，以IaC的方式进行管理。不过，Facebook并没有开源这个系统。如果你所在公司使用的是Docker，那可以使用Kubernetes实现类似的功能。

如果你们没有使用Docker，可以试试HashiCorp公司的[Terraform](https://www.terraform.io)，或者使用[Ansible](https://www.ansible.com)、[Chef](https://www.chef.io)之类的配置管理工具，来产生一套干净的环境供团队成员使用，之后再销毁，既方便又不浪费资源。

这里，我再与你分享一个我在**Stand公司使用AWS管理压测环境的例子**。当时我们没有使用Docker，于是，我们选择了AWS的OpsWorks框架。它是AWS基于Chef-Solo开发的一个应用程序管理解决方案，同时支持基础设施的建模和管理。

OpsWorks这个框架的用法也是声明式的，只不过这个声明不是纯代码，而是在AWS的网页上配置的，使用效果和TupperWare差不多，就是**首先**定义一个压测环境需要几台机器，需要运行什么操作系统、需要什么负载均衡器、需要什么数据库等。

**然后**，通过OpsWorks上暴露的钩子，使用代码来管理应用的生命周期，从而实现系统和应用的初始化。通过这种方式，我们可以很方便地使用OpsWorks产生一个云主机集群用于压测，结束之后马上删除，方便而且划算。

其实，使用Ansible或者Chef也可以实现类似功能，但需要自己开发些东西，这里我就不详细讨论了。

## 提供高效研发环境的原则

通过以上实践可以看出，配置高效的研发环境主要包括以下几条原则：

1. 舍得投入资源，用资源换取开发人员时间。Facebook之所以从不吝啬在开发机器硬件上的投入，是因为人力成本更高。
1. 对环境的获取进行服务化、自助化。这一点可以在开发机器、联调环境的获取上，得到很好的体现。同时常常使用IaC、配置管理系统（比如Chef）和机器池的方法来实现，同时利用弹性伸缩来节约资源。
1. 注重环境的一体化、一致性，也就是要把团队的最佳实践固化下来。比如Facebook一个常见的操作是，配置文件统一处理。以VIM为例，将VIM的配置文件存放到网络共享文件夹中，开发人员只要在自己的.bashrc文件中加上一行就可以搞定。

```
source /home/devtools/vimconfig

```

## 小结

今天，我首先按照开发流程，也就是开发机器、IDE、本地环境和联调环境、开发过程中使用的各种工具及配置，以及测试环境和类生产环境的顺序，与你讲述了高效研发环境的具体实践。然后，基于这些实践，总结了3个基本原则：一是，用资源换时间；二是，服务化、自助化环境的获取；三是，实现环境的一体化、一致性。

我认为，这些原则和实践的背后有一个重要思路，就是Facebook重视环境，并持续优化环境。这一点在IDE的演化上尤为明显，从命令行到Web IDE再到Nuclide，一直在进步。另外，在去年年底，Facebook停止了对Nuclide的开源项目的维护，这也意味着后面他们可能还会有对IDE的一轮新的优化。

以上种种做法，使得我在Facebook做开发的时候，对研发环境的感觉就是不用操心，需要使用的时候直接到网站上申请就可以使用；配置也方便，团队的配置都已经在那里了；同时，环境中的各种工具、流程都很顺畅，让我能够静下心来做开发、写算法，做我最能提供价值的事情。

## 思考题

我在“开发过程中使用的各种工具、数据和配置”这一章节中提到的截屏工具流程，你觉得价值大吗？值得引入你所在的公司吗？如果值得的话，可以怎么来实现？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！


