我最喜欢的基础设施

作者:Kyle Rankin

参观我构建过的最佳基础设施,包括架构、灾难恢复、配置管理、编排和安全。

在初创公司工作有很多优点和缺点,但与传统的成熟公司相比,主要优势之一是初创公司通常会给你一个从头开始构建全新基础设施的机会。当你在成熟公司从事新项目时,你通常必须考虑遗留系统和为你做出的设计选择,通常在你加入公司之前就已经做出了这些选择。但在初创公司,你通常会遇到真正的空白状态:没有预先存在的基础设施,也没有现有的设计选择需要考虑。

对于系统架构师级别的人来说,全新的、从零开始的基础设施尤其具有吸引力。高级系统管理员和架构师级别之间的区别之一是,你已经在高级职位上工作了足够长的时间,亲自管理了许多不同的高级项目,并且已经看到了哪些方法有效,哪些方法无效。当你达到这个级别时,能够根据你从过去的努力中学到的所有教训,从头开始构建一个全新的基础设施,而无需支持任何遗留基础设施,这非常令人兴奋。

在过去的十年里,我在几家不同的初创公司工作过,在那里我被要求完全从头开始开发新的基础设施,但要满足高安全性、正常运行时间和合规性要求,因此没有像你在初创公司通常可能面临的那样,为了速度而偷工减料的压力。我不仅体会到了设计新基础设施的乐趣,而且还能够多次这样做。每次,我都能带上所有有效的过去设计,抛弃那些无效的部分,并更新所有工具以利用新功能。这一系列的基础设施设计最终形成了我回顾时意识到是我最喜欢的基础设施——我将以此黄金标准来评判所有未来的尝试。

在本文中,我深入探讨了我最喜欢的基础设施的一些细节。我描述了围绕设计的一些约束,并探讨了基础设施的每个部分如何协同工作,为什么我做出这样的设计决策,以及这一切是如何运作的。我并不是说对我有用的方法也一定对你有效,但希望你可以从我的方法中获得一些启发,并根据你的需求进行调整。

约束

每当你描述一个你认为效果良好的解决方案时,重要的是要先说明你的设计约束。通常,当人们寻找基础设施线索时,他们首先会关注“大型科技公司”是如何做的。这种方法的问题在于,除非你也是一家大型科技公司(即使你是),否则你的约束可能与他们的非常不同。他们的预算、人力资源和他们试图解决的问题对他们有效的方法,很可能对你无效,除非你和他们非常相似。

此外,组织规模越大,就越有可能内部解决问题,而不是使用现成的解决方案。当一家科技公司发展到一定阶段,拥有足够的开发人员时,当它遇到新的问题要解决时,它很可能会使用其开发人员大军来创建定制的、专有的工具,仅仅为了自己,而不是使用现成的解决方案——即使现成的解决方案可以为公司解决 90% 的问题。这很遗憾,因为如果所有这些大型科技公司都将精力投入到改进现有工具和分享他们的更改上,我们所有人都会花更少的时间重复造轮子。如果你曾经面试过在大型科技公司工作很长时间的人,你会很快意识到他们非常擅长管理特定的基础设施,但如果没有这些定制工具,他们可能很难在其他地方工作。

初创公司的约束与大型公司的约束也非常不同,因此将适用于小型初创公司的解决方案应用于大型公司也可能是个错误。初创公司通常团队规模很小,但也需要非常快速地构建基础设施。进入生产环境的错误通常对初创公司的影响很小。他们最关心的是在资金耗尽之前,推出某种功能性产品以吸引更多投资。这意味着初创公司不仅更倾向于现成的解决方案,而且也更倾向于偷工减料。

所有这些都是想说,在我的约束条件下对我有用的方法,在你的约束条件下可能对你无效。因此,在我深入细节之前,你应该了解我所处的约束条件。

约束 1:种子轮融资的金融初创公司

这个基础设施是为一个正在开发金融领域 Web 应用程序的初创公司构建的。我们在构建基础设施上可以花费的时间和可用于构建它的团队规模都有限制。在许多情况下,都是单人团队。在我以前构建理想基础设施的迭代中,我至少有一个其他人,甚至多个人来帮助我构建基础设施,但这次我只能靠自己。

