prometheus-community-PushProx介绍
prometheus-community-PushProx介绍1. pushprox 应用场景2. 架构3. 部署和测试3.1. 组件3.2. proxy 对 client 进行 TLS 认证3.2.1. 生成支持 SAN 的自签名证书3.2.2. 启动 pushprox-proxy3.2.3. 配置 nginx3.2.4. 启动 client3.2.5. 启动 prometheus4. 代码走读
prometheus-community-PushProx介绍
1. pushprox 应用场景
监控系统有 pull 和 push 两种模式。pull 模式下,server 侧会主动去找各个 node 获取 metric ,主动权在 server,server需要知悉 node 的信息,可以随时调整收集哪些 node 的哪些 metric,以什么频次去收集。pull 模式比较适合数据中心的场景,prometheus是典型的 pull 模式。 push 模式是由 node 将 metric 上报给 server,node 侧需要配置要上报什么 metric,以什么频次上报给什么 server。push 模式更适合 iot 的场景,open-falcon 是 push 模式。部署监控系统时除了考虑 pull、push 的因素,还会考虑监控系统的生态,比如 k8s 环境下选择 prometheus 会带来较多的便利性。
在使用 prometheus 的情况下,由于 prometheus server 需要能够直接访问到 node。如果 node 是在 NAT 环境下,仍然需要 prometheus server 去收集这些 node 的 metric,就需要使用一些附加的方式,pushprox 就是应用于这种场景。
2. 架构
pushprox 主要由 proxy 和 client 两部分组成,proxy 负责接收来自 prometheus server 的 metric 请求,然后 proxy 将请求信息 proxy 给 client,client 找本地设备获取到 metric 后,将结果返回给 proxy。要求 proxy 部署在 prometheus 能够访问到的地方,client 则部署在需要采集的 node 所在的网络。
- 启动 proxy、client
- client 使用 http POST 调用 proxy 的 /poll 接口,这一步会建立 client 和 proxy 之间的一个长连接,这个长连接依赖 http1.1 的 KeepAlive 来实现。这条连接建立后表示 client 已经做好准备,可以获取 client 对应的 node(fqdn-x) 的 metric 了。
- prometheus 找 proxy 要 fqdn-x 的 metric
- proxy 收到请求后会将该次请求作为 第(1)步的 /poll 接口的 response 报文返回给 client
- client 收到第(3)步的 scrape 的请求后,新起一个 http 请求找 node(fqdn-x) 请求 metric
- node(fqdn-x) 将 metric 返回给 client
- client 获取到 node(fqdn-x) 的 metric 后,使用区别 poll 的另一个长连接用于 client 将 metric push 给 proxy。之后 client 有其他的 metric 需要 push 给 proxy 会复用这个长连接。所以,如果没有 第(2)步 prometheus 或其他服务来跟 proxy 要 metric,client 和 proxy 之间只会维持一个 poll 相关的长连接。当有 metric 请求后,client 和 proxy 之间会在维持一个用于 push metric 的长连接。
- proxy 收到 第(6)步 client push 的 metric 后,将 metric 返回给 prometheus
我们通常说 HTTP 是无状态的,上面说的长连接主要是居于 http1.1 的 KeepAlive 实现的类似于长连接的效果。具体的连接情况和对应的抓包如下:
# ~ netstat -natp|grep 43.243.130.151
tcp 0 0 192.168.10.212:45922 43.243.130.151:8080 ESTABLISHED 4019233/./pushprox- # 用于 poll
tcp 0 0 192.168.10.212:45930 43.243.130.151:8080 ESTABLISHED 4019233/./pushprox- # 用于 push
3. 部署和测试
3.1. 组件
- node_exporter 暴露 node 的一些基础 metric
- pushprox-client 和 node_exporter 一起部署在需要收集的 NAT 环境下的 node 上
- pushprox-proxy 和 prometheus 都部署在外网。prometheus 可以访问到 pushprox-proxy 的服务。
3.2. proxy 对 client 进行 TLS 认证
由于目前 proxy 和 client 之间没有做认证,client 所在 ip 不确定,proxy 无法针对 client 配置防火墙策略,需要增加 proxy 和 client 之间的认证。
使用 tls 来对 client 进行校验。pushprox-proxy 不支持 tls,需要 nginx 作为反向代理到 pushprox-proxy,然后 nginx 上配置 tls,并且在 nginx 上开启 ssl_verify_client 。pushprox-client 支持 tls 客户端连接到 nginx 。pushprox-client 要求 tls 要支持 SAN(Subject Alternative Name) 是SSL 标准x509 中定义的一个扩展)。所以生成自签名证书的时候需要支持 SAN 。
3.2.1. 生成支持 SAN 的自签名证书
参考 https://github.com/ljq/gen-tlsv3-san-ca 来生成自签名证书
3.2.2. 启动 pushprox-proxy
pushprox-proxy 需要部署在公网的服务器上,而且 pushprox-proxy 要跟每个 client 保持一个长连接,如果 client 比较多, proxy 要考虑负载均衡。
./pushprox-proxy
3.2.3. 配置 nginx
nginx 反向代理到 pushprox-proxy, nginx 上配置 tls
pushprox.conf
upstream prox-backend {
server 127.0.0.1:8080;
}
server {
listen 443 ssl;
server_name promprox.xk.com;
ssl on;
ssl_certificate /opt/xk-self-cert/server.crt;#配置证书位置
ssl_certificate_key /opt/xk-self-cert/server.key;#配置秘钥位置
ssl_client_certificate /opt/xk-self-cert/ca.crt;#双向认证
ssl_verify_client on; #双向认证
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; #按照这个协议配置
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; #按照这个套件配置
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://prox-backend/;
}
}
3.2.4. 启动 client
pushprox-client 要配置自签名证书中的 ca 和 client crt
./pushprox-client --proxy-url=https://promprox.xk.com --tls.cacert=/opt/xk-self-cert/ca.crt --tls.cert=/opt/xk-self-cert/client.crt --tls.key=/opt/xk-self-cert/client.key
3.2.5. 启动 prometheus
./prometheus --config.file=prometheus-prox.yml
# prometheus-prox.yml
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
- job_name: node
proxy_url: http://43.243.130.151:8080/ # 配置为 proxy 的地址
static_configs:
- targets: ['ceph-mon2:9100'] # Presuming the FQDN of the client is "client".
4. 代码走读
4.1. 代码目录
代码地址: https://github.com/prometheus-community/PushProx
主要的代码都在 cmd 目录下:
├── client
│ ├── main.go
│ └── main_test.go
└── proxy
├── coordinator.go
└── main.go
4.2. client 的代码
代码流转:
- main()
- Coordinator.loop(),这里会循环执行 Coordinator.doPoll,如果调用失败使用 backoff 策略来确定失败后再次调用 doPoll 的频率
- Coordinator.doPoll,doPoll 是使用 http POST 调用 proxy 的 poll 接口,如果没有收到 proxy 的 response 报文,doPoll 会 hung 在这边等 proxy 给 response。如果收到 proxy 的 response 报文,表名有 scrape metric 的任务,会新起一个携程执行 Coordinator.doScrape
- Coordinator.doScrape 会找 node(fqdn-x) 要到 metric,然后调用 proxy 的 push 接口将 metric push 给 proxy
4.3. proxy 的代码
代码流转:
- main()
- newHTTPHandler(),这个是处理的路口,http server 的路由在这里配置,执行 poll,push 等接口对应的 handler 函数。proxy 还暴露了一些关于接口调用的 metric,比如pushprox_http_requests_total,pushproxy_proxied_requests_total,在 newHTTPHandler 里面使用了 promhttp.InstrumentHandlerCounter 这样的方式作为 http handlerFunc 的 middleware 在实际的 handler 函数前后插入了一些 metric 打点的逻辑。
- httpHandler.handlePoll 用于处理 poll 请求
5. proxy 和 client 之间如何复用 TCP 连接
pushprox 是如何处理 client 和 proxy 的连接,来满足 prometheus metric 获取的低耗时以及一台 proxy 能够支持尽量多的 client。应该说 pushprox 仅仅使用 http 协议的交互来达到 client 和 proxy 之间长连接的效果,然后多个请求复用同一个 tcp 连接,还是可以借鉴的,因为实现起来非常容易。
原本一直认为 HTTP 是无状态的,所以天生就应该是短连接(这边的短连接是指 client 发一个 request 给 server,server 回应一个 response,然后连接关闭,下次再要通信就在发起一个 tcp 连接)的。目前 pushprox 这个 client 和 proxy 的依赖于 http1.1 的长连接使用起来还是比较稳定的,资源消耗也挺小。
5.1 http keepalive
keepalive 在 http 1.0(rfc1945)中默认是关闭的,需要在http头加入"Connection: Keep-Alive",才能启用Keep-Alive;http 1.1(rfc2068)中默认启用Keep-Alive,如果加入"Connection: close ",才关闭。
使用 keepalive 的好处在 rfc2068 有说明,比如请求一个网页,可能需要有图片、css等多个文件请求,放在一个 tcp 连接里面可以提升效率。
那么如果启用了 keepalive,server 和 client 之前如何知道什么时候该关闭请求呢?比如,client 就找 server 要资源A,然后结束,server 给了 client response 报文后,如果 server 不关闭连接,client 如何知道已经收到了 server 关于资源A的所有内容更应该要关闭连接了呢?目前主要是两种情况:
- 如果资源A是静态资源而且比较小能够一次响应给 client,通常 server 侧会在 response 报文的 header 添加 Conent-Length,用来表示 response 报文的大小,client 收到该大小的 response 后就知道已经收到所有内容,可以主动关闭 tcp 连接。
- 如果资源A是动态的或者比较大,需要多次分段给 client,这种情况下通常使用 chunk 模式。server 将 response 报文的 “Transfer-Encoding: chunked”,chunk 编码将数据分成一块一块的发送,最后一块由一个标明长度为0的chunk标示结束。关于 chunk 模式可以参考 RFC 2616 。
5.2 golang http server 和 client 之间的 keepalive
我原本以为 golang 实现 http server 和 client 的长连接需要在 client 侧设置 http.Transport 中 DialContext 的 KeepAlive 等相关的值,但是使用 gin 作为 http server,默认的 http client,很容易就能实现这个长连接的效果。
5.2.1 gin http server
func main() {
r := gin.Default()
r.POST("/echo", func(c *gin.Context) {
body, _ := ioutil.ReadAll(c.Request.Body)
fmt.Println("---body/--- \r\n " + string(body))
time.Sleep(1000000000 * time.Second) # hung 住请求
c.JSON(200, gin.H{
"msg": "ok",
})
})
r.Run(":8282") // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
5.2.2 http client
var server = flag.String("server", "http://127.0.0.1:8282/echo", "http server url")
func init() {
flag.Parse()
}
func main() {
client := &http.Client{}
doPost(client)
}
func doPost(client *http.Client) {
resp, _ := client.Post(*server, "application/x-www-form-urlencoded", strings.NewReader("name=moon"))
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
5.2.3 gin http server 和 http client 抓包
从上面这张抓包的图可以看出来 http1.1 是如何来实现 keepalive 的,因为 server 侧一直没有给 response 报文,所以 client 一直到等报文,但是这个 tcp 连接并没有超时,依赖的是 tcp 的 keepalive 报文,基本上是每 15s,处在应用层的 http1.1 协议会为了维持住这个 tcp 连接,server 侧会给 client 发 tcp keepalive 报文,这个 15s 是要参考服务器上关于 tcp 连接超时的配置的。
6. 参考
- https://github.com/prometheus-community/PushProx
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)