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 }