-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
util.go
170 lines (152 loc) · 5.79 KB
/
util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// 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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"github.com/elastic/beats/v7/libbeat/logp"
)
const defaultNode = "localhost"
func getKubeConfigEnvironmentVariable() string {
envKubeConfig := os.Getenv("KUBECONFIG")
if _, err := os.Stat(envKubeConfig); !os.IsNotExist(err) {
return envKubeConfig
}
return ""
}
// 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(kubeconfig string) (kubernetes.Interface, error) {
if kubeconfig == "" {
kubeconfig = getKubeConfigEnvironmentVariable()
}
cfg, err := buildConfig(kubeconfig)
if err != nil {
return nil, fmt.Errorf("unable to build kube config due to error: %+v", err)
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, fmt.Errorf("unable to build kubernetes clientset: %+v", err)
}
return client, nil
}
// buildConfig is a helper function that builds configs from a kubeconfig filepath.
// If kubeconfigPath is not passed in we fallback to inClusterConfig.
// If inClusterConfig fails, we fallback to the default config.
// This is a copy of `clientcmd.BuildConfigFromFlags` of `client-go` but without the annoying
// klog messages that are not possible to be disabled.
func buildConfig(kubeconfigPath string) (*restclient.Config, error) {
if kubeconfigPath == "" {
kubeconfig, err := restclient.InClusterConfig()
if err == nil {
return kubeconfig, nil
}
}
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
&clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}}).ClientConfig()
}
// IsInCluster takes a kubeconfig file path as input and deduces if Beats is running in cluster or not,
// taking into consideration the existence of KUBECONFIG variable
func IsInCluster(kubeconfig string) bool {
if kubeconfig != "" || getKubeConfigEnvironmentVariable() != "" {
return false
}
return true
}
// 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(log *logp.Logger, host string, inCluster bool, client kubernetes.Interface) (node string) {
if host != "" {
log.Infof("kubernetes: Using node %s provided in the config", host)
return host
}
ctx := context.TODO()
if inCluster {
ns, err := InClusterNamespace()
if err != nil {
log.Errorf("kubernetes: Couldn't get namespace when beat is in cluster with error: %+v", err.Error())
return defaultNode
}
podName, err := os.Hostname()
if err != nil {
log.Errorf("kubernetes: Couldn't get hostname as beat pod name in cluster with error: %+v", err.Error())
return defaultNode
}
log.Infof("kubernetes: Using pod name %s and namespace %s to discover kubernetes node", podName, ns)
pod, err := client.CoreV1().Pods(ns).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
log.Errorf("kubernetes: Querying for pod failed with error: %+v", err)
return defaultNode
}
log.Infof("kubernetes: Using node %s discovered by in cluster pod node query", pod.Spec.NodeName)
return pod.Spec.NodeName
}
mid := machineID()
if mid == "" {
log.Error("kubernetes: Couldn't collect info from any of the files in /etc/machine-id /var/lib/dbus/machine-id")
return defaultNode
}
nodes, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
log.Errorf("kubernetes: Querying for nodes failed with error: %+v", err)
return defaultNode
}
for _, n := range nodes.Items {
if n.Status.NodeInfo.MachineID == mid {
name := n.GetObjectMeta().GetName()
log.Infof("kubernetes: Using node %s discovered by machine-id matching", name)
return name
}
}
log.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
}