Skip to content

Commit 99a43e8

Browse files
committed
feat(scheduler): allow setting gracefulTerminationPeriod and account for in terminations
Allows users to set how long k8s allows pods to stay alive after a SIGTERM before they send SIGKILL - Defaults to 30 (like k8s) but lets users set a lower value for faster deploys if the apps can handle it. Reduces the pod termination wait to the new setting and uses the existance of "deletionTimestamp" as an indicator if a pod is Terminating, also checks if the pod is past the graceful period and hides it from the pod list via ps:list Fixes #620 Fixes #621 Fixes #631
1 parent 45a41e1 commit 99a43e8

3 files changed

Lines changed: 66 additions & 10 deletions

File tree

rootfs/api/models/app.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,9 +582,28 @@ def list_pods(self, *args, **kwargs):
582582
if p['metadata']['labels']['type'] == 'run':
583583
continue
584584

585+
state = self._scheduler.resolve_state(p).name
586+
587+
# follows kubelete convention - these are hidden unless show-all is set
588+
if state in ['down', 'crashed']:
589+
continue
590+
591+
# hide pods that are past their graceful termination
592+
# https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/devel/api-conventions.md#metadata
593+
# http://kubernetes.io/docs/user-guide/pods/#termination-of-pods
594+
if 'deletionTimestamp' in p['metadata']:
595+
deletion = datetime.strptime(
596+
p['metadata']['deletionTimestamp'],
597+
settings.DEIS_DATETIME_FORMAT
598+
)
599+
600+
# past the graceful deletion period
601+
if deletion < datetime.utcnow():
602+
continue
603+
585604
item = Pod()
586605
item['name'] = p['metadata']['name']
587-
item['state'] = self._scheduler.resolve_state(p).name
606+
item['state'] = state
588607
item['release'] = p['metadata']['labels']['version']
589608
item['type'] = p['metadata']['labels']['type']
590609
if 'startTime' in p['status']:

rootfs/deis/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@
268268
SLUG_BUILDER_IMAGE_PULL_POLICY = os.environ.get('SLUG_BUILDER_IMAGE_PULL_POLICY', "Always") # noqa
269269
DOCKER_BUILDER_IMAGE_PULL_POLICY = os.environ.get('DOCKER_BUILDER_IMAGE_PULL_POLICY', "Always") # noqa
270270

271+
# How long k8s waits for a pod to finish work after a SIGTERM before sending SIGKILL
272+
KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS = int(os.environ.get('KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS', 30)) # noqa
273+
271274
# registry settings
272275
REGISTRY_HOST = os.environ.get('DEIS_REGISTRY_SERVICE_HOST', '127.0.0.1')
273276
REGISTRY_PORT = os.environ.get('DEIS_REGISTRY_SERVICE_PORT', 5000)

rootfs/scheduler/__init__.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime
12
import json
23
import logging
34
import os
@@ -63,13 +64,14 @@
6364
}
6465
],
6566
"volumes":[
66-
{
67+
{
6768
"name":"objectstorage-keyfile",
6869
"secret":{
6970
"secretName":"objectstorage-keyfile"
7071
}
71-
}
72+
}
7273
],
74+
"terminationGracePeriodSeconds": "$terminationGracePeriodSeconds",
7375
"restartPolicy": "Never"
7476
}
7577
}
@@ -90,6 +92,7 @@
9092
"env": []
9193
}
9294
],
95+
"terminationGracePeriodSeconds": "$terminationGracePeriodSeconds",
9396
"restartPolicy": "Never"
9497
}
9598
}
@@ -126,6 +129,7 @@
126129
}
127130
},
128131
"spec": {
132+
"terminationGracePeriodSeconds": "$terminationGracePeriodSeconds",
129133
"containers": [
130134
{
131135
"name": "$containername",
@@ -181,6 +185,7 @@
181185
}
182186
},
183187
"spec": {
188+
"terminationGracePeriodSeconds": "$terminationGracePeriodSeconds",
184189
"containers": [
185190
{
186191
"name": "$containername",
@@ -217,11 +222,11 @@
217222
}
218223
],
219224
"volumeMounts":[
220-
{
225+
{
221226
"name":"objectstorage-keyfile",
222227
"mountPath":"/var/run/secrets/deis/objectstore/creds",
223228
"readOnly":true
224-
}
229+
}
225230
]
226231
}
227232
],
@@ -508,7 +513,8 @@ def run(self, namespace, name, image, entrypoint, command, **kwargs):
508513
'version': self.apiversion,
509514
'image': imgurl,
510515
'image_pull_policy': settings.DOCKER_BUILDER_IMAGE_PULL_POLICY,
511-
'storagetype': os.getenv("APP_STORAGE")
516+
'storagetype': os.getenv("APP_STORAGE"),
517+
'terminationGracePeriodSeconds': settings.KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS # noqa
512518
}
513519