时间限制以及我独自完成这项工作的事实意味着,我更有可能选择过去对我有用的稳定解决方案,并使用我非常熟悉的技术。特别是,我非常重视自动化,以便能够倍增我的努力。当你以正确的方式使用配置管理和编排时,你可以建立一种势头。

约束 2:非系统管理员的紧急情况升级

我基本上是独自一人构建基础设施,并且在管理紧急情况时也是如此。通常,我尽量遵守一个规则,将生产环境的访问权限限制在系统管理员身上,但在这种情况下,这意味着如果我无法提供服务,我们将没有冗余。这个约束意味着,如果我因任何原因无法提供服务,警报需要升级到主要具有开发人员背景,只有一些 Linux 服务器经验的人员。因此,我必须确保响应最常见的紧急情况相对简单。

约束 3:PCI 合规性

我喜欢在初创公司从头开始开发基础设施,并结合严格的安全约束,防止你偷工减料。安全领域的很多人都有些看不起 PCI 合规性,因为很多公司都认为它只是一个需要勾选的框,并聘请以最小的麻烦勾选该框而闻名的公司。然而,如果你将 PCI-DSS 中的许多良好实践视为诚实管理的最低安全标准,而不是可以敷衍了事的最高安全标准,那么 PCI-DSS 中有很多好的实践。我们对 PCI 合规性有硬性依赖,因此满足和超越该政策对设计产生了一些最大的影响。

约束 4:定制的 Rails Web 应用程序

开发团队在 Rails 方面有很强的背景,因此大多数内部软件开发都是基于标准数据库支持的 Rails 应用程序堆栈的定制中间件应用程序。存在许多不同的方法来打包和分发这种应用程序,因此这也影响了设计。

约束 5:最小化供应商锁定

风险投资支持的初创公司通常会收到云提供商的信用额度,以帮助他们起步,这在某种程度上很常见。它不仅可以帮助初创公司在试图弄清楚他们的基础设施时管理成本,而且如果初创公司设法使用云特定的功能,它还会带来一个附带好处,即一旦他们的云账单增加,就更难让初创公司转移到不同的提供商。

我们的初创公司拥有不止一家云提供商的信用额度,因此我们希望可以选择切换到另一家提供商,以防我们在信用额度用完时资金紧张。这意味着我们的基础设施必须设计为可移植的,并尽可能少地使用云特定的功能。我们确实使用的云特定功能需要被抽象出来并易于识别,以便我们以后可以更轻松地将它们移植到另一家提供商。

架构

PCI 政策非常关注管理敏感持卡人数据的系统。这些系统被标记为“范围内”,这意味着它们必须符合 PCI-DSS 标准。此范围扩展到与这些敏感系统交互的系统,并且非常强调隔离——将范围内的系统与其余系统分离和隔离,以便你可以对它们的网络访问实施严格的控制,包括哪些管理员可以访问它们以及如何访问。

我们的架构从严格区分开发环境和生产环境开始。在传统的数据中心中,你可能通过使用单独的物理网络和服务器设备(或使用抽象来虚拟化隔离)来实现这一点。在云提供商的情况下,最简单、最安全和最可移植的方法之一是为每个环境使用完全独立的帐户。通过这种方式,不会存在配置错误导致生产环境暴露给开发环境的风险,并且它还具有一个附带好处,即可以轻松计算每个环境每月的成本。

当涉及到实际的服务器架构时,我们将服务器划分为单独的角色,并为它们指定通用的基于角色的名称。然后,我们利用 Amazon Web Services 中的虚拟私有云功能,将每个角色隔离到其自己的子网中,以便我们可以将每种类型的服务器与其他服务器隔离,并严格控制它们之间的访问。

默认情况下,虚拟私有云服务器要么位于 DMZ 中并具有公共 IP 地址,要么它们只有内部地址。我们选择尽可能少地将服务器放在 DMZ 中,因此环境中的大多数服务器只有私有 IP 地址。我们有意不设置将所有这些服务器的流量路由到互联网的网关服务器——它们与互联网的隔离是一项功能!

当然,一些内部服务器确实需要一些互联网访问。对于这些服务器,仅用于与少量外部 Web 服务通信。我们在 DMZ 中设置了一系列 HTTP 代理,这些代理处理不同的用例,并具有严格的白名单。这样,我们可以将来自主机外部的互联网访问限制为仅限于它需要的站点,同时也不必担心收集特定服务的 IP 块列表(尤其是在现在每个人都使用云服务器的情况下,这尤其具有挑战性)。

容错

云服务通常不可靠,但我们的服务必须能够扩展并在任何一台特定服务器上发生故障时幸存下来,这一点至关重要。我们首先为每项服务至少使用三台服务器,因为为两个系统设计的容错系统往往会落入传统的primary/failover架构,这种架构无法很好地扩展到两个以上。可以考虑三台服务器的设计可能也可以容纳四台或六台或更多。

云系统依靠虚拟化来最大限度地利用裸机,因此你使用的任何服务器都不是真正的物理机器,而是某种与物理硬件上的其他服务器一起运行的虚拟机。这给容错带来了一个问题:如果你的所有冗余虚拟机最终都在同一台物理机器上,并且该机器宕机了,会发生什么?

为了解决这个问题,一些云供应商将一个特定的站点分成多个独立的 数据中心,每个数据中心都有自己的硬件、电源和网络,这些硬件、电源和网络彼此独立。在 Amazon 的情况下,这些被称为可用区,并且跨可用区分布你的冗余服务器被认为是最佳实践。我们决定设置三个可用区,并将我们的冗余服务器分布在这些可用区中。

在我们的例子中,我们希望一致且自动地分布服务器,因此我们根据主机名末尾的数字将服务器分成三组。我们用于生成实例的软件会查看主机名中的数字,对其应用模三运算,然后使用它来决定主机将进入哪个可用区。像 web1、web4 和 web7 这样的主机将位于一组;web2、web5 和 web8 位于另一组;web3、web6 和 web9 位于第三组区域。

当你有多个服务器时,你还需要某种方式让机器在其中一台服务器宕机时故障转移到另一台服务器。一些云提供商提供内部负载均衡,但由于我们需要可移植性,我们不想依赖任何云特定的功能。虽然我们可以将自定义负载均衡逻辑添加到我们的应用程序中,但我们选择了一种更通用的方法,使用轻量级且快速的 HAProxy 服务。

使用 HAProxy 的一种方法是设置一台运行 HAProxy 的负载均衡服务器,并让应用程序在各种端口上与它通信。这会很像一些云提供的负载均衡服务(或传统数据中心中的负载均衡设备)。当然,如果你使用这种方法,你会有另一个问题:当负载均衡器发生故障时会发生什么?为了实现真正的容错,你需要设置多个负载均衡器,然后使用它们自己的负载均衡逻辑配置主机,以便它们可以在负载均衡器发生故障时故障转移到冗余负载均衡器,或者依赖传统的primary/secondary负载均衡器故障转移,并使用浮动 IP,该浮动 IP 将分配给活动的负载均衡器。

这种传统方法对我们不起作用,因为我们意识到可能会出现整个可用区与网络其余部分隔离的情况。我们也不想添加额外的故障转移逻辑来考虑负载均衡器中断。相反,我们意识到,由于 HAProxy 非常轻量级(特别是与服务器上的常规应用程序相比),我们可以简单地在每个需要冗余地与另一项服务通信的服务器上嵌入一个 HAProxy 实例。该 HAProxy 实例将知道本地服务器需要与之通信的任何下游服务,并在 localhost 上呈现代表每项下游服务的端口。

以下是它在实践中是如何工作的:如果 webappA 需要与 middlewareB 通信,它只需连接到 localhost 端口 8001。HAProxy 将负责下游服务的健康检查,如果某项服务宕机,它将自动连接到另一项服务。在这种情况下,webappA 可能会看到它的连接断开,只需要重新连接即可。这意味着我们的应用程序唯一需要的容错逻辑是检测连接何时断开并重试的能力。

