1
1
package debugproxies
2
2
3
3
import (
4
- "context"
5
4
"errors"
6
5
"fmt"
6
+ "io/ioutil"
7
7
"strconv"
8
+ "strings"
8
9
"time"
9
10
10
- "github.com/ericchiang/k8s"
11
- corev1 "github.com/ericchiang/k8s/apis/core/v1"
12
11
"github.com/inconshreveable/log15"
12
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13
+ "k8s.io/apimachinery/pkg/watch"
14
+ "k8s.io/client-go/kubernetes"
15
+ v1 "k8s.io/client-go/kubernetes/typed/core/v1"
16
+ "k8s.io/client-go/rest"
13
17
)
14
18
15
19
// Represents an endpoint
@@ -26,47 +30,21 @@ type Endpoint struct {
26
30
// ScanConsumer is the callback to consume scan results.
27
31
type ScanConsumer func ([]Endpoint )
28
32
29
- // Declares methods we use with k8s.Client. Useful to plug testing replacements or even logging middleware.
30
- type kubernetesClient interface {
31
- Watch (ctx context.Context , namespace string , r k8s.Resource , options ... k8s.Option ) (* k8s.Watcher , error )
32
- List (ctx context.Context , namespace string , resp k8s.ResourceList , options ... k8s.Option ) error
33
- Get (ctx context.Context , namespace , name string , resp k8s.Resource , options ... k8s.Option ) error
34
- Namespace () string
35
- }
36
-
37
- // "real" implementation that sends calls to the k8s.Client
38
- type k8sClientImpl struct {
39
- client * k8s.Client
40
- }
41
-
42
- func (kci * k8sClientImpl ) Watch (ctx context.Context , namespace string , r k8s.Resource , options ... k8s.Option ) (* k8s.Watcher , error ) {
43
- return kci .client .Watch (ctx , namespace , r , options ... )
44
- }
45
-
46
- func (kci * k8sClientImpl ) List (ctx context.Context , namespace string , resp k8s.ResourceList , options ... k8s.Option ) error {
47
- return kci .client .List (ctx , namespace , resp , options ... )
48
- }
49
-
50
- func (kci * k8sClientImpl ) Get (ctx context.Context , namespace , name string , resp k8s.Resource , options ... k8s.Option ) error {
51
- return kci .client .Get (ctx , namespace , name , resp , options ... )
52
- }
53
-
54
- func (kci * k8sClientImpl ) Namespace () string {
55
- return kci .client .Namespace
56
- }
57
-
58
33
// clusterScanner scans the cluster for endpoints belonging to services that have annotation sourcegraph.prometheus/scrape=true.
59
34
// It runs an event loop that reacts to changes to the endpoints set. Everytime there is a change it calls the ScanConsumer.
60
35
type clusterScanner struct {
61
- client kubernetesClient
62
- consume ScanConsumer
36
+ client v1.CoreV1Interface
37
+ namespace string
38
+ consume ScanConsumer
63
39
}
64
40
65
41
// Starts a cluster scanner with the specified client and consumer. Does not block.
66
- func startClusterScannerWithClient (client kubernetesClient , consumer ScanConsumer ) error {
42
+ func startClusterScannerWithClient (client * kubernetes.Clientset , ns string , consumer ScanConsumer ) error {
43
+
67
44
cs := & clusterScanner {
68
- client : client ,
69
- consume : consumer ,
45
+ client : client .CoreV1 (),
46
+ namespace : ns ,
47
+ consume : consumer ,
70
48
}
71
49
72
50
go cs .runEventLoop ()
@@ -75,13 +53,18 @@ func startClusterScannerWithClient(client kubernetesClient, consumer ScanConsume
75
53
76
54
// Starts a cluster scanner with the specified consumer. Does not block.
77
55
func StartClusterScanner (consumer ScanConsumer ) error {
78
- client , err := k8s .NewInClusterClient ()
56
+ config , err := rest .InClusterConfig ()
57
+ if err != nil {
58
+ return err
59
+ }
60
+ ns := namespace ()
61
+ // access to K8s clients
62
+ clientset , err := kubernetes .NewForConfig (config )
79
63
if err != nil {
80
64
return err
81
65
}
82
66
83
- kci := & k8sClientImpl {client : client }
84
- return startClusterScannerWithClient (kci , consumer )
67
+ return startClusterScannerWithClient (clientset , ns , consumer )
85
68
}
86
69
87
70
// Runs the k8s.Watch endpoints event loop, and triggers a rescan of cluster when something changes with endpoints.
@@ -102,21 +85,23 @@ func (cs *clusterScanner) runEventLoop() {
102
85
// watchEndpointEvents uses the k8s watch API operation to watch for endpoint events. Spins forever unless an error
103
86
// occurs that would necessitate creating a new watcher. The caller will then call again creating the new watcher.
104
87
func (cs * clusterScanner ) watchEndpointEvents () (bool , error ) {
105
- watcher , err := cs .client .Watch (context .Background (), cs .client .Namespace (), new (corev1.Endpoints ))
88
+
89
+ // TODO(Dax): Rewrite this to used NewSharedInformerFactory from k8s/client-go
90
+
91
+ watcher , err := cs .client .Endpoints (metav1 .NamespaceAll ).Watch (metav1.ListOptions {})
106
92
if err != nil {
107
93
return false , fmt .Errorf ("k8s client.Watch error: %w" , err )
108
94
}
109
- defer watcher .Close ()
95
+ defer watcher .Stop ()
110
96
111
97
for {
112
- var eps corev1.Endpoints
113
- eventType , err := watcher .Next (& eps )
98
+ event := <- watcher .ResultChan ()
114
99
if err != nil {
115
100
// we need a new watcher
116
101
return true , fmt .Errorf ("k8s watcher.Next error: %w" , err )
117
102
}
118
103
119
- if eventType == k8s . EventError {
104
+ if event . Type == watch . Error {
120
105
// we need a new watcher
121
106
return true , errors .New ("error event" )
122
107
}
@@ -128,66 +113,63 @@ func (cs *clusterScanner) watchEndpointEvents() (bool, error) {
128
113
// scanCluster looks for endpoints belonging to services that have annotation sourcegraph.prometheus/scrape=true.
129
114
// It derives the appropriate port from the prometheus.io/port annotation.
130
115
func (cs * clusterScanner ) scanCluster () {
131
- var services corev1.ServiceList
132
116
133
- err := cs .client .List (context .Background (), cs .client .Namespace (), & services )
117
+ // Get services from all namespaces
118
+ services , err := cs .client .Services (cs .namespace ).List (metav1.ListOptions {})
134
119
if err != nil {
135
120
log15 .Error ("k8s failed to list services" , "error" , err )
136
- return
137
121
}
138
122
139
123
var scanResults []Endpoint
140
124
141
125
for _ , svc := range services .Items {
142
- svcName := * svc . Metadata .Name
126
+ svcName := svc .Name
143
127
144
128
// TODO(uwedeportivo): pgsql doesn't work, figure out why
145
129
if svcName == "pgsql" {
146
130
continue
147
131
}
148
132
149
- if svc .Metadata . Annotations ["sourcegraph.prometheus/scrape" ] != "true" {
133
+ if svc .Annotations ["sourcegraph.prometheus/scrape" ] != "true" {
150
134
continue
151
135
}
152
136
153
137
var port int
154
- if portStr := svc .Metadata . Annotations ["prometheus.io/port" ]; portStr != "" {
138
+ if portStr := svc .Annotations ["prometheus.io/port" ]; portStr != "" {
155
139
port , err = strconv .Atoi (portStr )
156
140
if err != nil {
157
141
log15 .Debug ("k8s prometheus.io/port annotation for service is not an integer" , "service" , svcName , "port" , portStr )
158
142
continue
159
143
}
160
144
}
161
145
162
- var endpoints corev1.Endpoints
163
- err = cs .client .Get (context .Background (), cs .client .Namespace (), svcName , & endpoints )
146
+ endpoints , err := cs .client .Endpoints (cs .namespace ).Get (svcName , metav1.GetOptions {})
164
147
if err != nil {
165
148
log15 .Error ("k8s failed to get endpoints" , "error" , err )
166
149
return
167
150
}
168
-
169
151
for _ , subset := range endpoints .Subsets {
170
152
var ports []int
171
153
if port != 0 {
172
154
ports = []int {port }
173
155
} else {
174
- for _ , port := range subset .GetPorts () {
175
- ports = append (ports , int (port .GetPort () ))
156
+ for _ , port := range subset .Ports {
157
+ ports = append (ports , int (port .Port ))
176
158
}
177
159
}
178
160
179
161
for _ , addr := range subset .Addresses {
180
162
for _ , port := range ports {
181
- addrStr := fromStrPtr ( addr .Ip )
163
+ addrStr := addr .IP
182
164
if addrStr == "" {
183
- addrStr = fromStrPtr ( addr .Hostname )
165
+ addrStr = addr .Hostname
184
166
}
185
167
186
168
if addrStr != "" {
187
169
scanResults = append (scanResults , Endpoint {
188
170
Service : svcName ,
189
171
Addr : fmt .Sprintf ("%s:%d" , addrStr , port ),
190
- Hostname : fromStrPtr ( addr .Hostname ) ,
172
+ Hostname : addr .Hostname ,
191
173
})
192
174
}
193
175
}
@@ -198,10 +180,21 @@ func (cs *clusterScanner) scanCluster() {
198
180
cs .consume (scanResults )
199
181
}
200
182
201
- // fromStrPtr returns *s. If s is nil the empty string is returned.
202
- func fromStrPtr (s * string ) string {
203
- if s == nil {
204
- return ""
183
+ // namespace returns the namespace the pod is currently running in
184
+ // this is done because the k8s client we previously used set the namespace
185
+ // when the client was created, the official k8s client does not
186
+ func namespace () string {
187
+ const filename = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
188
+ data , err := ioutil .ReadFile (filename )
189
+ if err != nil {
190
+ log15 .Warn ("scanner: falling back to kubernetes default namespace" , "filename" , filename , "error" , err )
191
+ return "default"
192
+ }
193
+
194
+ ns := strings .TrimSpace (string (data ))
195
+ if ns == "" {
196
+ log15 .Warn ("file: " , filename , " empty using \" default\" ns" )
197
+ return "default"
205
198
}
206
- return * s
199
+ return ns
207
200
}
0 commit comments