diff --git a/README.md b/README.md index cb425f6..7e055d3 100644 --- a/README.md +++ b/README.md @@ -80,3 +80,12 @@ that every minute, and by default Prometheus will reload the file the minute it is written). After reloading your Prometheus master configuration, this program will begin informing via the discovery file of new targets that Prometheus must scrape. + +### Multiple Ports + +If your container exposes metrics on more than one port for a single task you can use multiple labels as so: + +* `PROMETHEUS_EXPORTER_PORT.1` `9180` - Set the first port exposing metrics +* `PROMETHEUS_EXPORTER_PORT.2` `8080` - Set the second port exposing metrics +* `PROMETHEUS_EXPORTER_PATH.2` `/api/v1/metrics` - The second port exposes metrics on a non-standard path of `/api/v1/metrics` +* `PROMETHEUS_EXPORTER_JOB_NAME.2` `logging` - The second port has a separate job name, `logging`, useful for differentiating otherwise identical metrics diff --git a/main.go b/main.go index 67feef3..5872fee 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "fmt" "io/ioutil" "log" + "regexp" "strconv" "strings" "time" @@ -203,8 +204,8 @@ func (t *AugmentedTask) ExporterInformation() []*PrometheusTaskInfo { // Nope, no match, this container cannot be exported. We continue. continue } + portPairs := make(map[string]PortPair) - var hostPort int64 if *prometheusDynamicPortDetection { v, ok := d.DockerLabels[dynamicPortLabel] if !ok || v != "1" { @@ -220,13 +221,43 @@ func (t *AugmentedTask) ExporterInformation() []*PrometheusTaskInfo { } if port := i.NetworkBindings[0].HostPort; port != nil { - hostPort = *port + portPairs[""] = PortPair{ + exporterPort: int(*port), + hostPort: int(*port), + } } } else { - v, ok := d.DockerLabels[*prometheusPortLabel] - if !ok { - // Nope, no Prometheus-exported port in this container def. - // This container is no good. We continue. + re := regexp.MustCompile(`(` + *prometheusPortLabel + `)(.*)`) + + var exporterPort int + for key := range d.DockerLabels { + result := re.FindAllSubmatch([]byte(key), -1) + if len(result) == 0 { + continue + } + + var portString string + var portName string + if len(result[0]) == 1 || len(result[0]) == 2 { + portString = d.DockerLabels[string(result[0][0])] + portName = "" + } else if len(result[0]) > 2 { + portString = d.DockerLabels[string(result[0][0])] + portName = string(result[0][2]) + } else { + continue + } + + if exporterPort, err := strconv.Atoi(portString); err != nil || exporterPort < 0 { + // This container has an invalid port definition. + // This container is no good. We continue. + continue + } else { + portPairs[portName] = PortPair{exporterPort: exporterPort} + } + } + + if len(portPairs) == 0 { continue } @@ -242,62 +273,65 @@ func (t *AugmentedTask) ExporterInformation() []*PrometheusTaskInfo { } } - var exporterPort int - var err error - if exporterPort, err = strconv.Atoi(v); err != nil || exporterPort < 0 { - // This container has an invalid port definition. - // This container is no good. We continue. - continue - } - - if len(i.NetworkBindings) > 0 { - for _, nb := range i.NetworkBindings { - if int(*nb.ContainerPort) == exporterPort { - hostPort = *nb.HostPort + for key, portPathPair := range portPairs { + if len(i.NetworkBindings) > 0 { + for _, nb := range i.NetworkBindings { + if int(*nb.ContainerPort) == portPathPair.exporterPort { + portPathPair.hostPort = int(*nb.HostPort) + portPairs[key] = portPathPair + } } - } - } else { - for _, ni := range i.NetworkInterfaces { - if *ni.PrivateIpv4Address != "" { - ip = *ni.PrivateIpv4Address + } else { + for _, ni := range i.NetworkInterfaces { + if *ni.PrivateIpv4Address != "" { + ip = *ni.PrivateIpv4Address + } } + portPathPair.hostPort = exporterPort + portPairs[key] = portPathPair } - hostPort = int64(exporterPort) } - } + var exporterServerName string + var exporterPath string + var exporterJobName string + var ok bool + exporterServerName, ok = d.DockerLabels[*prometheusServerNameLabel] + if ok { + host = strings.TrimRight(exporterServerName, "/") + } else { + // No server name, so fall back to ip address + host = ip + } - var exporterServerName string - var exporterPath string - var ok bool - exporterServerName, ok = d.DockerLabels[*prometheusServerNameLabel] - if ok { - host = strings.TrimRight(exporterServerName, "/") - } else { - // No server name, so fall back to ip address - host = ip - } + for key, portPathPair := range portPairs { + labels := labels{ + TaskArn: *t.TaskArn, + TaskName: *t.TaskDefinition.Family, + JobName: d.DockerLabels[*prometheusJobNameLabel], + TaskRevision: fmt.Sprintf("%d", *t.TaskDefinition.Revision), + TaskGroup: *t.Group, + ClusterArn: *t.ClusterArn, + ContainerName: *i.Name, + ContainerArn: *i.ContainerArn, + DockerImage: *d.Image, + } - labels := labels{ - TaskArn: *t.TaskArn, - TaskName: *t.TaskDefinition.Family, - JobName: d.DockerLabels[*prometheusJobNameLabel], - TaskRevision: fmt.Sprintf("%d", *t.TaskDefinition.Revision), - TaskGroup: *t.Group, - ClusterArn: *t.ClusterArn, - ContainerName: *i.Name, - ContainerArn: *i.ContainerArn, - DockerImage: *d.Image, - } + exporterPath, ok = d.DockerLabels[*prometheusPathLabel+key] + if ok { + labels.MetricsPath = exporterPath + } - exporterPath, ok = d.DockerLabels[*prometheusPathLabel] - if ok { - labels.MetricsPath = exporterPath - } + exporterJobName, ok = d.DockerLabels[*prometheusJobNameLabel+key] + if ok { + labels.JobName = exporterJobName + } - ret = append(ret, &PrometheusTaskInfo{ - Targets: []string{fmt.Sprintf("%s:%d", host, hostPort)}, - Labels: labels, - }) + ret = append(ret, &PrometheusTaskInfo{ + Targets: []string{fmt.Sprintf("%s:%d", host, portPathPair.hostPort)}, + Labels: labels, + }) + } + } } return ret } @@ -688,3 +722,8 @@ func main() { } } } + +type PortPair struct { + exporterPort int + hostPort int +}