<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用 Go 開發(fā) Prometheus Exporter

          共 2156字,需瀏覽 5分鐘

           ·

          2020-11-06 03:30

          Exporter 是 Prometheus 監(jiān)控的核心,如果你遇到一些應(yīng)用不存在相應(yīng)的 Exporter,那么我們可以自己去編寫 Exporter。下面我們簡單介紹如何使用 Golang 來快速編寫一個(gè) Exporter。

          1. 安裝 GO 和依賴包

          按照 https://golang.org/doc/install 上的步驟進(jìn)行安裝配置 GO 環(huán)境,創(chuàng)建一個(gè)名為 my_first_exporter 的文件夾。

          $?go?mod?init?my_first_exporter?
          $?go?get?github.com/prometheus/client_golang?
          $?go?get?github.com/joho/godotenv
          -->?creates?go.mod?file
          -->?Installs?dependency?into?the?go.mod?file

          2. 創(chuàng)建入口點(diǎn)和導(dǎo)入依賴包

          package?main

          import?(
          ?"github.com/joho/godotenv"
          ?"github.com/prometheus/client_golang/prometheus"
          ?"github.com/prometheus/client_golang/prometheus/promhttp"
          )

          3. 創(chuàng)建 main() 函數(shù)

          func?main()

          4. 添加 prometheus metrics 端點(diǎn),并在某個(gè)服務(wù)端口上監(jiān)聽

          func?main()?{
          ???http.Handle("/metrics",?promhttp.Handler())
          ???log.Fatal(http.ListenAndServe(":9101",?nil))
          }

          5. 使用 curl 請求外部服務(wù)接口

          比如我們這里監(jiān)控的應(yīng)用程序是 MirthConnect,所以我需要進(jìn)行兩個(gè) API 接口調(diào)用:

          • 獲取 channel 統(tǒng)計(jì)數(shù)據(jù)
          • 獲取 channel id 和名稱映射
          curl?-k?--location?--request?GET?'https://apihost/api/channels/statistics'?\
          --user?admin:admin

          curl?-k?--location?--request?GET?'https://apihost/api/channels/idsAndNames'?\
          --user?admin:admin

          6. 將 curl 調(diào)用轉(zhuǎn)換為 go http 調(diào)用,并解析結(jié)果

          如果你是 Go 新手,這應(yīng)該是最困難的一步。對于我這里的例子,端點(diǎn)返回的是 XML 格式的數(shù)據(jù),所以我必須用 "encoding/xml" 包來反序列化 XML。轉(zhuǎn)換成功后意味著我的 GO 程序可以執(zhí)行和 curl 命令一樣的 API 調(diào)用。

          7. 聲明 Prometheus metrics

          在 Prometheus 中,每個(gè) metric 指標(biāo)都由以下幾個(gè)部分組成:metric name/metric label values/metric help text/metric type/measurement ,例如:

          Example:
          #?HELP?promhttp_metric_handler_requests_total?Total?number?of?scrapes?by?HTTP?status?code.
          #?TYPE?promhttp_metric_handler_requests_total?counter
          promhttp_metric_handler_requests_total{code=”200"}?1.829159e+06
          promhttp_metric_handler_requests_total{code=”500"
          }?0
          promhttp_metric_handler_requests_total{code=”503"}?0

          對于應(yīng)用 scrapers,我們將定義 Prometheus metrics 描述信息,其中包括 metric 名稱、metric label 標(biāo)簽以及 metric 幫助信息。

          messagesReceived?=?prometheus.NewDesc(
          ?prometheus.BuildFQName(namespace,?"",?"messages_received_total"),
          ?"How?many?messages?have?been?received?(per?channel).",
          ?[]string{"channel"},?nil,
          )

          8. 定義一個(gè)結(jié)構(gòu)體實(shí)現(xiàn) Prometheus 的 Collector 接口

          Prometheus 的 client 庫提供了實(shí)現(xiàn)自定義 Exportor 的接口,Collector 接口定義了兩個(gè)方法 Describe 和 Collect,實(shí)現(xiàn)這兩個(gè)方法就可以暴露自定義的數(shù)據(jù):

          • Describe(chan<- *Desc)
          • Collect(chan<- Metric)

          如下所示:

          type?Exporter?struct?{
          ?mirthEndpoint,?mirthUsername,?mirthPassword?string
          }

          func?NewExporter(mirthEndpoint?string,?mirthUsername?string,?mirthPassword?string)?*Exporter?{
          ?return?&Exporter{
          ??mirthEndpoint:?mirthEndpoint,
          ??mirthUsername:?mirthUsername,
          ??mirthPassword:?mirthPassword,
          ?}
          }
          func?(e?*Exporter)?Describe(ch?chan<-?*prometheus.Desc)?{
          }
          func?(e?*Exporter)?Collect(ch?chan<-?prometheus.Metric)?{
          }

          9. 在 Describe 函數(shù)中,把第7步的 metric 描述信息發(fā)送給它

          func?(e?*Exporter)?Describe(ch?chan<-?*prometheus.Desc)?{
          ?ch?<-?up
          ?ch?<-?messagesReceived
          ?ch?<-?messagesFiltered
          ?ch?<-?messagesQueued
          ?ch?<-?messagesSent
          ?ch?<-?messagesErrored
          }

          10. 將接口調(diào)用邏輯從第6步移到 Collect 函數(shù)中

          直接將采集的數(shù)據(jù)發(fā)送到 prometheus.Metric 通道中。

          func?(e?*Exporter)?Collect(ch?chan<-?prometheus.Metric)?{
          ?channelIdNameMap,?err?:=?e.LoadChannelIdNameMap()
          ?if?err?!=?nil?{
          ??ch?<-?prometheus.MustNewConstMetric(
          ???up,?prometheus.GaugeValue,?0,
          ??)
          ??log.Println(err)
          ??return
          ?}
          ?ch?<-?prometheus.MustNewConstMetric(
          ??up,?prometheus.GaugeValue,?1,
          ?)

          ?e.HitMirthRestApisAndUpdateMetrics(channelIdNameMap,?ch)
          }

          當(dāng)執(zhí)行 api 調(diào)用時(shí),確保使用prometheus.MustNewConstMetric(prometheus.Desc, metric type, measurement)發(fā)送測量值,如果你需要傳入額外的標(biāo)簽,可以像下面這樣在參數(shù)列表的后面加入:

          channelError,?_?:=?strconv.ParseFloat(channelStatsList.Channels[i].Error,?64)
          ch?<-?prometheus.MustNewConstMetric(
          ?messagesErrored,?prometheus.GaugeValue,?channelError,?channelName,
          )

          11. 在 main 函數(shù)中聲明 exporter

          exporter?:=?NewExporter(mirthEndpoint,?mirthUsername,?mirthPassword)
          prometheus.MustRegister(exporter)

          到這里其實(shí)這個(gè) Exporter 就可以使用了,每次訪問 metrics 路由的時(shí)候,它會執(zhí)行 api 調(diào)用,并以 Prometheus Text 文本格式返回?cái)?shù)據(jù)。下面的步驟主要是方便部署了。

          12. 將硬編碼的 api 路徑放到 flag 中

          前面我們硬編碼了好多參數(shù),比如應(yīng)用程序的網(wǎng)址、metrics 路由地址以及 exporter 端口,我們可以通過從命令行參數(shù)中來解析這些值使程序更加靈活。

          var?(
          listenAddress?=?flag.String("web.listen-address",?":9141",
          ?"Address?to?listen?on?for?telemetry")
          metricsPath?=?flag.String("web.telemetry-path",?"/metrics",
          ?"Path?under?which?to?expose?metrics")
          )
          func?main()?{
          ???flag.Parse()
          ???...
          ???http.Handle(*metricsPath,?promhttp.Handler())
          ???log.Fatal(http.ListenAndServe(*listenAddress,?nil))
          }

          13. 將憑證放入環(huán)境變量

          如果應(yīng)用端點(diǎn)改變了或者登錄憑證改變了怎么辦?我們可以從環(huán)境變量中來加載這些數(shù)據(jù),在這個(gè)例子中,我們使用 godotenv 這個(gè)包來幫助將變量值存儲在本地的一個(gè)目錄中:

          import?(
          ??"os"
          )
          func?main()?{
          ?err?:=?godotenv.Load()
          ?if?err?!=?nil?{
          ??log.Println("Error?loading?.env?file,?assume?env?variables?are?set.")
          ?}
          ?mirthEndpoint?:=?os.Getenv("MIRTH_ENDPOINT")
          ?mirthUsername?:=?os.Getenv("MIRTH_USERNAME")
          ?mirthPassword?:=?os.Getenv("MIRTH_PASSWORD")
          }

          整個(gè) Exporter 完整的代碼如下所示:

          package?main

          import?(
          ?"crypto/tls"
          ?"encoding/xml"
          ?"flag"
          ?"io/ioutil"
          ?"log"
          ?"net/http"
          ?"os"
          ?"strconv"

          ?"github.com/joho/godotenv"
          ?"github.com/prometheus/client_golang/prometheus"
          ?"github.com/prometheus/client_golang/prometheus/promhttp"
          )

          /*

          ??
          ????101af57f-f26c-40d3-86a3-309e74b93512
          ????Send-Email-Notification
          ??


          */

          type?ChannelIdNameMap?struct?{
          ?XMLName?xml.Name???????`xml:"map"`
          ?Entries?[]ChannelEntry?`xml:"entry"`
          }
          type?ChannelEntry?struct?{
          ?XMLName?xml.Name?`xml:"entry"`
          ?Values??[]string?`xml:"string"`
          }

          /*

          ??
          ????c5e6a736-0e88-46a7-bf32-5b4908c4d859
          ????101af57f-f26c-40d3-86a3-309e74b93512
          ????0
          ????0
          ????0
          ????0
          ????0
          ??


          */

          type?ChannelStatsList?struct?{
          ?XMLName??xml.Name???????`xml:"list"`
          ?Channels?[]ChannelStats?`xml:"channelStatistics"`
          }
          type?ChannelStats?struct?{
          ?XMLName???xml.Name?`xml:"channelStatistics"`
          ?ServerId??string???`xml:"serverId"`
          ?ChannelId?string???`xml:"channelId"`
          ?Received??string???`xml:"received"`
          ?Sent??????string???`xml:"sent"`
          ?Error?????string???`xml:"error"`
          ?Filtered??string???`xml:"filtered"`
          ?Queued????string???`xml:"queued"`
          }

          const?namespace?=?"mirth"
          const?channelIdNameApi?=?"/api/channels/idsAndNames"
          const?channelStatsApi?=?"/api/channels/statistics"

          var?(
          ?tr?=?&http.Transport{
          ??TLSClientConfig:?&tls.Config{InsecureSkipVerify:?true},
          ?}
          ?client?=?&http.Client{Transport:?tr}

          ?listenAddress?=?flag.String("web.listen-address",?":9141",
          ??"Address?to?listen?on?for?telemetry")
          ?metricsPath?=?flag.String("web.telemetry-path",?"/metrics",
          ??"Path?under?which?to?expose?metrics")

          ?//?Metrics
          ?up?=?prometheus.NewDesc(
          ??prometheus.BuildFQName(namespace,?"",?"up"),
          ??"Was?the?last?Mirth?query?successful.",
          ??nil,?nil,
          ?)
          ?messagesReceived?=?prometheus.NewDesc(
          ??prometheus.BuildFQName(namespace,?"",?"messages_received_total"),
          ??"How?many?messages?have?been?received?(per?channel).",
          ??[]string{"channel"},?nil,
          ?)
          ?messagesFiltered?=?prometheus.NewDesc(
          ??prometheus.BuildFQName(namespace,?"",?"messages_filtered_total"),
          ??"How?many?messages?have?been?filtered?(per?channel).",
          ??[]string{"channel"},?nil,
          ?)
          ?messagesQueued?=?prometheus.NewDesc(
          ??prometheus.BuildFQName(namespace,?"",?"messages_queued"),
          ??"How?many?messages?are?currently?queued?(per?channel).",
          ??[]string{"channel"},?nil,
          ?)
          ?messagesSent?=?prometheus.NewDesc(
          ??prometheus.BuildFQName(namespace,?"",?"messages_sent_total"),
          ??"How?many?messages?have?been?sent?(per?channel).",
          ??[]string{"channel"},?nil,
          ?)
          ?messagesErrored?=?prometheus.NewDesc(
          ??prometheus.BuildFQName(namespace,?"",?"messages_errored_total"),
          ??"How?many?messages?have?errored?(per?channel).",
          ??[]string{"channel"},?nil,
          ?)
          )

          type?Exporter?struct?{
          ?mirthEndpoint,?mirthUsername,?mirthPassword?string
          }

          func?NewExporter(mirthEndpoint?string,?mirthUsername?string,?mirthPassword?string)?*Exporter?{
          ?return?&Exporter{
          ??mirthEndpoint:?mirthEndpoint,
          ??mirthUsername:?mirthUsername,
          ??mirthPassword:?mirthPassword,
          ?}
          }

          func?(e?*Exporter)?Describe(ch?chan<-?*prometheus.Desc)?{
          ?ch?<-?up
          ?ch?<-?messagesReceived
          ?ch?<-?messagesFiltered
          ?ch?<-?messagesQueued
          ?ch?<-?messagesSent
          ?ch?<-?messagesErrored
          }

          func?(e?*Exporter)?Collect(ch?chan<-?prometheus.Metric)?{
          ?channelIdNameMap,?err?:=?e.LoadChannelIdNameMap()
          ?if?err?!=?nil?{
          ??ch?<-?prometheus.MustNewConstMetric(
          ???up,?prometheus.GaugeValue,?0,
          ??)
          ??log.Println(err)
          ??return
          ?}
          ?ch?<-?prometheus.MustNewConstMetric(
          ??up,?prometheus.GaugeValue,?1,
          ?)

          ?e.HitMirthRestApisAndUpdateMetrics(channelIdNameMap,?ch)
          }

          func?(e?*Exporter)?LoadChannelIdNameMap()?(map[string]string,?error)?{
          ?//?Create?the?map?of?channel?id?to?names
          ?channelIdNameMap?:=?make(map[string]string)

          ?req,?err?:=?http.NewRequest("GET",?e.mirthEndpoint+channelIdNameApi,?nil)
          ?if?err?!=?nil?{
          ??return?nil,?err
          ?}

          ?//?This?one?line?implements?the?authentication?required?for?the?task.
          ?req.SetBasicAuth(e.mirthUsername,?e.mirthPassword)
          ?//?Make?request?and?show?output.
          ?resp,?err?:=?client.Do(req)
          ?if?err?!=?nil?{
          ??return?nil,?err
          ?}

          ?body,?err?:=?ioutil.ReadAll(resp.Body)
          ?resp.Body.Close()
          ?if?err?!=?nil?{
          ??return?nil,?err
          ?}
          ?//?fmt.Println(string(body))

          ?//?we?initialize?our?array
          ?var?channelIdNameMapXML?ChannelIdNameMap
          ?//?we?unmarshal?our?byteArray?which?contains?our
          ?//?xmlFiles?content?into?'users'?which?we?defined?above
          ?err?=?xml.Unmarshal(body,?&channelIdNameMapXML)
          ?if?err?!=?nil?{
          ??return?nil,?err
          ?}

          ?for?i?:=?0;?i?len(channelIdNameMapXML.Entries);?i++?{
          ??channelIdNameMap[channelIdNameMapXML.Entries[i].Values[0]]?=?channelIdNameMapXML.Entries[i].Values[1]
          ?}

          ?return?channelIdNameMap,?nil
          }

          func?(e?*Exporter)?HitMirthRestApisAndUpdateMetrics(channelIdNameMap?map[string]string,?ch?chan<-?prometheus.Metric)?{
          ?//?Load?channel?stats
          ?req,?err?:=?http.NewRequest("GET",?e.mirthEndpoint+channelStatsApi,?nil)
          ?if?err?!=?nil?{
          ??log.Fatal(err)
          ?}

          ?//?This?one?line?implements?the?authentication?required?for?the?task.
          ?req.SetBasicAuth(e.mirthUsername,?e.mirthPassword)
          ?//?Make?request?and?show?output.
          ?resp,?err?:=?client.Do(req)
          ?if?err?!=?nil?{
          ??log.Fatal(err)
          ?}

          ?body,?err?:=?ioutil.ReadAll(resp.Body)
          ?resp.Body.Close()
          ?if?err?!=?nil?{
          ??log.Fatal(err)
          ?}
          ?//?fmt.Println(string(body))

          ?//?we?initialize?our?array
          ?var?channelStatsList?ChannelStatsList
          ?//?we?unmarshal?our?byteArray?which?contains?our
          ?//?xmlFiles?content?into?'users'?which?we?defined?above
          ?err?=?xml.Unmarshal(body,?&channelStatsList)
          ?if?err?!=?nil?{
          ??log.Fatal(err)
          ?}

          ?for?i?:=?0;?i?len(channelStatsList.Channels);?i++?{
          ??channelName?:=?channelIdNameMap[channelStatsList.Channels[i].ChannelId]

          ??channelReceived,?_?:=?strconv.ParseFloat(channelStatsList.Channels[i].Received,?64)
          ??ch?<-?prometheus.MustNewConstMetric(
          ???messagesReceived,?prometheus.GaugeValue,?channelReceived,?channelName,
          ??)

          ??channelSent,?_?:=?strconv.ParseFloat(channelStatsList.Channels[i].Sent,?64)
          ??ch?<-?prometheus.MustNewConstMetric(
          ???messagesSent,?prometheus.GaugeValue,?channelSent,?channelName,
          ??)

          ??channelError,?_?:=?strconv.ParseFloat(channelStatsList.Channels[i].Error,?64)
          ??ch?<-?prometheus.MustNewConstMetric(
          ???messagesErrored,?prometheus.GaugeValue,?channelError,?channelName,
          ??)

          ??channelFiltered,?_?:=?strconv.ParseFloat(channelStatsList.Channels[i].Filtered,?64)
          ??ch?<-?prometheus.MustNewConstMetric(
          ???messagesFiltered,?prometheus.GaugeValue,?channelFiltered,?channelName,
          ??)

          ??channelQueued,?_?:=?strconv.ParseFloat(channelStatsList.Channels[i].Queued,?64)
          ??ch?<-?prometheus.MustNewConstMetric(
          ???messagesQueued,?prometheus.GaugeValue,?channelQueued,?channelName,
          ??)
          ?}

          ?log.Println("Endpoint?scraped")
          }

          func?main()?{
          ?err?:=?godotenv.Load()
          ?if?err?!=?nil?{
          ??log.Println("Error?loading?.env?file,?assume?env?variables?are?set.")
          ?}

          ?flag.Parse()

          ?mirthEndpoint?:=?os.Getenv("MIRTH_ENDPOINT")
          ?mirthUsername?:=?os.Getenv("MIRTH_USERNAME")
          ?mirthPassword?:=?os.Getenv("MIRTH_PASSWORD")

          ?exporter?:=?NewExporter(mirthEndpoint,?mirthUsername,?mirthPassword)
          ?prometheus.MustRegister(exporter)

          ?http.Handle(*metricsPath,?promhttp.Handler())
          ?http.HandleFunc("/",?func(w?http.ResponseWriter,?r?*http.Request)?{
          ??w.Write([]byte(`
          ?????????????Mirth?Channel?Exporter
          ?????????????
          ?????????????

          Mirth?Channel?Exporter


          ?????????????

          Metrics


          ?????????????
          ?????????????`
          ))
          ?})
          ?log.Fatal(http.ListenAndServe(*listenAddress,?nil))
          }

          14. 編寫一個(gè) Makefile 文件,方便在不同平臺上快速構(gòu)建

          Makefile 可以讓你在開發(fā)過程中省去很多多余的操作,比如我們要構(gòu)建多個(gè)平臺的構(gòu)建程序,可以創(chuàng)建如下所示的 Makefile 文件。

          linux:
          ???GOOS=linux?GOARCH=amd64?go?build
          mac:
          ???GOOS=darwin?GOARCH=amd64?go?build

          只要調(diào)用 make macmake linux 命令就可以看到不同的可執(zhí)行文件出現(xiàn)。

          15. 編寫一個(gè) service 文件,將這個(gè) go 應(yīng)用作為守護(hù)進(jìn)程運(yùn)行

          我們可以為這個(gè) Exporter 編寫一個(gè) service 文件或者 Dockerfile 文件來管理該應(yīng)用,比如這里我們直接在 Centos 7 上用 systemd 來管理該應(yīng)用。這可以編寫一個(gè)如下所示的 service 文件:

          [Unit]
          Description=mirth?channel?exporter
          After=network.target
          StartLimitIntervalSec=0
          [Service]
          Type=simple
          Restart=always
          RestartSec=1
          WorkingDirectory=/mirth/mirthconnect
          EnvironmentFile=/etc/sysconfig/mirth_channel_exporter
          ExecStart=/mirth/mirthconnect/mirth_channel_exporter

          [Install]
          WantedBy=multi-user.target

          到這里就完成了使用 Golang 編寫一個(gè)簡單的 Prometheus Exporter。

          原文鏈接:https://medium.com/teamzerolabs/15-steps-to-write-an-application-prometheus-exporter-in-go-9746b4520e26

          ?點(diǎn)擊屏末?|??|?即刻學(xué)習(xí)

          瀏覽 103
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  东方aV成人在线看 | 婷婷综合久久五月天丁香 | 欧美插插插 | 欧美成人无码一级A片蜜芽 | 高中学生妹毛片 |