在反向代理后面运行Grafana¶
我在 使用Helm 3在Kubernetes集群部署Prometheus和Grafana 为了快速解决服务输出,采用了 NodePort
模式,此时在节点输出的端口是随机(可以指定,但是范围限定为 30000–32767
)。为了方便用户访问,在主机上配置 Nginx反向代理 以便用户能够以众所周知的固定服务端口访问。
/etc/nginx/sites-enabled
目录下软连接/etc/nginx/sites-availabled
目录下的3个自定义配置文件:alertmanager grafana prometheus
alertmanager
配置文件内容:
upstream alertmanager {
server 192.168.6.115:30903;
}
server {
listen 9093;
#listen [::]:80;
server_name alertmanager alertmanager.cloud-atlas.io;
location / {
proxy_set_header Host $http_host;
proxy_pass http://alertmanager;
}
}
upstream grafana {
server 192.168.6.115:30080;
}
server {
listen 80;
#listen [::]:80;
server_name grafana grafana.cloud-atlas.io;
location / {
proxy_set_header Host $http_host;
proxy_pass http://grafana;
}
}
upstream prometheus {
server 192.168.6.115:30090;
}
server {
listen 9092;
#listen [::]:80;
server_name prometheus prometheus.cloud-atlas.io;
location / {
proxy_set_header Host $http_host;
proxy_pass http://prometheus;
}
}
备注
虽然 kube-prometheus-stack
的 values.yaml
对于配置域名方式访问 Grafana 需要配置 domain
(见下文)。但是我实践发现,对于 Nginx反向代理 ,仅参考 Run Grafana behind a reverse proxy 完成 Nginx反向代理 配置就可以以域名访问Grafana面板。
成功
9090
端口绑定错误¶
最初我按照 Prometheus监控 默认对外服务端口 9090
来配置 Nginx ,但是启动失败 journalctl -xeu nginx.service
检查服务器启动日志:
Apr 21 22:47:04 zcloud.staging.huatai.me nginx[26271]: nginx: [emerg] bind() to 0.0.0.0:9090 failed (98: Unknown error)
...
Apr 21 22:47:07 zcloud.staging.huatai.me nginx[26271]: nginx: [emerg] still could not bind()
Apr 21 22:47:07 zcloud.staging.huatai.me systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Subject: Unit process exited
通过 lsof | grep 9090
可以看到是 Systemd进程管理器 绑定了 9090
端口:
systemd 1 root 56u IPv6 41495 0t0 TCP *:9090 (LISTEN)
然后通过 grep -R 9090 /etc/systemd/*
可以看到该端口默认被 Cockpit服务器统一管理平台 占用:
system/sockets.target.wants/cockpit.socket:ListenStream=9090
所以将 Prometheus监控 端口修订为 9092
差异对比¶
但是我线下测试环境验证是通过的,所以考虑生产和线下环境的差异:
线上生产环境采用了
2次
反向代理,也就是在我的 Nginx反向代理 前面还有一层 NGINX 做反向代理(不是我的管理范围,所以我无法检查具体配置)生产环境的第一层NGINX反向代理启用了 SSL ,也就是第一层反向代理到我的第二层NGINX反向代理,我的第二层NGINX反向代理是没有SSL加密的
怀疑第一层反向代理的HTTP头部改写可能触发了Grafana的安全限制
验证思路:
将二层反向代理改为 iptables端口转发(port forwarding) ,然后直接本地绑定
/etc/hosts
以域名方式访问,验证是否实现可以域名形式访问GrafanaNodePort
模式(注意,没有配置Grafana的grafana.ini
的domain
)将二层反向代理改为 iptables端口转发(port forwarding) ,这样看第一层反向代理是否可以实现域名方式访问 Grafana(其实现在就是 iptables端口转发(port forwarding) 访问
NodePort
模式,只是没有经过第一层反向代理的域名访问方式而是使用IP访问)本地通过
/etc/hosts
绑定域名解析,绕过第一层NGINX反向代理,直接访问第二层反向代理(第二层由 iptables端口转发(port forwarding) 改为 Nginx反向代理 ),看能否实现域名方式访问 Grafana (类似我在线下测试环境部署)在二层反向代理配置
proxy_hide_header X-Frame-Options;
通过 curl检查HTTP请求头部 对比检查上述访问的差异
验证一¶
在物理主机主机上采用 iptables端口转发(port forwarding) ,本地绑定 /etc/hosts
以域名方式访问:
验证成功,可以直接使用域名访问 Grafana
NodePort
模式(没有配置Grafana的grafana.ini
的domain
)
验证二¶
在物理主机上保持 iptables端口转发(port forwarding) ,然后去除本地绑定 /etc/hosts
,这样客户端访问就会通过域名直接访问第一层反向代理,通过反向代理访问 iptables端口转发(port forwarding) 来访问 Grafana NodePort
:
第一层反向代理采用了SSL卸载,也就是强制转跳https,然后反向代理到后端物理主机 iptables端口转发(port forwarding) 80 端口,来访问 Grafana NodePort
果然,出问题在这里,此时再重现了 401 Unauthroized
报错
检查
grafana
日志:kubectl -n prometheus logs kube-prometheus-stack-1681228346-grafana-849b55868d-j62r9 -c grafana --follow
可以看到:
logger=http.server t=2023-04-22T13:15:36.598575746Z level=info msg="Successful Login" User=admin@localhost
logger=context userId=1 orgId=1 uname=admin t=2023-04-22T13:15:37.039013543Z level=info msg="Request Completed" method=GET path=/api/live/ws status=-1 remote_addr=192.168.147.143 time_ms=0 duration=739.195µs size=0 referer= handler=/api/live/ws
logger=context userId=0 orgId=0 uname= t=2023-04-22T13:15:37.084924294Z level=info msg="Request Completed" method=GET path=/api/login/ping status=401 remote_addr=192.168.147.143 time_ms=0 duration=374.008µs size=26 referer=https://grafana.cloud-atlas.io/ handler=/api/login/ping
logger=context userId=0 orgId=0 uname= t=2023-04-22T13:15:37.182058314Z level=info msg="Request Completed" method=GET path=/ status=302 remote_addr=192.168.147.143 time_ms=0 duration=348.336µs size=29 referer=https://grafana.cloud-atlas.io/ handler=/
注意到 grafana
记录了 admin@localhost
登陆成功,但是 https 域名是否会和 Grafana 自身的 80 端口不一致而导致异常呢?
果然,在 Grafana / HTTPS / Nginx Proxy 帖子中提到了修订 grafana.ini
配置,在 grafana
端依然是 protocol = http
,但是要修改 root_url = https://my.domain.name/
告知域名通过反向代理访问
对比
ConfigMap
配置:kubectl -n prometheus edit cm kube-prometheus-stack-1681228346-grafana
可以看到当前默认的 grafana
的 ConfigMap
是:
apiVersion: v1
data:
grafana.ini: |
[analytics]
check_for_updates = true
[grafana_net]
url = https://grafana.net
[log]
mode = console
[paths]
data = /var/lib/grafana/
logs = /var/log/grafana
plugins = /var/lib/grafana/plugins
provisioning = /etc/grafana/provisioning
[server]
domain = ''
kind: ConfigMap
登陆到容器内部检查:
kubectl -n prometheus exec -it kube-prometheus-stack-1681228346-grafana-849b55868d-j62r9 -c grafana -- /bin/bash
可以看到 /etc/grafana/grafana.ini
内容和 ConfigMap
对应:
[analytics]
check_for_updates = true
[grafana_net]
url = https://grafana.net
[log]
mode = console
[paths]
data = /var/lib/grafana/
logs = /var/log/grafana
plugins = /var/lib/grafana/plugins
provisioning = /etc/grafana/provisioning
[server]
domain = ''
修订
ConfigMap
配置:
apiVersion: v1
data:
grafana.ini: |
[analytics]
check_for_updates = true
[grafana_net]
url = https://grafana.net
[log]
mode = console
[paths]
data = /var/lib/grafana/
logs = /var/log/grafana
plugins = /var/lib/grafana/plugins
provisioning = /etc/grafana/provisioning
[server]
domain = grafana.cloud-atlas.io
protocol = http
enforce_domain = true
root_url = https://grafana.cloud-atlas.io/
kind: ConfigMap
备注
如果配置了 enforce_domain = true
,就会强制使用域名访问,即使你使用了IP地址访问,也会重定向到域名
但是,我发现还是没有解决我的这个生产环境问题,依然报错账号密码错误
对比了线上另外一个正常访问的grafana,在容器中
/etc/grafana/grafana.ini
有一项允许嵌入:[security] allow_embedding = true
我尝试添加也没有解决登陆(允许嵌入是不是降低了安全要求?)
诡异的是,用户账号密码正确,而且也有一瞬间能够正常登陆,但是立即退出系统
乌龙了¶
我发现原来是一个业务流程申请错误导致的:
我们在两个集群分别部署了
kube-prometheus-stack
,公司的负载均衡申请流程非常繁杂,完全是一个黑盒: 需要按照应用绑定,我现在回想起来,实际上就是按照 应用名 构建了 Nginx反向代理 的upstream
配置但是公司的负载均衡将一个简单的 Nginx反向代理 配置拆解成多个申请流程,没有任何原理说明,导致我将两个集群的
kube-prometheus-stack
申请为一个应用名,实际上反向带来会将upstream
随机发给两个集群中的两套grafana
,相互间数据踩踏
我是怎么发现这个问题的呢:
我发现有时候Grafana登陆时显示认证正确,直接登陆进去但是立即被系统推出(实际上是负载均衡恰好将认证发送到符合正确密码的那套 grafana
,但是认证后后续操作发送给错误的那套 grafana
,由于session不匹配直接被系统强制登出),我突然想到会不会搞混了后端 grafana
系统,所以在登陆的时候同时打开了两个集群的 grafana
pods日志观察,果然发现在登陆瞬间,两个不同集群的 grafana
都出现访问日志,而且从日志观察,错误接收数据的 grafana
系统出现了不应该出现的域名记录。
唉,真是一个惨痛的教训,折腾了好几天…
参考¶
Grafana 7.5.15 and 8.3.5 released with moderate severity security fixes
Origin not allowed error when reverse proxying grafana #8067
Alternative solution for “401: Unauthorized” in Grafana iframe card 在iframe card嵌入Grafana时候的解决方法讨论,思路就是不要出现跨站拒绝:
GF_AUTH_ANONYMOUS_ENABLED
或者GF_SECURITY_ALLOW_EMBEDDING
设置到Grafana配置中:- name: GF_AUTH_ANONYMOUS_ENABLED value: "true" - name: GF_DASHBOARDS_MIN_REFRESH_INTERVAL value: 30s - name: GF_DATE_FORMATS_INTERVAL_HOUR value: DD/MM HH:mm - name: GF_DATE_FORMATS_INTERVAL_DAY value: DD/MM
在NGINX反向代理中加上
proxy_hide_header X-Frame-Options;
丢弃掉
unauthorized errors when using reverse proxy with sub-path method #11757 有一些讨论