youtubebeat/vendor/github.com/elastic/beats/libbeat/common/kubernetes/util.go

143 lines
4.6 KiB
Go

// 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
}