我们还组织了 HAProxy 配置,以便每个主机都倾向于与其自身可用区内的主机通信。其他区域中的主机在 HAProxy 中被指定为“备份”主机,因此只有在主主机宕机时才会使用这些主机。这有助于优化网络流量,因为它在正常情况下会保留在其开始的可用区内。这也使得分析通过网络的流量流变得更加容易,因为我们可以假设通过 frontend2 进入的流量将被定向到 middleware2,middleware2 将访问 database2。由于我们确保进入我们网络的流量分布在我们的前端服务器上,因此我们可以确信负载相对均匀地分布,但各个连接在整个特定请求中往往会停留在同一组服务器上。

最后,我们需要将灾难恢复纳入我们的计划。为此,我们在与生产环境完全不同的地理区域创建了一个完整的灾难恢复环境,该环境在其他方面都模仿了生产环境中的服务器和配置。根据我们的恢复时间线,我们可以每隔几个小时同步一次数据库,并且由于这些环境彼此独立,因此我们可以在不影响生产环境的情况下测试我们的灾难恢复程序。

配置管理

在这个基础设施中,最重要的事情之一是做好配置管理。因为我基本上是独自构建和维护一切,并且时间线很紧张,所以我首先关注的是使用 Puppet 的强大配置管理基础。多年来,我在 Puppet 方面积累了丰富的经验,从它还不是今天这样成熟而强大的产品的时候就开始使用。但今天,我可以利用 Puppet 社区为常见任务编写的所有高质量模块来抢占先机。当主要的 Puppetlabs 模块已经完成了我需要的一切时,为什么要重新发明 nginx 配置?这种方法的关键之一是确保我们从一个基本的 vanilla 镜像开始,它上面没有任何自定义配置,并将其设置为所有将 vanilla 服务器变成,比如说,中间件应用服务器的配置更改都通过 Puppet 完成。

我选择 Puppet 的另一个关键原因是,正是许多人避免它的原因:Puppetmaster 可以使用 TLS 证书对 Puppet 客户端进行签名。许多人在尝试设置 Puppetmaster 来对客户端进行签名时遇到了很大的障碍,并选择了无主模式设置。在我的用例中,我将错过一个绝佳的机会。我有一个硬性要求,即云网络上的所有通信都必须使用 TLS 进行保护,通过拥有一个对主机进行签名的 Puppetmaster,我将获得一个受信任的本地证书颁发机构(Puppetmaster),并在我网络中的每台主机上免费获得有效的本地和签名证书!

许多人在 Puppet 客户端上启用自动签名时会让自己面临漏洞,但是必须手动签名新的 Puppet 客户端,尤其是在云实例中,可能会很麻烦。我利用了 Puppet 中的一项功能,该功能允许你将自定义的有效标头添加到 Puppet 客户端将生成的证书签名请求 (CSR) 中。我使用了一个特定的 x509 标头,该标头旨在将预共享密钥嵌入到 CSR 中。然后我使用了 Puppet 的能力来指定自定义的自动签名脚本。然后,此脚本会传递客户端 CSR,并决定是否对其进行签名。在我的脚本中,我们检查了 CSR 中的客户端名称和预共享密钥。如果它们与 Puppetmaster 上该主机名/预共享密钥对副本中的值匹配,我们就对其进行签名;否则,我们不签名。

这种方法之所以有效,是因为我们从 Puppetmaster 本身生成新的主机。在生成主机时,生成脚本会生成一个随机值,并将其作为预共享密钥存储在 Puppet 客户端的配置中。它还会将该值的副本存储在一个以客户端主机名命名的本地文件中,供 Puppetmaster 自动签名脚本读取。由于每个预共享密钥都是唯一的,并且仅用于特定的主机,因此一旦使用,我们就删除了该文件。

为了简化在每台服务器上配置 TLS 的过程,我添加了一个简单的内部 Puppet 模块,该模块允许我将本地 Puppet 客户端证书和本地证书颁发机构证书复制到我需要的任何地方,无论是 nginx、HAProxy、本地 Web 应用程序还是 Postgres。然后,我可以为我的所有内部服务启用 TLS,因为我知道它们都拥有有效的证书,可以用来相互信任。

