news 2026/9/16 22:48:22

Grafana PDF导出:Docker部署Image Renderer指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Grafana PDF导出:Docker部署Image Renderer指南

做运维的同学应该都经历过这个场景:领导一句“把昨天那几个监控大屏整理成 PDF 发我”,你在 Grafana 里翻了一圈,发现开源版居然没有一键导出 PDF 的按钮。撑死了能截个 PNG,分辨率一放大全是马赛克,数据标得密一点根本没法看。

Grafana 开源版导出 Dashboard PDF,官方给出的标准路径是部署 Grafana Image Renderer。这个插件本质上是一个基于 headless Chrome 的渲染服务,Grafana 把页面请求丢给它,它打开页面、等图表画完、再给你生成图片或者 PDF。今天这篇就聊聊怎么用 Docker 把它完整地跑起来,从架构原理到部署参数,再到实际导出和排坑,一次说清楚。适合正在用 Grafana 开源版做监控展示、需要把面板归档成报告或定期发给相关方的人参考。

1. 方案背景与选型思路

1.1 为什么开源版导出 PDF 要单独部署一个服务

最早期的 Grafana 确实自带 PDF 导出功能,但当时底层依赖的是 PhantomJS,一个已经停止维护的浏览器内核项目。后来 Grafana 官方考虑到安全性和维护成本,直接把内置导出能力摘掉了,并给出的替代方案就是 Grafana Image Renderer。

这里要理清一个概念:Grafana 和 Image Renderer 是两个独立的进程。Grafana 负责页面展示、权限控制和数据查询,Image Renderer 负责“把页面画出来”。你点击导出 PDF 时,Grafana 会像用户一样请求内部页面地址,把这个地址发给 Image Renderer,渲染器启动无头浏览器打开页面,等待图表和网络请求完成,然后将整页内容打印成 PDF 文件返回。

所以当你在 Grafana 菜单里找不到 PDF 选项时,不是配置漏了哪里,而是确实缺少了 Image Renderer 这个执行端。这个设计其实挺合理的,把“界面程序”和“渲染工人”分开,Grafana 主程序保持轻量,渲染这种重活可以独立部署、独立扩容。

1.2 Docker 部署 vs 二进制安装

Image Renderer 的安装方式有二进制和 Docker 两种。二进制方式看起来简单——下载一个可执行文件,配上系统里已有的 Chrome,启动就行。但实际操作中很容易踩坑:系统缺少 Chromium 依赖库、版本不匹配导致黑屏、字体缺失导致中文乱码、glibc 版本不对导致进程直接崩溃。

所以我的建议是无脑选 Docker。官方镜像grafana/grafana-image-renderer已经把 Chromium、系统依赖、字体环境都集成好了,你不需要在宿主系统上装任何浏览器相关的东西。用 Docker Compose 把 Grafana 和 Image Renderer 放在同一个自定义网络里,两个容器用服务名互相访问,既解决了网络互通,又便于版本升级和回滚。

1.3 版本兼容与锁定策略

Grafana Image Renderer 对 Grafana 主版本有兼容要求。以当前主流版本为例,Grafana 9.x、10.x、11.x 搭配 renderer 3.x 都是可行组合,但我建议两个镜像都锁定具体 tag,而不是用latest。渲染器跟随的 Chromium 升级频率不低,一次小版本升级可能改变页面的渲染行为,最终导出的 PDF 排版和之前不一样,排查起来很痛苦。

我在实际项目中会把grafana/grafana:11.1.0grafana/grafana-image-renderer:3.10.0同时写进 Compose 文件,并且把镜像版本记录在项目的 README 里。这样无论过去多久,新同事一把梭部署出来的效果和当初调试通过的版本完全一致。

2. Docker 部署 Image Renderer 完整步骤

2.1 编写 Docker Compose 文件

下面是完整可用的 Compose 配置,核心是两个服务、一个自定义网络、两个数据卷:

version: "3.8" services: grafana: image: grafana/grafana:11.1.0 container_name: grafana restart: unless-stopped ports: - "3000:3000" environment: - GF_RENDERING_SERVER_URL=http://renderer:8081/render - GF_RENDERING_CALLBACK_URL=http://grafana:3000/ - GF_RENDERING_MODE=sync - GF_RENDERING_IGNORE_HTTPS_ERRORS=true volumes: - grafana-storage:/var/lib/grafana networks: - grafana-net renderer: image: grafana/grafana-image-renderer:3.10.0 container_name: grafana-image-renderer restart: unless-stopped environment: - ENABLE_HTTP_SERVICE=true - HTTP_PORT=8081 - RENDERER_CHROME_NO_SANDBOX=true - IGNORE_HTTPS_ERRORS=true volumes: - renderer-fonts:/usr/share/fonts networks: - grafana-net volumes: grafana-storage: renderer-fonts: networks: grafana-net:

