2020-08-26 15:36:59 +00:00
|
|
|
// Copyright 2020 The Prometheus Authors
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package eureka
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-06-03 11:47:14 +00:00
|
|
|
"errors"
|
2024-09-10 01:41:53 +00:00
|
|
|
"log/slog"
|
2020-08-26 15:36:59 +00:00
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2023-10-23 13:55:36 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2020-08-26 15:36:59 +00:00
|
|
|
"github.com/prometheus/common/config"
|
|
|
|
"github.com/prometheus/common/model"
|
|
|
|
|
|
|
|
"github.com/prometheus/prometheus/discovery"
|
|
|
|
"github.com/prometheus/prometheus/discovery/refresh"
|
|
|
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
|
|
|
"github.com/prometheus/prometheus/util/strutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// metaLabelPrefix is the meta prefix used for all meta labels.
|
|
|
|
// in this discovery.
|
|
|
|
metaLabelPrefix = model.MetaLabelPrefix + "eureka_"
|
|
|
|
metaAppInstanceLabelPrefix = metaLabelPrefix + "app_instance_"
|
|
|
|
|
|
|
|
appNameLabel = metaLabelPrefix + "app_name"
|
|
|
|
appInstanceHostNameLabel = metaAppInstanceLabelPrefix + "hostname"
|
|
|
|
appInstanceHomePageURLLabel = metaAppInstanceLabelPrefix + "homepage_url"
|
|
|
|
appInstanceStatusPageURLLabel = metaAppInstanceLabelPrefix + "statuspage_url"
|
|
|
|
appInstanceHealthCheckURLLabel = metaAppInstanceLabelPrefix + "healthcheck_url"
|
|
|
|
appInstanceIPAddrLabel = metaAppInstanceLabelPrefix + "ip_addr"
|
|
|
|
appInstanceVipAddressLabel = metaAppInstanceLabelPrefix + "vip_address"
|
|
|
|
appInstanceSecureVipAddressLabel = metaAppInstanceLabelPrefix + "secure_vip_address"
|
|
|
|
appInstanceStatusLabel = metaAppInstanceLabelPrefix + "status"
|
|
|
|
appInstancePortLabel = metaAppInstanceLabelPrefix + "port"
|
|
|
|
appInstancePortEnabledLabel = metaAppInstanceLabelPrefix + "port_enabled"
|
|
|
|
appInstanceSecurePortLabel = metaAppInstanceLabelPrefix + "secure_port"
|
|
|
|
appInstanceSecurePortEnabledLabel = metaAppInstanceLabelPrefix + "secure_port_enabled"
|
|
|
|
appInstanceDataCenterInfoNameLabel = metaAppInstanceLabelPrefix + "datacenterinfo_name"
|
|
|
|
appInstanceDataCenterInfoMetadataPrefix = metaAppInstanceLabelPrefix + "datacenterinfo_metadata_"
|
|
|
|
appInstanceCountryIDLabel = metaAppInstanceLabelPrefix + "country_id"
|
|
|
|
appInstanceIDLabel = metaAppInstanceLabelPrefix + "id"
|
|
|
|
appInstanceMetadataPrefix = metaAppInstanceLabelPrefix + "metadata_"
|
|
|
|
)
|
|
|
|
|
|
|
|
// DefaultSDConfig is the default Eureka SD configuration.
|
|
|
|
var DefaultSDConfig = SDConfig{
|
2021-02-26 21:48:06 +00:00
|
|
|
RefreshInterval: model.Duration(30 * time.Second),
|
|
|
|
HTTPClientConfig: config.DefaultHTTPClientConfig,
|
2020-08-26 15:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
discovery.RegisterConfig(&SDConfig{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// SDConfig is the configuration for applications running on Eureka.
|
|
|
|
type SDConfig struct {
|
|
|
|
Server string `yaml:"server,omitempty"`
|
|
|
|
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
|
|
|
|
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
|
|
|
|
}
|
|
|
|
|
2024-01-23 15:53:55 +00:00
|
|
|
// NewDiscovererMetrics implements discovery.Config.
|
|
|
|
func (*SDConfig) NewDiscovererMetrics(reg prometheus.Registerer, rmi discovery.RefreshMetricsInstantiator) discovery.DiscovererMetrics {
|
|
|
|
return &eurekaMetrics{
|
|
|
|
refreshMetrics: rmi,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-26 15:36:59 +00:00
|
|
|
// Name returns the name of the Config.
|
|
|
|
func (*SDConfig) Name() string { return "eureka" }
|
|
|
|
|
|
|
|
// NewDiscoverer returns a Discoverer for the Config.
|
|
|
|
func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) {
|
2024-01-23 15:53:55 +00:00
|
|
|
return NewDiscovery(c, opts.Logger, opts.Metrics)
|
2020-08-26 15:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetDirectory joins any relative file paths with dir.
|
|
|
|
func (c *SDConfig) SetDirectory(dir string) {
|
|
|
|
c.HTTPClientConfig.SetDirectory(dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
|
|
func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
|
|
*c = DefaultSDConfig
|
|
|
|
type plain SDConfig
|
|
|
|
err := unmarshal((*plain)(c))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(c.Server) == 0 {
|
|
|
|
return errors.New("eureka_sd: empty or null eureka server")
|
|
|
|
}
|
|
|
|
url, err := url.Parse(c.Server)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(url.Scheme) == 0 || len(url.Host) == 0 {
|
|
|
|
return errors.New("eureka_sd: invalid eureka server URL")
|
|
|
|
}
|
|
|
|
return c.HTTPClientConfig.Validate()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Discovery provides service discovery based on a Eureka instance.
|
|
|
|
type Discovery struct {
|
|
|
|
*refresh.Discovery
|
|
|
|
client *http.Client
|
|
|
|
server string
|
|
|
|
}
|
|
|
|
|
2021-04-29 08:05:33 +00:00
|
|
|
// NewDiscovery creates a new Eureka discovery for the given role.
|
2024-09-10 01:41:53 +00:00
|
|
|
func NewDiscovery(conf *SDConfig, logger *slog.Logger, metrics discovery.DiscovererMetrics) (*Discovery, error) {
|
2024-01-23 15:53:55 +00:00
|
|
|
m, ok := metrics.(*eurekaMetrics)
|
|
|
|
if !ok {
|
2024-11-03 12:15:51 +00:00
|
|
|
return nil, errors.New("invalid discovery metrics type")
|
2024-01-23 15:53:55 +00:00
|
|
|
}
|
|
|
|
|
2021-09-26 21:16:12 +00:00
|
|
|
rt, err := config.NewRoundTripperFromConfig(conf.HTTPClientConfig, "eureka_sd")
|
2020-08-26 15:36:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
d := &Discovery{
|
|
|
|
client: &http.Client{Transport: rt},
|
|
|
|
server: conf.Server,
|
|
|
|
}
|
|
|
|
d.Discovery = refresh.NewDiscovery(
|
2023-10-23 13:55:36 +00:00
|
|
|
refresh.Options{
|
2024-01-23 15:53:55 +00:00
|
|
|
Logger: logger,
|
|
|
|
Mech: "eureka",
|
|
|
|
Interval: time.Duration(conf.RefreshInterval),
|
|
|
|
RefreshF: d.refresh,
|
|
|
|
MetricsInstantiator: m.refreshMetrics,
|
2023-10-23 13:55:36 +00:00
|
|
|
},
|
2020-08-26 15:36:59 +00:00
|
|
|
)
|
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
|
|
|
|
apps, err := fetchApps(ctx, d.server, d.client)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tg := &targetgroup.Group{
|
|
|
|
Source: "eureka",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, app := range apps.Applications {
|
|
|
|
targets := targetsForApp(&app)
|
|
|
|
tg.Targets = append(tg.Targets, targets...)
|
|
|
|
}
|
|
|
|
return []*targetgroup.Group{tg}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func targetsForApp(app *Application) []model.LabelSet {
|
|
|
|
targets := make([]model.LabelSet, 0, len(app.Instances))
|
|
|
|
|
|
|
|
// Gather info about the app's 'instances'. Each instance is considered a task.
|
|
|
|
for _, t := range app.Instances {
|
|
|
|
var targetAddress string
|
|
|
|
if t.Port != nil {
|
|
|
|
targetAddress = net.JoinHostPort(t.HostName, strconv.Itoa(t.Port.Port))
|
|
|
|
} else {
|
|
|
|
targetAddress = net.JoinHostPort(t.HostName, "80")
|
|
|
|
}
|
|
|
|
|
|
|
|
target := model.LabelSet{
|
|
|
|
model.AddressLabel: lv(targetAddress),
|
|
|
|
model.InstanceLabel: lv(t.InstanceID),
|
|
|
|
|
|
|
|
appNameLabel: lv(app.Name),
|
|
|
|
appInstanceHostNameLabel: lv(t.HostName),
|
|
|
|
appInstanceHomePageURLLabel: lv(t.HomePageURL),
|
|
|
|
appInstanceStatusPageURLLabel: lv(t.StatusPageURL),
|
|
|
|
appInstanceHealthCheckURLLabel: lv(t.HealthCheckURL),
|
|
|
|
appInstanceIPAddrLabel: lv(t.IPAddr),
|
|
|
|
appInstanceVipAddressLabel: lv(t.VipAddress),
|
|
|
|
appInstanceSecureVipAddressLabel: lv(t.SecureVipAddress),
|
|
|
|
appInstanceStatusLabel: lv(t.Status),
|
|
|
|
appInstanceCountryIDLabel: lv(strconv.Itoa(t.CountryID)),
|
|
|
|
appInstanceIDLabel: lv(t.InstanceID),
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.Port != nil {
|
|
|
|
target[appInstancePortLabel] = lv(strconv.Itoa(t.Port.Port))
|
|
|
|
target[appInstancePortEnabledLabel] = lv(strconv.FormatBool(t.Port.Enabled))
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.SecurePort != nil {
|
|
|
|
target[appInstanceSecurePortLabel] = lv(strconv.Itoa(t.SecurePort.Port))
|
|
|
|
target[appInstanceSecurePortEnabledLabel] = lv(strconv.FormatBool(t.SecurePort.Enabled))
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.DataCenterInfo != nil {
|
|
|
|
target[appInstanceDataCenterInfoNameLabel] = lv(t.DataCenterInfo.Name)
|
|
|
|
|
|
|
|
if t.DataCenterInfo.Metadata != nil {
|
|
|
|
for _, m := range t.DataCenterInfo.Metadata.Items {
|
|
|
|
ln := strutil.SanitizeLabelName(m.XMLName.Local)
|
|
|
|
target[model.LabelName(appInstanceDataCenterInfoMetadataPrefix+ln)] = lv(m.Content)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.Metadata != nil {
|
|
|
|
for _, m := range t.Metadata.Items {
|
|
|
|
ln := strutil.SanitizeLabelName(m.XMLName.Local)
|
|
|
|
target[model.LabelName(appInstanceMetadataPrefix+ln)] = lv(m.Content)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
targets = append(targets, target)
|
|
|
|
}
|
|
|
|
return targets
|
|
|
|
}
|
|
|
|
|
|
|
|
func lv(s string) model.LabelValue {
|
|
|
|
return model.LabelValue(s)
|
|
|
|
}
|