PlantUML 本地渲染、服务端部署与安全治理手册
本机能生成,评审站点为什么报错
一个常见故障发生在架构评审前:开发者本机的 IDE 插件能生成组件图,CI 却报 Cannot find Graphviz;团队临时改用公共 PlantUML Server 后,图终于出现了,但完整服务名、内部 URL 和鉴权流程也进入了请求地址。随后有人为了让 !include 生效,把服务端安全级别放宽,渲染进程开始能够读取宿主机文件和访问内网。
这不是“插件和服务器选哪个”的小问题。PlantUML 源码会经过预处理器、语法解析、布局引擎和导出器;本地 JAR、IDE 插件、站点服务和公共服务可能使用不同 PlantUML 版本、Graphviz、字体、主题与安全策略。图源码还能执行 !include、读取允许的资源或访问 URL,因此服务端渲染必须按代码执行入口治理,而不能当成无状态图片转换器。
下面以 PlantUML 1.2026.6 建立可复现版本线。这个数字是 PlantUML 的产品版本标识;升级时以 官方下载页 和 官方 release 为准,并在项目里锁定 JAR 校验值或容器 digest。
先建立一条不依赖 IDE 的本地基线
PlantUML CLI 需要 Java。官方快速开始建议 Java 11 或更高版本,并提供单独的 Java 8 构建;PlantUML Server 当前构建要求 JRE/JDK 17 或更高。团队基线不要把“某个 IDE 能预览”误写成 Java 与 CLI 已安装,先在终端确认:
java -version
java -jar tools/plantuml/plantuml-1.2026.6.jar -version
java -jar tools/plantuml/plantuml-1.2026.6.jar -licenseJAR 可从 PlantUML 官方下载页 取得。页面提供 GPL、LGPL、Apache、BSD、EPL、MIT 等不同许可构建,功能集合并不完全相同;例如某些非 GPL 构建缺少 ditaa 等能力。选定构建后记录来源、许可、文件散列和准入结论:
sha256sum tools/plantuml/plantuml-1.2026.6.jarPowerShell:
Get-FileHash tools/plantuml/plantuml-1.2026.6.jar -Algorithm SHA256散列应来自团队审核后的制品清单或可信发布流水线,不能把“本机算出的值”当成来源真实性证明。JAR 可以进入内部制品库,也可以由构建阶段从受控仓库下载;不要让每个 runner 从随机镜像站取同名 plantuml.jar。
部分图使用 Graphviz dot 布局。先用 PlantUML 自己的探针检查,而不是只看 PATH:
java -jar tools/plantuml/plantuml-1.2026.6.jar -testdot官方快速开始说明某些图才需要 Graphviz,Windows 的标准构建可带嵌入版本;实际能力仍应由 -testdot 和目标图验证。团队若切到 Smetana、VizJs 或 ELK,也要把布局差异当作渲染器变更评审,不能用引擎切换掩盖模型过密。
用一张部署关系图贯通源码与运行现场
创建 docs/diagrams/payment-context.puml:
@startuml payment-context
title 支付上下文与运行依赖
!theme spacelab
skinparam defaultFontName "Noto Sans CJK SC"
skinparam shadowing false
skinparam componentStyle rectangle
skinparam ArrowColor #52616f
skinparam BackgroundColor #ffffff
actor "用户" as user
component "Web 应用" as web
component "支付 API" as api
database "订单库" as db
queue "支付事件" as events
component "账务服务" as ledger
user --> web : HTTPS
web --> api : POST /payments
api --> db : 事务写入
api --> events : payment.created
events --> ledger : 至少一次投递
note right of events
消费者必须幂等
不在图中保存真实 topic 与凭证
end note
@enduml这段源码表达的是一个可核对的运行事实:同步请求在哪里结束,事务状态保存在哪里,异步消息从何处产生,消费者为什么必须幂等。图的价值不在于使用了多少 UML 元素,而在于边标签能和接口、消息契约、部署清单及故障证据互相印证。
先做语法检查,再生成 SVG:
mkdir -p build/diagrams
java -jar tools/plantuml/plantuml-1.2026.6.jar \
--check-syntax docs/diagrams/payment-context.puml
java -jar tools/plantuml/plantuml-1.2026.6.jar \
--format svg \
--output-dir build/diagrams \
docs/diagrams/payment-context.puml健康结果是两条命令退出码都为 0,build/diagrams/payment-context.svg 非空,并包含标题和关键节点:
test -s build/diagrams/payment-context.svg
grep -E '支付上下文|支付 API|payment.created' build/diagrams/payment-context.svg若目标版本的长选项与示例不同,以 java -jar ... --help 和 官方命令行文档 为准。脚本应固定并测试自己使用的参数,不能让 IDE 插件替 CLI 合约作证。
故意制造语法错误,确认 CI 真会失败
创建一份反例 docs/diagrams/payment-context-bad.puml:
@startuml
participant "调用方" aass Client
Client -> API : create payment
@enduml运行带标准报告的检查:
java -jar tools/plantuml/plantuml-1.2026.6.jar \
-stdrpt:1 \
--check-syntax \
docs/diagrams/payment-context-bad.puml
echo "exit=$?"预期证据包含错误文件、行号、status=ERROR 或 Syntax Error,并返回非零退出码。-stdrpt 适合 CI 把错误压成单行,-stdrpt:1 适合保留协议化字段;官方命令行文档给出了两种输出格式。
如果 CI 仍返回成功,先检查脚本是否吞掉退出码、是否只生成了错误图片、是否把 stderr 重定向后无条件 exit 0。流水线应删除旧产物后再渲染,否则失败时残留的上一版 SVG 会制造“发布成功”的假象:
rm -rf build/diagrams
mkdir -p build/diagrams
java -jar tools/plantuml/plantuml-1.2026.6.jar \
--check-syntax "docs/diagrams/**/*.puml"
java -jar tools/plantuml/plantuml-1.2026.6.jar \
--format svg --output-dir build/diagrams "docs/diagrams/**/*.puml"文件通配与递归行为受 shell 和 PlantUML 版本影响。Windows、Linux 与 CI runner 要分别用同一组测试文件验证,不要假设 ** 在每种 shell 中展开一致。
输出格式要服从消费端,而不是个人偏好
PlantUML 默认生成 PNG,也可输出 SVG、PDF、EPS、LaTeX、ASCII/Unicode 文本等格式;某些格式只适用于特定图或仍属实验能力,完整矩阵见 命令行输出格式。常用三类入口:
java -jar tools/plantuml/plantuml-1.2026.6.jar --png docs/diagrams/payment-context.puml
java -jar tools/plantuml/plantuml-1.2026.6.jar --svg docs/diagrams/payment-context.puml
java -jar tools/plantuml/plantuml-1.2026.6.jar --format pdf docs/diagrams/payment-context.puml| 格式 | 更适合 | 需要额外验证 |
|---|---|---|
| SVG | 文档站、放大查看、代码评审 | CSP、内联链接、字体、清洗、浏览器兼容 |
| PNG | 工单、聊天、富文本平台、不可执行图片链 | 分辨率、透明背景、元数据、文件体积 |
| 固定评审包、打印和归档 | 页面尺寸、字体嵌入、裁切和链接 |
PlantUML 生成的 PNG/SVG 可能携带可恢复的图源码或链接信息。即使图片肉眼看不到内部地址,上传外部系统前也要按敏感数据流程检查元数据。官方 FAQ 说明 PlantUML Server 的 URL 本身编码图源码,PNG 元数据也可保存源码;“服务器不保存图”不能推导出代理、URL、日志和下载文件没有数据。
SVG 中的链接只有在合适的嵌入方式下才能交互,直接作为 <img> 引用与内联 SVG 的行为不同,详见 PlantUML SVG 支持。公开站点若不需要交互,优先使用不可执行的展示方式,并通过 CSP 与 SVG 清洗器缩小攻击面。
主题、字体和布局必须作为构建输入
!theme 使用打包在 PlantUML 核心中的官方主题;可通过一张帮助图查看当前 JAR 真正包含的主题:
@startuml
help themes
@enduml官方主题文档 还支持本地或远程主题。生产文档不要在渲染时直接拉取不固定提交的远程主题,因为内容变化会让同一源码得到不同图片,也会给渲染器增加出网与供应链入口。更稳妥的方式是把审核后的主题文件复制进仓库或内部制品,记录许可与散列,再通过允许目录加载。
项目可以用 plantuml.cfg 统一基础样式:
skinparam defaultFontName "Noto Sans CJK SC"
skinparam defaultFontSize 14
skinparam shadowing false
skinparam BackgroundColor #ffffff
skinparam ArrowColor #52616f
skinparam defaultTextAlignment centerjava -jar tools/plantuml/plantuml-1.2026.6.jar \
-config plantuml.cfg \
--format svg \
--output-dir build/diagrams \
docs/diagrams/payment-context.puml-config 会把配置内容加到每份图之前。图内仍可能覆盖部分 skinparam,因此团队要决定哪些样式允许局部变化,并用视觉回归检查关键图。配置文件不应包含凭证、真实内部 URL 或用户目录绝对路径。
字体故障要在渲染环境中诊断。PlantUML 支持用 listfonts 生成可用字体列表,入口见 字体文档:
@startuml
listfonts
@enduml在干净容器或 CI runner 中渲染该文件,确认中文字体真实存在。字体名称相同不代表文件版本与字形覆盖相同;团队镜像要锁定字体包来源和版本,同时核对字体许可是否允许服务器安装、嵌入 PDF 和对外分发。缺字时应安装获准字体或调整字体栈,而不是把中文节点改成图片。
布局引擎会改变坐标、边路由和支持能力。Graphviz 是常见默认引擎,Smetana 是 Java 内部移植,VizJs 与 ELK 还有各自取舍,官方 布局引擎说明 给出了启用方式。引擎切换前用同一批复杂图比较:
!pragma layout smetanajava -jar tools/plantuml/plantuml-1.2026.6.jar -Playout=smetana docs/diagrams/payment-context.puml不要同时修改源码、主题、字体、PlantUML 和布局引擎,否则输出差异无法归因。
站点服务是一台会解析输入的服务器
需要 IDE、Wiki 或多个仓库共享渲染能力时,可以部署官方 PlantUML Server。官方镜像提供 Jetty 与 Tomcat 变体,当前服务端默认把 PLANTUML_SECURITY_PROFILE 设为 INTERNET。INTERNET 禁止普通本地文件访问,但仍允许访问 80/443 URL;对能够提交任意图源码的用户,这仍可能形成向外部或内部 HTTP 资源发起请求的能力。
个人本机先以回环地址、只读文件系统和 SANDBOX 启动:
docker run -d \
--name plantuml-server-lab \
--read-only \
--tmpfs /tmp/jetty:rw,noexec,nosuid,size=128m \
--cap-drop ALL \
--security-opt no-new-privileges \
--memory 512m \
--cpus 1 \
-e PLANTUML_SECURITY_PROFILE=SANDBOX \
-e PLANTUML_LIMIT_SIZE=4096 \
-p 127.0.0.1:18080:8080 \
plantuml/plantuml-server:jetty检查:
docker ps --filter name=plantuml-server-lab
docker logs --tail=100 plantuml-server-lab
curl --fail http://127.0.0.1:18080/把图源码编码成 URL,再请求 SVG:
encoded="$(java -jar tools/plantuml/plantuml-1.2026.6.jar \
-encodeurl docs/diagrams/payment-context.puml)"
curl --fail --output build/diagrams/payment-context-server.svg \
"http://127.0.0.1:18080/svg/${encoded}"不同 BASE_URL 配置会改变路径前缀,官方接口格式见 PlantUML Server web service。比较本地 JAR 和服务端 SVG 时先确认核心版本、主题、字体与布局引擎一致;像素或 XML 不同不一定是语义错误,但未经解释的差异不能直接发布。
共享服务至少还需要反向代理认证、TLS、请求速率限制、URL/请求头日志脱敏、并发队列、超时、输出大小限制、只读根文件系统、无云实例身份和受限出网。不要仅凭“图源码压缩在 URL 里”认为日志安全;网关、APM、浏览器历史和工单都可能记录完整路径。
安全 profile 决定渲染器能看见什么
PlantUML 官方 安全部署文档 定义了 UNSECURE、LEGACY、INTERNET、ALLOWLIST 和 SANDBOX:
| profile | 文件与网络能力 | 工程判断 |
|---|---|---|
UNSECURE | 可访问本地文件和 URL | 只可能出现在完全受信的离线个人脚本,不用于共享服务 |
LEGACY | 兼容旧行为,权限宽 | 迁移信号,不是新服务基线 |
INTERNET | 默认不读本地文件,可访问常规 HTTP/HTTPS | 官方服务器默认值,仍需评估 SSRF 与数据外传 |
ALLOWLIST | 只访问显式允许的路径和 URL | 需要受控共享主题或 include 时使用 |
SANDBOX | 不访问本地文件或 URL,忽略 allowlist | 不可信输入的首选起点 |
需要共享本地宏时,用专用只读目录和 allowlist,不要挂载仓库根目录:
java \
-DPLANTUML_SECURITY_PROFILE=ALLOWLIST \
-Dplantuml.include.path=/opt/plantuml/includes \
-Dplantuml.allowlist.path=/opt/plantuml/includes \
-jar tools/plantuml/plantuml-1.2026.6.jar \
docs/diagrams/payment-context.puml远程资源允许列表使用 plantuml.allowlist.url。允许范围应固定到受控 HTTPS 前缀,避免允许任意域名、短链、重定向或内网地址。主题与宏最好在构建前拉取、校验散列后本地使用,这样渲染 worker 可以保持 SANDBOX 和无出网。
反向实验应证明安全策略有效。创建:
@startuml
!include /etc/passwd
Alice -> Bob : should never render on shared service
@enduml在 Linux 测试容器的 SANDBOX profile 下渲染,预期 include 被拒绝,服务日志记录受控错误,响应不能包含文件内容。Windows 可换成一个仅含测试字符串的临时文件,绝不能拿真实密钥目录做实验。若图成功读出内容,应立即停止服务、保存脱敏证据、轮换可能暴露的凭证并检查历史请求。
把本地和服务端接入同一条项目链
推荐目录:
docs/diagrams/*.puml
docs/diagrams/includes/*.puml
plantuml.cfg
tools/plantuml/manifest.json
scripts/render-plantuml.sh
build/diagrams/manifest.json 记录 PlantUML 构建类型、版本、JAR SHA-256、Graphviz/布局引擎、字体镜像与许可审核编号,不保存下载 token。脚本先检查版本与散列,再检查语法并渲染:
#!/usr/bin/env bash
set -euo pipefail
jar="tools/plantuml/plantuml-1.2026.6.jar"
src="docs/diagrams"
out="build/diagrams"
java -jar "$jar" -version
rm -rf "$out"
mkdir -p "$out"
java -DPLANTUML_SECURITY_PROFILE=SANDBOX -jar "$jar" \
-config plantuml.cfg --check-syntax "$src/**/*.puml"
java -DPLANTUML_SECURITY_PROFILE=SANDBOX -jar "$jar" \
-config plantuml.cfg --format svg --output-dir "$out" "$src/**/*.puml"站点可以直接发布构建期 SVG,也可以调用内部 PlantUML Server。构建期渲染的优势是结果与提交绑定、服务故障不影响阅读、权限面小;共享服务的优势是插件接入方便、集中缓存和统一字体,但会增加身份、网络、容量、日志和升级责任。涉密架构图、离线构建与合规归档优先本地 JAR;大量 Wiki 即时预览才值得承担共享服务。
IDE 插件只能作为编辑反馈。最终合并仍由锁定 JAR 或锁定镜像完成,因为插件可能自带核心、调用公共服务器或沿用不同安全 profile。团队设置应禁用未知公共服务地址,并让开发者能看见当前渲染端点与版本。
容量、成本与故障传播要在上线前算清
PlantUML 默认图像宽高限制为 4096,PLANTUML_LIMIT_SIZE 可以调整;官方 FAQ 同时提醒该限制与大图风险。提高上限会增加布局 CPU、Java heap、SVG/PNG 大小、代理传输和浏览器内存,不能作为“图太大”的默认修复。
服务容量由这些乘积决定:提交请求率、每图节点与边、布局引擎、输出格式、字体数量、缓存命中和并发 worker 数。治理时记录:
图源码字节、节点/边近似数量与输出字节趋势。按成功、语法错误、超时、资源拒绝分组的请求数。队列等待、渲染耗时分位、Java heap 与容器重启。
缓存命中、缓存字节和淘汰速率。远程 include 次数、拒绝次数和目标域分类。
阈值由实际压测、SLO 和资源预算决定。大图持续触顶时应拆成上下文、容器、组件与关键动态链路,让每张图回答一个评审问题。扩容 worker 只能缓解吞吐,不能改善读者面对一张巨图时的认知成本。
成本不只有机器。还包括 JAR/镜像与字体漏洞修复、反向代理和证书、存储与 CDN、日志脱敏、插件支持、许可审查、缓存清理和故障值班。公共服务看似免部署,却增加数据合规、可用性依赖和退出成本。
图漂移比渲染失败更危险
语法错误会阻断构建,过时的正确图片却会误导决策。每张关键图都应有 owner,并和服务目录、OpenAPI/AsyncAPI、部署清单、ADR 或代码模块建立可点击关系。下面变化应触发同 PR 更新图源码:
新增或删除服务、数据库、队列、信任边界。同步调用改异步,或投递、重试、一致性语义变化。API 路径、事件名、数据所有权或网络区变化。
故障切换、限流、降级和人工补偿路径变化。
图中不要放真实 token、密码、客户标识、内网 IP、生产 topic、证书内容或可利用的管理 URL。服务名若本身敏感,发布外部版本时使用抽象角色,并从独立源码生成;不要在导出 SVG 后手工涂抹,因为元数据和源码仍可能保留原文。
升级流程要同时覆盖核心 JAR、Server、Java、Graphviz/布局引擎、主题、字体和插件。先在分支全量渲染并保存差异,抽查复杂图、中文、链接、include 与错误图,再更新制品散列和镜像 digest。回滚必须恢复整套渲染输入,而不是只把旧图片拷回来。
从现象回到第一组证据
| 现象 | 第一组证据 | 常见原因 | 修复与复测 |
|---|---|---|---|
| IDE 成功、CI 语法失败 | 插件核心版本、JAR -version、错误行 | 版本或预处理宏不同 | 对齐锁定 JAR,全量 --check-syntax |
Cannot find Graphviz 或空 SVG | -testdot、PATH、容器镜像 | dot 不存在、版本不兼容、图过密 | 安装/锁定 Graphviz或验证受支持引擎 |
| 中文方框、节点错位 | listfonts、字体包、SVG/PNG 实图 | 渲染节点缺字体或版本不同 | 锁字体镜像和许可,干净环境重渲染 |
!include 本地成功、服务端失败 | security profile、allowlist、include 路径 | 服务端正确阻断或路径未挂载 | 复制审核资源到专用只读目录,最小 allowlist |
| 服务内存激增或超时 | 图尺寸、输出大小、heap、队列 | 巨图、并发无界、限制被抬高 | 拆图,限制大小/并发/超时,压测复验 |
| 外部 URL 被访问 | profile、allowlist、出口日志 | INTERNET 或宽泛 URL allowlist | 切 SANDBOX/最小 ALLOWLIST,审计历史请求 |
| 本地与服务端图片不同 | 核心、主题、字体、Graphviz、配置 | 渲染输入未统一 | 输出运行清单,对齐后重新生成 |
停服务、清缓存和回滚都要可执行
个人服务清理:
docker stop plantuml-server-lab
docker rm plantuml-server-lab
docker image rm plantuml/plantuml-server:jetty
rm -rf build/diagrams共享服务不能直接照抄删除。先从负载均衡摘除实例,等待在途渲染结束,保存脱敏指标与错误样本,再删除临时目录和缓存。缓存键应包含 PlantUML 核心版本、配置、主题、字体与源码散列;否则升级后可能继续返回旧图。
回滚顺序是恢复 Server/CLI 版本与 digest、Java 和布局引擎镜像、主题与字体、plantuml.cfg,清除不兼容缓存,再从源码重渲染一组金丝雀图。若是安全 profile 回滚,绝不能为了恢复 include 把共享服务降到 UNSECURE 或 LEGACY;应恢复最后一份最小 allowlist。
合并和推广前的运行检查
Java、PlantUML 构建类型、版本、JAR 散列或镜像 digest 已记录。正向图通过语法检查并生成可读 SVG,反向图返回非零退出码与行号。Graphviz 或替代布局引擎在目标 runner 中实际验证。
主题、字体、配置和布局引擎都作为版本化构建输入。本地、IDE、CI 和服务端使用可解释的核心版本与安全 profile。不可信输入使用 SANDBOX;确需 include 时使用最小 ALLOWLIST。
服务端具备认证、TLS、限流、超时、资源上限、日志脱敏和受限出网。SVG、PNG、URL、缓存与日志都经过敏感数据检查。大图的源码、输出、耗时、heap、队列和失败类型有趋势记录。
每张关键图有 owner,并与代码、接口、部署事实和 ADR 同步变化。升级与回滚覆盖 JAR/镜像、Java、布局、主题、字体、缓存和插件。许可审查同时考虑 PlantUML 构建、主题、字体和再分发方式。
PlantUML 的强项是让复杂模型以文本参与评审和变更,但预处理能力与服务端部署也扩大了信任边界。把 CLI 基线、失败实验、profile、allowlist、字体、容量与图的 owner 一起落地,团队得到的才是可演进的架构表达系统,而不是一台能够生成图片却不知道读过什么数据的服务器。
