@@ -104,34 +104,7 @@ class HealthHandler(BaseHandler):
104104
105105 def initialize (self , hub_url = None ):
106106 self .hub_url = hub_url
107-
108- @at_most_every
109- async def _get_pods (self ):
110- """Get information about build and user pods"""
111- namespace = self .settings ["build_namespace" ]
112- k8s = self .settings ["kubernetes_client" ]
113- pool = self .settings ["executor" ]
114-
115- app_log .info (f"Getting pod statistics for { namespace } " )
116-
117- label_selectors = [
118- "app=jupyterhub,component=singleuser-server" ,
119- "component=binderhub-build" ,
120- ]
121- requests = [
122- asyncio .wrap_future (
123- pool .submit (
124- k8s .list_namespaced_pod ,
125- namespace ,
126- label_selector = label_selector ,
127- _preload_content = False ,
128- _request_timeout = KUBE_REQUEST_TIMEOUT ,
129- )
130- )
131- for label_selector in label_selectors
132- ]
133- responses = await asyncio .gather (* requests )
134- return [json .loads (resp .read ())["items" ] for resp in responses ]
107+ self .ignored_checks = set ()
135108
136109 @false_if_raises
137110 @retry
@@ -155,23 +128,9 @@ async def check_docker_registry(self):
155128 )
156129 return True
157130
158- async def check_pod_quota (self ):
159- """Compare number of active pods to available quota"""
160- user_pods , build_pods = await self ._get_pods ()
161-
162- n_user_pods = len (user_pods )
163- n_build_pods = len (build_pods )
164-
165- quota = self .settings ["pod_quota" ]
166- total_pods = n_user_pods + n_build_pods
167- usage = {
168- "total_pods" : total_pods ,
169- "build_pods" : n_build_pods ,
170- "user_pods" : n_user_pods ,
171- "quota" : quota ,
172- "ok" : total_pods <= quota if quota is not None else True ,
173- }
174- return usage
131+ async def check_quotas (self ):
132+ """Check whether any quotas are exceeded"""
133+ return {"ok" : True }
175134
176135 async def check_all (self ):
177136 """Runs all health checks and returns a tuple (overall, checks).
@@ -189,19 +148,20 @@ async def check_all(self):
189148 check_futures .append (self .check_jupyterhub_api (self .hub_url ))
190149 checks .append ({"service" : "JupyterHub API" , "ok" : False })
191150
192- check_futures .append (self .check_pod_quota ())
193- checks .append ({"service" : "Pod quota " , "ok" : False })
151+ check_futures .append (self .check_quotas ())
152+ checks .append ({"service" : "Quotas " , "ok" : False })
194153
195154 for result , check in zip (await asyncio .gather (* check_futures ), checks ):
196155 if isinstance (result , bool ):
197156 check ["ok" ] = result
198157 else :
199158 check .update (result )
200159
201- # The pod quota is treated as a soft quota this means being above
202- # quota doesn't mean the service is unhealthy
160+ # Some checks are for information but do not count as a health failure
203161 overall = all (
204- check ["ok" ] for check in checks if check ["service" ] != "Pod quota"
162+ check ["ok" ]
163+ for check in checks
164+ if check ["service" ] not in self .ignored_checks
205165 )
206166 if not overall :
207167 unhealthy = [check for check in checks if not check ["ok" ]]
@@ -218,3 +178,59 @@ async def head(self):
218178 overall , checks = await self .check_all ()
219179 if not overall :
220180 self .set_status (503 )
181+
182+
183+ class KubernetesHealthHandler (HealthHandler ):
184+ """Serve health status on Kubernetes"""
185+
186+ def initialize (self , ** args ):
187+ super ().initialize (** args )
188+ # The pod quota is treated as a soft quota
189+ # Being above quota doesn't mean the service is unhealthy
190+ self .ignored_checks .add ("Quotas" )
191+
192+ @at_most_every
193+ async def _get_pods (self ):
194+ """Get information about build and user pods"""
195+ namespace = self .settings ["example_builder" ].namespace
196+ k8s = self .settings ["example_builder" ].api
197+ pool = self .settings ["executor" ]
198+
199+ app_log .info (f"Getting pod statistics for { namespace } " )
200+
201+ label_selectors = [
202+ "app=jupyterhub,component=singleuser-server" ,
203+ "component=binderhub-build" ,
204+ ]
205+ requests = [
206+ asyncio .wrap_future (
207+ pool .submit (
208+ k8s .list_namespaced_pod ,
209+ namespace ,
210+ label_selector = label_selector ,
211+ _preload_content = False ,
212+ _request_timeout = KUBE_REQUEST_TIMEOUT ,
213+ )
214+ )
215+ for label_selector in label_selectors
216+ ]
217+ responses = await asyncio .gather (* requests )
218+ return [json .loads (resp .read ())["items" ] for resp in responses ]
219+
220+ async def check_quotas (self ):
221+ """Compare number of active pods to available quota"""
222+ user_pods , build_pods = await self ._get_pods ()
223+
224+ n_user_pods = len (user_pods )
225+ n_build_pods = len (build_pods )
226+
227+ quota = self .settings ["pod_quota" ]
228+ total_pods = n_user_pods + n_build_pods
229+ usage = {
230+ "total_pods" : total_pods ,
231+ "build_pods" : n_build_pods ,
232+ "user_pods" : n_user_pods ,
233+ "quota" : quota ,
234+ "ok" : total_pods <= quota if quota is not None else True ,
235+ }
236+ return usage
0 commit comments