diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5f2e8ac --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:alpine as build + +WORKDIR /build + +RUN apk add --no-cache git + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 go build -o keepalived_exporter -ldflags "-s -w" + + +FROM scratch + +COPY --from=build /build/keepalived_exporter /keepalived_exporter +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +USER 65534 +EXPOSE 9650 + +ENTRYPOINT ["/keepalived_exporter"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9084c58 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Keepalived Prometheus Exporter + +Prometheus exporter for [Keepalived](https://keepalived.org) metrics. + +**Note:** Requirement is to have Keepalived compiled with `--enable-json` configure option. + +### Installation + + go get -u github.com/gen2brain/keepalived_exporter + +### Usage + +Name | Description +-------------------|------------ +web.listen-address | Address to listen on for web interface and telemetry, defaults to `:9650`. +web.telemetry-path | Path under which to expose metrics, defaults to `/metrics`. +version | Display version information + +### Metrics + +| Counters | Notes +|-------------------------------------|------------------------------------------------ +| keepalived_vrrp_advert_rcvd | Advertisements received +| keepalived_vrrp_advert_sent | Advertisements sent +| keepalived_vrrp_become_master | Became master +| keepalived_vrrp_release_master | Released master +| keepalived_vrrp_packet_len_err | Packet length errors +| keepalived_vrrp_advert_interval_err | Advertisement interval errors +| keepalived_vrrp_ip_ttl_err | TTL errors +| keepalived_vrrp_invalid_type_rcvd | Invalid type errors +| keepalived_vrrp_addr_list_err | Address list errors +| keepalived_vrrp_invalid_authtype | Authentication invalid +| keepalived_vrrp_authtype_mismatch | Authentication mismatch +| keepalived_vrrp_auth_failure | Authentication failure +| keepalived_vrrp_pri_zero_rcvd | Priority zero received +| keepalived_vrrp_pri_zero_sent | Priority zero sent +| keepalived_lvs_vip_in_packets | VIP in packets +| keepalived_lvs_vip_out_packets | VIP out packets +| keepalived_lvs_vip_in_bytes | VIP in bytes +| keepalived_lvs_vip_out_bytes | VIP out bytes +| keepalived_lvs_vip_conn | VIP connections +| keepalived_lvs_rs_in_packets | RS in packets +| keepalived_lvs_rs_out_packets | RS out packets +| keepalived_lvs_rs_in_bytes | RS in bytes +| keepalived_lvs_rs_out_bytes | RS out bytes +| keepalived_lvs_rs_conn | RS connections diff --git a/collector/collector.go b/collector/collector.go new file mode 100644 index 0000000..41f6da1 --- /dev/null +++ b/collector/collector.go @@ -0,0 +1,288 @@ +package collector + +import ( + "encoding/json" + "errors" + "log" + "os" + "strconv" + "sync" + "syscall" + "time" + + "github.com/mqliang/libipvs" + "github.com/prometheus/client_golang/prometheus" + "github.com/shirou/gopsutil/process" +) + +// Signals. +const ( + SIGRTMIN = syscall.Signal(32) + SIGJSON = syscall.Signal(SIGRTMIN + 4) +) + +// KAStats type. +type KAStats struct { + Data Data `json:"data"` + Stats Stats `json:"stats"` +} + +// Data type. +type Data struct { + Iname string `json:"iname"` + DontTrackPrimary int `json:"dont_track_primary"` + SkipCheckAdvAddr int `json:"skip_check_adv_addr"` + StrictMode int `json:"strict_mode"` + VmacIfname string `json:"vmac_ifname"` + IfpIfname string `json:"ifp_ifname"` + MasterPriority int `json:"master_priority"` + LastTransition float64 `json:"last_transition"` + GarpDelay int `json:"garp_delay"` + GarpRefresh int `json:"garp_refresh"` + GarpRep int `json:"garp_rep"` + GarpRefreshRep int `json:"garp_refresh_rep"` + GarpLowerPrioDelay int `json:"garp_lower_prio_delay"` + GarpLowerPrioRep int `json:"garp_lower_prio_rep"` + LowerPrioNoAdvert int `json:"lower_prio_no_advert"` + HigherPrioSendAdvert int `json:"higher_prio_send_advert"` + Vrid int `json:"vrid"` + BasePriority int `json:"base_priority"` + EffectivePriority int `json:"effective_priority"` + Vipset bool `json:"vipset"` + PromoteSecondaries bool `json:"promote_secondaries"` + AdverInt int `json:"adver_int"` + MasterAdverInt int `json:"master_adver_int"` + Accept int `json:"accept"` + Nopreempt bool `json:"nopreempt"` + PreemptDelay int `json:"preempt_delay"` + State int `json:"state"` + Wantstate int `json:"wantstate"` + Version int `json:"version"` + SMTPAlert bool `json:"smtp_alert"` + Vips []string `json:"vips"` + AuthType int `json:"auth_type"` + AuthData string `json:"auth_data"` +} + +// Stats type. +type Stats struct { + AdvertRcvd int `json:"advert_rcvd"` + AdvertSent int `json:"advert_sent"` + BecomeMaster int `json:"become_master"` + ReleaseMaster int `json:"release_master"` + PacketLenErr int `json:"packet_len_err"` + AdvertIntervalErr int `json:"advert_interval_err"` + IPTTLErr int `json:"ip_ttl_err"` + InvalidTypeRcvd int `json:"invalid_type_rcvd"` + AddrListErr int `json:"addr_list_err"` + InvalidAuthtype int `json:"invalid_authtype"` + AuthtypeMismatch int `json:"authtype_mismatch"` + AuthFailure int `json:"auth_failure"` + PriZeroRcvd int `json:"pri_zero_rcvd"` + PriZeroSent int `json:"pri_zero_sent"` +} + +// KACollector type. +type KACollector struct { + metrics map[string]*prometheus.Desc + handle libipvs.IPVSHandle + mutex sync.Mutex +} + +// NewKACollector creates an KACollector. +func NewKACollector() (*KACollector, error) { + coll := &KACollector{} + + labelsVrrp := []string{"name", "intf", "vrid"} + metrics := map[string]*prometheus.Desc{ + "keepalived_up": prometheus.NewDesc("keepalived_up", "Status", nil, nil), + "keepalived_vrrp_advert_rcvd": prometheus.NewDesc("keepalived_vrrp_advert_rcvd", "Advertisements received", labelsVrrp, nil), + "keepalived_vrrp_advert_sent": prometheus.NewDesc("keepalived_vrrp_advert_sent", "Advertisements sent", labelsVrrp, nil), + "keepalived_vrrp_become_master": prometheus.NewDesc("keepalived_vrrp_become_master", "Became master", labelsVrrp, nil), + "keepalived_vrrp_release_master": prometheus.NewDesc("keepalived_vrrp_release_master", "Released master", labelsVrrp, nil), + "keepalived_vrrp_packet_len_err": prometheus.NewDesc("keepalived_vrrp_packet_len_err", "Packet length errors", labelsVrrp, nil), + "keepalived_vrrp_advert_interval_err": prometheus.NewDesc("keepalived_vrrp_advert_interval_err", "Advertisement interval errors", labelsVrrp, nil), + "keepalived_vrrp_ip_ttl_err": prometheus.NewDesc("keepalived_vrrp_ip_ttl_err", "TTL errors", labelsVrrp, nil), + "keepalived_vrrp_invalid_type_rcvd": prometheus.NewDesc("keepalived_vrrp_invalid_type_rcvd", "Invalid type errors", labelsVrrp, nil), + "keepalived_vrrp_addr_list_err": prometheus.NewDesc("keepalived_vrrp_addr_list_err", "Address list errors", labelsVrrp, nil), + "keepalived_vrrp_invalid_authtype": prometheus.NewDesc("keepalived_vrrp_invalid_authtype", "Authentication invalid", labelsVrrp, nil), + "keepalived_vrrp_authtype_mismatch": prometheus.NewDesc("keepalived_vrrp_authtype_mismatch", "Authentication mismatch", labelsVrrp, nil), + "keepalived_vrrp_auth_failure": prometheus.NewDesc("keepalived_vrrp_auth_failure", "Authentication failure", labelsVrrp, nil), + "keepalived_vrrp_pri_zero_rcvd": prometheus.NewDesc("keepalived_vrrp_pri_zero_rcvd", "Priority zero received", labelsVrrp, nil), + "keepalived_vrrp_pri_zero_sent": prometheus.NewDesc("keepalived_vrrp_pri_zero_sent", "Priority zero sent", labelsVrrp, nil), + } + + handle, err := libipvs.New() + if err != nil { + return coll, err + } + + labelsLVS := []string{"addr", "proto"} + metrics["keepalived_lvs_vip_in_packets"] = prometheus.NewDesc("keepalived_lvs_vip_in_packets", "VIP in packets", labelsLVS, nil) + metrics["keepalived_lvs_vip_out_packets"] = prometheus.NewDesc("keepalived_lvs_vip_out_packets", "VIP out packets", labelsLVS, nil) + metrics["keepalived_lvs_vip_in_bytes"] = prometheus.NewDesc("keepalived_lvs_vip_in_bytes", "VIP in bytes", labelsLVS, nil) + metrics["keepalived_lvs_vip_out_bytes"] = prometheus.NewDesc("keepalived_lvs_vip_out_bytes", "VIP out bytes", labelsLVS, nil) + metrics["keepalived_lvs_vip_conn"] = prometheus.NewDesc("keepalived_lvs_vip_conn", "VIP connections", labelsLVS, nil) + metrics["keepalived_lvs_rs_in_packets"] = prometheus.NewDesc("keepalived_lvs_rs_in_packets", "RS in packets", labelsLVS, nil) + metrics["keepalived_lvs_rs_out_packets"] = prometheus.NewDesc("keepalived_lvs_rs_out_packets", "RS out packets", labelsLVS, nil) + metrics["keepalived_lvs_rs_in_bytes"] = prometheus.NewDesc("keepalived_lvs_rs_in_bytes", "RS in bytes", labelsLVS, nil) + metrics["keepalived_lvs_rs_out_bytes"] = prometheus.NewDesc("keepalived_lvs_rs_out_bytes", "RS out bytes", labelsLVS, nil) + metrics["keepalived_lvs_rs_conn"] = prometheus.NewDesc("keepalived_lvs_rs_conn", "RS connections", labelsLVS, nil) + + coll.handle = handle + coll.metrics = metrics + + return coll, nil +} + +// Describe outputs metrics descriptions. +func (k *KACollector) Describe(ch chan<- *prometheus.Desc) { + for _, m := range k.metrics { + ch <- m + } +} + +// Collect fetches metrics from and sends them to the provided channel. +func (k *KACollector) Collect(ch chan<- prometheus.Metric) { + k.mutex.Lock() + defer k.mutex.Unlock() + + err := k.signal() + if err != nil { + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_up"], prometheus.GaugeValue, 0) + log.Printf("keepalived_exporter: %v", err) + return + } + + stats := make([]KAStats, 0) + + f, err := os.Open("/tmp/keepalived.json") + if err != nil { + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_up"], prometheus.GaugeValue, 0) + log.Printf("keepalived_exporter: %v", err) + return + } + + decoder := json.NewDecoder(f) + + err = decoder.Decode(&stats) + if err != nil { + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_up"], prometheus.GaugeValue, 0) + log.Printf("keepalived_exporter: %v", err) + return + } + + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_up"], prometheus.GaugeValue, 1) + + for _, st := range stats { + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_advert_rcvd"], prometheus.CounterValue, + float64(st.Stats.AdvertRcvd), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_advert_sent"], prometheus.CounterValue, + float64(st.Stats.AdvertSent), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_become_master"], prometheus.CounterValue, + float64(st.Stats.BecomeMaster), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_release_master"], prometheus.CounterValue, + float64(st.Stats.ReleaseMaster), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_packet_len_err"], prometheus.CounterValue, + float64(st.Stats.PacketLenErr), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_advert_interval_err"], prometheus.CounterValue, + float64(st.Stats.AdvertIntervalErr), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_ip_ttl_err"], prometheus.CounterValue, + float64(st.Stats.AdvertIntervalErr), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_invalid_type_rcvd"], prometheus.CounterValue, + float64(st.Stats.InvalidTypeRcvd), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_addr_list_err"], prometheus.CounterValue, + float64(st.Stats.AddrListErr), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_invalid_authtype"], prometheus.CounterValue, + float64(st.Stats.InvalidAuthtype), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_authtype_mismatch"], prometheus.CounterValue, + float64(st.Stats.AuthtypeMismatch), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_auth_failure"], prometheus.CounterValue, + float64(st.Stats.AuthFailure), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_pri_zero_rcvd"], prometheus.CounterValue, + float64(st.Stats.PriZeroRcvd), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_vrrp_pri_zero_sent"], prometheus.CounterValue, + float64(st.Stats.PriZeroSent), st.Data.Iname, st.Data.IfpIfname, strconv.Itoa(st.Data.Vrid)) + } + + svcs, err := k.handle.ListServices() + if err != nil { + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_up"], prometheus.GaugeValue, 0) + log.Printf("keepalived_exporter: %v", err) + return + } + + for _, s := range svcs { + dsts, err := k.handle.ListDestinations(s) + if err != nil { + log.Printf("keepalived_exporter: %v", err) + continue + } + + addr := s.Address.String() + ":" + strconv.Itoa(int(s.Port)) + + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_vip_in_packets"], prometheus.CounterValue, + float64(s.Stats.PacketsIn), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_vip_out_packets"], prometheus.CounterValue, + float64(s.Stats.PacketsOut), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_vip_in_bytes"], prometheus.CounterValue, + float64(s.Stats.BytesIn), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_vip_out_bytes"], prometheus.CounterValue, + float64(s.Stats.BytesOut), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_vip_conn"], prometheus.CounterValue, + float64(s.Stats.Connections), addr, s.Protocol.String()) + + for _, d := range dsts { + addr := d.Address.String() + ":" + strconv.Itoa(int(d.Port)) + + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_rs_in_packets"], prometheus.CounterValue, + float64(d.Stats.PacketsIn), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_rs_out_packets"], prometheus.CounterValue, + float64(d.Stats.PacketsOut), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_rs_in_bytes"], prometheus.CounterValue, + float64(d.Stats.BytesIn), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_rs_out_bytes"], prometheus.CounterValue, + float64(d.Stats.BytesOut), addr, s.Protocol.String()) + ch <- prometheus.MustNewConstMetric(k.metrics["keepalived_lvs_rs_conn"], prometheus.CounterValue, + float64(d.Stats.Connections), addr, s.Protocol.String()) + } + } +} + +// signal sends `SIGJSON` signal to keepalived process. +func (k *KACollector) signal() error { + ps, err := process.Processes() + if err != nil { + return err + } + + var pid int32 + for _, p := range ps { + name, err := p.Name() + if err != nil { + return err + } + + if name == "keepalived" { + pid = p.Pid + break + } + } + + if pid == 0 { + return errors.New("cannot find pid") + } + + proc, err := os.FindProcess(int(pid)) + if err != nil { + return err + } + + err = proc.Signal(SIGJSON) + if err != nil { + return err + } + + time.Sleep(10 * time.Millisecond) + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d65da1d --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/gen2brain/keepalived_exporter + +require ( + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/hkwi/nlgo v0.0.0-20170629055117-dbae43f4fc47 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/mqliang/libipvs v0.0.0-20181031074626-20f197c976a3 + github.com/prometheus/client_golang v1.1.0 + github.com/shirou/gopsutil v2.18.12+incompatible + github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..626a314 --- /dev/null +++ b/go.sum @@ -0,0 +1,83 @@ +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hkwi/nlgo v0.0.0-20170629055117-dbae43f4fc47 h1:kU+Nzavrq68kI5tezibMtT+xAvbcpT9e2ZHvK29B4qg= +github.com/hkwi/nlgo v0.0.0-20170629055117-dbae43f4fc47/go.mod h1:z3bABi4Q8MMLVkhLL04UCskrf2E443dPg9r6emBerDE= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mqliang/libipvs v0.0.0-20181031074626-20f197c976a3 h1:Cm3Te7sX6c6YuOwyFRDCCMHNXoi3+kLPuktiR+VImzw= +github.com/mqliang/libipvs v0.0.0-20181031074626-20f197c976a3/go.mod h1:lfiN9zq64J2aD2LoXQ9ha35B1PTJ1pH8NXb3GXUMFWA= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= +github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a99a487 --- /dev/null +++ b/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "flag" + "log" + "net" + "net/http" + "os" + "path/filepath" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/gen2brain/keepalived_exporter/collector" +) + +var version, commit, date string + +func main() { + listenAddr := flag.String("web.listen-address", ":9650", "Address to listen on for web interface and telemetry.") + metricsPath := flag.String("web.telemetry-path", "/metrics", "A path under which to expose metrics.") + appVersion := flag.Bool("version", false, "Display version information") + + flag.Parse() + + if *appVersion { + println(filepath.Base(os.Args[0]), version, commit, date) + os.Exit(0) + } + + registry := prometheus.NewRegistry() + + if coll, err := collector.NewKACollector(); err == nil { + registry.MustRegister(coll) + } else { + log.Fatal(err) + } + + http.Handle(*metricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(` + Keepalived Exporter + +

Keepalived Exporter

+

Metrics

+ + `)) + }) + + srv := &http.Server{} + listener, err := net.Listen("tcp4", *listenAddr) + if err != nil { + log.Fatal(err) + } + + log.Printf("Providing metrics at %s%s", *listenAddr, *metricsPath) + log.Fatal(srv.Serve(listener)) +}