逐行解释一下关键配置。

GF_RENDERING_SERVER_URL是 Grafana 调用渲染服务的地址,写的是http://renderer:8081/render。这里用的是 Compose 服务名renderer,不是 IP,也不是 localhost。因为两个容器在同一个自定义网络grafana-net里,Docker 内置 DNS 会把服务名解析成对应容器 IP。

GF_RENDERING_CALLBACK_URL是给 renderer 回调 Grafana 用的地址。很多教程会漏掉这个配置,导致异步渲染一直失败。它的含义是:renderer 渲染完成后需要通知 Grafana“我做完了”,所以 Grafana 必须给它一个自己能访问到的内部地址。这里填http://grafana:3000/,同样用服务名。

GF_RENDERING_MODE=sync表示同步渲染模式,适合单机或小规模场景,请求发出后等待结果返回即可。如果你的 Grafana 是多节点集群,后续可以考虑切换到cluster模式并引入 Redis 做队列,不过那就是进阶话题了。

RENDERER_CHROME_NO_SANDBOX=true是因为官方镜像默认以 root 身份运行容器,Chromium 的 sandbox 机制在 root 下会报错,必须显式关掉。这也是为什么我强烈推荐 Docker 而不是二进制安装的原因之一,这类与宿主环境强相关的参数已经被官方预设好了。

IGNORE_HTTPS_ERRORS=true解决的是 Grafana 使用了自签名证书时的证书校验问题。如果 Grafana 只在内网跑,没有对外暴露 HTTPS,这个可以留空或设为 false。但如果你后续给 Grafana 配了 Nginx 反代并加上了 HTTPS,务必打开这一项,否则渲染器访问回调地址会因为证书不受信任直接拒绝打开页面。

2.2 Grafana 容器如何对接渲染服务

上面的配置用了环境变量方式。Grafana 官方支持通过GF_前缀的环境变量覆盖grafana.ini中的配置,规则是把 ini 文件的 section 和 key 用下划线拼接并转大写。比如[rendering] server_url对应GF_RENDERING_SERVER_URL

这种方式比直接修改grafana.ini更适合容器部署,因为配置随 Compose 文件走,可审计、可迁移。如果你更习惯传统方式,也可以在 Grafana 的grafana.ini中写:

[rendering] server_url = http://renderer:8081/render callback_url = http://grafana:3000/ mode = sync ignore_https_errors = true

效果一致。但要注意一个关键点:如果你的 Grafana 是宿主机原生安装而非容器,而 Image Renderer 是 Docker 部署,那么server_url要填宿主机的局域网 IP,例如http://192.168.1.10:8081/rendercallback_url要填 Grafana 能被 renderer 访问到的地址。因为此时 Grafana 和 renderer 不在同一个 Docker 网络中,用 localhost 是互相找不到的。

另外,如果启用了GF_RENDERING_MODE=cluster异步模式,callback_url必须是 renderer 容器内可访问的地址,因为它实际是 renderer 容器向这个地址发回调请求。很多人在这一步踩坑,排查半天最后发现是回调地址写成了宿主机 localhost,两个容器互相不通。

2.3 启动服务与连通性验证

配置完成后执行:

docker compose up -d

查看容器状态:

docker ps

正常情况下两个容器都处于Up状态。接着看渲染服务日志有没有报错:

docker logs -f grafana-image-renderer

日志中出现类似HTTP server listening on :8081的内容说明服务已经起来了。

然后从 Grafana 容器内部测试网络连通性:

docker exec -it grafana curl -I http://renderer:8081/render

如果返回 HTTP 400 或 403,这是正常的,因为渲染接口需要带认证参数,但连接本身是通的。如果返回connection refused或者Could not resolve host,说明网络或者服务名解析有问题,回到 Compose 文件检查两个服务是否在同一个网络里。

最后打开 Grafana 界面,进入任意 Dashboard,点击右上角的 Share 按钮,如果菜单里出现了 PDF 标签,说明 Grafana 已经成功感知到了渲染服务,部署环节就基本完成了。

