本文展示了如何使用开源工具实现集群级日志记录基础设施,并使用与组合和管理正在记录的软件系统相同的抽象概念进行部署。收集和分析日志信息是运行生产系统以确保其可靠性并提供重要审计信息的必要方面。已经开发了许多工具来帮助聚合和收集特定服务器上运行的特定软件组件(例如,Apache Web 服务器)的日志(例如,Fluentd4 和 Logstash.9)。它们还伴随着诸如 Elasticsearch3 等工具,用于将日志信息摄取到持久存储中,以及诸如 Kibana7 等工具,用于查询日志信息。
然而,收集使用容器(例如来自 Docker2 的容器)实现的组件以及由诸如 Kubernetes8 等系统编排的组件的日志更具挑战性,因为不再存在特定的程序和特定的服务器。这是因为一个组件由许多匿名实例(副本)组成,这些实例的数量会根据系统负载进行扩展和缩减。此外,没有特定的服务器,因为每个副本都在编排器选择的服务器上运行。
本文着眼于如何通过描述如何在 Kubernetes 编排框架上实现集群级日志聚合和检查来克服这些挑战。本文描述的方法的一个关键方面是它利用了用于组合和管理要记录的系统的相同抽象概念来构建日志记录基础设施本身。这种方法利用了现有的开源工具,如 Fluentd、Elasticsearch 和 Kibana,这些工具部署在容器内部并进行编排,以收集集群中运行的其他容器的日志。
本文仅描述 Kubernetes 系统的足够信息,以帮助激发一个针对简单应用程序的日志收集和聚合场景。关于 Kubernetes 容器编排器的全面描述可以在其网站8 上找到,关于 Borg、Omega 和 Kubernetes 的概述文章可在 acmqueue1 上找到。
Kubernetes 系统可以在各种公共云以及私有集群上编排应用程序组件。在本文中,应用程序部署在 Kubernetes 集群上,该集群创建在公共云 Google Compute Engine5 上运行的 VM(虚拟机)集合上。集群可以使用 Google Container Engine (GKE)6 创建,后者自动化了集群创建和管理的许多方面。为了强调该方法的提供商无关性质,我们说明了显式创建 Kubernetes 集群,该集群使用开源工具执行日志收集和聚合。显式集群创建或使用像 GKE 这样的集群管理系统创建都允许我们使用开源工具执行日志收集和聚合,尽管 GKE 允许与 Google 的专有云日志记录系统更紧密地集成。
图 1 显示了一个四节点 Kubernetes 集群的部署,该集群用于本文中描述的示例应用程序。该集群有四个工作 VM,名为 kubernetes-minion-08on
、kubernetes-minion-7i2t
、kubernetes-minion-9l7k
和 kubernetes-minion-ei9f
。第五个 Kubernetes master VM 将工作编排到其他 VM 上。但是,对于 Kubernetes 在此集群上调度的工作,您应该忽略用于运行应用程序的特定节点的名称或 IP 地址,因为这是 Kubernetes 抽象化的细节之一。您不知道运行我们程序的机器的名称。此外,应用程序的组件将随着系统的发展和处理故障而扩大和缩小规模,因此一个逻辑组件可能跨多台不同的机器执行。运行您的程序的机器名称可能会更改。
因此,在 Kubernetes 模型中,认为特定程序 P 在特定机器 M 上运行是没有意义的。更符合习惯的做法是通过查询附加到 Kubernetes 编排器创建的匿名实体的标签来识别系统的各个部分,这将返回当前运行的与查询匹配的实体。这使我们能够谈论动态演变的基础设施,而无需提及特定资源的名称。
假设的音乐商店应用程序的 Kubernetes 部署用于帮助描述如何收集集群级容器日志。该应用程序有几个前端微服务,这些微服务接受对 Web 界面的 HTTP 请求,用于浏览和购买音乐。这些前端服务通过与后端 MySQL 实例和 Redis 集群通信来工作,后端 MySQL 实例和 Redis 集群提供应用程序所需的持久存储。托管在 Google Compute Engine 上的持久磁盘也提供了 MySQL 数据库所需的存储。
Kubernetes 中部署的基本单元是 Pod。Pod 是应该始终作为原子单元一起分配到同一节点上的资源规范,以及集群编排器可用于管理 Pod 行为的其他信息。音乐商店应用程序使用一个 Pod 来描述 MySQL 实例的部署,如 YAML 文件 (albums-db-pod.yaml) 中所示
apiVersion: v1
kind: Pod
metadata
name: albums-db
labels
app: music1983
role: db
tier: backend
spec
containers
- name: mysql
image: mysql:5.6
env
- name: MYSQL_ROOT_PASSWORD
value: REDACTED
ports
- containerPort: 3306
volumeMounts
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes
- name: mysql-persistent-storage
gcePersistentDisk
pdName: albums-disk
fsType: ext4
此规范可用于创建音乐商店数据库的部署
$ kubectl create -f albums-db-pod.yaml
图 2 说明了此 Pod 的部署,该 Pod 名为 albums-db
,Pod IP 地址为 10.240.0.5。它在名为 kubernetes-minion-917k
的 Google Compute Engine VM 上运行,包含 MySQL 实例的 Docker 镜像,并使用 Google Compute Engine 上名为 albums-disk
的持久磁盘。三个标签标识应用程序 music1983
、角色 db
和层级 backend
。Pod 公开了端口 3306,由 MySQL Docker 实例提供服务,供同一集群中的其他组件通过地址 10.240.0.5:3306 使用。
在 Kubernetes 集群内部,您可以连接到此数据库、填充它并进行查询。例如
$ mysql --host=10.240.0.5 --user=NAME --password=REDACTED albums
mysql> select * from pop where artist = 'Pink Floyd';
+------------+-----------------------+-----------+----------+
| artist | album | inventory | released |
+------------+-----------------------+-----------+----------+
| Pink Floyd | Dark Side of the Moon | 57 | 1973 |
| Pink Floyd | The Wall | 103 | 1983 |
+------------+-----------------------+-----------+----------+
2 行于集合 (0.08 秒)
可以使用 Kubernetes 命令行工具提取 Pod 的日志
$ kubectl logs albums-db
...
2016-03-01 00:43:20 1 [Note] InnoDB: 5.6.29 started; log sequence number 1710893
2016-03-01 00:43:20 1 [Note] Server hostname (bind-address): '*'; port: 3306
2016-03-01 00:43:20 1 [Note] IPv6 is available.
...
此命令获取当前正在运行的 MySQL Docker 镜像的日志。您可以要求 Kubernetes 报告正在运行的 MySQL 实例的 Docker 容器 ID
$ kubectl describe pod albums-db | grep "Container ID"
Container ID: docker://38ab5c9e9aa8004e9b61f19885...
这是否解决了从部署在 Kubernetes 集群上的应用程序收集日志的问题?一个问题是在 Pod 的生命周期内,已部署的底层 Docker 容器(或多个容器)可能会终止并创建新的替换容器(例如,为了处理以某种方式失败的容器)。以下操作通过破坏 MySQL 容器并查看 Kubernetes 如何响应来引发故障。这是通过 SSH 连接到运行容器的 Google Compute Engine VM 以杀死 MySQL Docker 容器来完成的。
$ gcloud compute --project "kubernetes-6502" ssh --zone "us-central1-b" "kubernetes-minion-9l7k"
$ sudo -s
# docker ps
CONTAINER ID IMAGE
38ab5c9e9aa8 mysql:5.6
# docker kill 38ab5c9e9aa8
38ab5c9e9aa8
# docker ps
CONTAINER ID
abdfca342daa mysql:5.6
不久之后,Kubernetes 系统的一部分在节点上运行的代理注意到容器不再运行。为了将系统的当前状态驱动到所需状态,创建了一个新的 MySQL Docker 实例,其容器 ID 以 abdfca342daa 开头。现在检查 albums-db
的日志会显示
$ kubectl logs albums-db
2016-03-01 01:33:25 0 [Note] mysqld (mysqld 5.6.29) starting as process 1 ...
...
2016-03-01 01:33:25 1 [Note] InnoDB: The log sequence numbers 1710893 and 1710893 in ibdata files do not match the log sequence number 1710903 in the ib_logfiles!
2016-03-01 01:33:25 1 [Note] InnoDB: Database was not shutdown normally!
2016-03-01 01:33:25 1 [Note] InnoDB: Starting crash recovery.
...
2016-03-01 01:33:25 1 [Note] InnoDB: 5.6.29 started; log sequence number 1710903
2016-03-01 01:33:25 1 [Note] Server hostname (bind-address): '*'; port: 3306
日志现在是针对当前正在运行的容器 (abdfca342daa) 的,并且先前 MySQL 容器实例 (38ab5c9e9aa8) 的日志已丢失。这些日志的生命周期由底层 Docker 容器的生命周期而不是 Pod 的生命周期决定。真正需要的是一种机制,用于收集和存储作为此 Pod 执行生命周期一部分运行的每个容器实例生成的所有日志信息。
虽然可以在 Kubernetes 集群中指定和部署单个 Pod,但更符合习惯的做法是指定一个复制控制器,该控制器创建 Pod 的多个副本。以下是一个复制控制器的示例,该控制器指定部署两个 Redis 从属 Pod (redis-slave-controller.yaml)
apiVersion: v1
kind: ReplicationController
metadata
name: redis-slave
labels
app: music1983
role: slave
tier: backend
spec
replicas: 2
template
metadata
labels
app: music1983
role: slave
tier: backend
spec
containers
- name: slave
image: redis
resources
requests
cpu: 300m
memory: 250Mi
ports
- containerPort: 6379
此规范声明了一个名为 redis-slave
的复制控制器,该控制器具有三个用户定义的元数据标签,这些标签附加到它创建的每个 Pod。标签标识了整体应用程序 music1983
的名称、Redis 实例的角色 slave
以及此 Pod 作为 backend
层级的成员。初始副本 Pod 的数量设置为两个,尽管此数量稍后可能会向上或向下调整。要复制的每个 Pod 都由一个 Redis Docker 容器、一个公开端口 6379(Redis 协议通过该端口运行)以及一些用于 CPU 和内存利用率的资源请求(已传达给调度程序)组成。可以将此规范提供给 Kubernetes 命令行工具,以启动 Redis 从属 Pod
$ kubectl create -f redis-slave-controller.yaml
图 3 显示了此类 Redis 从属控制器的示例部署,其中两个 Pod 在两个不同的 Google Compute Engine VM 上运行。Pod 具有自动生成的名称:redis-slave-tic4b 和 redis-slave-yazzp。不要对任何特定 Pod 的名称过于执着,因为 Pod 可能会因故障或复制控制器基数的更改而出现和消失。
每个 Pod 都有自己的 IP 地址,并且还显示了主机 VM 的 IP 地址,尽管此地址对于在集群上运行的 Kubernetes 应用程序永远不会有任何意义。如果您无法说出特定 Pod 的名称,那么您如何与它交互?标签选择器可以定义一个名为服务的实体,该实体为资源集合引入一个稳定名称。发送到服务提供的稳定名称的请求会自动路由到与服务标签选择器所定义的网络匹配的 Pod。以下是标识提供 Redis 从属功能的 Pod 的服务的定义 (redis-slave-service.yaml)
apiVersion: v1
kind: Service
metadata
name: redis-slave
labels
app: music1983
role: slave
tier: backend
spec
ports
- port: 6379
selector
app: music1983
role: slave
tier: backend
此服务的部署如图 4 所示。该服务在集群 redis-slave
内定义了一个 DNS(域名系统)可解析的名称,该名称接受端口 6379 上的请求,然后将它们转发到与其标签选择器匹配的任何 Pod(即,任何将 app 标签设置为 music1983
,将 role 标签设置为 slave
,并将 tier 标签设置为 backend
的 Pod)。现在,redis-slave
-read-replicated Pod 的使用者不再受用于服务其请求的特定 Pod 的名称以及这些 Pod 运行所在的特定节点的名称的影响。
图 5 显示了一个音乐商店网站的部署,该网站由多个前端微服务组成,这些微服务接受外部请求并呈现 Web 用户界面。这些前端服务将信息存储在由多个 Redis 键/值存储实例实现的键/值存储中。该系统旨在轻松独立扩展以下方面的容量:(a) 服务 Web 流量;(b) 从键/值存储系统读取;(c) 写入键/值存储系统。随着更多用户连接到音乐商店网站,可以向上调整前端微服务的数量。通常,您期望相对廉价的读取操作比对键/值存储的昂贵写入操作多得多。为了尽可能快地处理读取操作,将服务于来自 Redis 从属实例(在本例中为两个)的读取,并将单独的 Redis 微服务池部署为执行写入操作的主服务器(最初在本例中只有一个)。
收集前端服务 Pod 的日志带来了另一个生命周期问题。仅从当前正在运行的三个 Pod 中的每一个收集日志是不够的(即使在收集前端 Docker 镜像的多次调用日志时也是如此),因为 Pod 本身可能会被终止,然后重生(可能在不同的主机上)。在某些情况下,可能短暂地存在超过三个前端 Pod,或者可能少于三个。如果发生这种情况,Kubernetes 编排系统将注意到并创建或杀死 Pod,以将系统驱动到声明的具有三个前端 Pod 的状态。随着前端 Pod 的来来去去,您希望收集它们的所有日志,因此日志收集活动的生命周期与前端复制控制器的生命周期相关联,而不是与特定 Pod 的生命周期相关联。
开源日志聚合器 Fluentd 用于收集给定节点上运行的 Docker 容器的日志。尝试直接在每个节点(即,GCE VM)上运行 Fluentd 收集器进程会产生与创建 Pod 以解决的相同部署问题(例如,处理故障和执行更新)。因此,Docker 容器的节点级日志聚合实际上是从作为 Pod 规范一部分运行的 Docker 容器实现的。这种元方法允许日志记录层受益于 Kubernetes 模型为管理部署和生命周期事件的应用层提供的相同优势。例如,Kubernetes 的滚动更新机制可以更新每个节点上运行的 Pod,以便它们在使用集群仍在运行时使用更新版本的日志聚合软件。
Fluentd 收集器本身不存储日志。相反,它们将其日志发送到 Elasticsearch 集群,该集群将日志信息存储在复制节点集中。同样,与其直接在“裸机”上运行此 Elasticsearch 集群,不如定义 Pod 来指定单个 Elasticsearch 副本的行为,然后定义复制控制器来指定包含复制日志信息并提供查询界面的 Elasticsearch 节点集合,最后定义一个服务,该服务为平衡对 Elasticsearch 集群的查询提供一个稳定名称。
Fluentd 节点级收集器 Pod 的完整规范如下所示 (fluentd-es.yaml)
apiVersion: v1
kind: Pod
metadata
name: fluentd-elasticsearch
namespace: kube-system
spec
containers
- name: fluentd-elasticsearch
image: gcr.io/google_containers/fluentd-elasticsearch:1.11
resources
limits
cpu: 100m
args
- -q
volumeMounts
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes
- name: varlog
hostPath
path: /var/log
- name: varlibdockercontainers
hostPath
path: /var/lib/docker/containers
这指定了一个节点级收集器,它运行一个专门构建的 Fluentd 镜像,该镜像配置为使用 DNS 名称和端口 elasticsearch-logging:9200(它本身作为 Kubernetes 服务实现)将日志发送到 Elasticsearch 集群。该规范还描述了节点级文件系统上的 Docker 日志位置如何映射到 Pod 运行的 Docker 容器内部的文件系统。这允许节点上所有 Docker 容器的日志由此容器内运行的 Fluentd 实例收集。
当 Kubernetes 集群配置为使用 Elasticsearch 作为数据存储进行日志记录时,集群创建过程会在每个节点上实例化一个日志收集器 Pod。这些 Pod 可以在 kube-system 命名空间中观察到
$ kubectl get pods --namespace=kube-system
名称 就绪 状态 重启次数 年龄
fluentd-elasticsearch-kubernetes-minion-08on 1/1 正在运行 0 16天
fluentd-elasticsearch-kubernetes-minion-7i2t 1/1 正在运行 0 16天
fluentd-elasticsearch-kubernetes-minion-9l7k 1/1 正在运行 0 16天
fluentd-elasticsearch-kubernetes-minion-ei9f 1/1 正在运行 0 16天
...
每个节点上的特殊进程确保其中一个日志收集 Pod 在每个节点上运行。如果日志收集器 Pod 因任何原因失败,则会在其位置创建一个新的 Pod。这些 Pod 收集本地运行的 Docker 容器的日志,并将它们摄取到 kube-system 命名空间中运行的 Elasticsearch Kubernetes 服务中。
使用 Elasticsearch 创建的集群用于存储日志,默认情况下将实例化两个 Elasticsearch 实例。这些 Elasticsearch 日志记录 Pod 的规范可以在 es-controller.yaml 中找到,该文件描述了 Elasicsearch 实例的复制控制器以及 Elasticsearch 日志记录 Pod 的实际配置。这些可以在 kube-system 命名空间中观察到
$ kubectl get pods --namespace=kube-system
名称 就绪 状态 重启次数 年龄
elasticsearch-logging-v1-7rmo3 1/1 正在运行 0 16天
elasticsearch-logging-v1-v7lmv 1/1 正在运行 0 16天
...
节点级日志收集 Fluentd Pod 不直接与这些 Elasticsearch Pod 通信。相反,它们连接到 DNS 名称 elasticsearch-logging:9200,该名称由 Elasticsearch Kubernetes 服务 es-service.yaml 实现
apiVersion: v1
kind: Service
metadata
name: elasticsearch-logging
namespace: kube-system
labels
k8s-app: elasticsearch-logging
kubernetes.io/cluster-service: "true"
kubernetes.io/name: "Elasticsearch"
spec
ports
- port: 9200
protocol: TCP
targetPort: db
selector
k8s-app: elasticsearch-logging
您可以在 kube-system 命名空间中观察到此服务正在运行
$ kubectl get services --namespace=kube-system
名称 CLUSTER_IP EXTERNAL_IP 端口(S)
elasticsearch-logging 10.0.8.117 <无> 9200/TCP
...
可以查询 Elasticsearch 以获取标签选择器捕获的前端服务的所有 Pod 的日志。本地代理允许您使用管理员权限连接到集群,这是检索正在运行的容器的日志所必需的。您仅查询标记为 container_name
字段为 frontend-server
的容器的日志。
$ kubectl proxy
<在其他地方>
$ curl -XGET "https://127.0.0.1:8001/api/v1/proxy/namespaces/kube-system/services/elasticsearch-logging/_search?q=container_name:frontend-server&_source=false&fields=log&pretty=true"
...
}, {
"_index" : "logstash-2016.02.26",
"_type" : "fluentd",
"_id" : "AVMa-C0pcuStSsThK0M4",
"_score" : 2.8861463,
"fields" : {
"log" : [ "Slow read for key k103: 192 ms" ]
}
...
}, {
"_index" : "logstash-2016.02.26",
"_type" : "fluentd",
"_id" : "AVMa-C0pcuStSsThK0NE",
"_score" : 2.8861463,
"fields" : {
"log" : [ "[negroni] Started GET /lrange/k336" ]
}
...
}, {
"_index" : "logstash-2016.02.26",
"_type" : "fluentd",
"_id" : "AVMa-C_fcuStSsThK0Op",
"_score" : 2.8861463,
"fields" : {
"log" : [ "Slow write for key k970: 187 ms" ]
}
}, {
...
由于 Elasticsearch 集群是由复制控制器管理的 Pod 集合,因此它可以通过简单地增加 Elasticsearch 日志记录实例的副本节点数量来处理日志记录系统查询负载的增加。每个 Pod 都包含摄取日志的副本,因此如果一个 Pod 因某种原因死亡(例如,它运行所在的机器发生故障),则会创建一个新的 Pod 来替换它,并且它将与正在运行的 Pod 同步以复制摄取的日志。
可以使用 Kibana 查看 Elasticsearch 集群中聚合的日志。这提供了一个 Web 界面,该界面提供了一种更方便的交互式方法来查询摄取的日志,如图 6 所示。
Kibana Pod 也受到 Kubernetes 系统的监控,以确保它们运行状况良好并且存在预期的副本数量。这些 Pod 的生命周期由复制控制器规范控制,其性质类似于 Elasticsearch 集群的配置方式。以下输出显示了集群配置为维护两个 Elasticsearch 实例和一个 Kibana 实例。如果系统负载增加,可以发出一个简单的命令来增加 Elasticsearch 和 Kibana 副本的数量。此外,Elasticsearch 副本的数量可以独立于 Kibana 实例的数量进行扩展,从而允许您通过仅扩展满足该需求所需的子组件来响应不同类型负载的增加。
收集在编排集群中运行的容器的日志提出了一些挑战,这些挑战是手动部署的软件组件所没有面临的。特别是,我们无法通过名称显式标识特定容器(或其中包含它的 Pod 的名称)或容器运行所在的节点,因为这两者都可能在已部署应用程序的生命周期内发生变化。随着应用程序组件(微服务)的来来去去,我们需要收集和聚合在应用程序生命周期内作为应用程序一部分工作的所有容器的日志。通过使用标签选择器查询来识别在任何给定时刻哪些正在运行的活动属于感兴趣的应用程序,可以解决此挑战。然后,这些查询可以(通过 Kubernetes 服务)用于查询动态演变的应用程序的日志。
实现日志聚合和收集所需的基本基础设施本身可以使用与组合和管理需要记录的应用程序相同的抽象概念来实现:Pod、复制控制器和服务。这允许在日志记录系统运行时调整其容量并更新它,以及稳健地处理故障。这也为以模块化、灵活、可靠和可扩展的方式开发其他云计算系统基础设施组件提供了一个模型。
1. Burns, B., Grant, B., Oppenheimer, D., Brewer, E., Wilkes, J. 2016. Borg, Omega, and Kubernetes. acmqueue 14(1); https://queue.org.cn/detail.cfm?id=2898444.
2. Docker; www.docker.com.
3. Elasticsearch. Elastic; https://elastic.ac.cn/products/elasticsearch.
4. Fluentd; http://www.fluentd.org/.
5. Google Compute Engine; https://cloud.google.com/compute/.
6. Google Container Engine; https://cloud.google.com/container-engine/.
7. Kibana. Elastic; https://elastic.ac.cn/products/kibana.
8. Kubernetes; https://kubernetes.ac.cn/.
9. Logstash. Elastic; https://elastic.ac.cn/products/logstash.
Satnam Singh ([email protected]) 是 Facebook 的一名软件工程师,致力于移动应用程序的字节码优化。此前,他在 Google 从事 Kubernetes 项目的工作。
版权 © 2016 归所有者/作者所有。出版权已授权给 。
queue.acm.org 上的相关内容
开放 Web 服务架构
Stans Kleijnen 和 Srikanth Raju
https://queue.org.cn/detail.cfm?id=637961
网络的新角色
Taf Anthias 和 Krishna Sankar
https://queue.org.cn/detail.cfm?id=1142069
通过设备多样化社会实现的情境数据访问
George W. Fitzmaurice 等人
https://queue.org.cn/detail.cfm?id=966721
最初发表于 Queue vol. 14, no. 3—
在 数字图书馆 中评论本文
Peter Kriens - OSGi 如何改变了我的生活
在 20 世纪 80 年代初,我发现了 OOP(面向对象编程),并深深地爱上了它。像往常一样,这种爱意味着说服管理层投资这项新技术,最重要的是,送我去参加酷炫的会议。所以我向我的经理推销了这项技术。我向他勾勒出美好的未来,有一天我们将从现成的类创建应用程序。我们将从存储库中获取这些类,将它们组合在一起,瞧,一个新的应用程序将诞生。今天,我们或多或少地认为对象是理所当然的,但如果我说实话,我在 1985 年向我的经理提出的推销从未真正实现。
Len Takeuchi - ASPs:集成挑战
使用 ASP 和为 ASP 提供增值产品的第三方供应商的组织需要与它们集成。ASP 通过提供基于 Web 服务的 API 来实现这种集成。通过 Internet 与 ASP 集成和与本地应用程序集成之间存在显着差异。与 ASP 集成时,用户必须考虑许多问题,包括延迟、不可用性、升级、性能、负载限制和缺乏事务支持。
Chris Richardson - 解开企业 Java 的复杂性
关注点分离是计算机科学中最古老的概念之一。该术语由 Dijkstra 在 1974 年提出。1 它很重要,因为它简化了软件,使其更易于开发和维护。关注点分离通常通过将应用程序分解为组件来实现。然而,存在跨领域关注点,这些关注点跨越(或横跨)多个组件。这些类型的关注点无法通过传统的模块化形式来处理,并且会使应用程序更加复杂且难以维护。
Michi Henning - CORBA 的兴衰
根据确切的开始计数时间,CORBA 大约有 10-15 年的历史。在其生命周期中,CORBA 已从早期采用者的前沿技术,发展成为流行的中间件,再到相对默默无闻的小众技术。有必要研究一下为什么 CORBA——尽管曾经被誉为“电子商务的下一代技术”——遭受了这种命运。CORBA 的历史是计算行业多次看到的历史,而且似乎当前中间件的努力,特别是 Web 服务,将重演类似的历史。