logo
咨询企业版

产品实践

运维如何提效?手把手教你用 NebulaGraph 图技术搞定复杂运维系统

NebulaGraph 图数据库驱动的基础设施运维示例

本文系图技术在大型、复杂基础设施之中 SRE/DevOps 的实践参考,并以 OpenStack 系统之上的图数据库增强的运维案例为例,揭示图数据库、图算法在智能运维上的应用。本文所有示例代码开源。

最近,有些尚未使用过图技术、DevOps/Infra 领域的工程师在 NebulaGraph 社区询问是否有「图技术在运维的应用」相关案例参考。于是,我又可以“借题发挥”来实践下如何利用图的能力与优势去帮助运维工程师们基于复杂基础设施上构建辅助运维系统。如果你对本文有任何看法,欢迎评论区或者来论坛和我交流下,非常感谢。

通常,我们说的复杂的基础设施运维环境指的是资源(manifest)繁多且分布在不同层面的系统。为了让实践更加真实、贴近实际的运维情况,让运维问题复杂又可控,这里我选择了用一个基础设施平台:OpenStack。在 OpenStack 系统上,我分别利用 Push 和 Pull 两种模式将资源在图模型中对应点、边信息加载到 NebulaGraph 的 Graph ETL 管道的路径中。

在我们基于运维资源构建的图谱,会做如下用例图探索:

  • 告警、状态的推理与传导;
  • 网络直连与互联关系;
  • 镜像、云盘、快照血缘管理;
  • 高相关性虚机预警;
  • 秘钥泄漏的图上风控分析;
  • 镜像、云盘漏洞范围分析;
  • 宿主机逃离影响范围分析;
  • 脆弱依赖资源检测;

实验环境搭建

背景知识

OpenStack 是一个开源的云计算平台,提供了类似于 AWS 的云服务。它提供了一组可插拔的模块,包括了计算,存储和网络等功能,可以帮助用户构建和管理云环境。OpenStack 采用分布式架构,支持多种操作系统和硬件平台,可以在企业级和服务提供企业级环境中使用。

OpenStack-overview-diagram-new

最初,OpenStack 是由 NASARackspace Inc. 发起的 Nova(虚拟化计算项目)和 Swift(兼容 S3 的对象存储)项目组成。随着项目的发展,OpenStack 现在已经有非常多不同的子项目:

openstack-map-v20221001

本次实践中涉及到 OpenStack 的主要项目有:

  • Nova 是 OpenStack 的计算服务,用于管理虚拟机;
  • Cinder 是 OpenStack 的块存储服务,用于管理云存储;
  • Neutron 是 OpenStack 的网络服务,用于管理云网络;
  • Glance 是 OpenStack 的镜像服务,用于管理云镜像;
  • Horizon 是 OpenStack 的可视化控制台服务。

除此之外,我还引入了 Vitrage 项目辅助我们收集部分资源数据:

  • Vitrage 是 OpenStack 的一个高级分析和可视化工具,用于分析和可视化 OpenStack 环境中的资源和事件。它可以汇集来自 OpenStack 各个服务的数据,并以可视化的方式呈现。Vitrage 能发现和诊断问题,提高 OpenStack 环境的可用性和可维护性。

得益于 OpenStack Decouple 的设计理念,Vitrage 可以很容易、无侵入式(只用修改要收集的服务的两行配置)就可以在 OpenStack 的消息队列中订阅资源信息的 Push 消息。

不过,介于 Vitrage 许久没有大更新,且维护维艰,比如:在 zed 里 Vitrage Dashboard 作为 Horizon 插件已经无法正常工作了。所以,本实践只利用它的资源收集能力。

环境准备搭建

NebulaGraph 集群

快速试玩 NebulaGraph 的话,安装有这么几个选项:

OpenStack 集群

本文需要的 OpenStack 集群是一个多机的环境,因此我在 Linux Server 上用 Libvirt 和 Linux Bridge 搭建了多个虚拟机来模拟 OpenStack 的物理机。得益于 CPU 的嵌套虚拟化和 QEMU,我们完全可以在虚拟机搭建的实验环境中模拟可正常工作的 OpenStack Nova instance 虚机。

