143 lines
4.6 KiB
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
|
|
}
|