ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Grafana 页面嵌入:隐藏侧边栏、顶部菜单与代理注入实战

Grafana 页面嵌入:隐藏侧边栏、顶部菜单与代理注入实战 1. 为什么监控大盘总被要求“嵌进自家系统”Grafana 页面嵌入这件事看起来就是把一个 URL 塞进 iframe真做起来能让人从下午卡到深夜。前阵子帮一个团队做运维门户整合需求原话就一句把监控大盘嵌进我们自己的平台左侧那条长长的菜单和顶上的导航栏都别要只留图表。听起来很简单实际过程里先后撞上了 iframe 白屏、自动跳登录页、嵌进去导航还在、升级版本之后自定义样式全部失灵这几堵墙。所以这篇就把 Grafana 页面嵌入以及隐藏左侧边栏、顶部菜单栏这条链路完整讲清楚从配置项的含义到 URL 参数的拼接再到反向代理注入样式和版本适配都会给到可以直接抄的写法。不管你是刚接手 Grafana 的小白还是已经部署过 Prometheus Grafana 的老手只要碰到“把大盘嵌到别的系统里”这个需求这篇基本能覆盖你 90% 的坑。先说清楚这件事的边界。Grafana 本身是个完整的 Web 应用它默认假定访问者是在自己的域名下、带着完整导航来浏览的独立系统。而“嵌入”意味着你要把它降级成一个纯粹的渲染组件只保留 dashboard 的画布区域。这两者之间的落差就是所有问题的来源。搞懂了这个落差后面每一个配置项的存在理由就都能串起来了。1.1 三个高频场景决定了隐藏粒度我接触过的嵌入需求基本落在三类场景里而这三类对“隐藏多少”的要求完全不同。第一类是内部运维门户整合。公司已经有一个统一门户左侧是自己的业务菜单点进去某一块要直接看到 Grafana 大盘。这种场景要求最彻底Grafana 的左侧边栏、顶部导航、品牌 logo 全都要消失视觉上要让人感觉这就是自家门户的一个页面。这类需求通常还会要求主题色统一深浅色跟随主站切换。第二类是大屏 / 监控电视墙。挂在办公室墙上或者车间的大屏通常是用浏览器全屏或者整屏 iframe 展示。这种场景对“隐藏”的要求最高连时间选择器、变量下拉、刷新按钮都嫌碍事因为没人会在电视墙前面点鼠标。同时还需要自动刷新和轮播。第三类是交付给外部使用的报表页。比如把某个业务指标的看板嵌进客户的系统里这时候既要隐藏导航还要严格控制权限——客户只能看不能改不能看到别的 dashboard 列表更不能顺着 URL 摸到 Grafana 主界面。把场景先分类是有实际意义的。因为很多人一上来就问“怎么把侧边栏去掉”但如果是第二类场景你其实压根不该用整页 dashboard 嵌入而应该用单面板模式那样从根上就没有导航栏这回事。选错方案后面全是补丁。1.2 默认 iframe 撞上的三道墙直接写一个iframe srchttps://grafana.xxx/d/abc/main你会按顺序撞上三堵墙。第一堵是X-Frame-Options。Grafana 出于安全考虑默认不允许自己被别的站点嵌套响应头里会带上拒绝嵌入的指令浏览器直接给你一个空白框控制台里能看到明确的拒绝加载提示。第二堵是登录态丢失。就算你解决了第一堵墙iframe 里加载出来的多半是登录页因为 Grafana 拿不到你在主站的会话。这时候要么开匿名访问要么让两边同源共享 Cookie。第三堵才是样式问题。前两堵通了页面能渲染了你才会看到那条讨厌的左侧边栏和顶部导航栏依然大摇大摆地待在那里。顺序很重要。我见过不少人卡在第三堵墙上疯狂找 CSS其实前面两道门都没开。所以下面按配置层、参数层、样式层的顺序来讲逐层推进。2. 配置层拆解每个开关到底在管什么Grafana 的配置文件通常是grafana.ini容器化部署时一般通过挂载或者环境变量注入。有个细节需要注意如果你用的是官方镜像环境变量覆盖配置的格式是GF_加段名和键名的大写下划线形式比如GF_SECURITY_ALLOW_EMBEDDINGtrue就对应[security]段下的allow_embedding。这套命名规则在排查“我明明改了配置为什么没生效”时特别有用因为环境变量和 ini 文件的优先级关系经常是罪魁祸首。2.1 allow_embedding决定 iframe 能不能加载[security]段下的allow_embedding是嵌入功能的总开关默认值是false。这个默认值不是随便定的——它存在的原因是防止点击劫持也就是有人把你的 Grafana 界面套在钓鱼页面的透明层里骗用户操作。所以开启它本质上是在安全性和集成便利性之间做出的一个主动取舍。[security] allow_embedding true打开它之后Grafana 就不再返回拒绝嵌入的响应头。但要注意这个开关只解决“能不能嵌”的问题不解决“嵌进去是什么样”的问题。很多人改完这一项重启发现 iframe 里终于有内容了于是以为大功告成结果一看还是跳登录页。另外提醒一句改完配置必须重启 Grafana 进程。容器化部署的话docker restart或者docker compose restart grafana都行。我习惯在改完配置后先跑一次配置校验容器镜像里通常带了一个校验命令可以检查 ini 语法有没有写错比重启后看日志报错要快得多。2.2 匿名访问没有登录态的渲染通道iframe 里之所以跳登录页是因为浏览器在 iframe 中发起的请求没有带上 Grafana 的有效会话。解决思路有两条开匿名访问或者让两边共享登录态。匿名访问的配置落在[auth.anonymous]段[auth.anonymous] enabled true org_name Main Org. org_role Viewer这三个参数里的坑集中在后两个。org_name必须和你的实际组织名完全一致默认安装是Main Org.但一旦被改过名字这里写错就会导致匿名用户拿不到任何组织权限表现为大盘能加载但提示无权限或者图表全空。org_role建议就写Viewer这是只读角色能看不能改。千万不要图省事写成Editor甚至Admin那是给自己挖坑——匿名意味着任何能访问到这个地址的人都是这个角色写入权限一旦放开等于把大盘编辑器暴露在公网上。如果你确实不能开匿名比如合规要求不允许未认证访问还有一条替代路径认证代理。思路是在 Grafana 前面挡一层网关由网关完成身份校验后把用户名通过约定好的请求头传给 GrafanaGrafana 信任这个头并自动登录。[auth.proxy] enabled true header_name X-WEBAUTH-USER header_property username auto_sign_up true这条路的关键在于必须限制 Grafana 端口的直接访问。因为只要有人能绕过网关直连 Grafana 的端口他就能自己伪造那个请求头直接冒充任意用户。所以生产环境里 Grafana 的端口通常只绑定在本地回环或者内网地址上由前置代理对接。这一条是硬性要求不是建议。2.3 SameSite 与 root_url跨站嵌入最隐蔽的坑配置都改对了、匿名也开了有些人还是会碰上一种情况第一次打开正常刷新一下又跳登录页或者主站能正常访问嵌进 iframe 里就不行。这基本就是 Cookie 的 SameSite 属性在作祟。浏览器的规则是跨站 iframe 中发起的请求默认不会携带标记为 Lax 或 Strict 的 Cookie。Grafana 默认的 Cookie 策略会让它在跨站嵌入场景下丢会话。对应配置在[security]段[security] cookie_samesite none cookie_secure true这里有两个硬性前提一是cookie_samesite设为none时浏览器强制要求 Cookie 必须带 Secure 属性所以cookie_secure必须为true二是cookie_secure true要求你的 Grafana 必须通过 HTTPS 访问HTTP 下浏览器会直接拒收这个 Cookie。如果你的环境只有内网 HTTP那就老老实实走同源部署的路线把 Grafana 和主站放在同一个域名下用路径区分比如主站是/portalGrafana 通过反向代理挂在/grafana下面。顺带说root_url。当 Grafana 挂在反向代理的子路径下时必须显式配置根地址否则前端资源会以根路径加载导致样式全丢、接口 404[server] domain portal.example.com root_url https://portal.example.com/grafana/ serve_from_sub_path trueserve_from_sub_path这一项是配合子路径部署用的开启后 Grafana 会正确处理带前缀的资源请求。实测下来子路径部署相比独立子域名部署在嵌入场景里省心得多因为同源天然共享 Cookie前面那一堆 SameSite 的麻烦全都不存在了。3. 隐藏侧边栏和顶部菜单栏的四条路线配置打通之后终于进入正题。隐藏导航这件事Grafana 官方其实提供了不止一种机制选择哪条路线取决于你要隐藏到什么程度以及你能接受多大的维护成本。3.1 kiosk 参数一行 URL 解决八成需求成本最低的方案就是在 URL 后面挂上 kiosk 参数。这是 Grafana 自带的功能不需要改任何配置也不依赖版本属于首选尝试项。https://grafana.example.com/d/abc123/main-dashboard?orgId1kiosk加上kiosk之后左侧边栏和顶部导航栏会一起消失只留下 dashboard 本身的工具栏时间范围选择器、变量下拉、刷新按钮。对于大部分门户整合场景这已经够用了因为运维同学还是需要切时间范围的。如果你连时间选择器都不想要可以用带值的写法https://grafana.example.com/d/abc123/main-dashboard?orgId1kiosktv这个值原本是为电视墙场景设计的会把界面收得更紧一些同时自动开启刷新。需要说明的是不同 Grafana 版本对kiosktv的裁剪程度略有差异有的版本会连顶部工具栏一起隐藏有的还留着。所以我的建议是部署完先实测一次看你当前版本的实际渲染结果再决定用哪个。这里有个小提醒kiosk 参数的正确写法是不带值或者值等于 tv。我见过有人写成kiosk1、kiosktrue这些写法在部分版本上不会生效。参数拼不对的时候先别怀疑人生先检查拼写。顺带一个很实用的点refresh参数只在 kiosk 模式下生效。也就是说你想让嵌入的大盘自动刷新必须同时挂上 kiosk...kioskrefresh30s这一条知道的人不多但大屏场景里几乎是必须的。3.2 d-solo只嵌单个面板从根上就没有导航如果你的需求是“在大屏或者详情页里放某一个指标图”那其实完全不需要嵌入整个 dashboard。Grafana 提供了单面板渲染路径URL 骨架如下https://grafana.example.com/d-solo/abc123/main-dashboard?orgId1panelId2kioskd-solo渲染出来的是一个纯画布只有一个面板天然没有左侧边栏、没有顶部菜单连 dashboard 标题都不显示。panelId是必填的你可以在 dashboard 编辑界面里查看面板的 ID也可以从面板的分享链接里拿到。这条路线的优势非常明显加载的内容少渲染快不会有任何多余的界面元素适合大屏拼图和详情页局部嵌入。缺点是变量联动比较麻烦——单面板模式下 dashboard 级别的变量传参依然支持但如果你需要多个面板联动切换就得自己在外层用var-前缀的参数去拼灵活度不如整页嵌入。另外提一句还有一种更彻底的形态是把面板渲染成静态图片嵌入。这种方式连 JavaScript 渲染都省了适合做报表归档和邮件推送但它需要额外开渲染服务而且不支持交互。选型上实时监控用d-solo报表归档用图片渲染这两个别搞混。3.3 反向代理注入 CSS把 chrome 彻底洗掉有些场景 kiosk 满足不了。比如品牌方要求页面里必须出现自己的 logo 位置或者要求把 dashboard 顶部那一排工具栏也隐藏掉只留纯图表。这时候就得动用最后一招在反向代理层给返回的 HTML 注入一段自定义样式。以 Nginx 为例核心是sub_filter指令它能在响应体里做字符串替换location /grafana/ { proxy_pass http://127.0.0.1:3000/; proxy_set_header Host $host; proxy_set_header Accept-Encoding ; sub_filter_types text/html; sub_filter /head style [data-testiddata-testid Nav menu], [data-testiddata-testid navigation mega-menu], .sidemenu, .navbar { display: none !important; } .main-view { margin-left: 0 !important; } /style/head; sub_filter_once on; }这段配置里有三个细节值得单独拎出来讲。第一是proxy_set_header Accept-Encoding ;。这一行必须加。因为 Grafana 返回的 HTML 默认是 gzip 压缩的而sub_filter只能处理未压缩的文本不关掉压缩的话替换会静默失败——配置看起来完全正确但就是没效果。这是最容易卡住人的一个点。第二是sub_filter_once on;。它确保只替换第一次出现的位置避免重复注入。第三是选择器的写法。我给了四组选择器前两组是较新版本基于>docker run -d --namegrafana \ -p 3000:3000 \ -v /opt/grafana/grafana.ini:/etc/grafana/grafana.ini \ -v /opt/grafana/data:/var/lib/grafana \ grafana/grafana:latest版本选择上有个取舍。最新的稳定版功能全、安全补丁及时但前端结构变动也最频繁自定义 CSS 需要跟着调。如果你对版本升级没那么敏感选一个长期维护的稳定版本并把版本号锁死能省掉不少维护成本。我的习惯是生产环境用具体版本号而不是latest标签避免某天重启之后界面结构悄悄变了。顺带说下数据源侧。嵌入的大盘背后通常挂着 Prometheus 这类时序库部署顺序上建议先把数据源跑通、在 Grafana 里确认能出图再去折腾嵌入。因为嵌入过程里出现的空图、报错很容易被误判成嵌入配置问题实际上可能是数据源压根没连上。分开验证排查范围会小很多。4.2 grafana.ini 逐项配置与参数说明把前面散落的配置汇总成一份可以直接用的片段。每一项后面我都标了它的作用和注意事项。[server] domain portal.example.com root_url https://portal.example.com/grafana/ serve_from_sub_path true [security] allow_embedding true cookie_samesite none cookie_secure true [auth.anonymous] enabled true org_name Main Org. org_role Viewer逐项解释一下取舍逻辑。子路径部署是为了同源同源能省掉 Cookie 跨站的绝大部分麻烦如果因为架构限制必须跨域那cookie_samesite和cookie_secure这两项就必须按上面写并且确保全站 HTTPS。匿名角色锁死在Viewer这是权限最小化原则的直接体现。另外建议把用户注册、组织创建这些入口一并关掉避免匿名访问者顺着界面摸到其他功能。配置改完后重启然后用一个无痕窗口直接打开https://portal.example.com/grafana/确认匿名会话能正常进入。这一步是基线验证基线不通就不要往下走。4.3 嵌入链接拼接清单基线通了之后就该拼 URL 了。下面这张表是我实际用下来最常组合的参数可以直接对照着取用。参数作用典型写法注意事项kiosk隐藏左侧边栏与顶部导航栏kiosk不带值即可别写1kiosktv更彻底的隐藏适合大屏kiosktv各版本裁剪程度有差异theme指定主题themedark取值 light 或 darkfrom/to指定时间范围fromnow-6htonow也支持毫秒时间戳refresh自动刷新间隔refresh30s仅在 kiosk 模式下生效var-xxx传入模板变量var-instanceweb-01xxx 为变量名orgId指定组织orgId1多组织环境必填panelId单面板模式指定面板panelId2仅 d-solo 路径使用拼好的完整链接大概是这样https://portal.example.com/grafana/d/abc123/main-dashboard?orgId1kioskthemedarkfromnow-6htonowrefresh30svar-instanceweb-01建议把这个 URL 先在浏览器标签页里单独打开一次确认渲染结果符合预期再放进 iframe。因为 iframe 里的报错信息不好看单独打开排查效率高得多。4.4 前端封装与自适应处理最后一步是把它放进页面。最基础的写法iframe idgrafana-frame srchttps://portal.example.com/grafana/d/abc123/main-dashboard?orgId1kioskthemedarkrefresh30s width100% height900 frameborder0 loadinglazy /iframe这里有几个实际经验。loadinglazy值得加上尤其是门户页面里嵌了好几个 iframe 的时候能明显改善首屏加载。高度方面如果 Grafana 和主站同源理论上可以用脚本读取 iframe 内部文档高度来做自适应const frame document.getElementById(grafana-frame); frame.addEventListener(load, () { try { const doc frame.contentDocument || frame.contentWindow.document; frame.style.height doc.documentElement.scrollHeight px; } catch (err) { console.warn(跨域环境无法读取内部高度改用固定高度, err); } });但实测下来这个方案在 Grafana 上效果一般因为 Grafana 的画布内部有自己的滚动容器文档整体高度并不随图表数量线性变化。所以更稳的做法是按容器高度撑满外层给一个确定高度的容器iframe 设为height: 100%让 Grafana 自己在内部滚动。这样布局最稳定也不会出现高度抖动。主题跟随方面门户如果支持深浅色切换可以把theme参数做成动态拼接切换时重新赋值iframe.src。直接改src会触发整页重载体验上稍微重一点但胜在实现简单、不会有状态残留。5. 常见问题排查速查表嵌入这件事的报错比较集中整理成表可以大幅缩短排查时间。下面这些坑我基本都亲自踩过一遍。5.1 白屏与登录跳转类现象可能原因排查动作iframe 完全空白控制台报拒绝加载allow_embedding未开启检查配置项及环境变量覆盖反复跳登录页匿名未开或 Cookie 未带过去查auth.anonymous与cookie_samesiteHTTP 环境下开了跨站 Cookie 后失效cookie_secure与协议不匹配改用 HTTPS 或改同源部署页面能开但提示无权限org_name与实际组织名不一致在 Grafana 界面确认组织名称图表全空但界面正常数据源未连通或变量取值错误单独打开 URL 验证再查var-参数子路径部署后样式全丢root_url与serve_from_sub_path未配套检查两者是否同时配置这里单独说一下环境变量覆盖的问题。容器部署时如果同时存在 ini 文件和环境变量两者的优先级关系经常是“看起来改了但没生效”的根源。排查时最直接的办法是在 Grafana 的配置查询接口或者启动日志里确认最终生效值而不是盯着自己改的那个文件看。5.2 样式与布局类注入自定义 CSS 后没效果九成是前面提到的 gzip 问题。确认 Nginx 配置里有没有加proxy_set_header Accept-Encoding ;这一项的缺失会导致替换静默失败日志里也不会有任何提示。双滚动条是另一个高频现象。表现为外层页面一个滚动条、iframe 内部一个滚动条滚起来很别扭。根源在于外层容器高度没有锁死。解决办法是给外层容器设定明确高度并设置overflow: hidden让滚动只发生在 iframe 内部。还有一种情况是页面渲染出来了但左下角或者右上角残留一些固定定位的小元素。这类元素通常是浮动的工具栏或者提示条需要单独针对它们的选择器写隐藏规则。所以自定义 CSS 不是一劳永逸的升级之后建议实际打开页面扫一眼看有没有新的残留元素冒出来。5.3 升级与迁移后失效类升级 Grafana 之后最常见的变化是自定义 CSS 失效和嵌入行为微调。前者靠多组兜底选择器缓解后者只能靠实测确认。所以升级前建议把当前的嵌入页面截图存档升级后逐项对照。迁移场景里还有一个很典型的报错把大盘从一个 Grafana 实例导出再导入到另一个实例后打开面板提示数据源找不到。原因是导出的 JSON 里记录了数据源的唯一标识而新实例里对应的数据源标识不一样。解决办法是在导入时选择“为所有面板指定数据源”或者导入后手动在面板编辑里重新绑定一次数据源。这个坑和嵌入本身无关但嵌入的大盘如果来自迁移就一定会撞上。6. 权限收敛与个人踩坑记录6.1 匿名 Viewer 的权限到底要收到多紧开了匿名之后任何能访问到这个 URL 的人都是以Viewer身份进来的。这个角色能做什么取决于 Grafana 的默认权限设置。默认情况下Viewer 可以看到所在组织内的 dashboard 列表和文件夹结构。对于只想暴露一个大盘给外部使用的场景这显然是超出的。细化的思路是配合文件夹权限来做把要对外暴露的大盘单独放进一个文件夹然后在 Grafana 的角色权限配置里把这个文件夹的查看权限授予匿名角色其他文件夹一律不给。这样即便有人手动去改 URL 里的 dashboard 标识也访问不到别的内容。这个配置在不同版本里的入口位置不太一样有的在组织设置里有的需要走 API实际部署时按你所用版本的界面找一下。另外匿名开启的同时建议关掉用户注册入口和版本信息暴露。少暴露一点信息就少一分被针对性试探的可能。这不是过度谨慎而是运维的基本习惯——尤其是当 Grafana 的入口最终会被暴露在公司门户里访问面比原来大得多。6.2 几个反直觉的实测结论第一个结论是kiosk 模式下仍然存在一个极窄的导航入口。某些版本里鼠标移到页面左上角边缘会短暂浮出导航按钮。如果在意这个细节需要在自定义 CSS 里把对应的浮动元素也一起隐藏。这个是实际盯大屏才发现的文档里不会写。第二个结论是自定义 CSS 注入对首屏有轻微影响。因为样式是拼在 HTML 里跟着响应一起下发的理论上不会造成额外请求但如果你的代理做了复杂的替换规则响应体变大可能会有一点延迟。实际影响很小但如果对首屏时间极其敏感可以考虑把自定义样式放在独立文件里走缓存。第三个结论是同源部署几乎能消灭一半的问题。回过头看我在跨域嵌入上花的时间远超同源方案。Cookie、SameSite、协议、端口每一项都是独立的失败点。所以如果架构上还有选择余地优先把 Grafana 放在主站的子路径下哪怕多写一段反向代理配置也比后面反复调 Cookie 划算。我现在的习惯是接手嵌入需求时先问三个问题能不能同源、能不能锁Viewer角色、要不要自动刷新。这三个问题的答案基本决定了配置方案剩下的就是照表拼参数。至于自定义 CSS 那部分我会把它当成消耗品来对待——每次升级重新核对一遍不指望它跨版本长寿。这套心态摆正之后Grafana 嵌入这件事就从“玄学调试”变成了“按清单核对”稳定得多。
返回列表