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 }