// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package kubernetes import ( "context" "fmt" "io/ioutil" "os" "strings" "github.com/ericchiang/k8s" "github.com/ericchiang/k8s/apis/core/v1" "github.com/ghodss/yaml" "github.com/elastic/beats/libbeat/logp" ) const defaultNode = "localhost" // GetKubernetesClient returns a kubernetes client. If inCluster is true, it returns an // in cluster configuration based on the secrets mounted in the Pod. If kubeConfig is passed, // it parses the config file to get the config required to build a client. func GetKubernetesClient(inCluster bool, kubeConfig string) (client *k8s.Client, err error) { if inCluster == true { client, err = k8s.NewInClusterClient() if err != nil { return nil, fmt.Errorf("Unable to get in cluster configuration: %v", err) } } else { data, err := ioutil.ReadFile(kubeConfig) if err != nil { return nil, fmt.Errorf("read kubeconfig: %v", err) } // Unmarshal YAML into a Kubernetes config object. var config k8s.Config if err = yaml.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("unmarshal kubeconfig: %v", err) } client, err = k8s.NewClient(&config) if err != nil { return nil, err } } return client, nil } // DiscoverKubernetesNode figures out the Kubernetes node to use. // If host is provided in the config use it directly. // If beat is deployed in k8s cluster, use hostname of pod which is pod name to query pod meta for node name. // If beat is deployed outside k8s cluster, use machine-id to match against k8s nodes for node name. func DiscoverKubernetesNode(host string, inCluster bool, client *k8s.Client) (node string) { if host != "" { logp.Info("kubernetes: Using node %s provided in the config", host) return host } if inCluster { ns, err := inClusterNamespace() if err != nil { logp.Err("kubernetes: Couldn't get namespace when beat is in cluster with error: ", err.Error()) return defaultNode } podName, err := os.Hostname() if err != nil { logp.Err("kubernetes: Couldn't get hostname as beat pod name in cluster with error: ", err.Error()) return defaultNode } logp.Info("kubernetes: Using pod name %s and namespace %s to discover kubernetes node", podName, ns) pod := v1.Pod{} err = client.Get(context.TODO(), ns, podName, &pod) if err != nil { logp.Err("kubernetes: Querying for pod failed with error: %v", err.Error()) return defaultNode } logp.Info("kubernetes: Using node %s discovered by in cluster pod node query", pod.Spec.GetNodeName()) return pod.Spec.GetNodeName() } mid := machineID() if mid == "" { logp.Err("kubernetes: Couldn't collect info from any of the files in /etc/machine-id /var/lib/dbus/machine-id") return defaultNode } nodes := v1.NodeList{} err := client.List(context.TODO(), k8s.AllNamespaces, &nodes) if err != nil { logp.Err("kubernetes: Querying for nodes failed with error: ", err.Error()) return defaultNode } for _, n := range nodes.Items { if n.GetStatus().GetNodeInfo().GetMachineID() == mid { logp.Info("kubernetes: Using node %s discovered by machine-id matching", n.GetMetadata().GetName()) return n.GetMetadata().GetName() } } logp.Warn("kubernetes: Couldn't discover node, using localhost as default") return defaultNode } // machineID borrowed from cadvisor. func machineID() string { for _, file := range []string{ "/etc/machine-id", "/var/lib/dbus/machine-id", } { id, err := ioutil.ReadFile(file) if err == nil { return strings.TrimSpace(string(id)) } } return "" } // inClusterNamespace gets namespace from serviceaccount when beat is in cluster. // code borrowed from client-go with some changes. func inClusterNamespace() (string, error) { // get namespace associated with the service account token, if available data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") if err != nil { return "", err } return strings.TrimSpace(string(data)), nil }