Skip to content

Commit 5a19035

Browse files
committed
Merge pull request #633 from helgi/delete_rc_app
ref(release): cleanup old RCs and related kubernetes resources after deploy
2 parents 6aab1d1 + deadfa4 commit 5a19035

3 files changed

Lines changed: 82 additions & 24 deletions

File tree

rootfs/api/models/app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,9 @@ def deploy(self, release):
384384
log_event(self, err, logging.ERROR)
385385
raise
386386

387+
# cleanup old releases from kubernetes
388+
release.cleanup_old()
389+
387390
def _default_structure(self, release):
388391
"""Scale to default structure based on release type"""
389392
# if there is no SHA, assume a docker image is being promoted

rootfs/api/models/release.py

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,7 @@ def rollback(self, user, version):
151151
def delete(self, *args, **kwargs):
152152
"""Delete release DB record and any RCs from the affect release"""
153153
try:
154-
labels = {
155-
'app': self.app.id,
156-
'version': 'v{}'.format(self.version)
157-
}
158-
controllers = self._scheduler._get_rcs(self.app.id, labels=labels)
159-
for controller in controllers.json()['items']:
160-
self._scheduler._delete_rc(self.app.id, controller['metadata']['name'])
154+
self._delete_release_in_scheduler(self.app.id, self.version)
161155
except KubeHTTPException as e:
162156
# 404 means they were already cleaned up
163157
if e.status_code is not 404:
@@ -168,6 +162,68 @@ def delete(self, *args, **kwargs):
168162
finally:
169163
super(Release, self).delete(*args, **kwargs)
170164

165+
def cleanup_old(self):
166+
"""Cleanup all but the latest release from Kubernetes"""
167+
latest_version = 'v{}'.format(self.version)
168+
log_event(self.app, 'Cleaning up RCS for releases older than {} (latest)'.format(latest_version)) # noqa
169+
170+
# Cleanup controllers
171+
controller_removal = []
172+
controllers = self._scheduler._get_rcs(self.app.id).json()
173+
for controller in controllers['items']:
174+
current_version = controller['metadata']['labels']['version']
175+
# skip the latest release
176+
if current_version == latest_version:
177+
continue
178+
179+
# aggregate versions together to removal all at once
180+
if current_version not in controller_removal:
181+
controller_removal.append(current_version)
182+
183+
if controller_removal:
184+
log_event(self.app, 'Found the following versions to cleanup: {}'.format(', '.join(controller_removal))) # noqa
185+
186+
for version in controller_removal:
187+
self._delete_release_in_scheduler(self.app.id, version)
188+
189+
# find stray env secrets to remove that may have been missed
190+
log_event(self.app, 'Cleaning up orphaned environment var secrets')
191+
labels = {
192+
'app': self.app.id,
193+
'type': 'env'
194+
}
195+
secrets = self._scheduler._get_secrets(self.app.id, labels=labels).json()
196+
for secret in secrets['items']:
197+
current_version = secret['metadata']['labels']['version']
198+
# skip the latest release
199+
if current_version == latest_version:
200+
continue
201+
202+
self._scheduler._delete_secret(self.app.id, secret['metadata']['name'])
203+
204+
def _delete_release_in_scheduler(self, namespace, version):
205+
"""
206+
Deletes a specific release in k8s
207+
208+
Scale RCs to 0 then delete RCs and the version specific
209+
secret that container the env var
210+
"""
211+
labels = {
212+
'app': namespace,
213+
'version': 'v{}'.format(version)
214+
}
215+
controllers = self._scheduler._get_rcs(namespace, labels=labels)
216+
for controller in controllers.json()['items']:
217+
self._scheduler._scale_rc(namespace, controller['metadata']['name'], 0)
218+
self._scheduler._delete_rc(namespace, controller['metadata']['name'])
219+
220+
# remove secret that contains env vars for the release
221+
try:
222+
secret_name = "{}-{}-env".format(namespace, version)
223+
self._scheduler._delete_secret(namespace, secret_name)
224+
except KubeHTTPException:
225+
pass
226+
171227
def save(self, *args, **kwargs): # noqa
172228
if not self.summary:
173229
self.summary = ''

rootfs/scheduler/__init__.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,10 @@
275275
"apiVersion": "$version",
276276
"metadata": {
277277
"name": "$name",
278-
"namespace": "$id"
278+
"namespace": "$id",
279+
"labels": {
280+
"app": "$id"
281+
}
279282
},
280283
"type": "Opaque",
281284
"data": {}
@@ -386,31 +389,20 @@ def deploy(self, namespace, name, image, command, **kwargs): # noqa
386389
new_rc["metadata"]["name"], desired)
387390
)
388391

389-
# Remove new release
392+
# Remove new release of the RC
390393
self._scale_rc(namespace, new_rc["metadata"]["name"], 0)
391394
self._delete_rc(namespace, new_rc["metadata"]["name"])
392-
# remove secret that contains env vars for the release
393-
try:
394-
secret_name = "{}-{}-env".format(namespace, kwargs.get('version'))
395-
self._delete_secret(namespace, secret_name)
396-
except KubeHTTPException:
397-
pass
398395

399-
# Bring back old release if available
396+
# Bring back old release if available of the RC
400397
if old_rc:
401398
self._scale_rc(namespace, old_rc["metadata"]["name"], desired)
402399

403400
raise KubeException('{} (scheduler::deploy): {}'.format(name, e))
404401

405402
# New release is live and kicking. Clean up old release
406403
if old_rc:
404+
self._scale_rc(namespace, old_rc["metadata"]["name"], 0)
407405
self._delete_rc(namespace, old_rc["metadata"]["name"])
408-
# remove secret that contains env vars for the release
409-
secret_name = "{}-{}-env".format(namespace, old_rc['metadata']['labels']['version'])
410-
try:
411-
self._delete_secret(namespace, secret_name)
412-
except KubeHTTPException:
413-
pass
414406

415407
# Make sure the application is routable and uses the correct port
416408
# Done after the fact to let initial deploy settle before routing
@@ -601,7 +593,11 @@ def _set_environment(self, data, namespace, **kwargs):
601593
secret_name = "{}-{}-env".format(namespace, kwargs.get('version'))
602594
self._get_secret(namespace, secret_name)
603595
except KubeHTTPException:
604-
self._create_secret(namespace, secret_name, secrets_env)
596+
labels = {
597+
'version': kwargs.get('version'),
598+
'type': 'env'
599+
}
600+
self._create_secret(namespace, secret_name, secrets_env, labels)
605601
else:
606602
self._update_secret(namespace, secret_name, secrets_env)
607603

@@ -1075,13 +1071,16 @@ def _get_secrets(self, namespace, **kwargs):
10751071

10761072
return response
10771073

1078-
def _create_secret(self, namespace, name, data):
1074+
def _create_secret(self, namespace, name, data, labels={}):
10791075
template = json.loads(string.Template(SECRET_TEMPLATE).substitute({
10801076
"version": self.apiversion,
10811077
"id": namespace,
10821078
"name": name
10831079
}))
10841080

1081+
# add in any additional label info
1082+
template['metadata']['labels'].update(labels)
1083+
10851084
for key, value in data.items():
10861085
value = value if isinstance(value, bytes) else bytes(value, 'UTF-8')
10871086
item = base64.b64encode(value).decode(encoding='UTF-8')

0 commit comments

Comments
 (0)