抽象和元数据是系统工程架构的未来,就像它们之前在软件工程领域一样。在许多语言中,都存在抽象和元数据;然而,系统工程从未采用这种观点。系统总是被认为过于独特,无法进行任何标准化的抽象。现在我们已经标准化了较低级别的抽象,我们准备构建新的系统级抽象。
此处有龙
在讨论抽象时,首先保持适度的怀疑态度非常重要。Andrew Koenig 指出:“抽象是有选择性的无知。” 而 Joel Spolsky 创造了术语“泄漏抽象法则”,他描述了所有抽象都会泄漏它们所抽象的内容。
当您抽象一个系统时,要知道您选择对该系统保持无知。这并不意味着每个人都对底层系统一无所知,但这确实意味着您对系统的洞察力会降低。例如,Amazon Web Services 和 Google Cloud Platform 允许您抽象掉物理服务器甚至虚拟服务器。您不会了解任何关于底层物理主机或相关网络的信息;然而,这种抽象可能会泄漏。亚马逊 2011 年的 EC2 中断事件就是一个抽象泄漏底层故障的例子。这意味着您仍然需要熟悉基本原理以及抽象的工作方式。此外,您公司的一个团队应该完全了解内部运营的抽象;这不能可靠地外包。
平台之路
今天的数据中心非常混乱。一个应用程序在每个环境中都可能具有不同的操作系统和中间件版本。开发环境将拥有最新的版本,因为此处的更改更容易接受。生产环境将拥有最旧的版本,因为此处的更改令人担忧。并且每个应用程序都将具有与系统中任何其他应用程序不同的组合。因此,由于这些不一致性导致的故障频繁发生,更改被视为故障的根源,并且更改受到进一步限制。
现代数据中心基于抽象。物理层的主要抽象是一个平台。该平台允许使用 API 和更高级别的对象与计算、存储和网络进行交互。应用程序现在看到的计算资源是以不可变的 Docker 镜像的形式存在的,这些镜像作为容器在底层虚拟或物理主机上运行。应用程序与已经过测试的操作系统和中间件一起打包,并且相同的镜像被部署到每个环境中。应用程序现在在部署时自动从环境中获取其特定于环境的变量,而不是将它们打包到应用程序中或通过手动交互提供。
这种模型允许跨环境实现更高的一致性和可重复性,并且它提高了敏捷性,因为更改不再被视为所有问题的根源。现在,它通过更快的恢复成为解决问题的方案。我们现在优化更改,而不是优化稳定性和既无法实现快速更改也无法实现稳定性,从而同时获得快速更改和稳定性。
Docker
促进这种新模型的不可变镜像格式来自一个名为 Docker 的开源项目。Docker 容器是底层主机的抽象。它由一个分层文件系统组成,其中每一层都是不可变的。常见的模式是拥有一个操作系统层、一个中间件层,然后是一个应用程序层。每一层都会模糊其下方的层。如果应用程序层包含一个中间件层中存在的文件,则在启动镜像时只会看到应用程序层文件。中间件层文件仍然存在,但无法看到或使用。
如果您不了解 Docker 的工作原理,这可能会导致许多抽象泄漏问题。例如,如果您在一个层中复制了一堆文件,然后在下一层中 chmod 它们,那么所有文件将在镜像中存在两次。这可能会迅速累积并导致系统中出现许多问题。
Docker 还 使用 Linux 功能,例如 cgroups(大约十年前 Google 贡献的)和 命名空间。最基本的是,cgroups 确定进程可以消耗多少资源,而命名空间确定谁可以消耗它们。
多年来,Docker 还添加了其他抽象,包括网络、卷、密钥和服务。他们还添加了标签以允许分配元数据。元数据在这些现代的、抽象的、分布式系统中非常重要。我们不能再仅仅根据机器的名称、位置或地址来讨论机器。现在,我们根据位置、类型、功能、特性等多个属性来描述和引用它们。这会产生更灵活的抽象。
Kubernetes
Kubernetes 充分利用了这种新的灵活抽象模型。它最初构建在 Docker 之上,但现在它已经抽象掉了计算单元,因此即使虚拟机也可以用作实例容器(尽管这仍然是 高度实验性的)。Kubernetes 是 Google 基于其内部集群管理系统 Borg 创建的开源项目。
Kubernetes 具有与 Docker 类似的抽象,例如卷、服务和密钥;然而,Kubernetes 还有一个名为 pod 的抽象。Pod 是应该位于同一位置并共享存储和网络的容器组。它是 Kubernetes 中最小的可部署单元,而容器是 Docker 中最小的可部署单元。
Kubernetes 还利用插件系统,该系统为网络和存储以及计算提供抽象。然后,网络系统使用 网络策略 根据元数据描述连接。Kubernetes 中的每个对象都可以附加标签。这些标签不仅用于描述对象,还用于从众多对象中选择该对象以及所有具有匹配标签的对象。网络策略使用这些标签将策略应用于具有匹配标签的对象。
OpenShift
OpenShift 通过添加更多抽象来进一步扩展这一点,包括 BuildConfigs 和 ImageStreams。BuildConfig 用于描述如何构建应用程序,包括用于构建应用程序的镜像、用于运行应用程序的镜像、镜像应存储的位置以及应何时启动构建。ImageStreams 是镜像注册表抽象。ImageStreams 可以引用存储在 OpenShift 集成注册表、Docker Hub 或内部公司注册表中的镜像。所有这些引用和相关数据都从最终用户那里抽象出来,这极大地简化了应用程序开发人员的镜像管理。
创建整体配置
到目前为止描述的所有内容都具有自定义配置文件,需要非常专业的知识才能完成它们。这对我的工程团队来说是一个挑战,因为我们试图将这些技术引入拥有数千名开发人员的金融和医疗保健企业。我们必须教每个开发人员多种不同的格式,如果我们更改格式,我们将不得不重新培训所有人。我们还面临着我们的容器编排器之外仍然有工作负载的挑战。因此,我们围绕更高阶对象创建了自己的抽象。我们创建了一个系统,允许开发人员或管理员描述应用程序从诞生到消亡的整个过程。
有几种类型的文档可以提供这种能力。我们使用 namespace.yaml 将资源分配给一组对象。该文档必须由具有适当支出权限的个人批准,以支付所请求资源的估计成本。一旦这些资源获得批准,该命名空间内的任何应用程序或其他对象都可以使用这些资源,直到它们完全耗尽。
apiVersion: v1
kind: Namespace
name: a-namespace
spec:
environments:
- name: dev
clientFacing: false
resources:
cpu: 2
memory: 4Gi
storage: 10Gi
- name: test
resources:
cpu: 6
memory: 12Gi
storage: 20Gi
第二级文档描述特定对象。有多种类型,例如应用程序、数据库和文档。一切都有管道。这些文档描述了特定对象所需的资源、对象与其他命名对象的关系、此应用程序将运行的环境、应用程序应构建和测试的方式以及应如何部署和运行。然后,此文档被转换为与特定技术相关的多个文档,例如 Jenkins 的 Jenkinsfile 和 Kubernetes 的 Deployment。然后引用这些文档以确保维护每个环境的预期状态。
apiVersion: v1
kind: Application
name: application-name
metadata:
labels:
tier: frontend
spec:
build:
type: maven
runImageBase: tomcat7
environmentTemplate:
replicas: 1
resources:
min.memory: 256Mi
max.memory: 2Gi
environmentVariables:
- name: SHARED_ENV
value: 'shared value'
- name: ANOTHER_ENV
value: 'another value'
ports:
- name: https
port: 443
volumes:
- name: shared-data
emptyDir: {}
mountPath: /var/lib/pipeline_data
connections:
- name: authn
environments:
- name: dev
environmentVariables:
- name: ENVIRONMENT_SPECIFIC
value: 'dev value'
这种抽象允许我们的开发人员专注于创造业务价值,而中心团队可以使用单一文档界面将应用程序从 GitLab 转移到生产环境。我们的工具现在可以根据需要进行更改,而无需开发人员进行任何更改。维护通过这些文档达成的合同是中心团队的责任,以便开发人员的体验不会随着工具的更改而改变。由于该系统对我们来说非常有效,我们希望在不久的将来将其开源。
要深入了解此主题,请参加 Daniel 在 10 月 23 日至 24 日在北卡罗来纳州罗利市举行的 All Things Open 大会上发表的题为构建未来:抽象和元数据的演讲。
评论已关闭。