lib/promscrape/discovery/kubernetes/: unify apiConfig creation

This commit is contained in:
Aliaksandr Valialkin 2020-05-04 15:53:50 +03:00
parent 54414fefef
commit c50fd219dc
7 changed files with 101 additions and 95 deletions

View file

@ -301,12 +301,7 @@ type scrapeWorkConfig struct {
} }
func appendKubernetesScrapeWork(dst []ScrapeWork, sdc *kubernetes.SDConfig, baseDir string, swc *scrapeWorkConfig) []ScrapeWork { func appendKubernetesScrapeWork(dst []ScrapeWork, sdc *kubernetes.SDConfig, baseDir string, swc *scrapeWorkConfig) []ScrapeWork {
ac, err := promauth.NewConfig(baseDir, sdc.BasicAuth, sdc.BearerToken, sdc.BearerTokenFile, sdc.TLSConfig) targetLabels, err := kubernetes.GetLabels(sdc, baseDir)
if err != nil {
logger.Errorf("cannot parse auth config for `kubernetes_sd_config` for `job_name` %q: %s; skipping it", swc.jobName, err)
return dst
}
targetLabels, err := kubernetes.GetLabels(ac, sdc)
if err != nil { if err != nil {
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

View file

@ -16,34 +16,101 @@ import (
// apiConfig contains config for API server // apiConfig contains config for API server
type apiConfig struct { type apiConfig struct {
Server string client *fasthttp.HostClient
AuthConfig *promauth.Config server string
Namespaces []string hostPort string
Selectors []Selector authConfig *promauth.Config
namespaces []string
selectors []Selector
} }
func getAPIResponse(cfg *apiConfig, role, path string) ([]byte, error) { func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
hcv, err := getHostClient(cfg.Server, cfg.AuthConfig) 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, baseDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
query := joinSelectors(role, cfg.Namespaces, cfg.Selectors) 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, baseDir string) (*apiConfig, error) {
ac, err := promauth.NewConfig(baseDir, sdc.BasicAuth, sdc.BearerToken, sdc.BearerTokenFile, sdc.TLSConfig)
if err != nil {
return nil, fmt.Errorf("cannot parse auth config: %s", err)
}
hcv, err := newHostClient(sdc.APIServer, ac)
if err != nil {
return nil, fmt.Errorf("cannot create HTTP client for %q: %s", sdc.APIServer, err)
}
cfg := &apiConfig{
client: hcv.hc,
server: hcv.apiServer,
hostPort: hcv.hostPort,
authConfig: hcv.ac,
namespaces: sdc.Namespaces.Names,
selectors: sdc.Selectors,
}
return cfg, nil
}
func getAPIResponse(cfg *apiConfig, role, path string) ([]byte, error) {
query := joinSelectors(role, cfg.namespaces, cfg.selectors)
if len(query) > 0 { if len(query) > 0 {
path += "?" + query path += "?" + query
} }
requestURL := hcv.apiServer + path requestURL := cfg.server + path
var u fasthttp.URI var u fasthttp.URI
u.Update(requestURL) u.Update(requestURL)
var req fasthttp.Request var req fasthttp.Request
req.SetRequestURIBytes(u.RequestURI()) req.SetRequestURIBytes(u.RequestURI())
req.SetHost(hcv.hostPort) req.SetHost(cfg.hostPort)
req.Header.Set("Accept-Encoding", "gzip") req.Header.Set("Accept-Encoding", "gzip")
if hcv.ac != nil && hcv.ac.Authorization != "" { if cfg.authConfig != nil && cfg.authConfig.Authorization != "" {
req.Header.Set("Authorization", hcv.ac.Authorization) req.Header.Set("Authorization", cfg.authConfig.Authorization)
} }
var resp fasthttp.Response var resp fasthttp.Response
// There is no need in calling DoTimeout, since the timeout is already set in hc.ReadTimeout above. // There is no need in calling DoTimeout, since the timeout is already set in hc.ReadTimeout above.
if err := hcv.hc.Do(&req, &resp); err != nil { if err := cfg.client.Do(&req, &resp); err != nil {
return nil, fmt.Errorf("cannot fetch %q: %s", requestURL, err) return nil, fmt.Errorf("cannot fetch %q: %s", requestURL, err)
} }
var data []byte var data []byte
@ -64,67 +131,13 @@ func getAPIResponse(cfg *apiConfig, role, path string) ([]byte, error) {
return data, nil return data, nil
} }
func getHostClient(apiServer string, ac *promauth.Config) (*hcValue, error) {
if len(apiServer) == 0 {
// ac is ignored when apiServer should be auto-discovered when running inside k8s pod.
ac = nil
}
k := hcKey{
apiServer: apiServer,
ac: ac,
}
hcMapLock.Lock()
defer hcMapLock.Unlock()
if !hasHCMapCleaner {
go hcMapCleaner()
hasHCMapCleaner = true
}
hcv := hcMap[k]
if hcv == nil {
hcvNew, err := newHostClient(apiServer, ac)
if err != nil {
return hcv, fmt.Errorf("cannot create new HTTP client for %q: %s", apiServer, err)
}
hcMap[k] = hcvNew
hcv = hcvNew
}
hcv.lastAccessTime = time.Now()
return hcv, nil
}
func hcMapCleaner() {
tc := time.NewTicker(15 * time.Minute)
for currentTime := range tc.C {
hcMapLock.Lock()
for k, v := range hcMap {
if currentTime.Sub(v.lastAccessTime) > 10*time.Minute {
delete(hcMap, k)
}
}
hcMapLock.Unlock()
}
}
type hcKey struct {
apiServer string
ac *promauth.Config
}
type hcValue struct { type hcValue struct {
hc *fasthttp.HostClient hc *fasthttp.HostClient
ac *promauth.Config ac *promauth.Config
apiServer string apiServer string
hostPort string hostPort string
lastAccessTime time.Time
} }
var (
hasHCMapCleaner bool
hcMap = make(map[hcKey]*hcValue)
hcMapLock sync.Mutex
)
func newHostClient(apiServer string, ac *promauth.Config) (*hcValue, error) { func newHostClient(apiServer string, ac *promauth.Config) (*hcValue, error) {
if len(apiServer) == 0 { if len(apiServer) == 0 {
// Assume we run at k8s pod. // Assume we run at k8s pod.

View file

@ -29,14 +29,14 @@ func getEndpointsLabels(cfg *apiConfig) ([]map[string]string, error) {
} }
func getEndpoints(cfg *apiConfig) ([]Endpoints, error) { func getEndpoints(cfg *apiConfig) ([]Endpoints, error) {
if len(cfg.Namespaces) == 0 { if len(cfg.namespaces) == 0 {
return getEndpointsByPath(cfg, "/api/v1/endpoints") return getEndpointsByPath(cfg, "/api/v1/endpoints")
} }
// Query /api/v1/namespaces/* for each namespace. // Query /api/v1/namespaces/* for each namespace.
// This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432 // This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432
cfgCopy := *cfg cfgCopy := *cfg
namespaces := cfgCopy.Namespaces namespaces := cfgCopy.namespaces
cfgCopy.Namespaces = nil cfgCopy.namespaces = nil
cfg = &cfgCopy cfg = &cfgCopy
var result []Endpoints var result []Endpoints
for _, ns := range namespaces { for _, ns := range namespaces {

View file

@ -19,14 +19,14 @@ func getIngressesLabels(cfg *apiConfig) ([]map[string]string, error) {
} }
func getIngresses(cfg *apiConfig) ([]Ingress, error) { func getIngresses(cfg *apiConfig) ([]Ingress, error) {
if len(cfg.Namespaces) == 0 { if len(cfg.namespaces) == 0 {
return getIngressesByPath(cfg, "/apis/extensions/v1beta1/ingresses") return getIngressesByPath(cfg, "/apis/extensions/v1beta1/ingresses")
} }
// Query /api/v1/namespaces/* for each namespace. // Query /api/v1/namespaces/* for each namespace.
// This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432 // This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432
cfgCopy := *cfg cfgCopy := *cfg
namespaces := cfgCopy.Namespaces namespaces := cfgCopy.namespaces
cfgCopy.Namespaces = nil cfgCopy.namespaces = nil
cfg = &cfgCopy cfg = &cfgCopy
var result []Ingress var result []Ingress
for _, ns := range namespaces { for _, ns := range namespaces {

View file

@ -35,13 +35,11 @@ type Selector struct {
Field string `yaml:"field"` Field string `yaml:"field"`
} }
// GetLabels returns labels for the given k8s role and the given ac and sdc. // GetLabels returns labels for the given sdc and baseDir.
func GetLabels(ac *promauth.Config, sdc *SDConfig) ([]map[string]string, error) { func GetLabels(sdc *SDConfig, baseDir string) ([]map[string]string, error) {
cfg := &apiConfig{ cfg, err := getAPIConfig(sdc, baseDir)
Server: sdc.APIServer, if err != nil {
AuthConfig: ac, return nil, fmt.Errorf("cannot create API config: %s", err)
Namespaces: sdc.Namespaces.Names,
Selectors: sdc.Selectors,
} }
switch sdc.Role { switch sdc.Role {
case "node": case "node":

View file

@ -23,14 +23,14 @@ func getPodsLabels(cfg *apiConfig) ([]map[string]string, error) {
} }
func getPods(cfg *apiConfig) ([]Pod, error) { func getPods(cfg *apiConfig) ([]Pod, error) {
if len(cfg.Namespaces) == 0 { if len(cfg.namespaces) == 0 {
return getPodsByPath(cfg, "/api/v1/pods") return getPodsByPath(cfg, "/api/v1/pods")
} }
// Query /api/v1/namespaces/* for each namespace. // Query /api/v1/namespaces/* for each namespace.
// This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432 // This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432
cfgCopy := *cfg cfgCopy := *cfg
namespaces := cfgCopy.Namespaces namespaces := cfgCopy.namespaces
cfgCopy.Namespaces = nil cfgCopy.namespaces = nil
cfg = &cfgCopy cfg = &cfgCopy
var result []Pod var result []Pod
for _, ns := range namespaces { for _, ns := range namespaces {

View file

@ -21,14 +21,14 @@ func getServicesLabels(cfg *apiConfig) ([]map[string]string, error) {
} }
func getServices(cfg *apiConfig) ([]Service, error) { func getServices(cfg *apiConfig) ([]Service, error) {
if len(cfg.Namespaces) == 0 { if len(cfg.namespaces) == 0 {
return getServicesByPath(cfg, "/api/v1/services") return getServicesByPath(cfg, "/api/v1/services")
} }
// Query /api/v1/namespaces/* for each namespace. // Query /api/v1/namespaces/* for each namespace.
// This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432 // This fixes authorization issue at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/432
cfgCopy := *cfg cfgCopy := *cfg
namespaces := cfgCopy.Namespaces namespaces := cfgCopy.namespaces
cfgCopy.Namespaces = nil cfgCopy.namespaces = nil
cfg = &cfgCopy cfg = &cfgCopy
var result []Service var result []Service
for _, ns := range namespaces { for _, ns := range namespaces {