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