Add ability to collect pod labels to Kubernetes input (#6764)
This commit is contained in:
parent
4fbba13622
commit
f79ba10ab3
|
@ -1,6 +1,6 @@
|
|||
# Kubernetes Input Plugin
|
||||
|
||||
This input plugin talks to the kubelet api using the `/stats/summary` endpoint to gather metrics about the running pods and containers for a single host. It is assumed that this plugin is running as part of a `daemonset` within a kubernetes installation. This means that telegraf is running on every node within the cluster. Therefore, you should configure this plugin to talk to its locally running kubelet.
|
||||
This input plugin talks to the kubelet api using the `/stats/summary` and `/pods` endpoint to gather metrics about the running pods and containers for a single host. It is assumed that this plugin is running as part of a `daemonset` within a kubernetes installation. This means that telegraf is running on every node within the cluster. Therefore, you should configure this plugin to talk to its locally running kubelet.
|
||||
|
||||
To find the ip address of the host you are running on you can issue a command like the following:
|
||||
|
||||
|
@ -44,6 +44,11 @@ avoid cardinality issues:
|
|||
## OR
|
||||
# bearer_token_string = "abc_123"
|
||||
|
||||
# Labels to include and exclude
|
||||
# An empty array for include and exclude will include all labels
|
||||
# label_include = []
|
||||
# label_exclude = ["*"]
|
||||
|
||||
## Set response_timeout (default 5 seconds)
|
||||
# response_timeout = "5s"
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package kubernetes
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -23,6 +24,11 @@ type Kubernetes struct {
|
|||
BearerToken string `toml:"bearer_token"`
|
||||
BearerTokenString string `toml:"bearer_token_string"`
|
||||
|
||||
LabelInclude []string `toml:"label_include"`
|
||||
LabelExclude []string `toml:"label_exclude"`
|
||||
|
||||
labelFilter filter.Filter
|
||||
|
||||
// HTTP Timeout specified as a string - 3s, 1m, 1h
|
||||
ResponseTimeout internal.Duration
|
||||
|
||||
|
@ -42,6 +48,11 @@ var sampleConfig = `
|
|||
## OR
|
||||
# bearer_token_string = "abc_123"
|
||||
|
||||
# Labels to include and exclude
|
||||
# An empty array for include and exclude will include all labels
|
||||
# label_include = []
|
||||
# label_exclude = ["*"]
|
||||
|
||||
## Set response_timeout (default 5 seconds)
|
||||
# response_timeout = "5s"
|
||||
|
||||
|
@ -60,7 +71,10 @@ const (
|
|||
|
||||
func init() {
|
||||
inputs.Add("kubernetes", func() telegraf.Input {
|
||||
return &Kubernetes{}
|
||||
return &Kubernetes{
|
||||
LabelInclude: []string{},
|
||||
LabelExclude: []string{"*"},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -75,6 +89,7 @@ func (k *Kubernetes) Description() string {
|
|||
}
|
||||
|
||||
func (k *Kubernetes) Init() error {
|
||||
|
||||
// If neither are provided, use the default service account.
|
||||
if k.BearerToken == "" && k.BearerTokenString == "" {
|
||||
k.BearerToken = defaultServiceAccountPath
|
||||
|
@ -88,6 +103,12 @@ func (k *Kubernetes) Init() error {
|
|||
k.BearerTokenString = strings.TrimSpace(string(token))
|
||||
}
|
||||
|
||||
labelFilter, err := filter.NewIncludeExcludeFilter(k.LabelInclude, k.LabelExclude)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.labelFilter = labelFilter
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -107,48 +128,19 @@ func buildURL(endpoint string, base string) (*url.URL, error) {
|
|||
}
|
||||
|
||||
func (k *Kubernetes) gatherSummary(baseURL string, acc telegraf.Accumulator) error {
|
||||
url := fmt.Sprintf("%s/stats/summary", baseURL)
|
||||
var req, err = http.NewRequest("GET", url, nil)
|
||||
var resp *http.Response
|
||||
|
||||
tlsCfg, err := k.ClientConfig.TLSConfig()
|
||||
summaryMetrics := &SummaryMetrics{}
|
||||
err := k.LoadJson(fmt.Sprintf("%s/stats/summary", baseURL), summaryMetrics)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if k.RoundTripper == nil {
|
||||
// Set default values
|
||||
if k.ResponseTimeout.Duration < time.Second {
|
||||
k.ResponseTimeout.Duration = time.Second * 5
|
||||
}
|
||||
k.RoundTripper = &http.Transport{
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
TLSClientConfig: tlsCfg,
|
||||
ResponseHeaderTimeout: k.ResponseTimeout.Duration,
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+k.BearerTokenString)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
resp, err = k.RoundTripper.RoundTrip(req)
|
||||
podInfos, err := k.gatherPodInfo(baseURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making HTTP request to %s: %s", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("%s returned HTTP status %s", url, resp.Status)
|
||||
}
|
||||
|
||||
summaryMetrics := &SummaryMetrics{}
|
||||
err = json.NewDecoder(resp.Body).Decode(summaryMetrics)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`Error parsing response: %s`, err)
|
||||
return err
|
||||
}
|
||||
buildSystemContainerMetrics(summaryMetrics, acc)
|
||||
buildNodeMetrics(summaryMetrics, acc)
|
||||
buildPodMetrics(summaryMetrics, acc)
|
||||
buildPodMetrics(baseURL, summaryMetrics, podInfos, k.labelFilter, acc)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -200,7 +192,56 @@ func buildNodeMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator)
|
|||
acc.AddFields("kubernetes_node", fields, tags)
|
||||
}
|
||||
|
||||
func buildPodMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) {
|
||||
func (k *Kubernetes) gatherPodInfo(baseURL string) ([]Metadata, error) {
|
||||
var podApi Pods
|
||||
err := k.LoadJson(fmt.Sprintf("%s/pods", baseURL), &podApi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var podInfos []Metadata
|
||||
for _, podMetadata := range podApi.Items {
|
||||
podInfos = append(podInfos, podMetadata.Metadata)
|
||||
}
|
||||
return podInfos, nil
|
||||
}
|
||||
|
||||
func (k *Kubernetes) LoadJson(url string, v interface{}) error {
|
||||
var req, err = http.NewRequest("GET", url, nil)
|
||||
var resp *http.Response
|
||||
tlsCfg, err := k.ClientConfig.TLSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if k.RoundTripper == nil {
|
||||
if k.ResponseTimeout.Duration < time.Second {
|
||||
k.ResponseTimeout.Duration = time.Second * 5
|
||||
}
|
||||
k.RoundTripper = &http.Transport{
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
TLSClientConfig: tlsCfg,
|
||||
ResponseHeaderTimeout: k.ResponseTimeout.Duration,
|
||||
}
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+k.BearerTokenString)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
resp, err = k.RoundTripper.RoundTrip(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making HTTP request to %s: %s", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("%s returned HTTP status %s", url, resp.Status)
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`Error parsing response: %s`, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildPodMetrics(baseURL string, summaryMetrics *SummaryMetrics, podInfo []Metadata, labelFilter filter.Filter, acc telegraf.Accumulator) {
|
||||
for _, pod := range summaryMetrics.Pods {
|
||||
for _, container := range pod.Containers {
|
||||
tags := map[string]string{
|
||||
|
@ -209,6 +250,16 @@ func buildPodMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) {
|
|||
"container_name": container.Name,
|
||||
"pod_name": pod.PodRef.Name,
|
||||
}
|
||||
for _, info := range podInfo {
|
||||
if info.Name == pod.PodRef.Name && info.Namespace == pod.PodRef.Namespace {
|
||||
for k, v := range info.Labels {
|
||||
if labelFilter.Match(k) {
|
||||
tags[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fields := make(map[string]interface{})
|
||||
fields["cpu_usage_nanocores"] = container.CPU.UsageNanoCores
|
||||
fields["cpu_usage_core_nanoseconds"] = container.CPU.UsageCoreNanoSeconds
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package kubernetes
|
||||
|
||||
type Pods struct {
|
||||
Kind string `json:"kind"`
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
Items []Item `json:"items"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Metadata Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
|
@ -2,6 +2,7 @@ package kubernetes
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -12,13 +13,23 @@ import (
|
|||
|
||||
func TestKubernetesStats(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintln(w, response)
|
||||
if r.RequestURI == "/stats/summary" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintln(w, responseStatsSummery)
|
||||
}
|
||||
if r.RequestURI == "/pods" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintln(w, responsePods)
|
||||
}
|
||||
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
labelFilter, _ := filter.NewIncludeExcludeFilter([]string{"app", "superkey"}, nil)
|
||||
|
||||
k := &Kubernetes{
|
||||
URL: ts.URL,
|
||||
URL: ts.URL,
|
||||
labelFilter: labelFilter,
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
@ -89,6 +100,8 @@ func TestKubernetesStats(t *testing.T) {
|
|||
"container_name": "foocontainer",
|
||||
"namespace": "foons",
|
||||
"pod_name": "foopod",
|
||||
"app": "foo",
|
||||
"superkey": "foobar",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "kubernetes_pod_container", fields, tags)
|
||||
|
||||
|
@ -112,6 +125,8 @@ func TestKubernetesStats(t *testing.T) {
|
|||
"container_name": "stopped-container",
|
||||
"namespace": "foons",
|
||||
"pod_name": "stopped-pod",
|
||||
"app": "foo-stop",
|
||||
"superkey": "superfoo",
|
||||
}
|
||||
acc.AssertContainsTaggedFields(t, "kubernetes_pod_container", fields, tags)
|
||||
|
||||
|
@ -143,7 +158,39 @@ func TestKubernetesStats(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
var response = `
|
||||
var responsePods = `
|
||||
{
|
||||
"kind": "PodList",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {},
|
||||
"items": [
|
||||
{
|
||||
"metadata": {
|
||||
"name": "foopod",
|
||||
"namespace": "foons",
|
||||
"labels": {
|
||||
"superkey": "foobar",
|
||||
"app": "foo",
|
||||
"exclude": "exclude0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "stopped-pod",
|
||||
"namespace": "foons",
|
||||
"labels": {
|
||||
"superkey": "superfoo",
|
||||
"app": "foo-stop",
|
||||
"exclude": "exclude1"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
var responseStatsSummery = `
|
||||
{
|
||||
"node": {
|
||||
"nodeName": "node1",
|
||||
|
|
Loading…
Reference in New Issue