我使用了标准的角色/配置文件模式来组织我的 Puppet 模块,并确保每当我有一个基于 AWS 特定功能的 Puppet 配置时,我都将其拆分到 AWS 特定模块中。这样,如果我需要迁移到另一个云平台,我可以轻松地识别出我需要重写哪些模块。

所有 Puppet 更改都存储在 Git 中,主分支充当生产配置,其他环境使用其他分支。在开发环境中,Puppetmaster 会自动应用推送的任何更改,但由于该 Git 存储库托管在开发环境之外,因此我们有一个固定规则,即任何人都不应能够直接从开发环境更改生产环境。为了强制执行此规则,对主分支的更改将同步到生产 Puppetmaster,但永远不会自动应用——系统管理员需要登录到生产环境并使用我们的编排工具显式推送更改。

编排

当你想确保一组特定的服务器都具有相同的更改时,Puppet 非常棒,只要你不想以特定的顺序应用更改。不幸的是,你想要对系统进行的大量更改都遵循一定的顺序。特别是,当你执行软件更新时,你通常不希望它们在 30 分钟内以随机顺序到达你的服务器。如果更新出现问题,你希望能够停止更新过程,并在某些环境中回滚到以前的版本。当人们试图将 Puppet 用于它不打算做的事情时,他们通常会感到沮丧并责怪 Puppet,而实际上他们应该将 Puppet 用于配置管理,并将其他工具用于编排。

在我构建这个环境的时代,MCollective 是与 Puppet 配对的最流行的编排工具。与一些更接近每个人几十年前使用的 SSH for 循环脚本的编排工具不同,MCollective 具有强大的安全模型,系统管理员被限制在他们预先启用的模块中的一组有限的命令中。每个命令都在整个环境中并行运行,因此无论它是推送到一台主机还是每台主机,推送更改都非常快。

MCollective 客户端没有对主机的 SSH 访问权限;相反,它会对它发出的每个命令进行签名,并将其推送到作业队列。每台服务器都会检查该队列中是否有针对它的命令,并在执行之前验证签名。通过这种方式,破坏运行 MCollective 客户端的主机不会给你远程 SSH root 访问权限来访问环境的其余部分——它只给你访问你已启用的受限命令集的权限。

我们使用堡垒主机作为 MCollective 的命令中心,目标是最大限度地减少系统管理员必须登录到单个服务器的需求。首先,我们希望确保所有常见的系统管理任务都可以使用堡垒主机上的 MCollective 执行。MCollective 已经包含一些模块,这些模块允许你查询网络上与特定模式匹配的主机,并提取有关它们的 facts,例如特定软件包的版本。

MCollective 命令的优点在于,它们允许你构建一个用于特定目的的单独模块库,然后你可以将这些模块链接到脚本中,以用于常见的 工作流程。我过去曾写过关于如何使用 MCollective 编写有效的编排脚本的文章,而这正是 MCollective 大放异彩的环境。让我们以最常见的系统管理任务之一为例:更新软件。由于 MCollective 已经有内置模块来使用本机软件包管理器查询和更新软件包,因此我们也打包了我们所有的内部工具作为 Debian 软件包,并将它们放在内部软件包存储库中。要更新内部中间件软件包,系统管理员通常会手动执行以下一系列步骤

  • 获取运行该软件的服务器列表。
  • 从列表中的第一台服务器开始。
  • 为该服务器在监控中设置维护模式。
  • 告诉任何负载均衡器将流量从服务器移开。
  • 停止服务。
  • 更新软件。
  • 确认软件版本正确。
  • 启动服务。
  • 测试服务。
  • 告诉任何负载均衡器将流量移回服务器。
  • 结束维护模式。
  • 对其余主机重复执行。

我所做的只是将上述每个步骤都确保有一个相应的 MCollective 命令。大多数步骤已经有内置的 MCollective 插件,但在少数情况下,例如对于负载均衡器,我为 HAProxy 编写了一个简单的 MCollective 插件,用于控制负载均衡器。请记住,环境中的许多服务器都有自己的嵌入式 HAProxy 实例,但由于 MCollective 是并行运行的,因此我可以告诉它们同时重定向流量。