514520
if entrypoint == '/runner/init':
@@ -796,13 +802,33 @@ def _get_rcs(self, namespace, **kwargs):
796802
return response
797803

798804
def _wait_until_pods_terminate(self, namespace, labels, current, desired):
799-
delta = current - desired
805+
"""Wait until all the desired pods are terminated"""
806+
# http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_podspec
807+
# https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/devel/api-conventions.md#metadata
808+
# http://kubernetes.io/docs/user-guide/pods/#termination-of-pods
800809

801-
logger.debug("waiting for {} pods in {} namespace to be terminated (120s timeout)".format(delta, namespace)) # noqa
802-
for waited in range(120):
810+
timeout = settings.KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS
811+
delta = current - desired
812+
logger.debug("waiting for {} pods in {} namespace to be terminated ({}s timeout)".format(delta, namespace, timeout)) # noqa
813+
for waited in range(timeout):
803814
pods = self._get_pods(namespace, labels=labels).json()
804815
count = len(pods['items'])
805816

817+
# see if any pods are past their terminationGracePeriodsSeconds (as in stuck)
818+
# seems to be a problem in k8s around that:
819+
# https://github.com/kubernetes/kubernetes/search?q=terminating&type=Issues
820+
# these will be eventually GC'ed by k8s, ignoring them for now
821+
for pod in pods['items']:
822+
if 'deletionTimestamp' in pod['metadata']:
823+
deletion = datetime.strptime(
824+
pod['metadata']['deletionTimestamp'],
825+
settings.DEIS_DATETIME_FORMAT
826+
)
827+
828+
# past the graceful deletion period
829+
if deletion < datetime.utcnow():
830+
count -= 1
831+
806832
# stop when all pods are terminated as expected
807833
if count == desired:
808834
break
@@ -909,6 +935,7 @@ def _create_rc(self, namespace, name, image, command, **kwargs): # noqa
909935
"storagetype": storageType,
910936
"mHost": os.getenv("DEIS_MINIO_SERVICE_HOST"),
911937
"mPort": os.getenv("DEIS_MINIO_SERVICE_PORT"),
938+
"terminationGracePeriodSeconds": settings.KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS # noqa
912939
}
913940

914941
# Check if it is a slug builder image.
@@ -1313,9 +1340,16 @@ def _pod_readiness_status(self, pod):
13131340
if not container['ready']:
13141341
if 'running' in container['state'].keys():
13151342
return 'Starting'
1316-
elif 'terminated' in container['state'].keys():
1343+
elif (
1344+
'terminated' in container['state'].keys() or
1345+
'deletionTimestamp' in pod['metadata']
1346+
):
13171347
return 'Terminating'
13181348
else:
1349+
# See if k8s is in Terminating state
1350+
if 'deletionTimestamp' in pod['metadata']:
1351+
return 'Terminating'
1352+
13191353
return 'Running'
13201354

13211355
# Seems like the most sensible default

0 commit comments

Comments
 (0)