add openvpn_connected_clients
+ dockerfile + minor go review
This commit is contained in:
1 parent
26d010b2f0
commit
8221d6daf9
4 files changed
+124
-32
No files matched your search
+42
@@ -0,0 +1,42 @@
|
|||||||
|
# BUILD:
|
||||||
|
# docker build --force-rm=true -t openvpn_exporter .
|
||||||
|
|
||||||
|
# RUN:
|
||||||
|
# docker run -it -p 9176:9176 -v /path/to/openvpn_server.status:/etc/openvpn_exporter/server.status openvpn_exporter
|
||||||
|
|
||||||
|
FROM golang as builder
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
RUN mkdir /go/src/app
|
||||||
|
ADD . /go/src/app
|
||||||
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
# Go dep
|
||||||
|
RUN go get -d ./...
|
||||||
|
|
||||||
|
# Build a standalone binary
|
||||||
|
RUN set -ex && \
|
||||||
|
CGO_ENABLED=0 go build \
|
||||||
|
-tags netgo \
|
||||||
|
-o /app/openvpn_exporter \
|
||||||
|
-v -a \
|
||||||
|
-ldflags '-extldflags "-static"' && \
|
||||||
|
ls
|
||||||
|
|
||||||
|
# Create the second stage with a basic image.
|
||||||
|
# this will drop any previous
|
||||||
|
# stages (defined as `FROM <some_image> as <some_name>`)
|
||||||
|
# allowing us to start with a fat build image and end up with
|
||||||
|
# a very small runtime image.
|
||||||
|
|
||||||
|
FROM busybox
|
||||||
|
|
||||||
|
# add compiled binary
|
||||||
|
COPY --from=builder /app/openvpn_exporter /openvpn_exporter
|
||||||
|
|
||||||
|
# add a default file to be processed
|
||||||
|
ADD examples/server2.status /etc/openvpn_exporter/server.status
|
||||||
|
|
||||||
|
# run
|
||||||
|
EXPOSE 9176
|
||||||
|
CMD ["/openvpn_exporter", "-openvpn.status_paths", "/etc/openvpn_exporter/server.status"]
|
||||||
@@ -18,7 +18,9 @@ exported over TCP port 9176.
|
|||||||
Please refer to this utility's `main()` function for a full list of
|
Please refer to this utility's `main()` function for a full list of
|
||||||
supported command line flags.
|
supported command line flags.
|
||||||
|
|
||||||
## Client statistics
|
## Exposed metrics example
|
||||||
|
|
||||||
|
### Client statistics
|
||||||
|
|
||||||
For clients status files, the exporter generates metrics that may look
|
For clients status files, the exporter generates metrics that may look
|
||||||
like this:
|
like this:
|
||||||
@@ -37,7 +39,7 @@ openvpn_status_update_time_seconds{status_path="..."} 1.490092749e+09
|
|||||||
openvpn_up{status_path="..."} 1
|
openvpn_up{status_path="..."} 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Server statistics
|
### Server statistics
|
||||||
|
|
||||||
For server status files (both version 2 and 3), the exporter generates
|
For server status files (both version 2 and 3), the exporter generates
|
||||||
metrics that may look like this:
|
metrics that may look like this:
|
||||||
@@ -48,4 +50,49 @@ openvpn_server_client_sent_bytes_total{common_name="...",connection_time="...",r
|
|||||||
openvpn_server_route_last_reference_time_seconds{common_name="...",real_address="...",status_path="...",virtual_address="..."} 1.493018841e+09
|
openvpn_server_route_last_reference_time_seconds{common_name="...",real_address="...",status_path="...",virtual_address="..."} 1.493018841e+09
|
||||||
openvpn_status_update_time_seconds{status_path="..."} 1.490089154e+09
|
openvpn_status_update_time_seconds{status_path="..."} 1.490089154e+09
|
||||||
openvpn_up{status_path="..."} 1
|
openvpn_up{status_path="..."} 1
|
||||||
|
openvpn_server_connected_clients 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage of openvpn_exporter:
|
||||||
|
```
|
||||||
|
-openvpn.status_paths string
|
||||||
|
Paths at which OpenVPN places its status files. (default "examples/client.status,examples/server2.status,examples/server3.status")
|
||||||
|
-web.listen-address string
|
||||||
|
Address to listen on for web interface and telemetry. (default ":9176")
|
||||||
|
-web.telemetry-path string
|
||||||
|
Path under which to expose metrics. (default "/metrics")
|
||||||
|
```
|
||||||
|
|
||||||
|
E.g:
|
||||||
|
```
|
||||||
|
openvpn_exporter -openvpn.status_paths /etc/openvpn/openvpn-status.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
Build the image:
|
||||||
|
```
|
||||||
|
docker build --force-rm=true -t openvpn_exporter .
|
||||||
|
```
|
||||||
|
|
||||||
|
The final image is around 8MB. A temporary image has been downloaded(Golang) to make the final one. Once built, this temporary image become orphan, you can delete it:
|
||||||
|
```
|
||||||
|
docker rmi -f $(docker images | grep "<none>" | awk "{print \$3}")
|
||||||
|
```
|
||||||
|
|
||||||
|
To use with docker you must mount your status file to `/etc/openvpn_exporter/server.status`.
|
||||||
|
```
|
||||||
|
docker run -it -p 9176:9176 -v /path/to/openvpn_server.status:/etc/openvpn_exporter/server.status openvpn_exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
Metrics should be available on your host IP: http://<host_ip>:9176/metrics. E.g: http://10.39.9.94:9176/metrics
|
||||||
|
|
||||||
|
|
||||||
|
## Get a standalone executable binary
|
||||||
|
|
||||||
|
Use the docker image to copy the built binary into a mounted volume
|
||||||
|
```bash
|
||||||
|
docker run -it -v /local/empty/folder:/volume openvpn_exporter cp openvpn_exporter /volume
|
||||||
```
|
```
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
docker run -i -v `pwd`:/openvpn_exporter alpine:edge /bin/sh << 'EOF'
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
# Install prerequisites for the build process.
|
|
||||||
apk update
|
|
||||||
apk add ca-certificates git go libc-dev
|
|
||||||
update-ca-certificates
|
|
||||||
|
|
||||||
# Build the openvpn_exporter.
|
|
||||||
cd /openvpn_exporter
|
|
||||||
export GOPATH=/gopath
|
|
||||||
go get -d ./...
|
|
||||||
go build --ldflags '-extldflags "-static"'
|
|
||||||
strip openvpn_exporter
|
|
||||||
EOF
|
|
||||||
+33
-13
@@ -27,6 +27,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OpenvpnServerHeader struct {
|
type OpenvpnServerHeader struct {
|
||||||
@@ -52,8 +53,12 @@ var (
|
|||||||
[]string{"status_path"}, nil)
|
[]string{"status_path"}, nil)
|
||||||
|
|
||||||
// Metrics specific to OpenVPN servers.
|
// Metrics specific to OpenVPN servers.
|
||||||
|
openvpnConnectedClientsDesc = prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName("openvpn", "", "openvpn_server_connected_clients"),
|
||||||
|
"Number Of Connected Clients", nil, nil)
|
||||||
|
|
||||||
openvpnServerHeaders = map[string]OpenvpnServerHeader{
|
openvpnServerHeaders = map[string]OpenvpnServerHeader{
|
||||||
"CLIENT_LIST": OpenvpnServerHeader{
|
"CLIENT_LIST": {
|
||||||
LabelColumns: []string{
|
LabelColumns: []string{
|
||||||
"Common Name",
|
"Common Name",
|
||||||
"Connected Since (time_t)",
|
"Connected Since (time_t)",
|
||||||
@@ -62,7 +67,7 @@ var (
|
|||||||
"Username",
|
"Username",
|
||||||
},
|
},
|
||||||
Metrics: []OpenvpnServerHeaderField{
|
Metrics: []OpenvpnServerHeaderField{
|
||||||
OpenvpnServerHeaderField{
|
{
|
||||||
Column: "Bytes Received",
|
Column: "Bytes Received",
|
||||||
Desc: prometheus.NewDesc(
|
Desc: prometheus.NewDesc(
|
||||||
prometheus.BuildFQName("openvpn", "server", "client_received_bytes_total"),
|
prometheus.BuildFQName("openvpn", "server", "client_received_bytes_total"),
|
||||||
@@ -70,7 +75,7 @@ var (
|
|||||||
[]string{"status_path", "common_name", "connection_time", "real_address", "virtual_address", "username"}, nil),
|
[]string{"status_path", "common_name", "connection_time", "real_address", "virtual_address", "username"}, nil),
|
||||||
ValueType: prometheus.CounterValue,
|
ValueType: prometheus.CounterValue,
|
||||||
},
|
},
|
||||||
OpenvpnServerHeaderField{
|
{
|
||||||
Column: "Bytes Sent",
|
Column: "Bytes Sent",
|
||||||
Desc: prometheus.NewDesc(
|
Desc: prometheus.NewDesc(
|
||||||
prometheus.BuildFQName("openvpn", "server", "client_sent_bytes_total"),
|
prometheus.BuildFQName("openvpn", "server", "client_sent_bytes_total"),
|
||||||
@@ -80,14 +85,14 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ROUTING_TABLE": OpenvpnServerHeader{
|
"ROUTING_TABLE": {
|
||||||
LabelColumns: []string{
|
LabelColumns: []string{
|
||||||
"Common Name",
|
"Common Name",
|
||||||
"Real Address",
|
"Real Address",
|
||||||
"Virtual Address",
|
"Virtual Address",
|
||||||
},
|
},
|
||||||
Metrics: []OpenvpnServerHeaderField{
|
Metrics: []OpenvpnServerHeaderField{
|
||||||
OpenvpnServerHeaderField{
|
{
|
||||||
Column: "Last Ref (time_t)",
|
Column: "Last Ref (time_t)",
|
||||||
Desc: prometheus.NewDesc(
|
Desc: prometheus.NewDesc(
|
||||||
prometheus.BuildFQName("openvpn", "server", "route_last_reference_time_seconds"),
|
prometheus.BuildFQName("openvpn", "server", "route_last_reference_time_seconds"),
|
||||||
@@ -159,7 +164,7 @@ func CollectStatusFromReader(statusPath string, file io.Reader, ch chan<- promet
|
|||||||
// Client statistics.
|
// Client statistics.
|
||||||
return CollectClientStatusFromReader(statusPath, reader, ch)
|
return CollectClientStatusFromReader(statusPath, reader, ch)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Unexpected file contents: %q", buf)
|
return fmt.Errorf("unexpected file contents: %q", buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +173,9 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
|||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
scanner.Split(bufio.ScanLines)
|
scanner.Split(bufio.ScanLines)
|
||||||
headersFound := map[string][]string{}
|
headersFound := map[string][]string{}
|
||||||
|
// counter of connected client
|
||||||
|
numberConnectedClient := 0
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
fields := strings.Split(scanner.Text(), separator)
|
fields := strings.Split(scanner.Text(), separator)
|
||||||
if fields[0] == "END" && len(fields) == 1 {
|
if fields[0] == "END" && len(fields) == 1 {
|
||||||
@@ -179,17 +187,19 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
|||||||
headersFound[fields[1]] = fields[2:]
|
headersFound[fields[1]] = fields[2:]
|
||||||
} else if fields[0] == "TIME" && len(fields) == 3 {
|
} else if fields[0] == "TIME" && len(fields) == 3 {
|
||||||
// Time at which the statistics were updated.
|
// Time at which the statistics were updated.
|
||||||
time, err := strconv.ParseFloat(fields[2], 64)
|
timeStartStats, err := strconv.ParseFloat(fields[2], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
openvpnStatusUpdateTimeDesc,
|
openvpnStatusUpdateTimeDesc,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
time,
|
timeStartStats,
|
||||||
statusPath)
|
statusPath)
|
||||||
} else if fields[0] == "TITLE" && len(fields) == 2 {
|
} else if fields[0] == "TITLE" && len(fields) == 2 {
|
||||||
// OpenVPN version number.
|
// OpenVPN version number.
|
||||||
|
} else if fields[0] == "CLIENT_LIST"{
|
||||||
|
numberConnectedClient ++
|
||||||
} else if header, ok := openvpnServerHeaders[fields[0]]; ok {
|
} else if header, ok := openvpnServerHeaders[fields[0]]; ok {
|
||||||
// Entry that depends on a preceding HEADERS directive.
|
// Entry that depends on a preceding HEADERS directive.
|
||||||
columnNames, ok := headersFound[fields[0]]
|
columnNames, ok := headersFound[fields[0]]
|
||||||
@@ -230,9 +240,14 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Unsupported key: %q", fields[0])
|
return fmt.Errorf("unsupported key: %q", fields[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// add the number of connected client
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
openvpnConnectedClientsDesc,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
float64(numberConnectedClient))
|
||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,14 +264,14 @@ func CollectClientStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
|||||||
} else if fields[0] == "Updated" && len(fields) == 2 {
|
} else if fields[0] == "Updated" && len(fields) == 2 {
|
||||||
// Time at which the statistics were updated.
|
// Time at which the statistics were updated.
|
||||||
location, _ := time.LoadLocation("Local")
|
location, _ := time.LoadLocation("Local")
|
||||||
time, err := time.ParseInLocation("Mon Jan 2 15:04:05 2006", fields[1], location)
|
timeParser, err := time.ParseInLocation("Mon Jan 2 15:04:05 2006", fields[1], location)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
openvpnStatusUpdateTimeDesc,
|
openvpnStatusUpdateTimeDesc,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
float64(time.Unix()),
|
float64(timeParser.Unix()),
|
||||||
statusPath)
|
statusPath)
|
||||||
} else if desc, ok := openvpnClientDescs[fields[0]]; ok && len(fields) == 2 {
|
} else if desc, ok := openvpnClientDescs[fields[0]]; ok && len(fields) == 2 {
|
||||||
// Traffic counters.
|
// Traffic counters.
|
||||||
@@ -270,7 +285,7 @@ func CollectClientStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
|||||||
value,
|
value,
|
||||||
statusPath)
|
statusPath)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Unsupported key: %q", fields[0])
|
return fmt.Errorf("unsupported key: %q", fields[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
@@ -327,13 +342,18 @@ func main() {
|
|||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
log.Printf("Starting OpenVPN Exporter\n")
|
||||||
|
log.Printf("Listen address: %v\n", *listenAddress)
|
||||||
|
log.Printf("Metrics path: %v\n", *metricsPath)
|
||||||
|
log.Printf("openvpn.status_path: %v\n", *openvpnStatusPaths)
|
||||||
|
|
||||||
exporter, err := NewOpenVPNExporter(strings.Split(*openvpnStatusPaths, ","))
|
exporter, err := NewOpenVPNExporter(strings.Split(*openvpnStatusPaths, ","))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
prometheus.MustRegister(exporter)
|
prometheus.MustRegister(exporter)
|
||||||
|
|
||||||
http.Handle(*metricsPath, prometheus.Handler())
|
http.Handle(*metricsPath, promhttp.Handler())
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte(`
|
w.Write([]byte(`
|
||||||
<html>
|
<html>
|
||||||
|
|||||||
Reference in new issue
Block a user