一旦可以使用 MCollective 完成这些步骤中的每一个步骤,下一步就是将它们全部组合到一个通用的脚本中来部署应用程序。我还为每个阶段添加了适当的检查,因此,如果发生错误,脚本将停止并退出,并显示描述性错误。在开发环境中,我们在更新通过所有测试后自动推送更新,因此我也确保我们的持续集成服务器(我们使用 Jenkins)使用相同的脚本来部署我们的开发应用程序更新。这样,我可以确保脚本一直都在接受测试,并且可以首先在那里进行改进。

拥有一个可以为单个应用程序自动化所有这些步骤的脚本非常棒,但现实情况是,现代面向服务的架构有许多这样的小应用程序。你很少一次部署一个;相反,你有一个生产版本,其中可能包含五个或更多应用程序,每个应用程序都有自己的版本。在手动完成几次之后,我意识到这里也有自动化的空间。

自动化生产版本发布的第一步是提供一个我的脚本可以用来告诉它做什么的生产清单。生产清单列出了特定版本将拥有的所有不同软件以及你将使用的版本。在组织良好的公司中,这种事情将在你的工单系统中进行跟踪,因此你可以对何时将哪些软件投入生产进行适当的批准和可见性。如果你以后遇到问题,这尤其方便,因为你可以更轻松地回答“发生了什么变化?”这个问题。

我决定让正确的方法成为简单的方法,并将我们实际的生产清单工单用作脚本的输入。这意味着,如果你想要自动化的生产版本发布,第一步是创建一个格式正确的工单,其中包含适当的标题,其中包含一个项目符号列表,列出你要部署的每个软件以及你打算部署的版本,以及你希望部署它们的顺序。然后,你将登录到生产环境(从而证明你有权执行生产更改),并运行生产部署脚本,该脚本将把特定工单号作为输入进行读取。它将执行以下步骤

  • 解析工单,并向系统管理员提示它将部署的软件包列表,作为健全性检查,并在系统管理员说“是”之前不会继续。
  • 在群聊中发布消息,提醒团队生产版本发布正在开始,使用工单标题作为描述。
  • 更新本地软件包存储库镜像,以便它们具有最新版本的软件。
  • 对于每个应用程序:1) 通知群聊该应用程序正在更新,2) 运行应用程序部署自动化脚本,以及 3) 通知群聊该应用程序已成功更新。
  • 一旦所有应用程序都已成功更新,通知群聊。
  • 将所有更新的日志通过电子邮件发送给系统管理员别名,并作为评论添加到工单中。

与单个应用程序部署脚本一样,如果出现任何错误,我们会立即中止脚本,并将包含完整日志的警报发送到电子邮件、聊天和工单本身,以便我们可以调查哪里出了问题。我们将首先在位于单独区域的热灾难恢复环境中执行部署,如果成功,则在生产环境中也执行部署。一旦脚本在生产环境中成功运行,该脚本就会足够智能地关闭工单。最后,执行生产部署,无论你是想更新一个应用程序还是十个应用程序,都涉及以下步骤

  • 创建格式正确的工单。
  • 登录到灾难恢复环境并运行生产部署脚本。
  • 登录到生产环境并运行生产部署脚本。

自动化使该过程变得如此简单,生产部署相对轻松,同时仍然遵循我们所有的最佳实践。这意味着,当我休假或因其他原因无法提供服务时,即使我是团队中唯一的系统管理员,我具有强大开发背景的老板也可以轻松地接管生产部署。一致的日志记录和通知还使每个人都在同一个页面上,并且我们对生产环境中的每次软件更改都有一个很好的审计跟踪。

我还自动化了灾难恢复程序。只有在测试过恢复后,你才真正备份了某些内容。我设定了一个目标,每季度测试一次我们的灾难恢复程序,尽管在实践中,我实际上每月都做一次,因为在灾难恢复环境中拥有新鲜数据很有用,这样我们可以更好地在软件更新到达生产环境之前捕获任何数据驱动的错误。与许多环境相比,这是一个更频繁的测试,但我能够做到这一点,因为我编写了 MCollective 模块,该模块将从备份中恢复灾难恢复数据库,然后将整个过程包装在一个主脚本中,该脚本将其全部变成一个命令,该命令会将结果记录到工单中,这样我就可以跟踪我每次恢复环境的时间。

