master
go 204 lines 5.36 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package k8s_state
4
5 import (
6 "context"
7 "os"
8 "sync"
9 "time"
10
11 "github.com/netdata/netdata/go/plugins/logger"
12
13 appsv1 "k8s.io/api/apps/v1"
14 batchv1 "k8s.io/api/batch/v1"
15 corev1 "k8s.io/api/core/v1"
16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17 "k8s.io/apimachinery/pkg/runtime"
18 "k8s.io/apimachinery/pkg/watch"
19 "k8s.io/client-go/kubernetes"
20 "k8s.io/client-go/tools/cache"
21 "k8s.io/client-go/util/workqueue"
22 )
23
24 type discoverer interface {
25 run(ctx context.Context, in chan<- resource)
26 ready() bool
27 stopped() bool
28 }
29
30 func newKubeDiscovery(client kubernetes.Interface, l *logger.Logger) *kubeDiscovery {
31 return &kubeDiscovery{
32 client: client,
33 Logger: l,
34 readyCh: make(chan struct{}),
35 stopCh: make(chan struct{}),
36 }
37 }
38
39 type kubeDiscovery struct {
40 *logger.Logger
41 client kubernetes.Interface
42 discoverers []discoverer
43 readyCh chan struct{}
44 stopCh chan struct{}
45 }
46
47 func (d *kubeDiscovery) run(ctx context.Context, in chan<- resource) {
48 d.Info("kube_discoverer is started")
49 defer func() { close(d.stopCh); d.Info("kube_discoverer is stopped") }()
50
51 d.discoverers = d.setupDiscoverers(ctx)
52
53 var wg sync.WaitGroup
54 updates := make(chan resource)
55
56 for _, dd := range d.discoverers {
57 wg.Add(1)
58 go func(dd discoverer) { defer wg.Done(); dd.run(ctx, updates) }(dd)
59 }
60
61 wg.Go(func() { d.runDiscover(ctx, updates, in) })
62
63 close(d.readyCh)
64 wg.Wait()
65 <-ctx.Done()
66 }
67
68 func (d *kubeDiscovery) ready() bool {
69 if !isChanClosed(d.readyCh) {
70 return false
71 }
72 for _, dd := range d.discoverers {
73 if !dd.ready() {
74 return false
75 }
76 }
77 return true
78 }
79
80 func (d *kubeDiscovery) stopped() bool {
81 if !isChanClosed(d.stopCh) {
82 return false
83 }
84 for _, dd := range d.discoverers {
85 if !dd.stopped() {
86 return false
87 }
88 }
89 return true
90 }
91
92 func (d *kubeDiscovery) runDiscover(ctx context.Context, updates chan resource, in chan<- resource) {
93 for {
94 select {
95 case <-ctx.Done():
96 return
97 case r := <-updates:
98 select {
99 case <-ctx.Done():
100 return
101 case in <- r:
102 }
103 }
104 }
105 }
106
107 const resyncPeriod = 10 * time.Minute
108
109 var (
110 myNodeName = os.Getenv("MY_NODE_NAME")
111 )
112
113 func (d *kubeDiscovery) setupDiscoverers(ctx context.Context) []discoverer {
114 node := d.client.CoreV1().Nodes()
115 nodeWatcher := cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{
116 ListWithContextFunc: func(_ context.Context, options metav1.ListOptions) (runtime.Object, error) {
117 return node.List(ctx, options)
118 },
119 WatchFuncWithContext: func(_ context.Context, options metav1.ListOptions) (watch.Interface, error) {
120 return node.Watch(ctx, options)
121 },
122 }, d.client)
123
124 pod := d.client.CoreV1().Pods(corev1.NamespaceAll)
125 podWatcher := cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{
126 ListWithContextFunc: func(_ context.Context, options metav1.ListOptions) (runtime.Object, error) {
127 if myNodeName != "" {
128 options.FieldSelector = "spec.nodeName=" + myNodeName
129 }
130 return pod.List(ctx, options)
131 },
132 WatchFuncWithContext: func(_ context.Context, options metav1.ListOptions) (watch.Interface, error) {
133 if myNodeName != "" {
134 options.FieldSelector = "spec.nodeName=" + myNodeName
135 }
136 return pod.Watch(ctx, options)
137 },
138 }, d.client)
139
140 deploy := d.client.AppsV1().Deployments(corev1.NamespaceAll)
141 deployWatcher := cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{
142 ListWithContextFunc: func(_ context.Context, options metav1.ListOptions) (runtime.Object, error) {
143 return deploy.List(ctx, options)
144 },
145 WatchFuncWithContext: func(_ context.Context, options metav1.ListOptions) (watch.Interface, error) {
146 return deploy.Watch(ctx, options)
147 },
148 }, d.client)
149
150 cj := d.client.BatchV1().CronJobs(corev1.NamespaceAll)
151 cjWatcher := cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{
152 ListWithContextFunc: func(_ context.Context, options metav1.ListOptions) (runtime.Object, error) {
153 return cj.List(ctx, options)
154 },
155 WatchFuncWithContext: func(_ context.Context, options metav1.ListOptions) (watch.Interface, error) {
156 return cj.Watch(ctx, options)
157 },
158 }, d.client)
159
160 jobs := d.client.BatchV1().Jobs(corev1.NamespaceAll)
161 jobsWatcher := cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{
162 ListWithContextFunc: func(_ context.Context, options metav1.ListOptions) (runtime.Object, error) {
163 return jobs.List(ctx, options)
164 },
165 WatchFuncWithContext: func(_ context.Context, options metav1.ListOptions) (watch.Interface, error) {
166 return jobs.Watch(ctx, options)
167 },
168 }, d.client)
169
170 return []discoverer{
171 newNodeDiscoverer(cache.NewSharedInformer(nodeWatcher, &corev1.Node{}, resyncPeriod), d.Logger),
172 newPodDiscoverer(cache.NewSharedInformer(podWatcher, &corev1.Pod{}, resyncPeriod), d.Logger),
173 newDeploymentDiscoverer(cache.NewSharedInformer(deployWatcher, &appsv1.Deployment{}, resyncPeriod), d.Logger),
174 newCronJobDiscoverer(cache.NewSharedInformer(cjWatcher, &batchv1.CronJob{}, resyncPeriod), d.Logger),
175 newJobDiscoverer(cache.NewSharedInformer(jobsWatcher, &batchv1.Job{}, resyncPeriod), d.Logger),
176 }
177 }
178
179 func enqueue(queue *workqueue.Typed[string], obj any) {
180 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
181 if err != nil {
182 return
183 }
184 queue.Add(key)
185 }
186
187 func send(ctx context.Context, in chan<- resource, r resource) {
188 if r == nil {
189 return
190 }
191 select {
192 case <-ctx.Done():
193 case in <- r:
194 }
195 }
196
197 func isChanClosed(ch chan struct{}) bool {
198 select {
199 case <-ch:
200 return true
201 default:
202 return false
203 }
204 }