github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sources/kubelet/kubelet.go (about)

     1  // Copyright 2015 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kubelet
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"net/url"
    21  	"strings"
    22  	"time"
    23  
    24  	. "k8s.io/heapster/metrics/core"
    25  
    26  	"github.com/golang/glog"
    27  	cadvisor "github.com/google/cadvisor/info/v1"
    28  	"github.com/prometheus/client_golang/prometheus"
    29  	"k8s.io/heapster/metrics/util"
    30  	kube_api "k8s.io/kubernetes/pkg/api"
    31  	"k8s.io/kubernetes/pkg/client/cache"
    32  	kube_client "k8s.io/kubernetes/pkg/client/unversioned"
    33  	"k8s.io/kubernetes/pkg/fields"
    34  	"k8s.io/kubernetes/pkg/labels"
    35  )
    36  
    37  const (
    38  	infraContainerName = "POD"
    39  	// TODO: following constants are copied from k8s, change to use them directly
    40  	kubernetesPodNameLabel      = "io.kubernetes.pod.name"
    41  	kubernetesPodNamespaceLabel = "io.kubernetes.pod.namespace"
    42  	kubernetesPodUID            = "io.kubernetes.pod.uid"
    43  	kubernetesContainerLabel    = "io.kubernetes.container.name"
    44  )
    45  
    46  var (
    47  	// The Kubelet request latencies in microseconds.
    48  	kubeletRequestLatency = prometheus.NewSummaryVec(
    49  		prometheus.SummaryOpts{
    50  			Namespace: "heapster",
    51  			Subsystem: "kubelet",
    52  			Name:      "request_duration_microseconds",
    53  			Help:      "The Kubelet request latencies in microseconds.",
    54  		},
    55  		[]string{"node"},
    56  	)
    57  )
    58  
    59  func init() {
    60  	prometheus.MustRegister(kubeletRequestLatency)
    61  }
    62  
    63  // Kubelet-provided metrics for pod and system container.
    64  type kubeletMetricsSource struct {
    65  	host          Host
    66  	kubeletClient *KubeletClient
    67  	nodename      string
    68  	hostname      string
    69  	hostId        string
    70  }
    71  
    72  func NewKubeletMetricsSource(host Host, client *KubeletClient, nodeName string, hostName string, hostId string) MetricsSource {
    73  	return &kubeletMetricsSource{
    74  		host:          host,
    75  		kubeletClient: client,
    76  		nodename:      nodeName,
    77  		hostname:      hostName,
    78  		hostId:        hostId,
    79  	}
    80  }
    81  
    82  func (this *kubeletMetricsSource) Name() string {
    83  	return this.String()
    84  }
    85  
    86  func (this *kubeletMetricsSource) String() string {
    87  	return fmt.Sprintf("kubelet:%s:%d", this.host.IP, this.host.Port)
    88  }
    89  
    90  func (this *kubeletMetricsSource) handleSystemContainer(c *cadvisor.ContainerInfo, cMetrics *MetricSet) string {
    91  	glog.V(8).Infof("Found system container %v with labels: %+v", c.Name, c.Spec.Labels)
    92  	cName := c.Name
    93  	if strings.HasPrefix(cName, "/") {
    94  		cName = cName[1:]
    95  	}
    96  	cMetrics.Labels[LabelMetricSetType.Key] = MetricSetTypeSystemContainer
    97  	cMetrics.Labels[LabelContainerName.Key] = cName
    98  	return NodeContainerKey(this.nodename, cName)
    99  }
   100  
   101  func (this *kubeletMetricsSource) handleKubernetesContainer(cName, ns, podName string, c *cadvisor.ContainerInfo, cMetrics *MetricSet) string {
   102  	var metricSetKey string
   103  	if cName == infraContainerName {
   104  		metricSetKey = PodKey(ns, podName)
   105  		cMetrics.Labels[LabelMetricSetType.Key] = MetricSetTypePod
   106  	} else {
   107  		metricSetKey = PodContainerKey(ns, podName, cName)
   108  		cMetrics.Labels[LabelMetricSetType.Key] = MetricSetTypePodContainer
   109  		cMetrics.Labels[LabelContainerName.Key] = cName
   110  		cMetrics.Labels[LabelContainerBaseImage.Key] = c.Spec.Image
   111  	}
   112  	cMetrics.Labels[LabelPodId.Key] = c.Spec.Labels[kubernetesPodUID]
   113  	cMetrics.Labels[LabelPodName.Key] = podName
   114  	cMetrics.Labels[LabelNamespaceName.Key] = ns
   115  	// Needed for backward compatibility
   116  	cMetrics.Labels[LabelPodNamespace.Key] = ns
   117  	return metricSetKey
   118  }
   119  
   120  func (this *kubeletMetricsSource) decodeMetrics(c *cadvisor.ContainerInfo) (string, *MetricSet) {
   121  	if len(c.Stats) == 0 {
   122  		return "", nil
   123  	}
   124  
   125  	var metricSetKey string
   126  	cMetrics := &MetricSet{
   127  		CreateTime:   c.Spec.CreationTime,
   128  		ScrapeTime:   c.Stats[0].Timestamp,
   129  		MetricValues: map[string]MetricValue{},
   130  		Labels: map[string]string{
   131  			LabelNodename.Key: this.nodename,
   132  			LabelHostname.Key: this.hostname,
   133  			LabelHostID.Key:   this.hostId,
   134  		},
   135  		LabeledMetrics: []LabeledMetric{},
   136  	}
   137  
   138  	if isNode(c) {
   139  		metricSetKey = NodeKey(this.nodename)
   140  		cMetrics.Labels[LabelMetricSetType.Key] = MetricSetTypeNode
   141  	} else {
   142  		cName := c.Spec.Labels[kubernetesContainerLabel]
   143  		ns := c.Spec.Labels[kubernetesPodNamespaceLabel]
   144  		podName := c.Spec.Labels[kubernetesPodNameLabel]
   145  
   146  		// Support for kubernetes 1.0.*
   147  		if ns == "" && strings.Contains(podName, "/") {
   148  			tokens := strings.SplitN(podName, "/", 2)
   149  			if len(tokens) == 2 {
   150  				ns = tokens[0]
   151  				podName = tokens[1]
   152  			}
   153  		}
   154  		if cName == "" {
   155  			// Better this than nothing. This is a temporary hack for new heapster to work
   156  			// with Kubernetes 1.0.*.
   157  			// TODO: fix this with POD list.
   158  			// Parsing name like:
   159  			// k8s_kube-ui.7f9b83f6_kube-ui-v1-bxj1w_kube-system_9abfb0bd-811f-11e5-b548-42010af00002_e6841e8d
   160  			pos := strings.Index(c.Name, ".")
   161  			if pos >= 0 {
   162  				// remove first 4 chars.
   163  				cName = c.Name[len("k8s_"):pos]
   164  			}
   165  		}
   166  
   167  		// No Kubernetes metadata so treat this as a system container.
   168  		if cName == "" || ns == "" || podName == "" {
   169  			metricSetKey = this.handleSystemContainer(c, cMetrics)
   170  		} else {
   171  			metricSetKey = this.handleKubernetesContainer(cName, ns, podName, c, cMetrics)
   172  		}
   173  	}
   174  
   175  	for _, metric := range StandardMetrics {
   176  		if metric.HasValue != nil && metric.HasValue(&c.Spec) {
   177  			cMetrics.MetricValues[metric.Name] = metric.GetValue(&c.Spec, c.Stats[0])
   178  		}
   179  	}
   180  
   181  	for _, metric := range LabeledMetrics {
   182  		if metric.HasLabeledMetric != nil && metric.HasLabeledMetric(&c.Spec) {
   183  			labeledMetrics := metric.GetLabeledMetric(&c.Spec, c.Stats[0])
   184  			cMetrics.LabeledMetrics = append(cMetrics.LabeledMetrics, labeledMetrics...)
   185  		}
   186  	}
   187  
   188  	if c.Spec.HasCustomMetrics {
   189  	metricloop:
   190  		for _, spec := range c.Spec.CustomMetrics {
   191  			if cmValue, ok := c.Stats[0].CustomMetrics[spec.Name]; ok && cmValue != nil && len(cmValue) >= 1 {
   192  				newest := cmValue[0]
   193  				for _, metricVal := range cmValue {
   194  					if newest.Timestamp.Before(metricVal.Timestamp) {
   195  						newest = metricVal
   196  					}
   197  				}
   198  				mv := MetricValue{}
   199  				switch spec.Type {
   200  				case cadvisor.MetricGauge:
   201  					mv.MetricType = MetricGauge
   202  				case cadvisor.MetricCumulative:
   203  					mv.MetricType = MetricCumulative
   204  				default:
   205  					glog.V(4).Infof("Skipping %s: unknown custom metric type: %v", spec.Name, spec.Type)
   206  					continue metricloop
   207  				}
   208  
   209  				switch spec.Format {
   210  				case cadvisor.IntType:
   211  					mv.ValueType = ValueInt64
   212  					mv.IntValue = newest.IntValue
   213  				case cadvisor.FloatType:
   214  					mv.ValueType = ValueFloat
   215  					mv.FloatValue = float32(newest.FloatValue)
   216  				default:
   217  					glog.V(4).Infof("Skipping %s: unknown custom metric format", spec.Name, spec.Format)
   218  					continue metricloop
   219  				}
   220  
   221  				cMetrics.MetricValues[CustomMetricPrefix+spec.Name] = mv
   222  			}
   223  		}
   224  	}
   225  
   226  	return metricSetKey, cMetrics
   227  }
   228  
   229  func (this *kubeletMetricsSource) ScrapeMetrics(start, end time.Time) *DataBatch {
   230  	containers, err := this.scrapeKubelet(this.kubeletClient, this.host, start, end)
   231  	if err != nil {
   232  		glog.Errorf("error while getting containers from Kubelet: %v", err)
   233  	}
   234  	glog.V(2).Infof("successfully obtained stats for %v containers", len(containers))
   235  
   236  	result := &DataBatch{
   237  		Timestamp:  end,
   238  		MetricSets: map[string]*MetricSet{},
   239  	}
   240  	keys := make(map[string]bool)
   241  	for _, c := range containers {
   242  		name, metrics := this.decodeMetrics(&c)
   243  		if name == "" || metrics == nil {
   244  			continue
   245  		}
   246  		result.MetricSets[name] = metrics
   247  		keys[name] = true
   248  	}
   249  	return result
   250  }
   251  
   252  func (this *kubeletMetricsSource) scrapeKubelet(client *KubeletClient, host Host, start, end time.Time) ([]cadvisor.ContainerInfo, error) {
   253  	startTime := time.Now()
   254  	defer kubeletRequestLatency.WithLabelValues(this.hostname).Observe(float64(time.Since(startTime)))
   255  	return client.GetAllRawContainers(host, start, end)
   256  }
   257  
   258  type kubeletProvider struct {
   259  	nodeLister    *cache.StoreToNodeLister
   260  	reflector     *cache.Reflector
   261  	kubeletClient *KubeletClient
   262  }
   263  
   264  func (this *kubeletProvider) GetMetricsSources() []MetricsSource {
   265  	sources := []MetricsSource{}
   266  	nodes, err := this.nodeLister.List()
   267  	if err != nil {
   268  		glog.Errorf("error while listing nodes: %v", err)
   269  		return sources
   270  	}
   271  	if len(nodes.Items) == 0 {
   272  		glog.Error("No nodes received from APIserver.")
   273  		return sources
   274  	}
   275  
   276  	nodeNames := make(map[string]bool)
   277  	for _, node := range nodes.Items {
   278  		nodeNames[node.Name] = true
   279  		hostname, ip, err := getNodeHostnameAndIP(&node)
   280  		if err != nil {
   281  			glog.Errorf("%v", err)
   282  			continue
   283  		}
   284  		sources = append(sources, NewKubeletMetricsSource(
   285  			Host{IP: ip, Port: this.kubeletClient.GetPort()},
   286  			this.kubeletClient,
   287  			node.Name,
   288  			hostname,
   289  			node.Spec.ExternalID,
   290  		))
   291  	}
   292  	return sources
   293  }
   294  
   295  func getNodeHostnameAndIP(node *kube_api.Node) (string, string, error) {
   296  	for _, c := range node.Status.Conditions {
   297  		if c.Type == kube_api.NodeReady && c.Status != kube_api.ConditionTrue {
   298  			return "", "", fmt.Errorf("Node %v is not ready", node.Name)
   299  		}
   300  	}
   301  	hostname, ip := node.Name, ""
   302  	for _, addr := range node.Status.Addresses {
   303  		if addr.Type == kube_api.NodeHostName && addr.Address != "" {
   304  			hostname = addr.Address
   305  		}
   306  		if addr.Type == kube_api.NodeInternalIP && addr.Address != "" {
   307  			if net.ParseIP(addr.Address).To4() != nil {
   308  				ip = addr.Address
   309  			}
   310  		}
   311  		if addr.Type == kube_api.NodeLegacyHostIP && addr.Address != "" && ip == "" {
   312  			ip = addr.Address
   313  		}
   314  	}
   315  	if ip != "" {
   316  		return hostname, ip, nil
   317  	}
   318  	return "", "", fmt.Errorf("Node %v has no valid hostname and/or IP address: %v %v", node.Name, hostname, ip)
   319  }
   320  
   321  func NewKubeletProvider(uri *url.URL) (MetricsSourceProvider, error) {
   322  	// create clients
   323  	kubeConfig, kubeletConfig, err := GetKubeConfigs(uri)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	kubeClient := kube_client.NewOrDie(kubeConfig)
   328  	kubeletClient, err := NewKubeletClient(kubeletConfig)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  
   333  	// Get nodes to test if the client is configured well. Watch gives less error information.
   334  	if _, err := kubeClient.Nodes().List(kube_api.ListOptions{
   335  		LabelSelector: labels.Everything(),
   336  		FieldSelector: fields.Everything()}); err != nil {
   337  		glog.Errorf("Failed to load nodes: %v", err)
   338  	}
   339  
   340  	// watch nodes
   341  	nodeLister, reflector, _ := util.GetNodeLister(kubeClient)
   342  
   343  	return &kubeletProvider{
   344  		nodeLister:    nodeLister,
   345  		reflector:     reflector,
   346  		kubeletClient: kubeletClient,
   347  	}, nil
   348  }