mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
Adds dockerswarm sd (#818)
* adds dockerswarm service discovery https://github.com/VictoriaMetrics/VictoriaMetrics/issues/656 Following roles supported: services, tasks and nodes. Basic, token and tls auth supported. Added tests for labels generation. * added unix socket support to discovery utils Co-authored-by: Aliaksandr Valialkin <valyala@gmail.com>
This commit is contained in:
parent
7f983d461a
commit
9bd9f67718
@ -295,6 +295,7 @@ Currently the following [scrape_config](https://prometheus.io/docs/prometheus/la
|
||||
* [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)
|
||||
* [openstack_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#openstack_sd_config)
|
||||
* [dockerswarm_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dockerswarm_sd_config)
|
||||
|
||||
In the future other `*_sd_config` types will be supported.
|
||||
|
||||
|
@ -151,6 +151,8 @@ The following scrape types in [scrape_config](https://prometheus.io/docs/prometh
|
||||
* `openstack_sd_configs` - for scraping OpenStack targets.
|
||||
See [openstack_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#openstack_sd_config) for details.
|
||||
[OpenStack identity API v3](https://docs.openstack.org/api-ref/identity/v3/) is supported only.
|
||||
* `dockerswarm_sd_configs` - for scraping dockerswarm targets.
|
||||
See [dockerswarm_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dockerswarm_sd_config) for details.
|
||||
|
||||
File feature requests at [our issue tracker](https://github.com/VictoriaMetrics/VictoriaMetrics/issues) if you need other service discovery mechanisms to be supported by `vmagent`.
|
||||
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/consul"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/dns"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/dockerswarm"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/ec2"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/gce"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kubernetes"
|
||||
@ -72,6 +73,7 @@ type ScrapeConfig struct {
|
||||
KubernetesSDConfigs []kubernetes.SDConfig `yaml:"kubernetes_sd_configs"`
|
||||
OpenStackSDConfigs []openstack.SDConfig `yaml:"openstack_sd_configs"`
|
||||
ConsulSDConfigs []consul.SDConfig `yaml:"consul_sd_configs"`
|
||||
DockerSwarmConfigs []dockerswarm.SDConfig `yaml:"dockerswarm_sd_configs"`
|
||||
DNSSDConfigs []dns.SDConfig `yaml:"dns_sd_configs"`
|
||||
EC2SDConfigs []ec2.SDConfig `yaml:"ec2_sd_configs"`
|
||||
GCESDConfigs []gce.SDConfig `yaml:"gce_sd_configs"`
|
||||
@ -231,6 +233,34 @@ func (cfg *Config) getOpenStackSDScrapeWork(prev []ScrapeWork) []ScrapeWork {
|
||||
return dst
|
||||
}
|
||||
|
||||
// getDockerSwarmSDScrapeWork returns `dockerswarm_sd_configs` ScrapeWork from cfg.
|
||||
func (cfg *Config) getDockerSwarmSDScrapeWork(prev []ScrapeWork) []ScrapeWork {
|
||||
swsPrevByJob := getSWSByJob(prev)
|
||||
var dst []ScrapeWork
|
||||
for i := range cfg.ScrapeConfigs {
|
||||
sc := &cfg.ScrapeConfigs[i]
|
||||
dstLen := len(dst)
|
||||
ok := true
|
||||
for j := range sc.DockerSwarmConfigs {
|
||||
sdc := &sc.DockerSwarmConfigs[j]
|
||||
var okLocal bool
|
||||
dst, okLocal = appendDockerSwarmScrapeWork(dst, sdc, cfg.baseDir, sc.swc)
|
||||
if ok {
|
||||
ok = okLocal
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
swsPrev := swsPrevByJob[sc.swc.jobName]
|
||||
if len(swsPrev) > 0 {
|
||||
logger.Errorf("there were errors when discovering dockerswarm targets for job %q, so preserving the previous targets", sc.swc.jobName)
|
||||
dst = append(dst[:dstLen], swsPrev...)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// getConsulSDScrapeWork returns `consul_sd_configs` ScrapeWork from cfg.
|
||||
func (cfg *Config) getConsulSDScrapeWork(prev []ScrapeWork) []ScrapeWork {
|
||||
swsPrevByJob := getSWSByJob(prev)
|
||||
@ -483,6 +513,15 @@ func appendOpenstackScrapeWork(dst []ScrapeWork, sdc *openstack.SDConfig, baseDi
|
||||
return appendScrapeWorkForTargetLabels(dst, swc, targetLabels, "openstack_sd_config"), true
|
||||
}
|
||||
|
||||
func appendDockerSwarmScrapeWork(dst []ScrapeWork, sdc *dockerswarm.SDConfig, baseDir string, swc *scrapeWorkConfig) ([]ScrapeWork, bool) {
|
||||
targetLabels, err := dockerswarm.GetLabels(sdc, baseDir)
|
||||
if err != nil {
|
||||
logger.Errorf("error when discovering dockerswarm targets for `job_name` %q: %s; skipping it", swc.jobName, err)
|
||||
return dst, false
|
||||
}
|
||||
return appendScrapeWorkForTargetLabels(dst, swc, targetLabels, "dockerswarm_sd_config"), true
|
||||
}
|
||||
|
||||
func appendConsulScrapeWork(dst []ScrapeWork, sdc *consul.SDConfig, baseDir string, swc *scrapeWorkConfig) ([]ScrapeWork, bool) {
|
||||
targetLabels, err := consul.GetLabels(sdc, baseDir)
|
||||
if err != nil {
|
||||
|
39
lib/promscrape/discovery/dockerswarm/api.go
Normal file
39
lib/promscrape/discovery/dockerswarm/api.go
Normal file
@ -0,0 +1,39 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
var configMap = discoveryutils.NewConfigMap()
|
||||
|
||||
type apiConfig struct {
|
||||
client *discoveryutils.Client
|
||||
port int
|
||||
}
|
||||
|
||||
func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||
v, err := configMap.Get(sdc, func() (interface{}, error) { return newAPIConfig(sdc, baseDir) })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.(*apiConfig), nil
|
||||
}
|
||||
|
||||
func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||
cfg := &apiConfig{
|
||||
port: sdc.Port,
|
||||
}
|
||||
config, err := promauth.NewConfig(baseDir, sdc.BasicAuth, sdc.BearerToken, sdc.BearerTokenFile, sdc.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := discoveryutils.NewClient(sdc.Host, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create HTTP client for %q: %w", sdc.Host, err)
|
||||
}
|
||||
cfg.client = client
|
||||
return cfg, nil
|
||||
}
|
51
lib/promscrape/discovery/dockerswarm/dockerswarm.go
Normal file
51
lib/promscrape/discovery/dockerswarm/dockerswarm.go
Normal file
@ -0,0 +1,51 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||
)
|
||||
|
||||
// SDConfig represents docker swarm service discovery configuration
|
||||
//
|
||||
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dockerswarm_sd_config
|
||||
type SDConfig struct {
|
||||
Host string `yaml:"host"`
|
||||
Role string `yaml:"role"`
|
||||
Port int `yaml:"port"`
|
||||
TLSConfig *promauth.TLSConfig `yaml:"tls_config"`
|
||||
BasicAuth *promauth.BasicAuthConfig `yaml:"basic_auth"`
|
||||
BearerToken string `yaml:"bearer_token"`
|
||||
BearerTokenFile string `yaml:"bearer_token_file"`
|
||||
}
|
||||
|
||||
// joinLabels adds labels to destination from source with given key from destination matching given value.
|
||||
func joinLabels(source []map[string]string, destination map[string]string, key, value string) map[string]string {
|
||||
for _, sourceLabels := range source {
|
||||
if sourceLabels[key] == value {
|
||||
for k, v := range sourceLabels {
|
||||
destination[k] = v
|
||||
}
|
||||
return destination
|
||||
}
|
||||
}
|
||||
return destination
|
||||
}
|
||||
|
||||
// GetLabels returns gce labels according to sdc.
|
||||
func GetLabels(sdc *SDConfig, baseDir string) ([]map[string]string, error) {
|
||||
cfg, err := getAPIConfig(sdc, baseDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get API config: %w", err)
|
||||
}
|
||||
switch sdc.Role {
|
||||
case "tasks":
|
||||
return getTasksLabels(cfg)
|
||||
case "services":
|
||||
return getServicesLabels(cfg)
|
||||
case "nodes":
|
||||
return getNodesLabels(cfg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected `role`: %q; must be one of `tasks`, `services` or `nodes`; skipping it", sdc.Role)
|
||||
}
|
||||
}
|
61
lib/promscrape/discovery/dockerswarm/network.go
Normal file
61
lib/promscrape/discovery/dockerswarm/network.go
Normal file
@ -0,0 +1,61 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
// See https://docs.docker.com/engine/api/v1.40/#tag/Network
|
||||
type network struct {
|
||||
ID string
|
||||
Name string
|
||||
Scope string
|
||||
Internal bool
|
||||
Ingress bool
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
func getNetworksLabels(cfg *apiConfig) ([]map[string]string, error) {
|
||||
networks, err := getNetworks(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addNetworkLabels(networks), nil
|
||||
}
|
||||
|
||||
func getNetworks(cfg *apiConfig) ([]network, error) {
|
||||
resp, err := cfg.client.GetAPIResponse("/networks")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query dockerswarm api for networks: %w", err)
|
||||
}
|
||||
return parseNetworks(resp)
|
||||
}
|
||||
|
||||
func parseNetworks(data []byte) ([]network, error) {
|
||||
var networks []network
|
||||
if err := json.Unmarshal(data, &networks); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse networks: %w", err)
|
||||
}
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
func addNetworkLabels(networks []network) []map[string]string {
|
||||
var ms []map[string]string
|
||||
for _, network := range networks {
|
||||
m := map[string]string{
|
||||
"__meta_dockerswarm_network_id": network.ID,
|
||||
"__meta_dockerswarm_network_name": network.Name,
|
||||
"__meta_dockerswarm_network_scope": network.Scope,
|
||||
"__meta_dockerswarm_network_internal": strconv.FormatBool(network.Internal),
|
||||
"__meta_dockerswarm_network_ingress": strconv.FormatBool(network.Ingress),
|
||||
}
|
||||
for k, v := range network.Labels {
|
||||
m["__meta_dockerswarm_network_label_"+discoveryutils.SanitizeLabelName(k)] = v
|
||||
}
|
||||
ms = append(ms, m)
|
||||
}
|
||||
return ms
|
||||
}
|
166
lib/promscrape/discovery/dockerswarm/network_test.go
Normal file
166
lib/promscrape/discovery/dockerswarm/network_test.go
Normal file
@ -0,0 +1,166 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
func Test_addNetworkLabels(t *testing.T) {
|
||||
type args struct {
|
||||
networks []network
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want [][]prompbmarshal.Label
|
||||
}{
|
||||
{
|
||||
name: "ingress network",
|
||||
args: args{
|
||||
networks: []network{
|
||||
{
|
||||
ID: "qs0hog6ldlei9ct11pr3c77v1",
|
||||
Ingress: true,
|
||||
Scope: "swarm",
|
||||
Name: "ingress",
|
||||
Labels: map[string]string{
|
||||
"key1": "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: [][]prompbmarshal.Label{
|
||||
discoveryutils.GetSortedLabels(map[string]string{
|
||||
"__meta_dockerswarm_network_id": "qs0hog6ldlei9ct11pr3c77v1",
|
||||
"__meta_dockerswarm_network_ingress": "true",
|
||||
"__meta_dockerswarm_network_internal": "false",
|
||||
"__meta_dockerswarm_network_label_key1": "value1",
|
||||
"__meta_dockerswarm_network_name": "ingress",
|
||||
"__meta_dockerswarm_network_scope": "swarm",
|
||||
})},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := addNetworkLabels(tt.args.networks)
|
||||
var sortedLabelss [][]prompbmarshal.Label
|
||||
for _, labels := range got {
|
||||
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||
}
|
||||
if !reflect.DeepEqual(sortedLabelss, tt.want) {
|
||||
t.Errorf("addNetworkLabels() \ngot %v, \nwant %v", sortedLabelss, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseNetworks(t *testing.T) {
|
||||
type args struct {
|
||||
data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []network
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "parse two networks",
|
||||
args: args{
|
||||
data: []byte(`[
|
||||
{
|
||||
"Name": "ingress",
|
||||
"Id": "qs0hog6ldlei9ct11pr3c77v1",
|
||||
"Created": "2020-10-06T08:39:58.957083331Z",
|
||||
"Scope": "swarm",
|
||||
"Driver": "overlay",
|
||||
"EnableIPv6": false,
|
||||
"IPAM": {
|
||||
"Driver": "default",
|
||||
"Options": null,
|
||||
"Config": [
|
||||
{
|
||||
"Subnet": "10.0.0.0/24",
|
||||
"Gateway": "10.0.0.1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Internal": false,
|
||||
"Attachable": false,
|
||||
"Ingress": true,
|
||||
"ConfigFrom": {
|
||||
"Network": ""
|
||||
},
|
||||
"ConfigOnly": false,
|
||||
"Containers": null,
|
||||
"Options": {
|
||||
"com.docker.network.driver.overlay.vxlanid_list": "4096"
|
||||
},
|
||||
"Labels": {
|
||||
"key1": "value1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "host",
|
||||
"Id": "317f0384d7e5f5c26304a0b04599f9f54bc08def4d0535059ece89955e9c4b7b",
|
||||
"Created": "2020-10-06T08:39:52.843373136Z",
|
||||
"Scope": "local",
|
||||
"Driver": "host",
|
||||
"EnableIPv6": false,
|
||||
"IPAM": {
|
||||
"Driver": "default",
|
||||
"Options": null,
|
||||
"Config": []
|
||||
},
|
||||
"Internal": false,
|
||||
"Attachable": false,
|
||||
"Ingress": false,
|
||||
"ConfigFrom": {
|
||||
"Network": ""
|
||||
},
|
||||
"ConfigOnly": false,
|
||||
"Containers": {},
|
||||
"Options": {},
|
||||
"Labels": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
]`),
|
||||
},
|
||||
want: []network{
|
||||
{
|
||||
ID: "qs0hog6ldlei9ct11pr3c77v1",
|
||||
Ingress: true,
|
||||
Scope: "swarm",
|
||||
Name: "ingress",
|
||||
Labels: map[string]string{
|
||||
"key1": "value1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "317f0384d7e5f5c26304a0b04599f9f54bc08def4d0535059ece89955e9c4b7b",
|
||||
Scope: "local",
|
||||
Name: "host",
|
||||
Labels: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseNetworks(tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseNetworks() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseNetworks() \ngot %v, \nwant %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
90
lib/promscrape/discovery/dockerswarm/nodes.go
Normal file
90
lib/promscrape/discovery/dockerswarm/nodes.go
Normal file
@ -0,0 +1,90 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
// See https://docs.docker.com/engine/api/v1.40/#tag/Node
|
||||
type node struct {
|
||||
ID string
|
||||
Spec struct {
|
||||
Labels map[string]string
|
||||
Role string
|
||||
Availability string
|
||||
}
|
||||
Description struct {
|
||||
Hostname string
|
||||
Platform struct {
|
||||
Architecture string
|
||||
OS string
|
||||
}
|
||||
Engine struct {
|
||||
EngineVersion string
|
||||
}
|
||||
}
|
||||
Status struct {
|
||||
State string
|
||||
Message string
|
||||
Addr string
|
||||
}
|
||||
ManagerStatus *struct {
|
||||
Leader bool
|
||||
Reachability string
|
||||
Addr string
|
||||
}
|
||||
}
|
||||
|
||||
func getNodesLabels(cfg *apiConfig) ([]map[string]string, error) {
|
||||
nodes, err := getNodes(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addNodeLabels(nodes, cfg.port), nil
|
||||
}
|
||||
|
||||
func getNodes(cfg *apiConfig) ([]node, error) {
|
||||
resp, err := cfg.client.GetAPIResponse("/nodes")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query dockerswarm api for nodes: %w", err)
|
||||
}
|
||||
return parseNodes(resp)
|
||||
}
|
||||
|
||||
func parseNodes(data []byte) ([]node, error) {
|
||||
var nodes []node
|
||||
if err := json.Unmarshal(data, &nodes); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse nodes: %w", err)
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func addNodeLabels(nodes []node, port int) []map[string]string {
|
||||
var ms []map[string]string
|
||||
for _, node := range nodes {
|
||||
m := map[string]string{
|
||||
"__address__": discoveryutils.JoinHostPort(node.Status.Addr, port),
|
||||
"__meta_dockerswarm_node_id": node.ID,
|
||||
"__meta_dockerswarm_node_address": node.Status.Addr,
|
||||
"__meta_dockerswarm_node_availability": node.Spec.Availability,
|
||||
"__meta_dockerswarm_node_engine_version": node.Description.Engine.EngineVersion,
|
||||
"__meta_dockerswarm_node_hostname": node.Description.Hostname,
|
||||
"__meta_dockerswarm_node_platform_architecture": node.Description.Platform.Architecture,
|
||||
"__meta_dockerswarm_node_platform_os": node.Description.Platform.OS,
|
||||
"__meta_dockerswarm_node_role": node.Spec.Role,
|
||||
"__meta_dockerswarm_node_status": node.Status.State,
|
||||
}
|
||||
if node.ManagerStatus != nil {
|
||||
m["__meta_dockerswarm_node_manager_address"] = node.ManagerStatus.Addr
|
||||
m["__meta_dockerswarm_node_manager_manager_reachability"] = node.ManagerStatus.Reachability
|
||||
m["__meta_dockerswarm_node_manager_leader"] = fmt.Sprintf("%t", node.ManagerStatus.Leader)
|
||||
}
|
||||
for k, v := range node.Spec.Labels {
|
||||
m["__meta_dockerswarm_node_label_"+discoveryutils.SanitizeLabelName(k)] = v
|
||||
}
|
||||
ms = append(ms, m)
|
||||
}
|
||||
return ms
|
||||
}
|
185
lib/promscrape/discovery/dockerswarm/nodes_test.go
Normal file
185
lib/promscrape/discovery/dockerswarm/nodes_test.go
Normal file
@ -0,0 +1,185 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
func Test_parseNodes(t *testing.T) {
|
||||
type args struct {
|
||||
data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []node
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "parse ok",
|
||||
args: args{
|
||||
data: []byte(`[
|
||||
{
|
||||
"ID": "qauwmifceyvqs0sipvzu8oslu",
|
||||
"Version": {
|
||||
"Index": 16
|
||||
},
|
||||
"Spec": {
|
||||
"Role": "manager",
|
||||
"Availability": "active"
|
||||
},
|
||||
"Description": {
|
||||
"Hostname": "ip-172-31-40-97",
|
||||
"Platform": {
|
||||
"Architecture": "x86_64",
|
||||
"OS": "linux"
|
||||
},
|
||||
"Resources": {
|
||||
"NanoCPUs": 1000000000,
|
||||
"MemoryBytes": 1026158592
|
||||
},
|
||||
"Engine": {
|
||||
"EngineVersion": "19.03.11"
|
||||
}
|
||||
},
|
||||
"Status": {
|
||||
"State": "ready",
|
||||
"Addr": "172.31.40.97"
|
||||
}
|
||||
}
|
||||
]
|
||||
`),
|
||||
},
|
||||
want: []node{
|
||||
{
|
||||
ID: "qauwmifceyvqs0sipvzu8oslu",
|
||||
Spec: struct {
|
||||
Labels map[string]string
|
||||
Role string
|
||||
Availability string
|
||||
}{Role: "manager", Availability: "active"},
|
||||
Status: struct {
|
||||
State string
|
||||
Message string
|
||||
Addr string
|
||||
}{State: "ready", Addr: "172.31.40.97"},
|
||||
Description: struct {
|
||||
Hostname string
|
||||
Platform struct {
|
||||
Architecture string
|
||||
OS string
|
||||
}
|
||||
Engine struct{ EngineVersion string }
|
||||
}{
|
||||
Hostname: "ip-172-31-40-97",
|
||||
Platform: struct {
|
||||
Architecture string
|
||||
OS string
|
||||
}{
|
||||
Architecture: "x86_64",
|
||||
OS: "linux",
|
||||
},
|
||||
Engine: struct{ EngineVersion string }{
|
||||
EngineVersion: "19.03.11",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseNodes(tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseNodes() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseNodes() \ngot %v, \nwant %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_addNodeLabels(t *testing.T) {
|
||||
type args struct {
|
||||
nodes []node
|
||||
port int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want [][]prompbmarshal.Label
|
||||
}{
|
||||
{
|
||||
name: "add labels to one node",
|
||||
args: args{
|
||||
nodes: []node{
|
||||
{
|
||||
ID: "qauwmifceyvqs0sipvzu8oslu",
|
||||
Spec: struct {
|
||||
Labels map[string]string
|
||||
Role string
|
||||
Availability string
|
||||
}{Role: "manager", Availability: "active"},
|
||||
Status: struct {
|
||||
State string
|
||||
Message string
|
||||
Addr string
|
||||
}{State: "ready", Addr: "172.31.40.97"},
|
||||
Description: struct {
|
||||
Hostname string
|
||||
Platform struct {
|
||||
Architecture string
|
||||
OS string
|
||||
}
|
||||
Engine struct{ EngineVersion string }
|
||||
}{
|
||||
Hostname: "ip-172-31-40-97",
|
||||
Platform: struct {
|
||||
Architecture string
|
||||
OS string
|
||||
}{
|
||||
Architecture: "x86_64",
|
||||
OS: "linux",
|
||||
},
|
||||
Engine: struct{ EngineVersion string }{
|
||||
EngineVersion: "19.03.11",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
port: 9100,
|
||||
},
|
||||
want: [][]prompbmarshal.Label{
|
||||
discoveryutils.GetSortedLabels(map[string]string{
|
||||
"__address__": "172.31.40.97:9100",
|
||||
"__meta_dockerswarm_node_address": "172.31.40.97",
|
||||
"__meta_dockerswarm_node_availability": "active",
|
||||
"__meta_dockerswarm_node_engine_version": "19.03.11",
|
||||
"__meta_dockerswarm_node_hostname": "ip-172-31-40-97",
|
||||
"__meta_dockerswarm_node_id": "qauwmifceyvqs0sipvzu8oslu",
|
||||
"__meta_dockerswarm_node_platform_architecture": "x86_64",
|
||||
"__meta_dockerswarm_node_platform_os": "linux",
|
||||
"__meta_dockerswarm_node_role": "manager",
|
||||
"__meta_dockerswarm_node_status": "ready",
|
||||
})},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := addNodeLabels(tt.args.nodes, tt.args.port)
|
||||
|
||||
var sortedLabelss [][]prompbmarshal.Label
|
||||
for _, labels := range got {
|
||||
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||
}
|
||||
if !reflect.DeepEqual(sortedLabelss, tt.want) {
|
||||
t.Errorf("addNodeLabels() \ngot %v, \nwant %v", sortedLabelss, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
139
lib/promscrape/discovery/dockerswarm/services.go
Normal file
139
lib/promscrape/discovery/dockerswarm/services.go
Normal file
@ -0,0 +1,139 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
// https://docs.docker.com/engine/api/v1.40/#tag/Service
|
||||
type service struct {
|
||||
ID string
|
||||
Spec struct {
|
||||
Labels map[string]string
|
||||
Name string
|
||||
TaskTemplate struct {
|
||||
ContainerSpec struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}
|
||||
}
|
||||
Mode struct {
|
||||
Global interface{}
|
||||
Replicated interface{}
|
||||
}
|
||||
}
|
||||
UpdateStatus *struct {
|
||||
State string
|
||||
}
|
||||
Endpoint struct {
|
||||
Ports []portConfig
|
||||
VirtualIPs []struct {
|
||||
NetworkID string
|
||||
Addr string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type portConfig struct {
|
||||
Protocol string
|
||||
Name string
|
||||
PublishMode string
|
||||
PublishedPort int
|
||||
}
|
||||
|
||||
func getServicesLabels(cfg *apiConfig) ([]map[string]string, error) {
|
||||
services, err := getServices(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
networksLabels, err := getNetworksLabels(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addServicesLabels(services, networksLabels, cfg.port), nil
|
||||
}
|
||||
|
||||
func getServices(cfg *apiConfig) ([]service, error) {
|
||||
data, err := cfg.client.GetAPIResponse("/services")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query dockerswarm api for services: %w", err)
|
||||
}
|
||||
return parseServicesResponse(data)
|
||||
}
|
||||
|
||||
func parseServicesResponse(data []byte) ([]service, error) {
|
||||
var services []service
|
||||
if err := json.Unmarshal(data, &services); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse services: %w", err)
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func getServiceMode(svc service) string {
|
||||
if svc.Spec.Mode.Global != nil {
|
||||
return "global"
|
||||
}
|
||||
if svc.Spec.Mode.Replicated != nil {
|
||||
return "replicated"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func addServicesLabels(services []service, networksLabels []map[string]string, port int) []map[string]string {
|
||||
var ms []map[string]string
|
||||
for _, service := range services {
|
||||
m := map[string]string{
|
||||
"__meta_dockerswarm_service_id": service.ID,
|
||||
"__meta_dockerswarm_service_name": service.Spec.Name,
|
||||
"__meta_dockerswarm_service_task_container_hostname": service.Spec.TaskTemplate.ContainerSpec.Hostname,
|
||||
"__meta_dockerswarm_service_task_container_image": service.Spec.TaskTemplate.ContainerSpec.Image,
|
||||
"__meta_dockerswarm_service_mode": getServiceMode(service),
|
||||
}
|
||||
if service.UpdateStatus != nil {
|
||||
m["__meta_dockerswarm_service_updating_status"] = service.UpdateStatus.State
|
||||
}
|
||||
for k, v := range service.Spec.Labels {
|
||||
m["__meta_dockerswarm_service_label_"+discoveryutils.SanitizeLabelName(k)] = v
|
||||
}
|
||||
for _, vip := range service.Endpoint.VirtualIPs {
|
||||
var added bool
|
||||
ip, _, err := net.ParseCIDR(vip.Addr)
|
||||
if err != nil {
|
||||
logger.Errorf("cannot parse: %q as cidr for service label add, err: %v", vip.Addr, err)
|
||||
continue
|
||||
}
|
||||
for _, ep := range service.Endpoint.Ports {
|
||||
if ep.Protocol != "tcp" {
|
||||
continue
|
||||
}
|
||||
lbls := map[string]string{
|
||||
"__meta_dockerswarm_service_endpoint_port_name": ep.Name,
|
||||
"__meta_dockerswarm_service_endpoint_port_publish_mode": ep.PublishMode,
|
||||
"__address__": discoveryutils.JoinHostPort(ip.String(), ep.PublishedPort),
|
||||
}
|
||||
for k, v := range m {
|
||||
lbls[k] = v
|
||||
}
|
||||
lbls = joinLabels(networksLabels, lbls, "__meta_dockerswarm_network_id", vip.NetworkID)
|
||||
added = true
|
||||
ms = append(ms, lbls)
|
||||
}
|
||||
if !added {
|
||||
lbls := make(map[string]string, len(m))
|
||||
for k, v := range m {
|
||||
lbls[k] = v
|
||||
}
|
||||
lbls = joinLabels(networksLabels, lbls, "__meta_dockerswarm_network_id", vip.NetworkID)
|
||||
lbls["__address__"] = discoveryutils.JoinHostPort(ip.String(), port)
|
||||
ms = append(ms, lbls)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return ms
|
||||
}
|
293
lib/promscrape/discovery/dockerswarm/services_test.go
Normal file
293
lib/promscrape/discovery/dockerswarm/services_test.go
Normal file
@ -0,0 +1,293 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
func Test_parseServicesResponse(t *testing.T) {
|
||||
type args struct {
|
||||
data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []service
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "parse ok",
|
||||
args: args{
|
||||
data: []byte(`[
|
||||
{
|
||||
"ID": "tgsci5gd31aai3jyudv98pqxf",
|
||||
"Version": {
|
||||
"Index": 25
|
||||
},
|
||||
"CreatedAt": "2020-10-06T11:17:31.948808444Z",
|
||||
"UpdatedAt": "2020-10-06T11:17:31.950195138Z",
|
||||
"Spec": {
|
||||
"Name": "redis2",
|
||||
"Labels": {},
|
||||
"TaskTemplate": {
|
||||
"ContainerSpec": {
|
||||
"Image": "redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842",
|
||||
"Init": false,
|
||||
"DNSConfig": {},
|
||||
"Isolation": "default"
|
||||
},
|
||||
"Resources": {
|
||||
"Limits": {},
|
||||
"Reservations": {}
|
||||
}
|
||||
},
|
||||
"Mode": {
|
||||
"Replicated": {}
|
||||
},
|
||||
"EndpointSpec": {
|
||||
"Mode": "vip",
|
||||
"Ports": [
|
||||
{
|
||||
"Protocol": "tcp",
|
||||
"TargetPort": 6379,
|
||||
"PublishedPort": 8081,
|
||||
"PublishMode": "ingress"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Endpoint": {
|
||||
"Spec": {
|
||||
"Mode": "vip",
|
||||
"Ports": [
|
||||
{
|
||||
"Protocol": "tcp",
|
||||
"TargetPort": 6379,
|
||||
"PublishedPort": 8081,
|
||||
"PublishMode": "ingress"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Ports": [
|
||||
{
|
||||
"Protocol": "tcp",
|
||||
"TargetPort": 6379,
|
||||
"PublishedPort": 8081,
|
||||
"PublishMode": "ingress"
|
||||
}
|
||||
],
|
||||
"VirtualIPs": [
|
||||
{
|
||||
"NetworkID": "qs0hog6ldlei9ct11pr3c77v1",
|
||||
"Addr": "10.0.0.3/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]`),
|
||||
},
|
||||
want: []service{
|
||||
{
|
||||
ID: "tgsci5gd31aai3jyudv98pqxf",
|
||||
Spec: struct {
|
||||
Labels map[string]string
|
||||
Name string
|
||||
TaskTemplate struct {
|
||||
ContainerSpec struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}
|
||||
}
|
||||
Mode struct {
|
||||
Global interface{}
|
||||
Replicated interface{}
|
||||
}
|
||||
}{
|
||||
Labels: map[string]string{},
|
||||
Name: "redis2",
|
||||
TaskTemplate: struct {
|
||||
ContainerSpec struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}
|
||||
}{
|
||||
ContainerSpec: struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}{
|
||||
Hostname: "",
|
||||
Image: "redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842",
|
||||
},
|
||||
},
|
||||
Mode: struct {
|
||||
Global interface{}
|
||||
Replicated interface{}
|
||||
}{
|
||||
Replicated: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
Endpoint: struct {
|
||||
Ports []portConfig
|
||||
VirtualIPs []struct {
|
||||
NetworkID string
|
||||
Addr string
|
||||
}
|
||||
}{Ports: []portConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PublishMode: "ingress",
|
||||
PublishedPort: 8081,
|
||||
},
|
||||
}, VirtualIPs: []struct {
|
||||
NetworkID string
|
||||
Addr string
|
||||
}{
|
||||
{
|
||||
NetworkID: "qs0hog6ldlei9ct11pr3c77v1",
|
||||
Addr: "10.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseServicesResponse(tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseServicesResponse() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseServicesResponse() \ngot %v, \nwant %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_addServicesLabels(t *testing.T) {
|
||||
type args struct {
|
||||
services []service
|
||||
networksLabels []map[string]string
|
||||
port int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want [][]prompbmarshal.Label
|
||||
}{
|
||||
{
|
||||
name: "add 2 services with network labels join",
|
||||
args: args{
|
||||
port: 9100,
|
||||
networksLabels: []map[string]string{
|
||||
{
|
||||
"__meta_dockerswarm_network_id": "qs0hog6ldlei9ct11pr3c77v1",
|
||||
"__meta_dockerswarm_network_ingress": "true",
|
||||
"__meta_dockerswarm_network_internal": "false",
|
||||
"__meta_dockerswarm_network_label_key1": "value1",
|
||||
"__meta_dockerswarm_network_name": "ingress",
|
||||
"__meta_dockerswarm_network_scope": "swarm",
|
||||
},
|
||||
},
|
||||
services: []service{
|
||||
{
|
||||
ID: "tgsci5gd31aai3jyudv98pqxf",
|
||||
Spec: struct {
|
||||
Labels map[string]string
|
||||
Name string
|
||||
TaskTemplate struct {
|
||||
ContainerSpec struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}
|
||||
}
|
||||
Mode struct {
|
||||
Global interface{}
|
||||
Replicated interface{}
|
||||
}
|
||||
}{
|
||||
Labels: map[string]string{},
|
||||
Name: "redis2",
|
||||
TaskTemplate: struct {
|
||||
ContainerSpec struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}
|
||||
}{
|
||||
ContainerSpec: struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}{
|
||||
Hostname: "node1",
|
||||
Image: "redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842",
|
||||
},
|
||||
},
|
||||
Mode: struct {
|
||||
Global interface{}
|
||||
Replicated interface{}
|
||||
}{
|
||||
Replicated: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
Endpoint: struct {
|
||||
Ports []portConfig
|
||||
VirtualIPs []struct {
|
||||
NetworkID string
|
||||
Addr string
|
||||
}
|
||||
}{Ports: []portConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
Name: "redis",
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
}, VirtualIPs: []struct {
|
||||
NetworkID string
|
||||
Addr string
|
||||
}{
|
||||
{
|
||||
NetworkID: "qs0hog6ldlei9ct11pr3c77v1",
|
||||
Addr: "10.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: [][]prompbmarshal.Label{
|
||||
discoveryutils.GetSortedLabels(map[string]string{
|
||||
"__address__": "10.0.0.3:0",
|
||||
"__meta_dockerswarm_network_id": "qs0hog6ldlei9ct11pr3c77v1",
|
||||
"__meta_dockerswarm_network_ingress": "true",
|
||||
"__meta_dockerswarm_network_internal": "false",
|
||||
"__meta_dockerswarm_network_label_key1": "value1",
|
||||
"__meta_dockerswarm_network_name": "ingress",
|
||||
"__meta_dockerswarm_network_scope": "swarm",
|
||||
"__meta_dockerswarm_service_endpoint_port_name": "redis",
|
||||
"__meta_dockerswarm_service_endpoint_port_publish_mode": "ingress",
|
||||
"__meta_dockerswarm_service_id": "tgsci5gd31aai3jyudv98pqxf",
|
||||
"__meta_dockerswarm_service_mode": "replicated",
|
||||
"__meta_dockerswarm_service_name": "redis2",
|
||||
"__meta_dockerswarm_service_task_container_hostname": "node1",
|
||||
"__meta_dockerswarm_service_task_container_image": "redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842",
|
||||
})},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := addServicesLabels(tt.args.services, tt.args.networksLabels, tt.args.port)
|
||||
var sortedLabelss [][]prompbmarshal.Label
|
||||
for _, labels := range got {
|
||||
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||
}
|
||||
if !reflect.DeepEqual(sortedLabelss, tt.want) {
|
||||
t.Errorf("addServicesLabels() \ngot %v, \nwant %v", sortedLabelss, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
149
lib/promscrape/discovery/dockerswarm/tasks.go
Normal file
149
lib/promscrape/discovery/dockerswarm/tasks.go
Normal file
@ -0,0 +1,149 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
// See https://docs.docker.com/engine/api/v1.40/#tag/Task
|
||||
type task struct {
|
||||
ID string
|
||||
ServiceID string
|
||||
NodeID string
|
||||
Labels map[string]string
|
||||
DesiredState string
|
||||
NetworksAttachments []struct {
|
||||
Addresses []string
|
||||
Network struct {
|
||||
ID string
|
||||
}
|
||||
}
|
||||
Status struct {
|
||||
State string
|
||||
ContainerStatus *struct {
|
||||
ContainerID string
|
||||
}
|
||||
PortStatus struct {
|
||||
Ports []portConfig
|
||||
}
|
||||
}
|
||||
Slot int
|
||||
}
|
||||
|
||||
func getTasksLabels(cfg *apiConfig) ([]map[string]string, error) {
|
||||
tasks, err := getTasks(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services, err := getServices(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
networkLabels, err := getNetworksLabels(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svcLabels := addServicesLabels(services, networkLabels, cfg.port)
|
||||
nodeLabels, err := getNodesLabels(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addTasksLabels(tasks, nodeLabels, svcLabels, networkLabels, services, cfg.port), nil
|
||||
}
|
||||
|
||||
func getTasks(cfg *apiConfig) ([]task, error) {
|
||||
resp, err := cfg.client.GetAPIResponse("/tasks")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query dockerswarm api for tasks: %w", err)
|
||||
}
|
||||
return parseTasks(resp)
|
||||
}
|
||||
|
||||
func parseTasks(data []byte) ([]task, error) {
|
||||
var tasks []task
|
||||
if err := json.Unmarshal(data, &tasks); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse tasks: %w", err)
|
||||
}
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func addTasksLabels(tasks []task, nodesLabels, servicesLabels, networksLabels []map[string]string, services []service, port int) []map[string]string {
|
||||
var ms []map[string]string
|
||||
for _, task := range tasks {
|
||||
m := map[string]string{
|
||||
"__meta_dockerswarm_task_id": task.ID,
|
||||
"__meta_dockerswarm_task_desired_state": task.DesiredState,
|
||||
"__meta_dockerswarm_task_state": task.Status.State,
|
||||
"__meta_dockerswarm_task_slot": strconv.Itoa(task.Slot),
|
||||
}
|
||||
if task.Status.ContainerStatus != nil {
|
||||
m["__meta_dockerswarm_task_container_id"] = task.Status.ContainerStatus.ContainerID
|
||||
}
|
||||
for k, v := range task.Labels {
|
||||
m["__meta_dockerswarm_task_label_"+discoveryutils.SanitizeLabelName(k)] = v
|
||||
}
|
||||
var svcPorts []portConfig
|
||||
for i, v := range services {
|
||||
if v.ID == task.ServiceID {
|
||||
svcPorts = services[i].Endpoint.Ports
|
||||
break
|
||||
}
|
||||
}
|
||||
m = joinLabels(servicesLabels, m, "__meta_dockerswarm_service_id", task.ServiceID)
|
||||
m = joinLabels(nodesLabels, m, "__meta_dockerswarm_node_id", task.NodeID)
|
||||
|
||||
for _, port := range task.Status.PortStatus.Ports {
|
||||
if port.Protocol != "tcp" {
|
||||
continue
|
||||
}
|
||||
lbls := make(map[string]string, len(m))
|
||||
lbls["__meta_dockerswarm_task_port_publish_mode"] = port.PublishMode
|
||||
lbls["__address__"] = discoveryutils.JoinHostPort(m["__meta_dockerswarm_node_address"], port.PublishedPort)
|
||||
for k, v := range m {
|
||||
lbls[k] = v
|
||||
}
|
||||
ms = append(ms, lbls)
|
||||
}
|
||||
for _, na := range task.NetworksAttachments {
|
||||
for _, address := range na.Addresses {
|
||||
ip, _, err := net.ParseCIDR(address)
|
||||
if err != nil {
|
||||
logger.Errorf("cannot parse task network attachments address: %s as net CIDR: %v", address, err)
|
||||
continue
|
||||
}
|
||||
var added bool
|
||||
for _, v := range svcPorts {
|
||||
if v.Protocol != "tcp" {
|
||||
continue
|
||||
}
|
||||
lbls := make(map[string]string, len(m))
|
||||
for k, v := range m {
|
||||
lbls[k] = v
|
||||
}
|
||||
lbls = joinLabels(networksLabels, lbls, "__meta_dockerswarm_network_id", na.Network.ID)
|
||||
lbls["__address"] = discoveryutils.JoinHostPort(ip.String(), v.PublishedPort)
|
||||
lbls["__meta_dockerswarm_task_port_publish_mode"] = v.PublishMode
|
||||
ms = append(ms, lbls)
|
||||
added = true
|
||||
}
|
||||
|
||||
if !added {
|
||||
lbls := make(map[string]string, len(m))
|
||||
for k, v := range m {
|
||||
lbls[k] = v
|
||||
}
|
||||
lbls = joinLabels(networksLabels, lbls, "__meta_dockerswarm_network_id", na.Network.ID)
|
||||
lbls["__address__"] = discoveryutils.JoinHostPort(ip.String(), port)
|
||||
ms = append(ms, lbls)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ms
|
||||
}
|
352
lib/promscrape/discovery/dockerswarm/tasks_test.go
Normal file
352
lib/promscrape/discovery/dockerswarm/tasks_test.go
Normal file
@ -0,0 +1,352 @@
|
||||
package dockerswarm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
)
|
||||
|
||||
func Test_parseTasks(t *testing.T) {
|
||||
type args struct {
|
||||
data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []task
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "parse ok",
|
||||
args: args{
|
||||
data: []byte(`[
|
||||
{
|
||||
"ID": "t4rdm7j2y9yctbrksiwvsgpu5",
|
||||
"Version": {
|
||||
"Index": 23
|
||||
},
|
||||
"Labels": {},
|
||||
"Spec": {
|
||||
"ContainerSpec": {
|
||||
"Image": "redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842",
|
||||
"Init": false
|
||||
},
|
||||
"Resources": {
|
||||
"Limits": {},
|
||||
"Reservations": {}
|
||||
},
|
||||
"Placement": {
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "amd64",
|
||||
"OS": "linux"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ForceUpdate": 0
|
||||
},
|
||||
"ServiceID": "t91nf284wzle1ya09lqvyjgnq",
|
||||
"Slot": 1,
|
||||
"NodeID": "qauwmifceyvqs0sipvzu8oslu",
|
||||
"Status": {
|
||||
"State": "running",
|
||||
"ContainerStatus": {
|
||||
"ContainerID": "33034b69f6fa5f808098208752fd1fe4e0e1ca86311988cea6a73b998cdc62e8",
|
||||
"ExitCode": 0
|
||||
},
|
||||
"PortStatus": {}
|
||||
},
|
||||
"DesiredState": "running"
|
||||
}
|
||||
]
|
||||
`),
|
||||
},
|
||||
want: []task{
|
||||
{
|
||||
ID: "t4rdm7j2y9yctbrksiwvsgpu5",
|
||||
ServiceID: "t91nf284wzle1ya09lqvyjgnq",
|
||||
NodeID: "qauwmifceyvqs0sipvzu8oslu",
|
||||
Labels: map[string]string{},
|
||||
DesiredState: "running",
|
||||
Slot: 1,
|
||||
Status: struct {
|
||||
State string
|
||||
ContainerStatus *struct{ ContainerID string }
|
||||
PortStatus struct{ Ports []portConfig }
|
||||
}{
|
||||
State: "running",
|
||||
ContainerStatus: &struct{ ContainerID string }{
|
||||
ContainerID: "33034b69f6fa5f808098208752fd1fe4e0e1ca86311988cea6a73b998cdc62e8",
|
||||
},
|
||||
PortStatus: struct{ Ports []portConfig }{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseTasks(tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseTasks() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseTasks() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_addTasksLabels(t *testing.T) {
|
||||
type args struct {
|
||||
tasks []task
|
||||
nodesLabels []map[string]string
|
||||
servicesLabels []map[string]string
|
||||
networksLabels []map[string]string
|
||||
services []service
|
||||
port int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want [][]prompbmarshal.Label
|
||||
}{
|
||||
{
|
||||
name: "adds 1 task with nodes labels",
|
||||
args: args{
|
||||
port: 9100,
|
||||
tasks: []task{
|
||||
{
|
||||
ID: "t4rdm7j2y9yctbrksiwvsgpu5",
|
||||
ServiceID: "t91nf284wzle1ya09lqvyjgnq",
|
||||
NodeID: "qauwmifceyvqs0sipvzu8oslu",
|
||||
Labels: map[string]string{},
|
||||
DesiredState: "running",
|
||||
Slot: 1,
|
||||
Status: struct {
|
||||
State string
|
||||
ContainerStatus *struct{ ContainerID string }
|
||||
PortStatus struct{ Ports []portConfig }
|
||||
}{
|
||||
State: "running",
|
||||
ContainerStatus: &struct{ ContainerID string }{
|
||||
ContainerID: "33034b69f6fa5f808098208752fd1fe4e0e1ca86311988cea6a73b998cdc62e8",
|
||||
},
|
||||
PortStatus: struct{ Ports []portConfig }{
|
||||
Ports: []portConfig{
|
||||
{
|
||||
PublishMode: "ingress",
|
||||
Name: "redis",
|
||||
Protocol: "tcp",
|
||||
PublishedPort: 6379,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
nodesLabels: []map[string]string{
|
||||
{
|
||||
"__address__": "172.31.40.97:9100",
|
||||
"__meta_dockerswarm_node_address": "172.31.40.97",
|
||||
"__meta_dockerswarm_node_availability": "active",
|
||||
"__meta_dockerswarm_node_engine_version": "19.03.11",
|
||||
"__meta_dockerswarm_node_hostname": "ip-172-31-40-97",
|
||||
"__meta_dockerswarm_node_id": "qauwmifceyvqs0sipvzu8oslu",
|
||||
"__meta_dockerswarm_node_platform_architecture": "x86_64",
|
||||
"__meta_dockerswarm_node_platform_os": "linux",
|
||||
"__meta_dockerswarm_node_role": "manager",
|
||||
"__meta_dockerswarm_node_status": "ready",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: [][]prompbmarshal.Label{
|
||||
discoveryutils.GetSortedLabels(map[string]string{
|
||||
"__address__": "172.31.40.97:9100",
|
||||
"__meta_dockerswarm_node_address": "172.31.40.97",
|
||||
"__meta_dockerswarm_node_availability": "active",
|
||||
"__meta_dockerswarm_node_engine_version": "19.03.11",
|
||||
"__meta_dockerswarm_node_hostname": "ip-172-31-40-97",
|
||||
"__meta_dockerswarm_node_id": "qauwmifceyvqs0sipvzu8oslu",
|
||||
"__meta_dockerswarm_node_platform_architecture": "x86_64",
|
||||
"__meta_dockerswarm_node_platform_os": "linux",
|
||||
"__meta_dockerswarm_node_role": "manager",
|
||||
"__meta_dockerswarm_node_status": "ready",
|
||||
"__meta_dockerswarm_task_container_id": "33034b69f6fa5f808098208752fd1fe4e0e1ca86311988cea6a73b998cdc62e8",
|
||||
"__meta_dockerswarm_task_desired_state": "running",
|
||||
"__meta_dockerswarm_task_id": "t4rdm7j2y9yctbrksiwvsgpu5",
|
||||
"__meta_dockerswarm_task_port_publish_mode": "ingress",
|
||||
"__meta_dockerswarm_task_slot": "1",
|
||||
"__meta_dockerswarm_task_state": "running",
|
||||
})},
|
||||
},
|
||||
{
|
||||
name: "adds 1 task with nodes, network and services labels",
|
||||
args: args{
|
||||
port: 9100,
|
||||
tasks: []task{
|
||||
{
|
||||
ID: "t4rdm7j2y9yctbrksiwvsgpu5",
|
||||
ServiceID: "tgsci5gd31aai3jyudv98pqxf",
|
||||
NodeID: "qauwmifceyvqs0sipvzu8oslu",
|
||||
Labels: map[string]string{},
|
||||
DesiredState: "running",
|
||||
Slot: 1,
|
||||
NetworksAttachments: []struct {
|
||||
Addresses []string
|
||||
Network struct{ ID string }
|
||||
}{
|
||||
{
|
||||
Network: struct {
|
||||
ID string
|
||||
}{
|
||||
ID: "qs0hog6ldlei9ct11pr3c77v1",
|
||||
},
|
||||
Addresses: []string{"10.10.15.15/24"},
|
||||
},
|
||||
},
|
||||
Status: struct {
|
||||
State string
|
||||
ContainerStatus *struct{ ContainerID string }
|
||||
PortStatus struct{ Ports []portConfig }
|
||||
}{
|
||||
State: "running",
|
||||
ContainerStatus: &struct{ ContainerID string }{
|
||||
ContainerID: "33034b69f6fa5f808098208752fd1fe4e0e1ca86311988cea6a73b998cdc62e8",
|
||||
},
|
||||
PortStatus: struct{ Ports []portConfig }{}},
|
||||
},
|
||||
},
|
||||
networksLabels: []map[string]string{
|
||||
{
|
||||
"__meta_dockerswarm_network_id": "qs0hog6ldlei9ct11pr3c77v1",
|
||||
"__meta_dockerswarm_network_ingress": "true",
|
||||
"__meta_dockerswarm_network_internal": "false",
|
||||
"__meta_dockerswarm_network_label_key1": "value1",
|
||||
"__meta_dockerswarm_network_name": "ingress",
|
||||
"__meta_dockerswarm_network_scope": "swarm",
|
||||
},
|
||||
},
|
||||
nodesLabels: []map[string]string{
|
||||
{
|
||||
"__address__": "172.31.40.97:9100",
|
||||
"__meta_dockerswarm_node_address": "172.31.40.97",
|
||||
"__meta_dockerswarm_node_availability": "active",
|
||||
"__meta_dockerswarm_node_engine_version": "19.03.11",
|
||||
"__meta_dockerswarm_node_hostname": "ip-172-31-40-97",
|
||||
"__meta_dockerswarm_node_id": "qauwmifceyvqs0sipvzu8oslu",
|
||||
"__meta_dockerswarm_node_platform_architecture": "x86_64",
|
||||
"__meta_dockerswarm_node_platform_os": "linux",
|
||||
"__meta_dockerswarm_node_role": "manager",
|
||||
"__meta_dockerswarm_node_status": "ready",
|
||||
},
|
||||
},
|
||||
services: []service{
|
||||
{
|
||||
ID: "tgsci5gd31aai3jyudv98pqxf",
|
||||
Spec: struct {
|
||||
Labels map[string]string
|
||||
Name string
|
||||
TaskTemplate struct {
|
||||
ContainerSpec struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}
|
||||
}
|
||||
Mode struct {
|
||||
Global interface{}
|
||||
Replicated interface{}
|
||||
}
|
||||
}{
|
||||
Labels: map[string]string{},
|
||||
Name: "redis2",
|
||||
TaskTemplate: struct {
|
||||
ContainerSpec struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}
|
||||
}{
|
||||
ContainerSpec: struct {
|
||||
Hostname string
|
||||
Image string
|
||||
}{
|
||||
Hostname: "node1",
|
||||
Image: "redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842",
|
||||
},
|
||||
},
|
||||
Mode: struct {
|
||||
Global interface{}
|
||||
Replicated interface{}
|
||||
}{
|
||||
Replicated: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
Endpoint: struct {
|
||||
Ports []portConfig
|
||||
VirtualIPs []struct {
|
||||
NetworkID string
|
||||
Addr string
|
||||
}
|
||||
}{Ports: []portConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
Name: "redis",
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
}, VirtualIPs: []struct {
|
||||
NetworkID string
|
||||
Addr string
|
||||
}{
|
||||
{
|
||||
NetworkID: "qs0hog6ldlei9ct11pr3c77v1",
|
||||
Addr: "10.0.0.3/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servicesLabels: []map[string]string{},
|
||||
},
|
||||
want: [][]prompbmarshal.Label{
|
||||
discoveryutils.GetSortedLabels(map[string]string{
|
||||
"__address": "10.10.15.15:0",
|
||||
"__address__": "172.31.40.97:9100",
|
||||
"__meta_dockerswarm_network_id": "qs0hog6ldlei9ct11pr3c77v1",
|
||||
"__meta_dockerswarm_network_ingress": "true",
|
||||
"__meta_dockerswarm_network_internal": "false",
|
||||
"__meta_dockerswarm_network_label_key1": "value1",
|
||||
"__meta_dockerswarm_network_name": "ingress",
|
||||
"__meta_dockerswarm_network_scope": "swarm",
|
||||
"__meta_dockerswarm_node_address": "172.31.40.97",
|
||||
"__meta_dockerswarm_node_availability": "active",
|
||||
"__meta_dockerswarm_node_engine_version": "19.03.11",
|
||||
"__meta_dockerswarm_node_hostname": "ip-172-31-40-97",
|
||||
"__meta_dockerswarm_node_id": "qauwmifceyvqs0sipvzu8oslu",
|
||||
"__meta_dockerswarm_node_platform_architecture": "x86_64",
|
||||
"__meta_dockerswarm_node_platform_os": "linux",
|
||||
"__meta_dockerswarm_node_role": "manager",
|
||||
"__meta_dockerswarm_node_status": "ready",
|
||||
"__meta_dockerswarm_task_container_id": "33034b69f6fa5f808098208752fd1fe4e0e1ca86311988cea6a73b998cdc62e8",
|
||||
"__meta_dockerswarm_task_desired_state": "running",
|
||||
"__meta_dockerswarm_task_id": "t4rdm7j2y9yctbrksiwvsgpu5",
|
||||
"__meta_dockerswarm_task_port_publish_mode": "ingress",
|
||||
"__meta_dockerswarm_task_slot": "1",
|
||||
"__meta_dockerswarm_task_state": "running",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := addTasksLabels(tt.args.tasks, tt.args.nodesLabels, tt.args.servicesLabels, tt.args.networksLabels, tt.args.services, tt.args.port)
|
||||
var sortedLabelss [][]prompbmarshal.Label
|
||||
for _, labels := range got {
|
||||
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||
}
|
||||
if !reflect.DeepEqual(sortedLabelss, tt.want) {
|
||||
t.Errorf("addTasksLabels() \ngot %v, \nwant %v", sortedLabelss, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -41,11 +41,23 @@ type Client struct {
|
||||
|
||||
// NewClient returns new Client for the given apiServer and the given ac.
|
||||
func NewClient(apiServer string, ac *promauth.Config) (*Client, error) {
|
||||
var u fasthttp.URI
|
||||
var (
|
||||
dialFunc fasthttp.DialFunc
|
||||
tlsCfg *tls.Config
|
||||
u fasthttp.URI
|
||||
)
|
||||
u.Update(apiServer)
|
||||
|
||||
// special case for unix socket connection
|
||||
if string(u.Scheme()) == "unix" {
|
||||
dialAddr := string(u.Path())
|
||||
apiServer = "http://"
|
||||
dialFunc = func(_ string) (net.Conn, error) {
|
||||
return net.Dial("unix", dialAddr)
|
||||
}
|
||||
}
|
||||
hostPort := string(u.Host())
|
||||
isTLS := string(u.Scheme()) == "https"
|
||||
var tlsCfg *tls.Config
|
||||
if isTLS && ac != nil {
|
||||
tlsCfg = ac.NewTLSConfig()
|
||||
}
|
||||
@ -66,6 +78,7 @@ func NewClient(apiServer string, ac *promauth.Config) (*Client, error) {
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxResponseBodySize: 300 * 1024 * 1024,
|
||||
MaxConns: 2 * *maxConcurrency,
|
||||
Dial: dialFunc,
|
||||
}
|
||||
return &Client{
|
||||
hc: hc,
|
||||
|
@ -36,9 +36,11 @@ var (
|
||||
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")
|
||||
dockerswarmSDCheckInterval = flag.Duration("promscrape.dockerswarmSDCheckInterval", 30*time.Second, "Interval for checking for changes in dockerswarm. "+
|
||||
"This works only if `dockerswarm_sd_configs` is configured in '-promscrape.config' file. "+
|
||||
"See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dockerswarm_sd_config for details")
|
||||
promscrapeConfigFile = flag.String("promscrape.config", "", "Optional path to Prometheus config file with 'scrape_configs' section containing targets to scrape. "+
|
||||
"See https://victoriametrics.github.io/#how-to-scrape-prometheus-exporters-such-as-node-exporter for details")
|
||||
|
||||
suppressDuplicateScrapeTargetErrors = flag.Bool("promscrape.suppressDuplicateScrapeTargetErrors", false, "Whether to suppress `duplicate scrape target` errors; "+
|
||||
"see https://victoriametrics.github.io/vmagent.html#troubleshooting for details")
|
||||
)
|
||||
@ -96,6 +98,7 @@ func runScraper(configFile string, pushData func(wr *prompbmarshal.WriteRequest)
|
||||
scs.add("dns_sd_configs", *dnsSDCheckInterval, func(cfg *Config, swsPrev []ScrapeWork) []ScrapeWork { return cfg.getDNSSDScrapeWork(swsPrev) })
|
||||
scs.add("ec2_sd_configs", *ec2SDCheckInterval, func(cfg *Config, swsPrev []ScrapeWork) []ScrapeWork { return cfg.getEC2SDScrapeWork(swsPrev) })
|
||||
scs.add("gce_sd_configs", *gceSDCheckInterval, func(cfg *Config, swsPrev []ScrapeWork) []ScrapeWork { return cfg.getGCESDScrapeWork(swsPrev) })
|
||||
scs.add("dockerswarm_sd_configs", *dockerswarmSDCheckInterval, func(cfg *Config, swsPrev []ScrapeWork) []ScrapeWork { return cfg.getDockerSwarmSDScrapeWork(swsPrev) })
|
||||
|
||||
sighupCh := procutil.NewSighupChan()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user