3. 导出 PDF 的三种实操方式

3.1 从 Grafana 界面直接下载

部署成功之后,最直观的导出方式就是图形界面操作。

进入 Dashboard 右上角的 Share dashboard or panel 按钮,切到 PDF 标签页。这里有几个选项需要说明:

  • Output format 通常固定为 PDF,不需要改;
  • Layout 有 Landscape(横向)和 Portrait(纵向)两种,监控面板一般数据表多、列多,选 Landscape 更合适,横向排版能让每张图更宽,文字也不容易挤在一起;
  • Time range 默认是当前面板时间范围,如果你要导出的时间不是当前视图,在这里手动选,导出的 PDF 会使用这个时间范围重新查询数据,图表会按新时间重画。

点击 Download PDF 后,Grafana 会向 Image Renderer 发出渲染请求,经过几秒到几十秒不等的等待时间,浏览器会收到一个 PDF 文件并自动开始下载。

如果界面里没有出现 PDF 标签,说明GF_RENDERING_SERVER_URL配置没有生效,或者 renderer 服务实际不可达。如果出现了标签但点击下载后一直转圈,通常是渲染超时或渲染服务日志中有报错,这个在下一章详细说。

3.2 用 curl 调用渲染接口

图形界面适合临时导出,但如果你需要把导出动作集成到脚本里,就得走 HTTP 接口。

单个面板的 PDF 导出:

curl -u admin:admin \ 'http://localhost:3000/render/d-solo/abc123?panelId=2&from=1700000000000&to=1700003600000&width=1000&height=600' \ -o panel.pdf

这里解释几个参数:

  • d-solo表示只渲染单个面板,而不是整个 Dashboard;
  • abc123是 Dashboard 的 UID,可以从 Dashboard 的 URL 里获取;
  • panelId是面板 ID,在 Dashboard JSON 中可以查到,也可以在编辑面板时的 URL 参数里看到;
  • fromto是毫秒级时间戳,用于控制数据查询范围;
  • widthheight是渲染的像素尺寸,PDF 页面会依据这个比例排版。

如果要导出整个 Dashboard,把路径换成/render/d/abc123,同时建议加上kiosk参数,它会隐藏 Grafana 的侧边栏和顶栏,让 PDF 内容更干净:

curl -u admin:admin \ 'http://localhost:3000/render/d/abc123?from=1700000000000&to=1700003600000&kiosk' \ -o dashboard.pdf

注意这里的请求地址是 Grafana 的/render端点,Grafana 收到请求后会校验用户权限,然后把渲染任务转发给 Image Renderer。所以这个接口必须带认证信息,可以是用户名密码,也可以是 API Token。实践中我建议生成一个只读的 Service Account Token,避免在脚本里明文保存管理员密码。

3.3 脚本化批量导出,实现自动化巡检报表

前面两种方式解决的是手动需求,但很多场景是每天定时生成运营巡检报告,把所有核心面板在凌晨导出成 PDF,存档或者发给相关人员。

这里提供一个可行的 Bash 脚本思路:

#!/bin/bash TOKEN="glsa_xxxxxxxxxxxx" BASE="http://localhost:3000" TS_BEGIN=$(date -d "yesterday 00:00:00" +%s)000 TS_END=$(date -d "yesterday 23:59:59" +%s)000 for panel in 2 4 6 8; do curl -s -H "Authorization: Bearer $TOKEN" \ "$BASE/render/d-solo/abc123?panelId=$panel&from=$TS_BEGIN&to=$TS_END&width=1200&height=500" \ -o "/tmp/report-panel-$panel.png" done # 使用 ImageMagick 将多张图合并成一份 PDF convert /tmp/report-panel-*.png /tmp/report-$(date +%F).pdf

关键点是fromto的时间戳计算。用date -d "yesterday 00:00:00" +%s获取的是“昨天零点”的 Unix 秒数,乘以 1000 才是 Grafana 需要的毫秒单位。这里用了$(date -d ... +%s)000的方式,在字符串后面拼接 000,实现了秒转毫秒,不需要额外写复杂的算术逻辑。

将脚本加入 crontab:

0 1 * * * /opt/scripts/export-grafana-report.sh

每天凌晨 1 点执行,上班前就能看到昨日的巡检报告。如果想直接发送邮件或推送到内部系统,可以在脚本末尾追加发送命令,比如通过mailx发送附件,或者调用企业微信机器人的 Webhook 上传文件,这里就根据自己的环境扩展了。

4. 实践中的问题排查与调优

4.1 PDF 下载一直转圈 / 提示 Rendering failed

这是最常遇到的一类问题。界面点击 Download PDF 后,页面一直显示“正在渲染”或者弹出错误提示。遇到这种情况,第一步永远是看渲染服务的日志,而不是反复点击重试:

docker logs -f grafana-image-renderer

如果日志中出现context deadline exceededtimeout相关字眼,说明面板数据量太大,headless Chrome 在默认超时时间内没有画完图表。解决办法是调大渲染超时时间,在 Grafana 容器上加环境变量:

- GF_RENDERING_RENDERING_TIMEOUT=90s

默认值通常是 30 秒,调整为 60 到 90 秒一般就能覆盖大部分情况。如果面板确实特别重,比如图表数量极多或依赖的外部数据接口响应很慢,还可以继续往上加,但这时候更应该考虑优化面板本身而不是无限放宽超时。

如果是多用户同时使用导出功能,可能触发并发限制。可以把并发数调低一些,避免请求全部堆积:

- GF_RENDERING_CONCURRENT_RENDER_REQUEST_LIMIT=5

这个值并不是越大越好。每个渲染任务都会启动一个 headless Chrome 进程,内存消耗相当可观,并发太高会导致容器 OOM,整个服务直接挂掉,反而影响所有用户。

4.2 中文乱码或文字变成方块

这是 Docker 部署方案中一个非常典型的坑。官方镜像默认只带了基础英文字体,没有中文字体,当面板标题、图例、坐标轴标签里有中文时,pdf 里的中文字符会变成方框或乱码。

我的处理方案是给 renderer 容器挂载一个字体目录。在宿主机上准备一个包含中文字体的目录,然后在 Compose 文件中挂载覆盖:

volumes: - /opt/fonts:/usr/share/fonts/custom:ro

字体文件可以从fonts-noto-cjk包中提取,或者直接复制 Windows 系统的msyh.ttc(微软雅黑)到该目录。挂载完成后重启容器:

docker compose restart renderer

然后重新导出 PDF,中文就能正常显示了。需要注意,Grafana 侧配置的主题和字体也会影响渲染结果,但如果面板在网页上显示正常而 PDF 乱码,问题基本就在 renderer 容器的字体环境上。

4.3 导出的 PDF 页面空白或只有部分图表

这种问题通常不是渲染服务挂掉,而是 headless Chrome 没有等到图表完全画出来就执行了打印。Grafana 图表依赖数据请求和 JavaScript 渲染,如果网络慢或数据查询时间长,Chrome 可能在空页面状态下就抓取了内容。

先检查 Grafana 的查询响应时间。如果某个面板的数据源是外部数据库,查询耗时本身就超过 3 秒,那么渲染器默认的等待时间可能不够。除了调大GF_RENDERING_RENDERING_TIMEOUT,还可以在 Dashboard 的设置里把面板的查询缓存时间调短,或者间接降低数据量。

另一种更隐蔽的情况是 Dashboard 使用了“动态变量”,例如时间范围选择器或模板变量,渲染器在无人工操作的情况下无法自动切换这些变量,导出的 PDF 可能显示的是默认状态。因此,在导出前要确认面板的时间范围和变量状态是否符合要求。

4.4 自签名 HTTPS 导致的渲染失败

如果你的 Grafana 通过 Nginx 反代暴露成了 HTTPS 域名,但证书是自签的,renderer 访问 Grafana 回调地址时会在证书校验阶段失败,表现为日志中频繁出现类似于net::ERR_CERT_AUTHORITY_INVALID的错误。

解决办法是在两侧同时设置忽略证书错误的参数。Grafana 侧:

- GF_RENDERING_IGNORE_HTTPS_ERRORS=true

renderer 侧:

- IGNORE_HTTPS_ERRORS=true

两个都要配,因为它们分别负责不同的请求方向。

4.5 渲染服务的性能与资源规划

Image Renderer 是一个重量级服务,每个并发渲染任务都会拉起一个 Chromium 进程,内存占用随页面复杂度线性增长。我在实际使用中观察过,一个中等复杂度的 Dashboard(四行图表,每行四个面板)渲染一次大约需要 2GB 左右的内存峰值。

因此部署 renderer 的宿主机至少要给容器预留 2GB 可用内存,建议在 Compose 中显式声明资源限制:

deploy: resources: limits: memory: 2G cpus: "2.0"

