共计 3105 个字符,预计需要花费 8 分钟才能阅读完成。
前言
Grafana 是一个跨平台的开源的度量分析和可视化工具,可以通过将采集的数据查询然后可视化的展示,并及时通知。在实际的运维统一平台中,经常需要将 grafana 的图表集成进来。之所以不选择自己利用 echart 自己实现,主要有以下两个原因:
- grafana 太强大了,在曲线比较密集的时候,依旧可以保持高性能和良好的自适应。支持众多的数据源。
- 节省时间。没必要重复造轮子。
一般来说,我们的 grafana 是开启了登录验证的,尤其是对于一个企业而言,大多集成了 ldap 或是公司的统一身份体系。而集成的方法大多是通过 iframe。这种解决方案,会遇到登录验证的问题。用户浏览 grafana 仪表板页面的时候需要强制它们进行双重验证(一次用于我的网站,一次用于 grafana)。这种体验非常不好,我们希望可以只登录一次。那大家可能首先想到了 sso。对于很多开源项目来说,支持 sso,可能需要定制开发,不仅仅是 grafana。
目前支持的是两种方案:
- 一个选项是 enable anonymous access in grafana。这种可以说安全性太差了。
- 另外一个使用代理。
今天我们主要利用的是代理的思路去解决。
如何利用代理解决
我们的总体需求是集成端不需要额外做过多的工作,让代理完成 basic auth。
grafana 默认开启了 basic auth 的认证,可以通过修改 grafana.ini 配置文件实现:
#################################### Basic Auth ##########################
; [auth.basic]
; ;enabled = true
;
当然认证的用户必须是真实存在的,也就是需要提前在 grafana 中创建。这样的好处是:我们可以给该用户设置具体的权限,比如只具备浏览权限,而且可以限定只能访问某个文件夹下的图表。
此外一般来说,类似 grafana 这种公司级别的产品,不会开启外网访问。
同时,也可以在代理实现一些白名单的功能,比如办公网的网段,就更加安全了。
总体相对来说,利用代理解决 iframe 集成 grafana 强制登录两次的问题,
在安全上和操作性上,比较合适。
envoy 的具体设置
我们知道 http 的 basic auth 其实是相对简单的一种认证方式。Authorization 请求首部中,包含了用户填写的用户名、密码。
GET /protected_docs HTTP/1.1
Authorization: Basic Y2h5aW5ncDoxMjM0NTY=
其中 Y2h5aW5ncDoxMjM0NTY= 是 base64(用户名: 密码)。
envoy 并没有提供类似的 filter。但是 envoy 支持了 lua 编写 filter。所以这个问题就变得很简单了,编写 basic auth filter。此时想到 openresty。那么利用 openresty 也可以实现。
直接上 envoy.yaml:
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"
format: "[ACCESS_LOG][%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\"%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\"\"%REQ(USER-AGENT)%\"\"%REQ(X-REQUEST-ID)%\"\"%REQ(:AUTHORITY)%\"\"%UPSTREAM_HOST%\"\"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\"\n"
route_config:
name: local_route
virtual_hosts:
- name: gateway
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: grafana
retry_policy:
retry_on: "5xx"
num_retries: 2
http_filters:
- name: envoy.lua
config:
inline_code: |
function envoy_on_request(request_handle)
request_handle:headers():add("Authorization", "Basic Z2FvaGo6bG92ZioxMzE0")
end
- name: envoy.router
config: {}
clusters:
- name: grafana
connect_timeout: 0.5s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: grafana.xxx.com
port_value: 80
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 90
大家注意看 envoy.lua 处配置。
大致上利用 envoy_on_request 方法,对请求添加 Authorization 头。
详细请参考 envoy 官方 docs。
这里简单说下,其实 envoy 的 lua 插件,可以对 request 和 response 做拦截处理。如下:
-- Called on the request path.
function envoy_on_request(request_handle)
-- Wait for the entire request body and add a request header with the body size.
request_handle:headers():add("request_body_size", request_handle:body():length())
end
-- Called on the response path.
function envoy_on_response(response_handle)
-- Wait for the entire response body and a response header with the the body size.
response_handle:headers():add("response_body_size", response_handle:body():length())
-- Remove a response header named 'foo'
response_handle:headers():remove("foo")
end
那么实现其他的扩展功能,也很容易了。
感想
其实 envoy 很快将支持通过 Web Assembly 来扩展,wasm 性能更高,可以利用 rust,c,go 等语言编写逻辑,然后编译成机器码执行。届时 envoy 必将大放光彩。