mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
lib/promscrape: initial implementation for gce_sd_configs
aga Prometheus-compatible service discovery for Google Compute Engine
This commit is contained in:
parent
b80e6b4d56
commit
9ef5935552
19 changed files with 655 additions and 77 deletions
|
@ -260,6 +260,7 @@ Currently the following [scrape_config](https://prometheus.io/docs/prometheus/la
|
||||||
* [static_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#static_config)
|
* [static_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#static_config)
|
||||||
* [file_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config)
|
* [file_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config)
|
||||||
* [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config)
|
* [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config)
|
||||||
|
* [gce_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config)
|
||||||
|
|
||||||
In the future other `*_sd_config` types will be supported.
|
In the future other `*_sd_config` types will be supported.
|
||||||
|
|
||||||
|
|
|
@ -133,11 +133,12 @@ The following scrape types in [scrape_config](https://prometheus.io/docs/prometh
|
||||||
See [these docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config) for details.
|
See [these docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config) for details.
|
||||||
* `kubernetes_sd_configs` - for scraping targets in Kubernetes (k8s).
|
* `kubernetes_sd_configs` - for scraping targets in Kubernetes (k8s).
|
||||||
See [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) for details.
|
See [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) for details.
|
||||||
|
* `gce_sd_configs` - for scraping targets in Google Compute Engine (GCE).
|
||||||
|
See [gce_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config) for details.
|
||||||
|
|
||||||
The following service discovery mechanisms will be added to `vmagent` soon:
|
The following service discovery mechanisms will be added to `vmagent` soon:
|
||||||
|
|
||||||
* [ec2_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config)
|
* [ec2_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config)
|
||||||
* [gce_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config)
|
|
||||||
* [consul_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config)
|
* [consul_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config)
|
||||||
* [dns_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config)
|
* [dns_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config)
|
||||||
|
|
||||||
|
|
|
@ -260,6 +260,7 @@ Currently the following [scrape_config](https://prometheus.io/docs/prometheus/la
|
||||||
* [static_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#static_config)
|
* [static_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#static_config)
|
||||||
* [file_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config)
|
* [file_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config)
|
||||||
* [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config)
|
* [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config)
|
||||||
|
* [gce_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config)
|
||||||
|
|
||||||
In the future other `*_sd_config` types will be supported.
|
In the future other `*_sd_config` types will be supported.
|
||||||
|
|
||||||
|
|
|
@ -133,11 +133,12 @@ The following scrape types in [scrape_config](https://prometheus.io/docs/prometh
|
||||||
See [these docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config) for details.
|
See [these docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config) for details.
|
||||||
* `kubernetes_sd_configs` - for scraping targets in Kubernetes (k8s).
|
* `kubernetes_sd_configs` - for scraping targets in Kubernetes (k8s).
|
||||||
See [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) for details.
|
See [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) for details.
|
||||||
|
* `gce_sd_configs` - for scraping targets in Google Compute Engine (GCE).
|
||||||
|
See [gce_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config) for details.
|
||||||
|
|
||||||
The following service discovery mechanisms will be added to `vmagent` soon:
|
The following service discovery mechanisms will be added to `vmagent` soon:
|
||||||
|
|
||||||
* [ec2_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config)
|
* [ec2_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config)
|
||||||
* [gce_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config)
|
|
||||||
* [consul_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config)
|
* [consul_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config)
|
||||||
* [dns_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config)
|
* [dns_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config)
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -17,6 +17,7 @@ require (
|
||||||
github.com/valyala/histogram v1.0.1
|
github.com/valyala/histogram v1.0.1
|
||||||
github.com/valyala/quicktemplate v1.4.1
|
github.com/valyala/quicktemplate v1.4.1
|
||||||
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd // indirect
|
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd // indirect
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f
|
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f
|
||||||
golang.org/x/tools v0.0.0-20200423205358-59e73619c742 // indirect
|
golang.org/x/tools v0.0.0-20200423205358-59e73619c742 // indirect
|
||||||
google.golang.org/api v0.22.0
|
google.golang.org/api v0.22.0
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/gce"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kubernetes"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kubernetes"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -60,6 +61,7 @@ type ScrapeConfig struct {
|
||||||
StaticConfigs []StaticConfig `yaml:"static_configs"`
|
StaticConfigs []StaticConfig `yaml:"static_configs"`
|
||||||
FileSDConfigs []FileSDConfig `yaml:"file_sd_configs"`
|
FileSDConfigs []FileSDConfig `yaml:"file_sd_configs"`
|
||||||
KubernetesSDConfigs []kubernetes.SDConfig `yaml:"kubernetes_sd_configs"`
|
KubernetesSDConfigs []kubernetes.SDConfig `yaml:"kubernetes_sd_configs"`
|
||||||
|
GCESDConfigs []gce.SDConfig `yaml:"gce_sd_configs"`
|
||||||
RelabelConfigs []promrelabel.RelabelConfig `yaml:"relabel_configs"`
|
RelabelConfigs []promrelabel.RelabelConfig `yaml:"relabel_configs"`
|
||||||
MetricRelabelConfigs []promrelabel.RelabelConfig `yaml:"metric_relabel_configs"`
|
MetricRelabelConfigs []promrelabel.RelabelConfig `yaml:"metric_relabel_configs"`
|
||||||
SampleLimit int `yaml:"sample_limit"`
|
SampleLimit int `yaml:"sample_limit"`
|
||||||
|
@ -147,6 +149,14 @@ func (cfg *Config) kubernetesSDConfigsCount() int {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) gceSDConfigsCount() int {
|
||||||
|
n := 0
|
||||||
|
for i := range cfg.ScrapeConfigs {
|
||||||
|
n += len(cfg.ScrapeConfigs[i].GCESDConfigs)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *Config) fileSDConfigsCount() int {
|
func (cfg *Config) fileSDConfigsCount() int {
|
||||||
n := 0
|
n := 0
|
||||||
for i := range cfg.ScrapeConfigs {
|
for i := range cfg.ScrapeConfigs {
|
||||||
|
@ -168,6 +178,19 @@ func (cfg *Config) getKubernetesSDScrapeWork() []ScrapeWork {
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getGCESDScrapeWork returns `gce_sd_configs` ScrapeWork from cfg.
|
||||||
|
func (cfg *Config) getGCESDScrapeWork() []ScrapeWork {
|
||||||
|
var dst []ScrapeWork
|
||||||
|
for i := range cfg.ScrapeConfigs {
|
||||||
|
sc := &cfg.ScrapeConfigs[i]
|
||||||
|
for j := range sc.GCESDConfigs {
|
||||||
|
sdc := &sc.GCESDConfigs[j]
|
||||||
|
dst = appendGCEScrapeWork(dst, sdc, sc.swc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
// getFileSDScrapeWork returns `file_sd_configs` ScrapeWork from cfg.
|
// getFileSDScrapeWork returns `file_sd_configs` ScrapeWork from cfg.
|
||||||
func (cfg *Config) getFileSDScrapeWork(prev []ScrapeWork) []ScrapeWork {
|
func (cfg *Config) getFileSDScrapeWork(prev []ScrapeWork) []ScrapeWork {
|
||||||
// Create a map for the previous scrape work.
|
// Create a map for the previous scrape work.
|
||||||
|
@ -297,13 +320,25 @@ func appendKubernetesScrapeWork(dst []ScrapeWork, sdc *kubernetes.SDConfig, base
|
||||||
logger.Errorf("error when discovering kubernetes nodes for `job_name` %q: %s; skipping it", swc.jobName, err)
|
logger.Errorf("error when discovering kubernetes nodes for `job_name` %q: %s; skipping it", swc.jobName, err)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
return appendScrapeWorkForTargetLabels(dst, swc, targetLabels, "kubernetes_sd_config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendGCEScrapeWork(dst []ScrapeWork, sdc *gce.SDConfig, swc *scrapeWorkConfig) []ScrapeWork {
|
||||||
|
targetLabels, err := gce.GetLabels(sdc)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("error when discovering gce nodes for `job_name` %q: %s; skippint it", swc.jobName, err)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
return appendScrapeWorkForTargetLabels(dst, swc, targetLabels, "gce_sd_config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendScrapeWorkForTargetLabels(dst []ScrapeWork, swc *scrapeWorkConfig, targetLabels []map[string]string, sectionName string) []ScrapeWork {
|
||||||
for _, metaLabels := range targetLabels {
|
for _, metaLabels := range targetLabels {
|
||||||
target := metaLabels["__address__"]
|
target := metaLabels["__address__"]
|
||||||
var err error
|
var err error
|
||||||
dst, err = appendScrapeWork(dst, swc, target, nil, metaLabels)
|
dst, err = appendScrapeWork(dst, swc, target, nil, metaLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("error when parsing `kubernetes_sd_config` target %q with role %q for `job_name` %q: %s; skipping it",
|
logger.Errorf("error when parsing `%s` target %q for `job_name` %q: %s; skipping it", sectionName, target, swc.jobName, err)
|
||||||
target, sdc.Role, swc.jobName, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
97
lib/promscrape/discovery/gce/api.go
Normal file
97
lib/promscrape/discovery/gce/api.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package gce
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
)
|
||||||
|
|
||||||
|
type apiConfig struct {
|
||||||
|
client *http.Client
|
||||||
|
apiURL string
|
||||||
|
project string
|
||||||
|
tagSeparator string
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAPIConfig(sdc *SDConfig) (*apiConfig, error) {
|
||||||
|
apiConfigMapLock.Lock()
|
||||||
|
defer apiConfigMapLock.Unlock()
|
||||||
|
|
||||||
|
if !hasAPIConfigMapCleaner {
|
||||||
|
hasAPIConfigMapCleaner = true
|
||||||
|
go apiConfigMapCleaner()
|
||||||
|
}
|
||||||
|
|
||||||
|
e := apiConfigMap[sdc]
|
||||||
|
if e != nil {
|
||||||
|
e.lastAccessTime = time.Now()
|
||||||
|
return e.cfg, nil
|
||||||
|
}
|
||||||
|
cfg, err := newAPIConfig(sdc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apiConfigMap[sdc] = &apiConfigMapEntry{
|
||||||
|
cfg: cfg,
|
||||||
|
lastAccessTime: time.Now(),
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiConfigMapCleaner() {
|
||||||
|
tc := time.NewTicker(15 * time.Minute)
|
||||||
|
for currentTime := range tc.C {
|
||||||
|
apiConfigMapLock.Lock()
|
||||||
|
for k, e := range apiConfigMap {
|
||||||
|
if currentTime.Sub(e.lastAccessTime) > 10*time.Minute {
|
||||||
|
delete(apiConfigMap, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apiConfigMapLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiConfigMapEntry struct {
|
||||||
|
cfg *apiConfig
|
||||||
|
lastAccessTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiConfigMap = make(map[*SDConfig]*apiConfigMapEntry)
|
||||||
|
apiConfigMapLock sync.Mutex
|
||||||
|
hasAPIConfigMapCleaner bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func newAPIConfig(sdc *SDConfig) (*apiConfig, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
client, err := google.DefaultClient(ctx, "https://www.googleapis.com/auth/compute.readonly")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot create oauth2 client for gce: %s", err)
|
||||||
|
}
|
||||||
|
// See https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
|
||||||
|
apiURL := fmt.Sprintf("https://compute.googleapis.com/compute/v1/projects/%s/zones/%s/instances", sdc.Project, sdc.Zone)
|
||||||
|
if len(sdc.Filter) > 0 {
|
||||||
|
apiURL += fmt.Sprintf("?filter=%s", url.QueryEscape(sdc.Filter))
|
||||||
|
}
|
||||||
|
tagSeparator := ","
|
||||||
|
if sdc.TagSeparator != nil {
|
||||||
|
tagSeparator = *sdc.TagSeparator
|
||||||
|
}
|
||||||
|
port := 80
|
||||||
|
if sdc.Port != nil {
|
||||||
|
port = *sdc.Port
|
||||||
|
}
|
||||||
|
return &apiConfig{
|
||||||
|
client: client,
|
||||||
|
apiURL: apiURL,
|
||||||
|
project: sdc.Project,
|
||||||
|
tagSeparator: tagSeparator,
|
||||||
|
port: port,
|
||||||
|
}, nil
|
||||||
|
}
|
31
lib/promscrape/discovery/gce/gce.go
Normal file
31
lib/promscrape/discovery/gce/gce.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package gce
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SDConfig represents service discovery config for gce.
|
||||||
|
//
|
||||||
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config
|
||||||
|
type SDConfig struct {
|
||||||
|
Project string `yaml:"project"`
|
||||||
|
Zone string `yaml:"zone"`
|
||||||
|
Filter string `yaml:"filter"`
|
||||||
|
// RefreshInterval time.Duration `yaml:"refresh_interval"`
|
||||||
|
// refresh_interval is obtained from `-promscrape.gceSDCheckInterval` command-line option.
|
||||||
|
Port *int `yaml:"port"`
|
||||||
|
TagSeparator *string `yaml:"tag_separator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabels returns gce labels according to sdc.
|
||||||
|
func GetLabels(sdc *SDConfig) ([]map[string]string, error) {
|
||||||
|
cfg, err := getAPIConfig(sdc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get API config: %s", err)
|
||||||
|
}
|
||||||
|
ms, err := getInstancesLabels(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error when fetching instances data from GCE: %s", err)
|
||||||
|
}
|
||||||
|
return ms, nil
|
||||||
|
}
|
170
lib/promscrape/discovery/gce/instance.go
Normal file
170
lib/promscrape/discovery/gce/instance.go
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
package gce
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getInstancesLabels returns labels for gce instances obtained from the given cfg
|
||||||
|
func getInstancesLabels(cfg *apiConfig) ([]map[string]string, error) {
|
||||||
|
insts, err := getInstances(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var ms []map[string]string
|
||||||
|
for _, inst := range insts {
|
||||||
|
ms = inst.appendTargetLabels(ms, cfg.project, cfg.tagSeparator, cfg.port)
|
||||||
|
}
|
||||||
|
return ms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInstances(cfg *apiConfig) ([]Instance, error) {
|
||||||
|
var result []Instance
|
||||||
|
pageToken := ""
|
||||||
|
for {
|
||||||
|
insts, nextPageToken, err := getInstancesPage(cfg, pageToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, insts...)
|
||||||
|
if len(nextPageToken) == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
pageToken = nextPageToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInstancesPage(cfg *apiConfig, pageToken string) ([]Instance, string, error) {
|
||||||
|
apiURL := cfg.apiURL
|
||||||
|
if len(pageToken) > 0 {
|
||||||
|
// See https://cloud.google.com/compute/docs/reference/rest/v1/instances/list about pageToken
|
||||||
|
prefix := "?"
|
||||||
|
if strings.Contains(apiURL, "?") {
|
||||||
|
prefix = "&"
|
||||||
|
}
|
||||||
|
apiURL += fmt.Sprintf("%spageToken=%s", prefix, url.QueryEscape(pageToken))
|
||||||
|
}
|
||||||
|
resp, err := cfg.client.Get(apiURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("cannot obtain instances data from API server: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("cannot read instances data from API server: %s", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, "", fmt.Errorf("unexpected status code when reading instances data from API server; got %d; want %d; response body: %q",
|
||||||
|
resp.StatusCode, http.StatusOK, data)
|
||||||
|
}
|
||||||
|
il, err := parseInstanceList(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("cannot parse instances response from API server: %s", err)
|
||||||
|
}
|
||||||
|
return il.Items, il.NextPageToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceList is response to https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
|
||||||
|
type InstanceList struct {
|
||||||
|
Items []Instance
|
||||||
|
NextPageToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance is instance from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
|
||||||
|
type Instance struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string
|
||||||
|
Status string
|
||||||
|
MachineType string
|
||||||
|
Zone string
|
||||||
|
NetworkInterfaces []NetworkInterface
|
||||||
|
Tags TagList
|
||||||
|
Metadata MetadataList
|
||||||
|
Labels discoveryutils.SortedLabels
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkInterface is network interface from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
|
||||||
|
type NetworkInterface struct {
|
||||||
|
Network string
|
||||||
|
Subnetwork string
|
||||||
|
NetworkIP string
|
||||||
|
AccessConfigs []AccessConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessConfig is access config from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
|
||||||
|
type AccessConfig struct {
|
||||||
|
Type string
|
||||||
|
NatIP string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagList is tag list from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
|
||||||
|
type TagList struct {
|
||||||
|
Items []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataList is metadataList from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
|
||||||
|
type MetadataList struct {
|
||||||
|
Items []MetadataEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetadataEntry is a single entry from metadata
|
||||||
|
type MetadataEntry struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInstanceList parses InstanceList from data.
|
||||||
|
func parseInstanceList(data []byte) (*InstanceList, error) {
|
||||||
|
var il InstanceList
|
||||||
|
if err := json.Unmarshal(data, &il); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot unmarshal InstanceList from %q: %s", data, err)
|
||||||
|
}
|
||||||
|
return &il, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inst *Instance) appendTargetLabels(ms []map[string]string, project, tagSeparator string, port int) []map[string]string {
|
||||||
|
if len(inst.NetworkInterfaces) == 0 {
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
iface := inst.NetworkInterfaces[0]
|
||||||
|
addr := discoveryutils.JoinHostPort(iface.NetworkIP, port)
|
||||||
|
m := map[string]string{
|
||||||
|
"__address__": addr,
|
||||||
|
"__meta_gce_instance_id": inst.ID,
|
||||||
|
"__meta_gce_instance_status": inst.Status,
|
||||||
|
"__meta_gce_instance_name": inst.Name,
|
||||||
|
"__meta_gce_machine_type": inst.MachineType,
|
||||||
|
"__meta_gce_network": iface.Network,
|
||||||
|
"__meta_gce_private_ip": iface.NetworkIP,
|
||||||
|
"__meta_gce_project": project,
|
||||||
|
"__meta_gce_subnetwork": iface.Subnetwork,
|
||||||
|
"__meta_gce_zone": inst.Zone,
|
||||||
|
}
|
||||||
|
if len(inst.Tags.Items) > 0 {
|
||||||
|
// We surround the separated list with the separator as well. This way regular expressions
|
||||||
|
// in relabeling rules don't have to consider tag positions.
|
||||||
|
m["__meta_gce_tags"] = tagSeparator + strings.Join(inst.Tags.Items, tagSeparator) + tagSeparator
|
||||||
|
}
|
||||||
|
for _, item := range inst.Metadata.Items {
|
||||||
|
key := discoveryutils.SanitizeLabelName(item.Key)
|
||||||
|
m["__meta_gce_metadata_"+key] = item.Value
|
||||||
|
}
|
||||||
|
for _, label := range inst.Labels {
|
||||||
|
name := discoveryutils.SanitizeLabelName(label.Name)
|
||||||
|
m["__meta_gce_label_"+name] = label.Value
|
||||||
|
}
|
||||||
|
if len(iface.AccessConfigs) > 0 {
|
||||||
|
ac := iface.AccessConfigs[0]
|
||||||
|
if ac.Type == "ONE_TO_ONE_NAT" {
|
||||||
|
m["__meta_gce_public_ip"] = ac.NatIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ms = append(ms, m)
|
||||||
|
return ms
|
||||||
|
}
|
179
lib/promscrape/discovery/gce/instance_test.go
Normal file
179
lib/promscrape/discovery/gce/instance_test.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package gce
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseInstanceListFailure(t *testing.T) {
|
||||||
|
f := func(s string) {
|
||||||
|
t.Helper()
|
||||||
|
il, err := parseInstanceList([]byte(s))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expecting non-nil error")
|
||||||
|
}
|
||||||
|
if il != nil {
|
||||||
|
t.Fatalf("unexpected non-nil InstanceList: %v", il)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f(``)
|
||||||
|
f(`[1,23]`)
|
||||||
|
f(`{"items":[{"metadata":1}]}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInstanceListSuccess(t *testing.T) {
|
||||||
|
data := `
|
||||||
|
{
|
||||||
|
"id": "projects/victoriametrics-test/zones/us-east1-b/instances",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "7897352091592122",
|
||||||
|
"creationTimestamp": "2020-02-16T07:10:14.357-08:00",
|
||||||
|
"name": "play-1m-1-vmagent",
|
||||||
|
"tags": {
|
||||||
|
"items": [
|
||||||
|
"play",
|
||||||
|
"play-1m-1",
|
||||||
|
"vmagent"
|
||||||
|
],
|
||||||
|
"fingerprint": "O44NvJ36CCo="
|
||||||
|
},
|
||||||
|
"machineType": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/zones/us-east1-b/machineTypes/f1-micro",
|
||||||
|
"status": "RUNNING",
|
||||||
|
"zone": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/zones/us-east1-b",
|
||||||
|
"networkInterfaces": [
|
||||||
|
{
|
||||||
|
"network": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/global/networks/default",
|
||||||
|
"subnetwork": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/regions/us-east1/subnetworks/play-1m-1-snw",
|
||||||
|
"networkIP": "10.11.2.7",
|
||||||
|
"name": "nic0",
|
||||||
|
"fingerprint": "O4eNOfaplJ4=",
|
||||||
|
"kind": "compute#networkInterface"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disks": [
|
||||||
|
{
|
||||||
|
"type": "PERSISTENT",
|
||||||
|
"mode": "READ_WRITE",
|
||||||
|
"source": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/zones/us-east1-b/disks/play-1m-1-vmagent",
|
||||||
|
"deviceName": "boot",
|
||||||
|
"index": 0,
|
||||||
|
"boot": true,
|
||||||
|
"autoDelete": true,
|
||||||
|
"licenses": [
|
||||||
|
"https://www.googleapis.com/compute/v1/projects/cos-cloud-shielded/global/licenses/shielded-cos",
|
||||||
|
"https://www.googleapis.com/compute/v1/projects/cos-cloud/global/licenses/cos",
|
||||||
|
"https://www.googleapis.com/compute/v1/projects/cos-cloud/global/licenses/cos-pcid"
|
||||||
|
],
|
||||||
|
"interface": "SCSI",
|
||||||
|
"guestOsFeatures": [
|
||||||
|
{
|
||||||
|
"type": "VIRTIO_SCSI_MULTIQUEUE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "UEFI_COMPATIBLE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"diskSizeGb": "10",
|
||||||
|
"kind": "compute#attachedDisk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"fingerprint": "BAFZwTyaAxQ=",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"key": "gce-container-declaration",
|
||||||
|
"value": "foobar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kind": "compute#metadata"
|
||||||
|
},
|
||||||
|
"serviceAccounts": [
|
||||||
|
{
|
||||||
|
"email": "12-compute@developer.gserviceaccount.com",
|
||||||
|
"scopes": [
|
||||||
|
"https://www.googleapis.com/auth/devstorage.read_write",
|
||||||
|
"https://www.googleapis.com/auth/logging.write",
|
||||||
|
"https://www.googleapis.com/auth/monitoring.write",
|
||||||
|
"https://www.googleapis.com/auth/servicecontrol",
|
||||||
|
"https://www.googleapis.com/auth/service.management.readonly"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selfLink": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/zones/us-east1-b/instances/play-1m-1-vmagent",
|
||||||
|
"scheduling": {
|
||||||
|
"onHostMaintenance": "MIGRATE",
|
||||||
|
"automaticRestart": true,
|
||||||
|
"preemptible": false
|
||||||
|
},
|
||||||
|
"cpuPlatform": "Intel Haswell",
|
||||||
|
"labels": {
|
||||||
|
"goog-dm": "play-deployment",
|
||||||
|
"cluster_num": "1",
|
||||||
|
"cluster_retention": "1m",
|
||||||
|
"env": "play",
|
||||||
|
"type": "vmagent"
|
||||||
|
},
|
||||||
|
"labelFingerprint": "-CXeRXMQiVc=",
|
||||||
|
"startRestricted": false,
|
||||||
|
"deletionProtection": false,
|
||||||
|
"shieldedInstanceConfig": {
|
||||||
|
"enableSecureBoot": false,
|
||||||
|
"enableVtpm": true,
|
||||||
|
"enableIntegrityMonitoring": true
|
||||||
|
},
|
||||||
|
"shieldedInstanceIntegrityPolicy": {
|
||||||
|
"updateAutoLearnPolicy": true
|
||||||
|
},
|
||||||
|
"fingerprint": "hd3NB2-9QIg=",
|
||||||
|
"kind": "compute#instance"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
il, err := parseInstanceList([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
if len(il.Items) != 1 {
|
||||||
|
t.Fatalf("unexpected length of InstanceList.Items; got %d; want %d", len(il.Items), 1)
|
||||||
|
}
|
||||||
|
inst := il.Items[0]
|
||||||
|
|
||||||
|
// Check inst.appendTargetLabels()
|
||||||
|
project := "proj-1"
|
||||||
|
tagSeparator := ","
|
||||||
|
port := 80
|
||||||
|
labelss := inst.appendTargetLabels(nil, project, tagSeparator, port)
|
||||||
|
var sortedLabelss [][]prompbmarshal.Label
|
||||||
|
for _, labels := range labelss {
|
||||||
|
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||||
|
}
|
||||||
|
expectedLabelss := [][]prompbmarshal.Label{
|
||||||
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
|
"__address__": "10.11.2.7:80",
|
||||||
|
"__meta_gce_instance_id": "7897352091592122",
|
||||||
|
"__meta_gce_instance_name": "play-1m-1-vmagent",
|
||||||
|
"__meta_gce_instance_status": "RUNNING",
|
||||||
|
"__meta_gce_label_cluster_num": "1",
|
||||||
|
"__meta_gce_label_cluster_retention": "1m",
|
||||||
|
"__meta_gce_label_env": "play",
|
||||||
|
"__meta_gce_label_goog_dm": "play-deployment",
|
||||||
|
"__meta_gce_label_type": "vmagent",
|
||||||
|
"__meta_gce_machine_type": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/zones/us-east1-b/machineTypes/f1-micro",
|
||||||
|
"__meta_gce_metadata_gce_container_declaration": "foobar",
|
||||||
|
"__meta_gce_network": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/global/networks/default",
|
||||||
|
"__meta_gce_private_ip": "10.11.2.7",
|
||||||
|
"__meta_gce_project": "proj-1",
|
||||||
|
"__meta_gce_subnetwork": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/regions/us-east1/subnetworks/play-1m-1-snw",
|
||||||
|
"__meta_gce_tags": ",play,play-1m-1,vmagent,",
|
||||||
|
"__meta_gce_zone": "https://www.googleapis.com/compute/v1/projects/victoriametrics-test/zones/us-east1-b",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(sortedLabelss, expectedLabelss) {
|
||||||
|
t.Fatalf("unexpected labels:\ngot\n%v\nwant\n%v", sortedLabelss, expectedLabelss)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,10 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,8 +15,8 @@ type ObjectMeta struct {
|
||||||
Name string
|
Name string
|
||||||
Namespace string
|
Namespace string
|
||||||
UID string
|
UID string
|
||||||
Labels SortedLabels
|
Labels discoveryutils.SortedLabels
|
||||||
Annotations SortedLabels
|
Annotations discoveryutils.SortedLabels
|
||||||
OwnerReferences []OwnerReference
|
OwnerReferences []OwnerReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,33 +33,6 @@ func (om *ObjectMeta) registerLabelsAndAnnotations(prefix string, m map[string]s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortedLabels represents sorted labels.
|
|
||||||
type SortedLabels []prompbmarshal.Label
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals JSON from data.
|
|
||||||
func (sls *SortedLabels) UnmarshalJSON(data []byte) error {
|
|
||||||
var m map[string]string
|
|
||||||
if err := json.Unmarshal(data, &m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*sls = getSortedLabels(m)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSortedLabels(m map[string]string) SortedLabels {
|
|
||||||
a := make([]prompbmarshal.Label, 0, len(m))
|
|
||||||
for k, v := range m {
|
|
||||||
a = append(a, prompbmarshal.Label{
|
|
||||||
Name: k,
|
|
||||||
Value: v,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
sort.Slice(a, func(i, j int) bool {
|
|
||||||
return a[i].Name < a[j].Name
|
|
||||||
})
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// OwnerReference represents OwnerReferense from k8s API.
|
// OwnerReference represents OwnerReferense from k8s API.
|
||||||
//
|
//
|
||||||
// See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#ownerreference-v1-meta
|
// See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#ownerreference-v1-meta
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseEndpointsListFailure(t *testing.T) {
|
func TestParseEndpointsListFailure(t *testing.T) {
|
||||||
|
@ -85,10 +86,10 @@ func TestParseEndpointsListSuccess(t *testing.T) {
|
||||||
labelss := endpoint.appendTargetLabels(nil, nil, nil)
|
labelss := endpoint.appendTargetLabels(nil, nil, nil)
|
||||||
var sortedLabelss [][]prompbmarshal.Label
|
var sortedLabelss [][]prompbmarshal.Label
|
||||||
for _, labels := range labelss {
|
for _, labels := range labelss {
|
||||||
sortedLabelss = append(sortedLabelss, getSortedLabels(labels))
|
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||||
}
|
}
|
||||||
expectedLabelss := [][]prompbmarshal.Label{
|
expectedLabelss := [][]prompbmarshal.Label{
|
||||||
getSortedLabels(map[string]string{
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"__address__": "172.17.0.2:8443",
|
"__address__": "172.17.0.2:8443",
|
||||||
"__meta_kubernetes_endpoint_address_target_kind": "Pod",
|
"__meta_kubernetes_endpoint_address_target_kind": "Pod",
|
||||||
"__meta_kubernetes_endpoint_address_target_name": "coredns-6955765f44-lnp6t",
|
"__meta_kubernetes_endpoint_address_target_name": "coredns-6955765f44-lnp6t",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseIngressListFailure(t *testing.T) {
|
func TestParseIngressListFailure(t *testing.T) {
|
||||||
|
@ -83,10 +84,10 @@ func TestParseIngressListSuccess(t *testing.T) {
|
||||||
labelss := ig.appendTargetLabels(nil)
|
labelss := ig.appendTargetLabels(nil)
|
||||||
var sortedLabelss [][]prompbmarshal.Label
|
var sortedLabelss [][]prompbmarshal.Label
|
||||||
for _, labels := range labelss {
|
for _, labels := range labelss {
|
||||||
sortedLabelss = append(sortedLabelss, getSortedLabels(labels))
|
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||||
}
|
}
|
||||||
expectedLabelss := [][]prompbmarshal.Label{
|
expectedLabelss := [][]prompbmarshal.Label{
|
||||||
getSortedLabels(map[string]string{
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"__address__": "foobar",
|
"__address__": "foobar",
|
||||||
"__meta_kubernetes_ingress_annotation_kubectl_kubernetes_io_last_applied_configuration": `{"apiVersion":"networking.k8s.io/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"test-ingress","namespace":"default"},"spec":{"backend":{"serviceName":"testsvc","servicePort":80}}}` + "\n",
|
"__meta_kubernetes_ingress_annotation_kubectl_kubernetes_io_last_applied_configuration": `{"apiVersion":"networking.k8s.io/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"test-ingress","namespace":"default"},"spec":{"backend":{"serviceName":"testsvc","servicePort":80}}}` + "\n",
|
||||||
"__meta_kubernetes_ingress_annotationpresent_kubectl_kubernetes_io_last_applied_configuration": "true",
|
"__meta_kubernetes_ingress_annotationpresent_kubectl_kubernetes_io_last_applied_configuration": "true",
|
||||||
|
|
|
@ -6,30 +6,6 @@ import (
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLabels returns labels for the given k8s role and the given cfg.
|
|
||||||
func GetLabels(ac *promauth.Config, sdc *SDConfig) ([]map[string]string, error) {
|
|
||||||
cfg := &apiConfig{
|
|
||||||
Server: sdc.APIServer,
|
|
||||||
AuthConfig: ac,
|
|
||||||
Namespaces: sdc.Namespaces.Names,
|
|
||||||
Selectors: sdc.Selectors,
|
|
||||||
}
|
|
||||||
switch sdc.Role {
|
|
||||||
case "node":
|
|
||||||
return getNodesLabels(cfg)
|
|
||||||
case "service":
|
|
||||||
return getServicesLabels(cfg)
|
|
||||||
case "pod":
|
|
||||||
return getPodsLabels(cfg)
|
|
||||||
case "endpoints":
|
|
||||||
return getEndpointsLabels(cfg)
|
|
||||||
case "ingress":
|
|
||||||
return getIngressesLabels(cfg)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unexpected `role`: %q; must be one of `node`, `service`, `pod`, `endpoints` or `ingress`; skipping it", sdc.Role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SDConfig represents kubernetes-based service discovery config.
|
// SDConfig represents kubernetes-based service discovery config.
|
||||||
//
|
//
|
||||||
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config
|
||||||
|
@ -58,3 +34,27 @@ type Selector struct {
|
||||||
Label string `yaml:"label"`
|
Label string `yaml:"label"`
|
||||||
Field string `yaml:"field"`
|
Field string `yaml:"field"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLabels returns labels for the given k8s role and the given ac and sdc.
|
||||||
|
func GetLabels(ac *promauth.Config, sdc *SDConfig) ([]map[string]string, error) {
|
||||||
|
cfg := &apiConfig{
|
||||||
|
Server: sdc.APIServer,
|
||||||
|
AuthConfig: ac,
|
||||||
|
Namespaces: sdc.Namespaces.Names,
|
||||||
|
Selectors: sdc.Selectors,
|
||||||
|
}
|
||||||
|
switch sdc.Role {
|
||||||
|
case "node":
|
||||||
|
return getNodesLabels(cfg)
|
||||||
|
case "service":
|
||||||
|
return getServicesLabels(cfg)
|
||||||
|
case "pod":
|
||||||
|
return getPodsLabels(cfg)
|
||||||
|
case "endpoints":
|
||||||
|
return getEndpointsLabels(cfg)
|
||||||
|
case "ingress":
|
||||||
|
return getIngressesLabels(cfg)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected `role`: %q; must be one of `node`, `service`, `pod`, `endpoints` or `ingress`; skipping it", sdc.Role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package kubernetes
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseNodeListFailure(t *testing.T) {
|
func TestParseNodeListFailure(t *testing.T) {
|
||||||
|
@ -236,7 +238,7 @@ func TestParseNodeListSuccess(t *testing.T) {
|
||||||
if meta.Name != "m01" {
|
if meta.Name != "m01" {
|
||||||
t.Fatalf("unexpected ObjectMeta.Name; got %q; want %q", meta.Name, "m01")
|
t.Fatalf("unexpected ObjectMeta.Name; got %q; want %q", meta.Name, "m01")
|
||||||
}
|
}
|
||||||
expectedLabels := getSortedLabels(map[string]string{
|
expectedLabels := discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"beta.kubernetes.io/arch": "amd64",
|
"beta.kubernetes.io/arch": "amd64",
|
||||||
"beta.kubernetes.io/os": "linux",
|
"beta.kubernetes.io/os": "linux",
|
||||||
"kubernetes.io/arch": "amd64",
|
"kubernetes.io/arch": "amd64",
|
||||||
|
@ -251,7 +253,7 @@ func TestParseNodeListSuccess(t *testing.T) {
|
||||||
if !reflect.DeepEqual(meta.Labels, expectedLabels) {
|
if !reflect.DeepEqual(meta.Labels, expectedLabels) {
|
||||||
t.Fatalf("unexpected ObjectMeta.Labels\ngot\n%v\nwant\n%v", meta.Labels, expectedLabels)
|
t.Fatalf("unexpected ObjectMeta.Labels\ngot\n%v\nwant\n%v", meta.Labels, expectedLabels)
|
||||||
}
|
}
|
||||||
expectedAnnotations := getSortedLabels(map[string]string{
|
expectedAnnotations := discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"kubeadm.alpha.kubernetes.io/cri-socket": "/var/run/dockershim.sock",
|
"kubeadm.alpha.kubernetes.io/cri-socket": "/var/run/dockershim.sock",
|
||||||
"node.alpha.kubernetes.io/ttl": "0",
|
"node.alpha.kubernetes.io/ttl": "0",
|
||||||
"volumes.kubernetes.io/controller-managed-attach-detach": "true",
|
"volumes.kubernetes.io/controller-managed-attach-detach": "true",
|
||||||
|
@ -275,8 +277,8 @@ func TestParseNodeListSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check node.appendTargetLabels()
|
// Check node.appendTargetLabels()
|
||||||
labels := getSortedLabels(node.appendTargetLabels(nil)[0])
|
labels := discoveryutils.GetSortedLabels(node.appendTargetLabels(nil)[0])
|
||||||
expectedLabels = getSortedLabels(map[string]string{
|
expectedLabels = discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"instance": "m01",
|
"instance": "m01",
|
||||||
"__address__": "172.17.0.2:10250",
|
"__address__": "172.17.0.2:10250",
|
||||||
"__meta_kubernetes_node_name": "m01",
|
"__meta_kubernetes_node_name": "m01",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParsePodListFailure(t *testing.T) {
|
func TestParsePodListFailure(t *testing.T) {
|
||||||
|
@ -240,10 +241,10 @@ func TestParsePodListSuccess(t *testing.T) {
|
||||||
labelss := pod.appendTargetLabels(nil)
|
labelss := pod.appendTargetLabels(nil)
|
||||||
var sortedLabelss [][]prompbmarshal.Label
|
var sortedLabelss [][]prompbmarshal.Label
|
||||||
for _, labels := range labelss {
|
for _, labels := range labelss {
|
||||||
sortedLabelss = append(sortedLabelss, getSortedLabels(labels))
|
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||||
}
|
}
|
||||||
expectedLabels := [][]prompbmarshal.Label{
|
expectedLabels := [][]prompbmarshal.Label{
|
||||||
getSortedLabels(map[string]string{
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"__address__": "172.17.0.2:1234",
|
"__address__": "172.17.0.2:1234",
|
||||||
|
|
||||||
"__meta_kubernetes_namespace": "kube-system",
|
"__meta_kubernetes_namespace": "kube-system",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseServiceListFailure(t *testing.T) {
|
func TestParseServiceListFailure(t *testing.T) {
|
||||||
|
@ -100,7 +101,7 @@ func TestParseServiceListSuccess(t *testing.T) {
|
||||||
if meta.Name != "kube-dns" {
|
if meta.Name != "kube-dns" {
|
||||||
t.Fatalf("unexpected ObjectMeta.Name; got %q; want %q", meta.Name, "kube-dns")
|
t.Fatalf("unexpected ObjectMeta.Name; got %q; want %q", meta.Name, "kube-dns")
|
||||||
}
|
}
|
||||||
expectedLabels := getSortedLabels(map[string]string{
|
expectedLabels := discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"k8s-app": "kube-dns",
|
"k8s-app": "kube-dns",
|
||||||
"kubernetes.io/cluster-service": "true",
|
"kubernetes.io/cluster-service": "true",
|
||||||
"kubernetes.io/name": "KubeDNS",
|
"kubernetes.io/name": "KubeDNS",
|
||||||
|
@ -108,7 +109,7 @@ func TestParseServiceListSuccess(t *testing.T) {
|
||||||
if !reflect.DeepEqual(meta.Labels, expectedLabels) {
|
if !reflect.DeepEqual(meta.Labels, expectedLabels) {
|
||||||
t.Fatalf("unexpected ObjectMeta.Labels\ngot\n%v\nwant\n%v", meta.Labels, expectedLabels)
|
t.Fatalf("unexpected ObjectMeta.Labels\ngot\n%v\nwant\n%v", meta.Labels, expectedLabels)
|
||||||
}
|
}
|
||||||
expectedAnnotations := getSortedLabels(map[string]string{
|
expectedAnnotations := discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"prometheus.io/port": "9153",
|
"prometheus.io/port": "9153",
|
||||||
"prometheus.io/scrape": "true",
|
"prometheus.io/scrape": "true",
|
||||||
})
|
})
|
||||||
|
@ -148,10 +149,10 @@ func TestParseServiceListSuccess(t *testing.T) {
|
||||||
labelss := service.appendTargetLabels(nil)
|
labelss := service.appendTargetLabels(nil)
|
||||||
var sortedLabelss [][]prompbmarshal.Label
|
var sortedLabelss [][]prompbmarshal.Label
|
||||||
for _, labels := range labelss {
|
for _, labels := range labelss {
|
||||||
sortedLabelss = append(sortedLabelss, getSortedLabels(labels))
|
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||||
}
|
}
|
||||||
expectedLabelss := [][]prompbmarshal.Label{
|
expectedLabelss := [][]prompbmarshal.Label{
|
||||||
getSortedLabels(map[string]string{
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"__address__": "kube-dns.kube-system.svc:53",
|
"__address__": "kube-dns.kube-system.svc:53",
|
||||||
"__meta_kubernetes_namespace": "kube-system",
|
"__meta_kubernetes_namespace": "kube-system",
|
||||||
"__meta_kubernetes_service_name": "kube-dns",
|
"__meta_kubernetes_service_name": "kube-dns",
|
||||||
|
@ -174,7 +175,7 @@ func TestParseServiceListSuccess(t *testing.T) {
|
||||||
"__meta_kubernetes_service_annotationpresent_prometheus_io_port": "true",
|
"__meta_kubernetes_service_annotationpresent_prometheus_io_port": "true",
|
||||||
"__meta_kubernetes_service_annotationpresent_prometheus_io_scrape": "true",
|
"__meta_kubernetes_service_annotationpresent_prometheus_io_scrape": "true",
|
||||||
}),
|
}),
|
||||||
getSortedLabels(map[string]string{
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"__address__": "kube-dns.kube-system.svc:53",
|
"__address__": "kube-dns.kube-system.svc:53",
|
||||||
"__meta_kubernetes_namespace": "kube-system",
|
"__meta_kubernetes_namespace": "kube-system",
|
||||||
"__meta_kubernetes_service_name": "kube-dns",
|
"__meta_kubernetes_service_name": "kube-dns",
|
||||||
|
@ -197,7 +198,7 @@ func TestParseServiceListSuccess(t *testing.T) {
|
||||||
"__meta_kubernetes_service_annotationpresent_prometheus_io_port": "true",
|
"__meta_kubernetes_service_annotationpresent_prometheus_io_port": "true",
|
||||||
"__meta_kubernetes_service_annotationpresent_prometheus_io_scrape": "true",
|
"__meta_kubernetes_service_annotationpresent_prometheus_io_scrape": "true",
|
||||||
}),
|
}),
|
||||||
getSortedLabels(map[string]string{
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"__address__": "kube-dns.kube-system.svc:9153",
|
"__address__": "kube-dns.kube-system.svc:9153",
|
||||||
"__meta_kubernetes_namespace": "kube-system",
|
"__meta_kubernetes_namespace": "kube-system",
|
||||||
"__meta_kubernetes_service_name": "kube-dns",
|
"__meta_kubernetes_service_name": "kube-dns",
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package discoveryutils
|
package discoveryutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SanitizeLabelName replaces anything that doesn't match
|
// SanitizeLabelName replaces anything that doesn't match
|
||||||
|
@ -25,3 +29,31 @@ func JoinHostPort(host string, port int) string {
|
||||||
portStr := strconv.Itoa(port)
|
portStr := strconv.Itoa(port)
|
||||||
return net.JoinHostPort(host, portStr)
|
return net.JoinHostPort(host, portStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SortedLabels represents sorted labels.
|
||||||
|
type SortedLabels []prompbmarshal.Label
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals JSON from data.
|
||||||
|
func (sls *SortedLabels) UnmarshalJSON(data []byte) error {
|
||||||
|
var m map[string]string
|
||||||
|
if err := json.Unmarshal(data, &m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*sls = GetSortedLabels(m)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSortedLabels returns SortedLabels built from m.
|
||||||
|
func GetSortedLabels(m map[string]string) SortedLabels {
|
||||||
|
a := make([]prompbmarshal.Label, 0, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
a = append(a, prompbmarshal.Label{
|
||||||
|
Name: k,
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(a, func(i, j int) bool {
|
||||||
|
return a[i].Name < a[j].Name
|
||||||
|
})
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
|
@ -18,10 +18,13 @@ var (
|
||||||
configCheckInterval = flag.Duration("promscrape.configCheckInterval", 0, "Interval for checking for changes in '-promscrape.config' file. "+
|
configCheckInterval = flag.Duration("promscrape.configCheckInterval", 0, "Interval for checking for changes in '-promscrape.config' file. "+
|
||||||
"By default the checking is disabled. Send SIGHUP signal in order to force config check for changes")
|
"By default the checking is disabled. Send SIGHUP signal in order to force config check for changes")
|
||||||
fileSDCheckInterval = flag.Duration("promscrape.fileSDCheckInterval", 30*time.Second, "Interval for checking for changes in 'file_sd_config'. "+
|
fileSDCheckInterval = flag.Duration("promscrape.fileSDCheckInterval", 30*time.Second, "Interval for checking for changes in 'file_sd_config'. "+
|
||||||
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config")
|
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config for details")
|
||||||
kubernetesSDCheckInterval = flag.Duration("promscrape.kubernetesSDCheckInterval", 30*time.Second, "Interval for checking for changes in Kubernetes API server. "+
|
kubernetesSDCheckInterval = flag.Duration("promscrape.kubernetesSDCheckInterval", 30*time.Second, "Interval for checking for changes in Kubernetes API server. "+
|
||||||
"This works only if `kubernetes_sd_configs` is configured in '-promscrape.config' file. "+
|
"This works only if `kubernetes_sd_configs` is configured in '-promscrape.config' file. "+
|
||||||
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config for details")
|
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config for details")
|
||||||
|
gceSDCheckInterval = flag.Duration("promscrape.gceSDCheckInterval", time.Minute, "Interval for checking for changes in gce. "+
|
||||||
|
"This works only if `gce_sd_configs` is configured in '-promscrape.config' file. "+
|
||||||
|
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config for details")
|
||||||
promscrapeConfigFile = flag.String("promscrape.config", "", "Optional path to Prometheus config file with 'scrape_configs' section containing targets to scrape. "+
|
promscrapeConfigFile = flag.String("promscrape.config", "", "Optional path to Prometheus config file with 'scrape_configs' section containing targets to scrape. "+
|
||||||
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config for details")
|
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config for details")
|
||||||
)
|
)
|
||||||
|
@ -89,6 +92,11 @@ func runScraper(configFile string, pushData func(wr *prompbmarshal.WriteRequest)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
runKubernetesSDScrapers(cfg, pushData, stopCh)
|
runKubernetesSDScrapers(cfg, pushData, stopCh)
|
||||||
}()
|
}()
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
runGCESDScrapers(cfg, pushData, stopCh)
|
||||||
|
}()
|
||||||
|
|
||||||
waitForChans:
|
waitForChans:
|
||||||
select {
|
select {
|
||||||
|
@ -194,6 +202,51 @@ var (
|
||||||
kubernetesSDReloads = metrics.NewCounter(`vm_promscrape_reloads_total{type="kubernetes_sd"}`)
|
kubernetesSDReloads = metrics.NewCounter(`vm_promscrape_reloads_total{type="kubernetes_sd"}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func runGCESDScrapers(cfg *Config, pushData func(wr *prompbmarshal.WriteRequest), stopCh <-chan struct{}) {
|
||||||
|
if cfg.gceSDConfigsCount() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sws := cfg.getGCESDScrapeWork()
|
||||||
|
ticker := time.NewTicker(*gceSDCheckInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
mustStop := false
|
||||||
|
for !mustStop {
|
||||||
|
localStopCh := make(chan struct{})
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func(sws []ScrapeWork) {
|
||||||
|
defer wg.Done()
|
||||||
|
logger.Infof("starting %d scrapers for `gce_sd_config` targets", len(sws))
|
||||||
|
gceSDTargets.Set(uint64(len(sws)))
|
||||||
|
runScrapeWorkers(sws, pushData, localStopCh)
|
||||||
|
gceSDTargets.Set(0)
|
||||||
|
logger.Infof("stopped all the %d scrapers for `gce_sd_config` targets", len(sws))
|
||||||
|
}(sws)
|
||||||
|
waitForChans:
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
swsNew := cfg.getGCESDScrapeWork()
|
||||||
|
if equalStaticConfigForScrapeWorks(swsNew, sws) {
|
||||||
|
// Nothing changed, continue waiting for updated scrape work
|
||||||
|
goto waitForChans
|
||||||
|
}
|
||||||
|
logger.Infof("restarting scrapers for changed `gce_sd_config` targets")
|
||||||
|
sws = swsNew
|
||||||
|
case <-stopCh:
|
||||||
|
mustStop = true
|
||||||
|
}
|
||||||
|
|
||||||
|
close(localStopCh)
|
||||||
|
wg.Wait()
|
||||||
|
gceSDReloads.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
gceSDTargets = metrics.NewCounter(`vm_promscrape_targets{type="gce_sd"}`)
|
||||||
|
gceSDReloads = metrics.NewCounter(`vm_promscrape_reloads_total{type="gce_sd"}`)
|
||||||
|
)
|
||||||
|
|
||||||
func runFileSDScrapers(cfg *Config, pushData func(wr *prompbmarshal.WriteRequest), stopCh <-chan struct{}) {
|
func runFileSDScrapers(cfg *Config, pushData func(wr *prompbmarshal.WriteRequest), stopCh <-chan struct{}) {
|
||||||
if cfg.fileSDConfigsCount() == 0 {
|
if cfg.fileSDConfigsCount() == 0 {
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue