首页
/ diagrams:Diagram as Code 实践——用 Python 代码绘制云系统架构图

diagrams:Diagram as Code 实践——用 Python 代码绘制云系统架构图

2026-09-05 21:54:58作者:苗圣禹Peter

本文围绕 diagrams 项目的主 README 展开,系统讲解 "Diagram as Code"(图即代码)这一架构原型设计方法:如何在本地环境安装配置 diagrams,如何用它以纯 Python 代码描述 AWS、Azure、GCP、Kubernetes、本地机房等多种技术栈的系统架构,并结合仓库源码(diagrams/init.pypyproject.tomltests/test_diagram.py)深入剖析 DiagramClusterNodeEdge 四大核心对象的参数、操作符链式写法与底层渲染机制,帮助读者独立完成从安装到复杂架构图输出的完整流程。

一、项目定位:什么是 Diagram as Code

diagrams 的核心理念是一句话:用 Python 代码来绘制云系统架构图(draw the cloud system architecture in Python code)。它最初诞生于对新系统架构进行原型设计(prototyping) 的场景——无需任何图形化设计工具,写一段 Python 脚本就能得到一张规范的架构图;同时也可以用来描述和可视化已经存在的系统架构。

README 中明确给出了它的能力边界,这对正确理解项目定位非常重要:

  • 只画图,不动资源:它不控制任何真实的云资源,也不会生成 CloudFormation 或 Terraform 代码,用途仅限于绘制云系统架构图;
  • 架构即文本,可进版本控制:因为图由代码定义,架构图的每一次变更都可以提交到任意版本控制系统中做 diff、review 和回溯——这是 "Diagram as Code" 相对传统画图工具最本质的区别。

从源码结构看,渲染层完全建立在 Graphviz 之上:diagrams/init.py 直接 from graphviz import Digraph,所有 Diagram 最终都会转译为一张 Graphviz 有向图。因此 diagrams 的价值在于:把 "手写 DOT 语言" 的繁琐,封装成一套面向云资源、带厂商图标的 Python API。

二、环境要求与安装

根据 README.mdpyproject.toml,使用 diagrams 有两个硬性前置条件:

  1. Python 3.9 或更高版本pyproject.toml 中声明 python = "^3.9",当前仓库版本为 0.24.1
  2. 系统已安装 Graphviz。diagrams 依赖 Graphviz 完成布局与渲染,需先安装 Graphviz 再安装 Python 包(macOS 用户可用 Homebrew 安装 Graphviz)。

安装 Python 包支持三种常见方式:

# using pip (pip3)
$ pip install diagrams

# using pipenv
$ pipenv install diagrams

# using poetry
$ poetry add diagrams

pyproject.toml 的依赖声明看,运行时核心依赖为 graphviz>=0.13.2,<0.21.0)与 jinja2>=2.10,<4.0),后者服务于节点代码的自动生成流程(后文第五节展开)。

版本提示:仓库内 docs/getting-started/installation.md 中仍写有 "Python 3.7 or higher" 的旧表述,与 README 和 pyproject.toml 不一致。以 README 与打包配置为准,实际要求是 Python 3.9+

三、Quick Start:五分钟画出一张架构图

安装文档 给出了最小可运行示例,这里完整继承并补充说明:

# diagram.py
from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

with Diagram("Web Service", show=False):
    ELB("lb") >> EC2("web") >> RDS("userdb")

执行:

$ python diagram.py

工作目录下会生成 web_service.png(文件名由 name 参数自动推导)。生成的效果如下图所示——一条 "负载均衡 → Web 服务器 → 数据库" 的经典三层链路,每个节点都带官方风格的资源图标:

diagrams 快速上手示例生成的 Web Service 架构图:ELB 负载均衡指向 EC2 再指向 RDS

这段代码值得逐行拆解,它几乎浓缩了 diagrams 的全部核心概念:

  • with Diagram(...):创建一个图的全局上下文。show=False 表示只保存图片、不自动打开(默认 show=True 会在渲染完成后调用系统查看器打开图片);
  • from diagrams.aws.compute import EC2:节点按 厂商包 / 资源类别模块 / 资源类 三级导入。EC2 属于 aws 厂商的 compute 类别;
  • >>:数据流操作符,表示从左到右的有向连接。整条语句等价于 "lb 的数据流向 web,web 的数据流向 userdb"。

四、支持的 Provider 清单

README 的 Providers 一节列出了 diagrams 当前支持的厂商与领域。结合仓库 diagrams/ 目录结构,各 provider 与 Python 子包的对应关系如下表:

Provider 对应子包 典型节点(见各模块源码)
AWS diagrams/aws/ EC2、ECS、Lambda、RDS、ELB、S3、SQS、EKS
Azure diagrams/azure/ FunctionApps、BlobStorage、AppService 等
GCP diagrams/gcp/ AppEngine、Functions、GCS、PubSub、BigQuery
Kubernetes diagrams/k8s/ Pod、Deployment、Service、Ingress、PV/PVC
Alibaba Cloud diagrams/alibabacloud/ ECS、ObjectTableStore 等
Oracle Cloud (OCI) diagrams/oci/ VirtualMachine、FileStorage、Firewall
IBM diagrams/ibm/ 计算、数据库、网络、区块链等
OpenStack diagrams/openstack/ 计算、网络、存储、编排等
Firebase diagrams/firebase/ 开发、扩展、质量监控类
DigitalOcean diagrams/digitalocean/ 计算、数据库、网络、存储
Elastic diagrams/elastic/ Elasticsearch、Beats、Agent、Security
Outscale diagrams/outscale/ 计算、网络、安全、存储
On-Premises diagrams/onprem/ Server、PostgreSQL、Redis、Kafka、Nginx、Prometheus
Generic(通用) diagrams/generic/ 空白、计算、设备、网络、存储等抽象节点
Programming(编程语言/框架) diagrams/programming/ 语言、运行时、框架、流程图
SaaS diagrams/saas/ CRM、聊天、社交、分析等 SaaS 服务
C4(架构建模方法) diagrams/c4/ 支持 C4 模型风格的架构表示
Custom(自定义) diagrams/custom/ 用任意本地图片作为节点图标

所有厂商的节点类都是由自动化脚本批量生成的:diagrams/aws/compute.py 开头即注明 This module is automatically generated by autogen.sh. DO NOT EDIT.,每个类只声明一个类属性 _icon(如 EC2 对应 ec2.png),继承链为 资源类 → _类别基类 → _厂商基类 → Node。新增/修改资源的工作流是:放入 resources/<provider>/<type>/ 下的图标,再由 autogen.sh 驱动 scripts/generate.py 配合 templates/module.tmpltemplates/apidoc.tmpl 模板重新生成节点代码与文档。tests/test_diagram.py 中的 ResourcesTest 还专门校验资源目录深度不超过 2 层(即 resources/<provider>/<type>/<image>),保证图标加载逻辑稳定。

五、核心对象与关键参数

5.1 Diagram:图的全局上下文

docs/guides/diagram.md 描述了 Diagram 的用法,而完整参数集可以从 diagrams/init.py 的构造函数签名中确认。参数说明如下表(含源码中的取值校验规则):

参数 默认值 说明与取值范围
name "" 图标题,同时用于推导输出文件名;若 namefilename 都为空则落盘为 diagrams_image.*
filename name 推导 输出文件名(不含扩展名)。推导规则见 源码"_".join(self.name.split()).lower(),即把标题空格替换为下划线并转小写
direction "LR" 数据流方向,合法值 TB/BT/LR/RL(大小写不敏感,非法值抛 ValueError
curvestyle "ortho" 连线弯曲风格,合法值 ortho(正交折线)/curved(曲线)
outformat "png" 输出格式,单值 png/jpg/svg/pdf/dot也支持列表一次输出多种格式(如 ["jpg", "png", "dot"]
autolabel False 为 True 时自动在节点标签前加上类名前缀(如 EC2\nweb
show True 渲染后是否自动打开图片,CI 或批量生成场景建议 show=False
strict False 透传给 Graphviz Digraph,控制多边合并行为
graph_attr / node_attr / edge_attr {} 透传任意 Graphviz dot 属性,用于覆盖默认样式

关于默认样式,源码 中定义了三层默认属性,理解了它们就能解释为什么 diagrams 的图"开箱即美观":

  • 图级(_default_graph_attrs):pad=2.0splines=ortho、节点间距 nodesep=0.60、层间距 ranksep=0.75、字体 Sans-Serif 15px;
  • 节点级(_default_node_attrs):shape=boxstyle=rounded、固定尺寸 1.4 x 1.4、标签在底部(labelloc=b)、字体 13px;
  • 边级(_default_edge_attrs):默认线色 #7B8894(灰蓝色)。

这三组属性均可通过 graph_attr/node_attr/edge_attr 参数覆盖。例如:

from diagrams import Diagram
from diagrams.aws.compute import EC2

graph_attr = {
    "fontsize": "45",
    "bgcolor": "transparent",
}

with Diagram("Simple Diagram", show=False, graph_attr=graph_attr):
    EC2("web")

此外,源码 实现了 _repr_png_() 协议,配合 docs/guides/diagram.md 的说明,Diagram 可以直接在 Jupyter Notebook 中内联渲染。

Diagram 的生命周期是一个上下文管理器:__enter__ 通过 contextvars源码 中的 __diagram ContextVar)把自身注册为全局图上下文;__exit__ 时调用 render() 输出图片,随后 os.remove(self.filename) 删除中间产生的 Graphviz 源文件,只保留最终图片。这也解释了为什么节点必须在 with Diagram(...) 块内创建——Node 构造函数 会调用 getdiagram(),取不到上下文时抛出 EnvironmentError("Global diagrams context not set up")tests/test_diagram.pytest_node_not_in_diagram 正是对此行为的回归验证。

5.2 数据流操作符:>><<-

docs/guides/node.md 定义了三种连接语义:

  • >>:左到右的有向连接;
  • <<:右到左的有向连接;
  • -:无方向连接。

它们支持单节点与列表的任意组合,并且返回值设计成链式友好的——>>/<< 返回对端节点,对列表操作时返回该列表,因此可以像 示例文档 里那样写出 ELB("lb") >> [EC2("worker1"), ..., EC2("worker5")] >> RDS("events") 这样的"一对多再汇聚"表达式。

Node 的操作符重载实现 看,链式表达式的机制是:__rshift__ 实现 Self >> Node__rrshift__ 处理 [Nodes] >> Self(列表本身没有右移运算符,Python 会回调列表元素侧的反向操作符),__rshift__ 遇到 Edge 时则把 forward=True 标记传给 Edge。最终的边属性由 Edge.attrs 汇总:当 forwardreverse 同时为真时输出 dir="both"(双向箭头),否则分别为 forward/back/noneEdgeTest 中对单节点、节点列表、Edge 串联、自环、双向等场景做了密集验证,可以认为这几种组合写法都是被单元测试覆盖的可靠用法。

5.3 Edge:为连线着色、加标签、改样式

Edge 允许在连接处注入标签、颜色和线型(docs/guides/edge.md 有更多细节)。Edge 构造函数 接受 labelcolorstyle 以及任意额外的 dot 属性。一个典型的 "带颜色与标签的有向边" 写法:

from diagrams import Diagram, Edge
from diagrams.onprem.monitoring import Grafana, Prometheus

with Diagram("Monitoring", show=False):
    metrics = Prometheus("metric")
    metrics << Edge(color="firebrick", style="dashed", label="scrape") << Grafana("monitoring")

其中 style 可取 Graphviz 的线型值,如 dashed(虚线)、dotted(点线)、bold(加粗)等;color 可以是任意 CSS 颜色名或十六进制色值。

5.4 Cluster:嵌套分组与自动底色

Cluster 用于在图中划出虚线边框的分组(如 "VPC"、"K8s 集群"、"机房"),并支持任意层级的嵌套。从 Cluster 实现 可以确认几个值得知道的细节:

  • 嵌套层级由 depth 追踪,并按深度在 4 种浅色底纹(#E5F5FD#EBF3E7#ECE8F6#FDF7E3)间循环取色,因此多层嵌套时各组底色自动错开,无需手动指定;
  • 子图在 with 块退出时通过 subgraph() 挂回父 Cluster 或顶层 Diagram;
  • 源码注释 明确标注了一个已知限制:Cluster 级别的 direction 参数目前对渲染结果不生效(Graphviz 对子图使用不同 rankdir 的渲染问题),实际布局仍以顶层 Diagram 的方向为准;
  • GroupCluster 的别名(源码),两种命名可互换。

六、实战示例:从分组到跨栈的完整架构图

示例文档 收录了多个由简入繁的完整脚本,README 的 Examples 一节也引用了其中三张代表性图片。下面选取四段有代表性的示例代码完整继承,覆盖"分组、集群、K8s、跨云+本地机房"四类典型画法。

6.1 分组 Worker(一对多扇出)

from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

with Diagram("Grouped Workers", show=False, direction="TB"):
    ELB("lb") >> [EC2("worker1"),
                  EC2("worker2"),
                  EC2("worker3"),
                  EC2("worker4"),
                  EC2("worker5")] >> RDS("events")

这里展示了 direction="TB"(Top to Bottom)改变整体布局方向,以及 "单节点 >> 列表" 的扇出语法。

6.2 集群化 Web 服务(Cluster 分组 + 多路读写)

from diagrams import Cluster, Diagram
from diagrams.aws.compute import ECS
from diagrams.aws.database import ElastiCache, RDS
from diagrams.aws.network import ELB
from diagrams.aws.network import Route53

with Diagram("Clustered Web Services", show=False):
    dns = Route53("dns")
    lb = ELB("lb")

    with Cluster("Services"):
        svc_group = [ECS("web1"),
                     ECS("web2"),
                     ECS("web3")]

    with Cluster("DB Cluster"):
        db_primary = RDS("userdb")
        db_primary - [RDS("userdb ro")]

    memcached = ElastiCache("memcached")

    dns >> lb >> svc_group
    svc_group >> db_primary
    svc_group >> memcached

集群化 Web 服务架构图:Route53 经 ELB 到三个 ECS 服务组,读写分离的 RDS 集群与 ElastiCache 缓存

这个例子覆盖了三个高频技巧:Cluster 分组表达 "服务集群" 与 "数据库集群";db_primary - [RDS("userdb ro")] 用无向边表达主从副本关系;svc_group >> db_primary 让列表整体作为边的起点。

6.3 AWS 事件处理架构(多层嵌套 Cluster)

from diagrams import Cluster, Diagram
from diagrams.aws.compute import ECS, EKS, Lambda
from diagrams.aws.database import Redshift
from diagrams.aws.integration import SQS
from diagrams.aws.storage import S3

with Diagram("Event Processing", show=False):
    source = EKS("k8s source")

    with Cluster("Event Flows"):
        with Cluster("Event Workers"):
            workers = [ECS("worker1"),
                       ECS("worker2"),
                       ECS("worker3")]

        queue = SQS("event queue")

        with Cluster("Processing"):
            handlers = [Lambda("proc1"),
                        Lambda("proc2"),
                        Lambda("proc3")]

    store = S3("events store")
    dw = Redshift("analytics")

    source >> workers >> queue >> handlers
    handlers >> store
    handlers >> dw

AWS 事件处理架构图:EKS 事件源经三层 Worker、SQS 队列到 Lambda 处理器,汇聚到 S3 与 Redshift

这是 Cluster 嵌套 自动底色机制的典型受益者:外层 "Event Flows" 与内层 "Event Workers"、"Processing" 会自动获得不同层次的底色,视觉上一眼可辨从属关系。

6.4 跨云 + 本地机房 + 彩色连线

示例文档 最后给出了 "Advanced Web Service with On-Premises" 的彩色增强版,是 Edge 定制能力的集大成:

from diagrams import Cluster, Diagram, Edge
from diagrams.onprem.analytics import Spark
from diagrams.onprem.compute import Server
from diagrams.onprem.database import PostgreSQL
from diagrams.onprem.inmemory import Redis
from diagrams.onprem.aggregator import Fluentd
from diagrams.onprem.monitoring import Grafana, Prometheus
from diagrams.onprem.network import Nginx
from diagrams.onprem.queue import Kafka

with Diagram(name="Advanced Web Service with On-Premise (colored)", show=False):
    ingress = Nginx("ingress")

    metrics = Prometheus("metric")
    metrics << Edge(color="firebrick", style="dashed") << Grafana("monitoring")

    with Cluster("Service Cluster"):
        grpcsvc = [
            Server("grpc1"),
            Server("grpc2"),
            Server("grpc3")]

    with Cluster("Sessions HA"):
        primary = Redis("session")
        primary - Edge(color="brown", style="dashed") - Redis("replica") << Edge(label="collect") << metrics
        grpcsvc >> Edge(color="brown") >> primary

    with Cluster("Database HA"):
        primary = PostgreSQL("users")
        primary - Edge(color="brown", style="dotted") - PostgreSQL("replica") << Edge(label="collect") << metrics
        grpcsvc >> Edge(color="black") >> primary

    aggregator = Fluentd("logging")
    aggregator >> Edge(label="parse") >> Kafka("stream") >> Edge(color="black", style="bold") >> Spark("analytics")

    ingress >> Edge(color="darkgreen") << grpcsvc >> Edge(color="darkorange") >> aggregator

带彩色连线的 On-Premises 高级 Web 服务架构图:Nginx 入口、Redis/PostgreSQL 高可用集群、Prometheus 监控与 Kafka/Spark 数据链路

注意 grpcsvc >> Edge(color="brown") >> primary 这种 "列表 >> Edge >> 单节点" 的写法:由 Edge.append 实现,它把同一套边属性复制成多条边批量连接到列表中的每个节点——一次声明即可让三条服务到主库的连线统一着色。此外,若只需要 "无方向 + 特定样式" 的关系(如主从复制),用 - 搭配 Edge 即可。

6.5 自定义节点:接入任意图标

对于 diagrams 尚未收录的服务,diagrams/custom/init.py 提供了 Custom 节点:传入本地图片路径作为图标即可。示例文档 中的 RabbitMQ 消费者案例:

from urllib.request import urlretrieve

from diagrams import Cluster, Diagram
from diagrams.aws.database import Aurora
from diagrams.custom import Custom
from diagrams.k8s.compute import Pod

# Download an image to be used into a Custom Node class
rabbitmq_url = "https://jpadilla.github.io/rabbitmqapp/assets/img/icon.png"
rabbitmq_icon = "rabbitmq.png"
urlretrieve(rabbitmq_url, rabbitmq_icon)

with Diagram("Broker Consumers", show=False):
    with Cluster("Consumers"):
        consumers = [
            Pod("worker"),
            Pod("worker"),
            Pod("worker")]

    queue = Custom("Message queue", rabbitmq_icon)

    queue >> consumers >> Aurora("Database")

Custom._load_icon 直接返回构造时传入的 icon_path源码),因此图标可以是任意本地图片路径。

七、渲染流程与可验证性

把前几节的机制串起来,一次 python diagram.py 的完整链路是:

  1. with Diagram(...) 进入上下文,向 ContextVar 注册全局图对象(enter);
  2. 块内每实例化一个节点类(如 EC2("web")),节点会:生成 UUID 作为 nodeid(或接受显式 nodeid)、读取当前 getcluster() 决定把自己挂到子图还是主图、把 _load_icon() 解析出的图标绝对路径写入节点属性(Node 初始化)。图标路径由 Node._load_iconresources/<provider>/<type>/<icon>.png 拼接——这与 resources/ 目录的两层深度约束一一对应;
  3. 每次 >>/<</- 都会调用 connect(),边统一加到顶层 Diagram 上(源码注释:边必须加在全局图而非子图上,这是 Graphviz 子图语法规则所要求的);
  4. with 块退出时 render()outformat(单个或多个)逐一调用 Graphviz 渲染,然后删除中间 .dot 源文件(exit)。

这套行为基本都有单元测试背书,可对照 tests/test_diagram.py 复核:

  • test_validate_direction / test_validate_curvestyle / test_validate_outformat:非法取值抛 ValueError
  • test_default_filename / test_custom_filename / test_empty_name:文件名推导规则(含空名回落 diagrams_image);
  • test_outformat_list:一次调用输出 pngdot 两种产物;
  • test_with_nested_cluster:嵌套 Cluster 的上下文进出与恢复;
  • EdgeTest 全组用例:Edge 在单节点、节点列表、自环、双向、串联 Edge 等组合下的行为。

这意味着如果你在二次开发中改动连接逻辑或样式逻辑,直接跑仓库自带测试套件即可快速定位回归点。

八、生态与许可

  • 使用者:README 的 "Who uses it?" 一节指出,Apache Airflow 在其文档中使用 diagrams 生成架构图;Cloudiscovery 可以基于对云账号资源的分析,用 diagrams 绘制现有云基础设施拓扑;Airflow Diagrams 插件则用它把 Airflow DAG 可视化到服务级(AWS/GCP/Azure 等)。
  • 多语言实现:README 提及,如果你熟悉 Go,也存在同类思路的 go-diagrams 项目可作参照。
  • 贡献:向 diagrams 贡献新节点或修复问题,参见 CONTRIBUTING.mdDEVELOPMENT.md;结合 scripts/generate.pytemplates/ 目录可以理解节点代码与 API 文档的自动生成链路。
  • 许可MIT 开源协议,对商用与二次开发基本无限制。

九、小结

diagrams 用极少的概念面——Diagram(画布与全局参数)、Cluster(分组)、Node(带厂商图标的资源)、Edge(连线样式)——加上三个数据流操作符 >><<-,把云架构图变成了可运行、可 diff、可进 CI 的 Python 代码。上手路径非常直接:安装 Graphviz 与 Python 包 → 用 with Diagram(...) 声明画布 → 从对应厂商子包导入节点类 → 用操作符描述数据流 → 运行脚本得到 PNG/SVG/PDF。当图越画越复杂时,嵌套 Cluster 的自动底色、Edge 的颜色/标签/线型定制、outformat 多格式输出和 Custom 节点,构成了从 "原型草图" 走到 "正式架构文档" 的完整工具箱。

登录后查看全文
热门项目推荐
相关项目推荐