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
|
||||
supported command line flags.
|
||||
|
||||
## Client statistics
|
||||
## Exposed metrics example
|
||||
|
||||
### Client statistics
|
||||
|
||||
For clients status files, the exporter generates metrics that may look
|
||||
like this:
|
||||
@@ -37,7 +39,7 @@ openvpn_status_update_time_seconds{status_path="..."} 1.490092749e+09
|
||||
openvpn_up{status_path="..."} 1
|
||||
```
|
||||
|
||||
## Server statistics
|
||||
### Server statistics
|
||||
|
||||
For server status files (both version 2 and 3), the exporter generates
|
||||
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_status_update_time_seconds{status_path="..."} 1.490089154e+09
|
||||
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"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
type OpenvpnServerHeader struct {
|
||||
@@ -52,8 +53,12 @@ var (
|
||||
[]string{"status_path"}, nil)
|
||||
|
||||
// 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{
|
||||
"CLIENT_LIST": OpenvpnServerHeader{
|
||||
"CLIENT_LIST": {
|
||||
LabelColumns: []string{
|
||||
"Common Name",
|
||||
"Connected Since (time_t)",
|
||||
@@ -62,7 +67,7 @@ var (
|
||||
"Username",
|
||||
},
|
||||
Metrics: []OpenvpnServerHeaderField{
|
||||
OpenvpnServerHeaderField{
|
||||
{
|
||||
Column: "Bytes Received",
|
||||
Desc: prometheus.NewDesc(
|
||||
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),
|
||||
ValueType: prometheus.CounterValue,
|
||||
},
|
||||
OpenvpnServerHeaderField{
|
||||
{
|
||||
Column: "Bytes Sent",
|
||||
Desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName("openvpn", "server", "client_sent_bytes_total"),
|
||||
@@ -80,14 +85,14 @@ var (
|
||||
},
|
||||
},
|
||||
},
|
||||
"ROUTING_TABLE": OpenvpnServerHeader{
|
||||
"ROUTING_TABLE": {
|
||||
LabelColumns: []string{
|
||||
"Common Name",
|
||||
"Real Address",
|
||||
"Virtual Address",
|
||||
},
|
||||
Metrics: []OpenvpnServerHeaderField{
|
||||
OpenvpnServerHeaderField{
|
||||
{
|
||||
Column: "Last Ref (time_t)",
|
||||
Desc: prometheus.NewDesc(
|
||||
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.
|
||||
return CollectClientStatusFromReader(statusPath, reader, ch)
|
||||
} 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.Split(bufio.ScanLines)
|
||||
headersFound := map[string][]string{}
|
||||
// counter of connected client
|
||||
numberConnectedClient := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
fields := strings.Split(scanner.Text(), separator)
|
||||
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:]
|
||||
} else if fields[0] == "TIME" && len(fields) == 3 {
|
||||
// Time at which the statistics were updated.
|
||||
time, err := strconv.ParseFloat(fields[2], 64)
|
||||
timeStartStats, err := strconv.ParseFloat(fields[2], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnStatusUpdateTimeDesc,
|
||||
prometheus.GaugeValue,
|
||||
time,
|
||||
timeStartStats,
|
||||
statusPath)
|
||||
} else if fields[0] == "TITLE" && len(fields) == 2 {
|
||||
// OpenVPN version number.
|
||||
} else if fields[0] == "CLIENT_LIST"{
|
||||
numberConnectedClient ++
|
||||
} else if header, ok := openvpnServerHeaders[fields[0]]; ok {
|
||||
// Entry that depends on a preceding HEADERS directive.
|
||||
columnNames, ok := headersFound[fields[0]]
|
||||
@@ -230,9 +240,14 @@ func CollectServerStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
||||
}
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
|
||||
@@ -249,14 +264,14 @@ func CollectClientStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
||||
} else if fields[0] == "Updated" && len(fields) == 2 {
|
||||
// Time at which the statistics were updated.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
openvpnStatusUpdateTimeDesc,
|
||||
prometheus.GaugeValue,
|
||||
float64(time.Unix()),
|
||||
float64(timeParser.Unix()),
|
||||
statusPath)
|
||||
} else if desc, ok := openvpnClientDescs[fields[0]]; ok && len(fields) == 2 {
|
||||
// Traffic counters.
|
||||
@@ -270,7 +285,7 @@ func CollectClientStatusFromReader(statusPath string, file io.Reader, ch chan<-
|
||||
value,
|
||||
statusPath)
|
||||
} else {
|
||||
return fmt.Errorf("Unsupported key: %q", fields[0])
|
||||
return fmt.Errorf("unsupported key: %q", fields[0])
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
@@ -327,13 +342,18 @@ func main() {
|
||||
)
|
||||
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, ","))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prometheus.MustRegister(exporter)
|
||||
|
||||
http.Handle(*metricsPath, prometheus.Handler())
|
||||
http.Handle(*metricsPath, promhttp.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`
|
||||
<html>
|
||||
|
||||
Reference in new issue
Block a user