虚拟机搭建完成后,还需要模拟真实的多资源 Infra 环境。这边略去具体的操作步骤,感兴趣的小伙伴可以阅读本文的参考文献,当中有详细的实践过程。

完成 OpenStack 环境的搭建后,我们通过 Horizon Dashboard 查看集群和资源:

虚拟机情况:

nova_instance

网盘情况,其中四个挂载在不同的虚拟机上

cinder_volume

集群租户的网络拓扑:

neutron_topology

通过 OpenStack Vitrage 的 API/CLI 可获得部分主要资源的拓扑:

source openrc admin admin
vitrage topology show --all-tenants

这个结果是一个 JSON,数据已经是边(links)和点(nodes)的序列化图结构了。

{
  "directed": true,
  "graph": {},
  "links": [
    {
      "vitrage_is_deleted": false,
      "relationship_type": "contains",
      "source": 0,
      "target": 11,
      "key": "contains"
    },
    {
      "vitrage_is_deleted": false,
      "relationship_type": "contains",
      "source": 0,
      "target": 13,
      "key": "contains"
    },
...
    {
      "vitrage_is_deleted": false,
      "relationship_type": "attached",
      "source": 27,
      "target": 28,
      "key": "attached"
    }
  ],
  "multigraph": true,
  "nodes": [
    {
      "id": "node0",
      "vitrage_type": "nova.host",
      "vitrage_category": "RESOURCE",
      "vitrage_is_deleted": false,
      "update_timestamp": "2023-01-13T08:06:48Z",
      "vitrage_sample_timestamp": "2023-01-13T08:06:49Z",
      "vitrage_is_placeholder": false,
      "vitrage_id": "630b4c2c-5347-4073-91a3-255ec18dadfc",
      "name": "node0",
      "vitrage_cached_id": "d043d278a6a712909e30e50ca8ec2364",
      "is_real_vitrage_id": true,
      "vitrage_aggregated_state": "AVAILABLE",
      "vitrage_operational_state": "OK",
      "vitrage_datasource_name": "nova.host",
      "state": "available",
      "graph_index": 0
    },
    {
      "id": "nova",
      "vitrage_type": "nova.zone",
      "vitrage_category": "RESOURCE",
      "vitrage_is_deleted": false,
      "vitrage_sample_timestamp": "2023-01-12T03:06:48Z",
      "vitrage_is_placeholder": false,
      "vitrage_id": "a1e9c808-dac8-4b59-8f80-f21a90e9869d",
      "vitrage_cached_id": "125f1d8c4451a6385cc2cfa2b0ba45be",
      "is_real_vitrage_id": true,
      "vitrage_aggregated_state": "AVAILABLE",
      "vitrage_operational_state": "OK",
      "state": "available",
      "update_timestamp": "2023-01-12T03:06:48Z",
      "name": "nova",
      "vitrage_datasource_name": "nova.zone",
      "graph_index": 1
    },
...
  "raw": true
}

图谱建模

将资源映射成图谱:

  • nova instance 是 Nova 服务中的虚拟机实例,每个 nova instance 都有自己的配置信息(如 CPU、内存、磁盘等),有时候我们就叫它 server 或者 VM、虚机。
  • nova host 是 Nova 服务中的物理主机,是 nova instance 运行的物理环境。nova host 上面会运行 nova-compute 服务,这个服务负责管理和调度 nova instance。nova host 上面还可能运行其他服务,如网络服务等。
  • nova keypair 是 Nova 服务中的密钥对,用于访问 nova instance。
  • cinder volume 是 Cinder 服务中的云存储卷,可以 attach 到 nova instance 上做为硬盘。
  • cinder snapshot 是 Cinder 服务中的云存储快照,可以在 cinder volume 上做快照。
  • glance image 是 Glance 服务中的镜像,可以作为创建 nova instance 时候的启动硬盘。
  • neutron network 是 Neutron 服务中的网络,可以用于配置 nova instance 的网络连接。
  • neutron port 是 Neutron 服务中的端口,用来连接 nova instance 和 neutron network 之间。在 nova instance 虚拟机上,如果不是 trunk port 的话,一个 port 常常对应一个网卡。

它们之间的关系如下:

schema_draft

基础设施图 ETL

接下来我们解决从基础设施中抽取资源元数据的问题:

push 模式

这里的 push 指的是以基础设施为出发点,通过事件驱动主动地发出资源变动的信息。它的好处是实时掌握资源情况,坏处是过于依赖基础设施,很多非常瘦的、软件定义/可编程程度不高的组件、某些硬件设备是没有 push 机制的。比如:有些年份的软件系统不一定能存在 push 的接口,改造起来有侵入性。

前面提及过,OpenStack 自身是存在 Push Hook 机制的,它的子项目 vitrage 就利用这个机制很优雅地收集系统资源、告警等信息进入图中,类似的机制在其他平台中也是可以实现的。

本实验中我们就利用 vitrage 的机制去收集一部分图谱中的资源信息,如下图,可以看到 vitrage 会在 OpenStack message bus 中订阅 nova/cinder/neutron 等服务中的资源时间,把事件传入 Entity Queue,经过处理,存储到 Entity Graph 中。

在此之上,我们可以通过 vitrage API 获取图谱的拓扑,来消费它。

注意:实际上 Vitrage 服务还提供了推理告警、推理状态、定义决策事件的能力,这里我们并没有采用,后边我们在图上做的一些事情甚至还和它的能力有一些重叠。

vitrage_arch

这里我只是用它来展示 push 模式的工作机制,如果没有 Virtrage 这个项目存在,我们也可以比较容易通过 OpenStack 的 oslo.messaging 这个库很容易写出在 Message Bus(可能是 Kafka, RabbitMQ 等不同底层实现)上订阅资源时间的应用,然后把事件通过 Flink/ Kafka/ Pulsar 等方式接驳 NebulaGraph。

因为 Vitrage 的存在,我就偷懒不用去实现这部分逻辑,只消写一小部分代码调用 Vitrage API 取这个数据就可以了,讽刺的是,从这个角度来看,这其实是一种 pull 的模式了,不用拘泥它本质上算是哪一种方式,至少在资源发起测,我们把它当做 push 模式的例子看待吧。

这部分从 Vitrage 抓取的代码我放在 https://github.com/wey-gu/openstack-graph/blob/main/utils/vitrage_to_graph.py 了,调用方式很简单,在有 OpenStack 客户端的环境中,执行它就可以了,比如:

# 连到 node0 上
ssh stack@node0_ip

# 进入 devstack 目录
cd devstack

# 下载 vitrage 中图数据,解析为 NeublaGraph DML/DQL 的工具
wget https://raw.githubusercontent.com/wey-gu/openstack-graph/main/utils/vitrage_to_graph.py

# 执行它
python3 vitrage_to_graph.py

执行之后,会生成如下文件:

  • schema.ngql 图数据的 Schema 定义
  • vertices/ 点数据的文件夹
  • edges/ 边数据的文件夹

pull 模式

反过来,pull 模式是从资源外部定期或者事件驱动地拉取资源,存入图谱的方式。刚好,本实验中 Vitrage 抓取的资源是有限,部分额外的资源单独写了 Python 的代码来主动全量抓取。pull 模式的好处是对资源方没有任何侵入性,只需要调用它的接口获取信息就可以,坏处则是有的系统不太容易获得增量变化,可能只能全量去取。

这部分我抓取的关系如下:

  • glance_used_by:image -[:used_by]-> instance (get from instance)
  • glance_created_from:image -[:created_from]-> volume (get from image)
  • nova_keypair_used_by:keypair -[:used_by]-> instance (get from instance)
  • cinder_snapshot_created_from:volume snapshot -[:created_from]-> volume (get from snapshot)
  • cinder_volume_created_from:volume -[:created_from]-> volume snapshot (get from volume)
  • cinder_volume_created_from:volume -[:created_from]-> image (get from volume)

代码在 https://github.com/wey-gu/openstack-graph/blob/main/utils/pull_resources_to_graph.py。在真实场景下,我们可能会用 Apache Airflow、Dagster 甚至是 Cron Job 等方式定期执行它。

手动执行的方式也很简单:

# 连到 node0 上
ssh stack@node0_ip

# 进入 devstack 目录
cd devstack

# 下载抓取 OpenStack 资源,生成 NeublaGraph DML/DQL 的工具
wget https://raw.githubusercontent.com/wey-gu/openstack-graph/main/utils/pull_resources_to_graph.py.py

# 执行它
python3 pull_resources_to_graph.py

执行之后,会生成点、边的 nGQL 语句在两个文件夹下:

  • vertices/ 点数据的文件夹
  • edges/ 边数据的文件夹

加载数据到 NebulaGraph

我们只需要在 NebulaGraph Studio Console,Explorer Console 或者 NebulaGraph 命令行 Console 中执行上边生成的 .ngql 文件就好了:

# DDL from vitrage
cat schema.ngql

# DDL and DML for both push and pull mode data
cat edges/*.ngql
cat vertices/*.ngql

之后,在 NebulaGraph 中我们会有一个叫做 openstack 的图空间,用这个查询可以查到所有数据:

MATCH (n) WITH n LIMIT 1000
OPTIONAL MATCH p=(n)--()
RETURN p, n

在 NebulaGraph Explorer 中渲染,手动设置一下数据的图标,就可以看到 OpenStack 集群里的所有租户的资源图了:

all_graph_view

接下来,我们终于可以在图上看看有意思的洞察了!

基于图谱的基础设施运维示例

作为非 SRE、DevOps 人员,我尝试藉由自己在 OpenStack 和图技术的理解模拟出以下实例,希望对你有所帮助。

告警、状态的推理与传导

受启发于 Vitrage 项目,我们可以借助资源图谱实时图查询、图计算甚至图可视化能力,在图上推理、传导信息,把重要的事件藉由图上组织好的知识分发给需要收到通知的人、组织、系统。

一个简单的例子,我们在 nova host(虚拟机的宿主机、Hypervisor 机器,以下简称宿主机)中获得了一个告警、事件的时候,可能是网卡失败、物理硬盘预警、CPU 占用过高之类的告警。借助图谱查询获得所有相关联的虚机后,再把 WARN 级别的告警发出去或者设置它们为亚健康 unhealthy 状态。

获得通知的对象,往往是一些用户系统,它们可以根据预先定义好的策略做些自动化运维,或者通知 Hook:

  • 收到“宿主机 CPU 过高”的告警情况,根据用户自己设定的不同策略把虚机迁移走,或者采用更高级、复杂的撤离方式,像是开始不接受新流量,创建新的替代 workload,再优雅地关闭这个 workload;
  • “控制面网络故障”告警情况,这时候往往无法成功进行主机的撤离、迁移,故可以考虑触发备份主机、启动新 workload、关机;
  • 其他“亚健康状态”,可以作为负载层面出问题的根因分析 RCA 依据。

这里给出一个在图谱上进行告警、状态传递的查询例子。假设 vid 为 node0 的宿主机出现了高 CPU 告警,下面这个查询可以得到所有该宿主机上的虚机,获得时间、告警通知列表:

MATCH (vm:nova_instance)<-[:`contains`]-(host_CPU_high:nova_host)
    WHERE id(host_CPU_high) == "node0"
RETURN vm.nova_instance.name AS VM_to_raise_CPU_alarms

这条语句的查询图模式是从 host_CPU_high 这个 nova_host 向外经由 contains 这个关系指向 vm 这个 nova_instance 的。

(vm:nova_instance)<-[:`contains`]-(host_CPU_high:nova_host)

它的结果是:

VM_to_raise_CPU_alarms
server-4
server-3
server-1
server-0

如果我们把查询改动一下,选择输出全路径,则可以看到这个信息传递的方向:

MATCH p=(vm:nova_instance)<-[:`contains`]-(host_CPU_high:nova_host)
    WHERE id(host_CPU_high) == "node0"
RETURN p

我们在 Explorer 中渲染下,点击 N 跳检测:

all_graph_view

这个例子比较简单,甚至用不到图能力。因为一跳查询在表结构中也能很轻松地用一、两个 nova API call 搞定。实际上,图上是可以做很多更 Graphy(具有图属性的)、复杂、独特的工作的,我们慢慢来看:

网络可达检测

考虑下这样的场景,在 OpenStack 中,不同的主机可以连接到相同的子网 VPC,主机也可以连接到多个子网之中。这样,主机之间的网络连通性信息、与网络联通相关的推理、传导都可以在图上进行。

在真实世界中,这里可能还要考虑 Security Group、Router、Switch 等因素。本示例中我们用到的 OpenStack 是比较简化的 L2 only Setup。

我们要获得与虚机 server_a 同一 VPC 的所有其他虚机很容易表达:

MATCH (server_a)--(:neutron_port)--(:neutron_network)--(:neutron_port)--(server_b:`nova_instance`)
    WHERE id(server_a) == "server-0"
RETURN server_b.nova_instance.name AS L2_connected_server

结果如下:

L2_connected_server
server-1

看起来很初级,接下来我们再查询与虚机 server_a 同一 VPC、有可能通过跨网络虚机而互联的主机的所有其他虚机。这时候,我们除了共享 neutron network(VPC) 的情况,还要查询所有二层直连的虚机可能通过其他 VPC 连出去的的虚机。下面的例子,我们用到了 OPTIONAL MATCH 的表达,表示可能匹配到的模式:

MATCH (server_a)--(:neutron_port)--(net:neutron_network)--(:neutron_port)--(server_b:`nova_instance`)
    WHERE id(server_a) == "server-0"
OPTIONAL MATCH (server_b)--()--(other_net:neutron_network)--(:neutron_port)--(server_c:`nova_instance`)
    WITH server_a, server_b AS same_subnet_machines, server_c AS routeable_machines WHERE routeable_machines != server_a

RETURN same_subnet_machines.nova_instance.name AS L2_connected_server,
       routeable_machines.nova_instance.name AS cross_vpc_server

可以看到结果里,跨网络潜在的相连主机还有 server-3:

L2_connected_server cross_vpc_server
server-1 server-3

可视化下,同样,我们修改输出为路径 pp1

MATCH p=(server_a)--(:neutron_port)--(net:neutron_network)--(:neutron_port)--(server_b:`nova_instance`)
    WHERE id(server_a) == "server-0"
OPTIONAL MATCH p1=(server_b)--()--(other_net:neutron_network)--(:neutron_port)--(server_c:`nova_instance`)
RETURN p, p1

它可能的连接路径一目了然了:

cross_vpc_vm

有了获得这些信息的能力,我们可以可编程地连接告警、状态、安全风控、网络等方方面面系统。当然这不是本文的重点,就不加以赘述了,你有相关的实践想要分享的话,记得来 NebulaGraph 社区

下面,我们来看看存储相关的例子。

镜像、云盘、快照的血缘

在基础设施中,云盘(iSCSI、Ceph、NFS)、镜像、快照之间有多重复杂的关系,比如:

  • 一个系统镜像可能从某一个虚拟机挂载的云盘或者一个快照创建
  • 一个云盘可能是从一个系统镜像、一个快照或者另一个云盘创建
  • 一个快照是从一个云盘创建的

这种血缘信息的识别和管理是很有必要的。下面的查询可以获得指定虚机 server-0 的所有存储血缘:

MATCH p=(server_a)-[:`attached`|created_from|used_by]-(step1)
    WHERE id(server_a) == "server-0"
OPTIONAL MATCH p1=(step1)-[:created_from*1..5]-(step2)
    RETURN p, p1

我们可以看到结果中:

  • server-0 的启动镜像(这里它是从本地盘启动的,没有挂载云盘)是从 volume-1 创建的;
  • volume-1 是从 cirros-0.5.2-x86_64-disk 这个镜像创建的;

此外,还有其他有分叉关系的存储资源和它们也息息相关:

storage_lineage_0

下面,我们不只考虑存储资源,看下涉及云盘 cinder_volume 挂载 attached 这层关系下的血缘关系:

MATCH p=(server_a)-[:`attached`|created_from|used_by]-(step1)
    WHERE id(server_a) == "server-4"
OPTIONAL MATCH p1=(step1)-[:created_from|attached*1..5]-(step2)
    RETURN p, p1

storage_lineage_1

我们可以从渲染图中读出这样的洞察:

  • server-4 的启动镜像(这里它是从本地盘启动的)是从 volume-1 创建的
    • volume-1 现在挂载在 server-6
    • volume-1 是从 cirros-0.5.2-x86_64-disk 这个镜像创建的
    • 同样 cirros-0.5.2-x86_64-disk 镜像被很多其他虚机在采用
  • server-4 同时挂载了数据盘 volume-2
    • volume-2 是一个多挂载的盘,它同时挂载在 server-3 之上
    • server-3 的系统启动盘是从快照 snapshot-202301111800-volume-1 克隆创建的
    • 快照 snapshot-202301111800-volume-1 是曾经从 volume-1 创建的
    • volume-1 现在挂载在 server-6 上,快照不一定是从 server-6 而来,因为镜像可能被重新挂载过。而这些血缘信息可以被用在资源生命周期管理、根因分析、安全告警、状态传递上,这里不加以赘述。

高相关性虚机预警

下面这个例子,会给出一个节点相似度的应用。在全图或者子图上,利用图算法找到与指定虚机图拓扑结构最相似的其他虚机,并在这种相关性基础上增加新的关系,做风险事件预警。

本次实践,我们会按照一个典型的从「快速子图验证」到「全图生产应用」的工作流。

子图快速验证:浏览器内算法

server-0 的三度子图上做算法的验证:

GET SUBGRAPH 3 STEPS FROM "server-0"
YIELD VERTICES AS nodes, EDGES AS relationships;

将结果渲染在画布上,我们可以看到子图中包含了其他几个虚机:

server_subgraph

我们利用 Explorer 中的浏览器内图算法,可以非常方便地验证我们的想法。这里,我们使用 Jaccard Similarity 相似性算法,让 server-0server-1,server-3,server-4,server-6 迭代分别得到相似性:

jacc_sim_browser

可以看出,在 3 步子图内,和 server-0 最近接的虚机是 server-4。进一步,我们可以简单在子图上看看两者之间的路径作为相似性的解释:

sim_explain

在这个可解释结果中,我们知道 server-0server-4 相似的原因可能是:

  • 坐落在同一个宿主机:node-0
  • 使用同一个镜像:cirros_mod_from_volume-1

因此,我们最终落地的预警机制可能是,当 server-0 出现某一问题、告警时候,给相似的 server-4 也设定预警,预警理由就是它们在同样主机、同样镜像。

全图生产应用

有了上面的快速实验,借助 Workflow + NebulaGraph Analytics 把它落地为全图上的算法,利用 Analytics 分布式能力去执行。

在生产上,我们利用 Workflow 的 DAG 编排能力,创建两个前后相连的任务:

  • 取临近虚机
  • 全图算相似度

第一个任务如下,实时从指定虚机出发给出其他虚机 vid。这里查询语句写死了 server-0,但是在 Workflow 里可以参数化,并封装任务为可被 API 触发的异步服务:

MATCH (n)-[*1..5]-(m:`nova_instance`)
    WHERE id(n) == "server-0" AND n != m
RETURN distinct id(m)

query_sim_server

而在 Jaccard Similarity Job 中,我们选择 ids1 为 server-0,ids2 从上游(上面的 Query Job)取,选择在 OpenStack 全图扫描所有边类型。

jacc_sim_workflow

保存、运行。结果如下,这次它运算了更多的目标虚机,并且迭代作用范围是全图而非一个子图。可以看到同上次的结果是一样,因为子图上关联度大的点和相近的边在 Jaccard 算法里起到了更主要的作用。

jacc_result

安全相关场景

基础设施资源中的关联关系和金融、内容系统、电商领域的风控场景有相似的地方,很多场景本质上利用到了图谱关系中的知识,在图库上实时获取这些复杂但又天然可解释的安全洞察。

秘钥泄漏风控分析

先看一个秘钥泄漏的场景:假设 key-0 被安全部门确定被泄漏了,我们可以在毫秒内获得如下查询:

  • 直接使用该密钥的虚机
  • 与使用该秘钥的虚机网络直连的机器
  • 与使用该秘钥的虚机跨网络相连的机器
MATCH (key_leaked)-[:`used_by`]->(involved_server:nova_instance)--(:neutron_port)--(net:neutron_network)--(:neutron_port)--(server_b:nova_instance)
       WHERE id(key_leaked) == "key-0"
OPTIONAL MATCH (server_b)--()--(other_net:neutron_network)--(:neutron_port)--(server_c:nova_instance)
    WITH involved_server, server_b AS same_subnet_machines, server_c AS cross_net_machines
        WHERE cross_net_machines != involved_server
RETURN involved_server.nova_instance.name AS with_key,
        same_subnet_machines.nova_instance.name AS l2_vms,
        cross_net_machines.nova_instance.name AS cross_vpc_vms

贴一下部分结果,我们知道 server-4 采用了这个 keypair,并且 server-6 和它在同一个网络。同时,有一定几率通过 server-6、server-1、server-2、server-0、server-5 也受到了影响。针对这种情况,相关的机器可以设置不同告警级别来降低安全事故的影响。

with_key l2_vms cross_vpc_vms
server-4 server-6 server-1
server-4 server-6 server-2
server-4 server-6 server-0
server-4 server-6 server-5

这个查询改造为可视化结果:

MATCH p=(key_leaked)-[:`used_by`]->(involved_server:nova_instance)--(:neutron_port)--(net:neutron_network)--(:neutron_port)--(server_b:nova_instance)
    WHERE id(key_leaked) == "key-0"
OPTIONAL MATCH p1=(server_b)--()--(other_net:neutron_network)--(:neutron_port)--(server_c:nova_instance)
RETURN p,p1

在 Explorer 中应用 Dagre-LR 的布局,相关的关联关系可以很清晰地被展示出来。介于可视化展示的直观性,我们可以考虑把这个图放入安全报告,随同其他安全信息一同分发给虚机租户。

key_leaked

镜像、云盘漏洞范围分析

类似的,一个镜像被扫出漏洞,我们可以瞬间查到波及的资源,并做出相应应对之策。

镜像文件有漏洞:

MATCH p=(image_risky)-[:`created_from`]-(step1)
    WHERE id(image_risky) == "cirros-0.5.2-x86_64-disk"
OPTIONAL MATCH p1=(step1)-[:created_from*1..5]-(step2)
RETURN p, p1

image_vulnerability

某个云盘有漏洞:

MATCH p=(volume_risky)-[:`created_from`]-(step1)
    WHERE id(volume_risky) == "volume-1"
OPTIONAL MATCH p1=(step1)-[:created_from*1..5]-(step2)
RETURN p, p1

volume_vulnerability

潜在宿主机逃离影响范围分析

最后,我们讨论一个严重的安全问题:宿主机逃离。

在极端的情况下,server-0 发生了有可能影响宿主机的安全事件,此时仅仅关闭这个宿主机是不够的,因为受影响的范围可能已经扩大。但我们又不能因为这样不知影响范围多广的安全事件来关闭整个机房。所以,利用图谱辅助找出受影响范围就非常有用了。

下面的查询模式是:

  • 找出可能被影响的子网(VPC),标记最高级别风险子网为后续定位做准备
  • 找到可能被控制了的宿主机
  • 从宿主机触发,找出同主机的其他虚机
  • 从其他虚机触发,找到它们的子网(VPC)
  • 从其他虚机触发,找到可能已经被影响的网盘。这是为了防止被挂载到其他机器,这会扩大影响。
MATCH (server_escaping_hypervisor)<-[:`contains`]-(hypervisor_compromised:nova_host)
    WHERE id(server_escaping_hypervisor) == "server-0"
OPTIONAL MATCH (server_escaping_hypervisor)<-[:attached]-(:neutron_port)<-[:contains]-(impacted_subnet_high:neutron_network)
OPTIONAL MATCH (hypervisor_compromised)-[:`contains`]->(server_same_host:nova_instance)
OPTIONAL MATCH (server_same_host)<-[:attached]-(:neutron_port)<-[:contains]-(impacted_subnet:neutron_network)
OPTIONAL MATCH (server_same_host)<-[:attached]-(impacted_volume:cinder_volume)

RETURN impacted_subnet_high.neutron_network.name AS impacted_subnet_high,
       hypervisor_compromised.nova_host.name AS hypervisor_compromised,
       impacted_subnet.neutron_network.name AS impacted_subnet,
       [server_same_host.nova_instance.name, server_same_host.nova_instance.instance_name] AS server_same_host,
       impacted_volume.cinder_volume.name AS impacted_volume

下面的结果集中,列出了 server-0 被控制之后,考虑宿主机逃离的情况下可能受影响的扩散范围。

impacted_subnet_high hypervisor_compromised impacted_subnet server_same_host impacted_volume
shared node0 shared ["server-0", "instance-00000001"] Empty
shared node0 shared ["server-1", "instance-00000002"] ffaeb199-47f4-4d95-89b2-97fba3c1bcfe
shared node0 private ["server-1", "instance-00000002"] ffaeb199-47f4-4d95-89b2-97fba3c1bcfe
shared node0 private ["server-3", "instance-00000005"] c9db7c2e-c712-49d6-8019-14b82de8542d
shared node0 private ["server-3", "instance-00000005"] volume-2
shared node0 public ["server-4", "instance-00000006"] volume-2

咱们再看看它的可视化结果。

``shell MATCH p=(server_escaping_hypervisor)<-[:contains]-(hypervisor_compromised:nova_host) WHERE id(server_escaping_hypervisor) == "server-0" OPTIONAL MATCH p0=(server_escaping_hypervisor)<-[:attached]-(:neutron_port)<-[:contains]-(impacted_subnet_high:neutron_network) OPTIONAL MATCH p1=(hypervisor_compromised)-[:contains`]->(server_same_host:nova_instance) OPTIONAL MATCH p2=(server_same_host)<-[:attached]-(:neutron_port)<-[:contains]-(impacted_subnet:neutron_network) OPTIONAL MATCH p3=(server_same_host)<-[:attached]-(impacted_volume:cinder_volume) RETURN p,p0,p1,p2,p3

还是和之前一样,我们在可视化图探索工具 Explorer 中选择 Dagre 布局,它能比较清晰看出影响资源的范围。从这些可能受影响的虚机、网络、网盘出发,可以进一步采取需要的安全措施了。

![hypervisor_escape](https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/infra-devops/hypervisor_escape.jpeg)

## 重点关注资源检测

最后,利用 Betweenness Centrality 算法,我们可以获得基础设施中影响面大的那些”脆弱环节“。这些资源不一定真的处在危险的状态,只是说,它们处在了比较重要的资源之间的交汇处,一旦它们出问题,出问题的代价可能会非常大。

因此,识别关键资源后,我们可以考虑下面的安全机制:

- 有针对性采用更激进、昂贵的健康检查策略;
- 设定更高的支持、关注级别;
- 主动迁移相关联的资源,以降低”脆弱环节“对整体基础设施可用性的影响范围;

在这里,我们只在浏览器内部的子图上做算法流程验证。机智的你,可以自己试着利用开源的 [NebulaGraph Algorithm](https://github.com/vesoft-inc/nebula-algorithm) 或者付费的 NebulaGraph Workflow + Analytics 做全图上的等价操作。

首先,我们用之前的方式去扫描图上 1,000 个点,并且从它们出发,跳一跳,获得一个比较随机的子图。实际上,由于我们的数据集并不是很大,这个操作是捞取了全图的数据:

shell MATCH (n) WITH n LIMIT 1000 OPTIONAL MATCH p=(n)--() RETURN p, n ```

随机子图搞定之后,我们运行 Betweenness Centrality 算法,得到 node0 是分值最大的”脆弱一环“。的确,它是我们当前实验中负载最大的宿主机,可以想象它确实是故障之后全局影响最大的一个资源。

bwteeness_centrality

总结

在海量数据、企业云、混合云的复杂基础设施运维场景下,利用图数据库图算法的能力做高效的辅助运维工作是一个十分值得的尝试与技术投资。

NebulaGraph 作为高性能、开源、分布式的新一代云原生图数据库,是一个很值得考虑的图基础设施选型目标。

欢迎大家在文末留言讨论,本文的可复现环境和示例的 ETL 管道的代码、示例数据全都在 https://github.com/wey-gu/openstack-graph/ 开源,欢迎大家来一起完善。

本文用到的可视化图探索工具为 NebulaGraph Explorer,目前可以免费试用 30 天

此外,我把本实验中的图谱放在了 NebulaGraph Studio/Explorer 的示例数据集中,大家也可以在里边下载试玩。

参考文献


  • 相关推荐

《如何用 NebulaGraph 图技术搞定 7 个典型社交网络应用?》

《如何使用 dbt 将多种数据类型转换为 NebulaGraph 中的图数据?》