安全

我们对我们的环境有非常严格的安全要求,这些要求始于(但并未止于)PCI-DSS 合规性。这意味着服务之间的所有网络通信都使用 TLS 加密(以及 Puppet 提供的方便的内部证书颁发机构),并且所有敏感数据都存储在静态加密的磁盘上。这也意味着每台服务器通常只执行一个角色。

环境的大部分与互联网隔离,我们进一步定义了每个主机上的入口和出口防火墙规则,并在 Amazon 的安全组中强制执行这些规则。我们从“默认拒绝”方法开始,并且仅在绝对必要时才打开服务之间的端口。我们还采用了“最小权限原则”,因此只有少数员工拥有生产环境访问权限,并且我们开发人员无权访问堡垒主机。

每个环境都有自己的 VPN,因此要访问除公共服务以外的任何内容,您首先需要连接到受双因素身份验证保护的 VPN。 从那里,您可以访问我们的日志聚合服务器以及其他监控和趋势仪表板的 Web 界面。 要登录到任何特定服务器,您首先需要 ssh 登录到堡垒主机,该主机仅接受 SSH 密钥,并且还需要自己的双因素身份验证。 它是唯一允许访问其他机器上 SSH 端口的主机,但通常,我们尽可能使用编排脚本,因此我们不必超出堡垒主机来管理生产环境。

每台主机都有自己的基于主机的入侵检测系统 (HIDS),使用 ossec。它不仅会警报服务器上的可疑活动,还会解析日志以查找可疑活动。 我们还使用 OpenVAS 对整个环境执行例行的网络漏洞扫描。

为了管理密钥,我们使用了 Puppet 的 hiera-eyaml 模块,它允许您以加密形式存储键值对的层次结构。 每个环境的 Puppetmaster 都有自己的 GPG 密钥,它可以用来解密这些密钥。 因此,我们可以将开发或生产密钥推送到同一个 Git 存储库,但是由于这些文件是为不同的接收者加密的,因此开发 Puppetmaster 无法查看生产密钥,而生产 Puppetmaster 也无法查看开发密钥。 hiera 的优点在于,它允许您组合纯文本和加密的配置文件,并非常仔细地定义哪些密钥可供哪些类型的主机使用。 除非 Puppetmaster 允许,否则客户端永远无法访问密钥。

在生产环境和灾难恢复环境之间发送的数据使用灾难恢复环境中的密钥进行 GPG 加密,并且环境之间也使用加密传输。 灾难恢复测试脚本完成了所有解密备份和应用备份所需的繁重工作,因此管理员无需处理它们。 所有这些密钥都存储在 Puppet 的 hiera-eyaml 模块中,因此我们不必担心在主机发生故障时丢失它们。

结论

尽管我在这篇基础设施文章中涵盖了很多内容,但我仍然只涵盖了很多更高级别的细节。 例如,部署容错、可扩展的 Postgres 数据库本身就可以写一篇文章。 我也没有过多地谈论我编写的大量文档,这些文档很像我在Linux Journal中的文章,引导读者了解如何使用我们构建的所有这些工具。

正如我在本文开头提到的那样,这只是我发现对我来说在我的约束条件下效果良好的一种基础设施设计示例。 您的约束条件可能不同,并可能导致不同的设计。 这里的目标是为您提供一种成功的方法,以便您受到启发并将其调整为适合您自己的需求。

资源

Kyle Rankin 是 Linux Journal 的技术编辑和专栏作家,也是 Purism 的首席安全官。 他是Linux Hardening in Hostile NetworksDevOps TroubleshootingThe Official Ubuntu Server BookKnoppix HacksKnoppix Pocket ReferenceLinux Multimedia HacksUbuntu Hacks 的作者,也是许多其他 O'Reilly 书籍的贡献者。 Rankin 经常就安全和开源软件发表演讲,包括在 BsidesLV、O'Reilly Security Conference、OSCON、SCALE、CactusCon、Linux World Expo 和 Penguicon 上。 您可以在 @kylerankin 上关注他。

加载 Disqus 评论