当然这个配置在 Docker Compose v2 中属于 Swarm 模式专属,单机使用docker compose up时不一定生效,更通用的做法是用docker run --memory=2g --cpus=2或直接在 Compose 文件中写mem_limit。但不管用哪种方式,核心思路是别让渲染服务把宿主资源吃干抹净,否则 Grafana 本身也会受到影响。

4.6 时间范围与时区问题

定时导出报表时,最容易出现的是日期边界错误。比如你想导出“昨天”的数据,但脚本跑在凌晨 1 点,如果用$(date +%s)取当前时间作为结束时间,就会把今天凌晨到现在的空数据段也包含进去。所以我上一章的脚本特意用了yesterday 00:00:00yesterday 23:59:59来限定范围,这在生成日报场景中非常实用。

时区方面,Grafana 默认使用用户偏好时区,renderer 在打开页面时也会继承 Grafana 会话的时区设置。如果你的服务器是 UTC 时区而业务数据基于北京时间,导出的 PDF 上时间轴显示的是 UTC 时间,看起来会很别扭。建议在 Grafana 的用户配置中,将默认时区设置为Asia/Shanghai,或者在导出脚本中通过 URL 参数显式指定时区:

&timezone=Asia%2FShanghai

5. 几个实操层面的小建议

部署 Image Renderer 这件事本身不复杂,复杂的是让它稳定、持续地工作。我个人实践下来有几个体会。

第一,日志是你的第一手证据。任何导出失败,不要猜,先docker logs -f grafana-image-renderer看输出。渲染服务会把具体失败的步骤和 URL 打出来,很多时候问题一眼就能定位。

第二,版本锁定比“跟着最新走”更让人安心。Grafana Image Renderer 升级带来的行为变化是隐性的,外观和排版可能在某个版本后悄悄变化。在非必要情况下,固定版本长期运行,只在确认新版本兼容后再手动升级。

第三,PDF 标签能否在 Share 菜单中出现,是判断配置是否生效的快速探针。如果标签没出现,优先排查GF_RENDERING_SERVER_URL是否正确;如果标签出现了但下载失败,再看渲染服务日志。这个排查顺序能帮你省下大量时间。

最后再分享一个扩展方向:导出的 PDF 文件可以直接作为邮件附件发送,或者归档到对象存储。我目前的做法是每天晚上定时把核心业务面板渲染成 PDF,脚本结束后通过邮件发送给当天值班人员,全程无需人工参与。Grafana 开源版配上 Image Renderer 之后,整个监控报告的自动化闭环就完整了,这也是这个方案最值得投入时间打磨的地方。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 22:47:04

用友U8越用越慢?数据库与SQL Server配置优化实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 22:45:59

YOLOv8+ByteTrack多目标跟踪实战:原理、代码与鱼群检测调参指南

做目标跟踪最怕什么?模型调好了,检测框也在跳,但每个框是谁根本没搞清——ID频繁切换、目标跟丢、前后帧对不上号。早几年想解决这个问题,要么上DeepSort,要么啃一堆关联算法的论文,代码写起来头都大。现在…

作者头像 李华
网站建设 2026/9/16 22:45:46

工业机器视觉实战:机电光软强耦合系统设计与落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 22:44:07

SCAN欠定盲源分离:双麦克风分离多声源的工程实现

简介:本资源是一套面向信号处理研究者与研究生的欠定盲源分离(UBSS)MATLAB实现工具包,聚焦音频分离、脑电信号解混等实际场景中的源数多于通道数这一典型难题。包内共5个文件,含4个核心MATLAB函数(demosig2…

作者头像 李华
网站建设 2026/9/16 22:42:18

Linux命令查询三件套:man、tldr、explain实战指南

干了十来年 Linux,我见过太多新人捧着一本《Linux 命令大全》翻到吐,也见过不少老手在聊天群里急吼吼地问“这个参数是啥来着”。说实话,大家缺的从来不是某条具体命令,而是“怎么快速弄懂一条陌生命令”的方法。这篇要聊的三个指…

作者头像 李华
网站建设 2026/9/16 22:41:05

使用Python批量自动化CIC-FlowMeter提取流量特征

做过网络流量分析的人应该都有体会:抓包容易,特征工程难。尤其是当你准备训练一个流量分类模型,手头攒了几百个pcap文件要转成结构化特征时,光是在CIC-FlowMeter的图形界面里一个文件一个文件地“选输入、选输出、点运行”&#x…

作者头像 李华