1
1
import express from 'express' ;
2
2
import { Gauge , register } from 'prom-client' ;
3
3
import http from 'http'
4
- import { AllAppwrappers } from './appwrapper-utils' ;
5
4
import * as k8s from '@kubernetes/client-node' ;
6
- import * as fs from 'fs' ;
7
5
8
6
const app = express ( ) ;
9
7
const PORT = 9101 ;
@@ -12,19 +10,13 @@ const PORT = 9101;
12
10
const isRunningInKubernetes = process . env . KUBERNETES_SERVICE_HOST ;
13
11
const kc = new k8s . KubeConfig ( ) ;
14
12
if ( isRunningInKubernetes ) {
15
- const token = fs . readFileSync ( '/var/run/secrets/kubernetes.io/serviceaccount/token' , 'utf8' ) ;
16
13
kc . loadFromCluster ( ) ;
14
+ console . log ( `Connected to k8s api via cluster credentials` ) ;
17
15
} else {
18
16
kc . loadFromDefault ( ) ;
17
+ console . log ( `Connected to k8s api via local kubeconfig` ) ;
19
18
}
20
19
const k8sApi = kc . makeApiClient ( k8s . CustomObjectsApi ) ;
21
- console . log ( `connected to kubernetes... testing appwrapper call` ) ;
22
- // test k8s
23
- const list = k8sApi . listClusterCustomObject ( 'workload.codeflare.dev' , 'v1beta1' , 'appwrappers' ) ;
24
- list . then ( ( res ) => {
25
- console . log ( `test resuld: ${ res } ` )
26
- } )
27
- console . log ( `finished kubernetes connection test` ) ;
28
20
29
21
interface AppwrapperObject extends k8s . KubernetesObject {
30
22
metadata : {
@@ -71,7 +63,7 @@ function getAppwrapperStatus(status: string) {
71
63
}
72
64
73
65
// initialize count metrics
74
- console . log ( `initializing prometheus metrics` ) ;
66
+ console . log ( `Initializing Prometheus metrics` ) ;
75
67
appwrapperCountMetric . labels ( "Running" ) . set ( 0 ) ;
76
68
appwrapperCountMetric . labels ( "Pending" ) . set ( 0 ) ;
77
69
appwrapperCountMetric . labels ( "Failed" ) . set ( 0 ) ;
@@ -80,56 +72,76 @@ appwrapperCountMetric.labels("Other").set(0);
80
72
/** END initialize prometheus metrics **/
81
73
82
74
/** initialize informer **/
83
- console . log ( `initializing informer` )
75
+ // need to store previous state for each appwrapper so that upon change we can update labels
76
+ // the key is `<appwrapper_namespace>,<appwrapper_name>` as a string
77
+ // this assumes commas are not included in namespaces names or appwrapper names
78
+ const previousAppwrapperStates : Map < string , string > = new Map ( ) ;
79
+
80
+ console . log ( `Initializing informer` )
84
81
async function listFn ( ) : Promise < { response : http . IncomingMessage ; body : k8s . KubernetesListObject < AppwrapperObject > ; } > {
85
82
const list = await k8sApi . listClusterCustomObject ( 'workload.codeflare.dev' , 'v1beta1' , 'appwrappers' ) ;
86
83
let k8sBody = list . body as k8s . KubernetesListObject < AppwrapperObject > ;
87
84
let value = { response : list . response , body : k8sBody } ;
88
85
let returnedPromise : Promise < { response : http . IncomingMessage ; body : k8s . KubernetesListObject < AppwrapperObject > ; } > = new Promise ( ( resolve , reject ) => {
89
86
resolve ( value ) ;
90
87
} )
91
- console . log ( `listFN called: ` )
92
- console . log ( list . response )
93
- console . log ( k8sBody )
94
- console . log ( value )
95
- console . log ( returnedPromise ) ;
96
- console . log ( `listFN called: ${ returnedPromise } , ${ value } , ${ list . response } , ${ k8sBody } ` ) ;
97
88
return returnedPromise
98
89
}
99
90
100
91
const informer = k8s . makeInformer ( kc , '/apis/workload.codeflare.dev/v1beta1/appwrappers' , listFn ) ;
101
- informer . on ( 'change' , ( obj ) => {
102
- //console.log(`hello ch!`);
103
- //console.log(`changed: ${obj.metadata.name}`);
92
+ informer . on ( 'add' , ( obj ) => {
93
+ let appwrapperName : string = obj . metadata . name ;
94
+ let appwrapperNamespace = obj . metadata . namespace ;
95
+ let appwrapperStatus = obj . status . state ;
96
+ console . log ( `add received: ${ appwrapperName } , ${ appwrapperNamespace } , ${ appwrapperStatus } ` )
97
+ appwrapperStatusMetric . labels ( appwrapperName , appwrapperNamespace ) . set ( getAppwrapperStatus ( appwrapperStatus ) )
98
+ appwrapperCountMetric . labels ( appwrapperStatus ) . inc ( 1 ) ;
99
+ // set previous state
100
+ previousAppwrapperStates . set ( `${ appwrapperNamespace } ,${ appwrapperName } ` , appwrapperStatus ) ;
101
+ } ) ;
102
+ informer . on ( 'update' , ( obj ) => {
104
103
let appwrapperName : string = obj . metadata . name ;
105
104
let appwrapperNamespace = obj . metadata . namespace ;
106
105
let appwrapperStatus = obj . status . state ;
106
+ console . log ( `update received: ${ appwrapperName } , ${ appwrapperNamespace } , ${ appwrapperStatus } ` ) ;
107
+ console . log ( informer . get ( appwrapperName , appwrapperNamespace ) )
107
108
appwrapperStatusMetric . labels ( appwrapperName , appwrapperNamespace ) . set ( getAppwrapperStatus ( appwrapperStatus ) )
109
+ // decrement count of previous state
110
+ let previousState = previousAppwrapperStates . get ( `${ appwrapperNamespace } ,${ appwrapperName } ` ) ;
111
+ if ( ! previousState ) {
112
+ console . log ( `error: update received but no previous state recorded` )
113
+ return ;
114
+ }
115
+ appwrapperCountMetric . labels ( previousState ) . dec ( 1 ) ;
116
+ appwrapperCountMetric . labels ( appwrapperStatus ) . inc ( 1 ) ;
117
+ // set previous state
118
+ previousAppwrapperStates . set ( `${ appwrapperNamespace } ,${ appwrapperName } ` , appwrapperStatus ) ;
108
119
} ) ;
109
120
informer . on ( 'delete' , ( obj ) => {
110
- //console.log(`hello de!`);
111
- //console.log(`deled: ${obj.metadata.name}`);
112
121
let appwrapperName : string = obj . metadata . name ;
113
122
let appwrapperNamespace = obj . metadata . namespace ;
114
- //let metricName = `appwrapper_status{appwrapper_name="${appwrapperName}",appwrapper_namespace="${appwrapperNamespace}"}`;
115
- //metricName = `appwrapper_status{appwrapper_name="0001-aw-generic-deployment-3-5",appwrapper_namespace="test1"}`;
116
- //console.log(metricName)
117
- //register.removeSingleMetric(metricName);
123
+ let appwrapperStatus = obj . status . state ;
124
+ // Prometheus does not support removing a single tagged series
125
+ // Instead, we set to NaN to effectively end the series, as shown in Prometheus charts
126
+ // NOTE: Consequently, when querying Prometheus, if one appwrapper is added and deleted with same
127
+ // name and namespace multiple times, a query may get information for both
118
128
appwrapperStatusMetric . labels ( appwrapperName , appwrapperNamespace ) . set ( NaN )
119
-
129
+ appwrapperCountMetric . labels ( appwrapperStatus ) . dec ( 1 ) ;
130
+ // set previous state
131
+ previousAppwrapperStates . delete ( `${ appwrapperNamespace } ,${ appwrapperName } ` ) ;
120
132
} ) ;
121
133
informer . on ( 'error' , ( err ) => {
122
- console . log ( `hello e!` ) ;
123
- console . log ( `errord: ${ err } ` ) ;
134
+ console . log ( `Informer errored: ${ err } ` ) ;
135
+ console . log ( `NOTE: if the above error says "forbidden" check you or the relevant deployment has permission to "watch" appwrappers` ) ;
136
+
124
137
} ) ;
125
138
informer . on ( 'connect' , ( err ) => {
126
- console . log ( `hello c!` ) ;
127
- console . log ( `connected: ${ err } ` ) ;
139
+ console . log ( `Informer connected!` ) ;
128
140
} ) ;
129
141
130
142
// start informer
131
143
informer . start ( ) ;
132
- console . log ( `informer started...` ) ;
144
+ console . log ( `Informer started watching ...` ) ;
133
145
134
146
/** end initialize informer **/
135
147
0 commit comments