Skip to content

Commit d488220

Browse files
committed
feat(routable): ingress support routable
1 parent cc32c4e commit d488220

7 files changed

Lines changed: 70 additions & 160 deletions

File tree

charts/controller/templates/controller-service.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ metadata:
44
name: drycc-controller
55
labels:
66
heritage: drycc
7-
router.drycc.cc/routable: "true"
8-
annotations:
9-
router.drycc.cc/domains: drycc
10-
router.drycc.cc/connectTimeout: "10"
11-
router.drycc.cc/tcpTimeout: "1200"
127
spec:
138
ports:
149
- name: http

rootfs/api/models/app.py

Lines changed: 45 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ def _get_entrypoint(self, container_type):
150150
# if this is a procfile-based app, switch the entrypoint to slugrunner's default
151151
# FIXME: remove slugrunner's hardcoded entrypoint
152152
release = self.release_set.filter(failed=False).latest()
153-
if release.build.procfile and \
154-
release.build.sha and not \
155-
release.build.dockerfile:
153+
if release.build.procfile \
154+
and release.build.sha \
155+
and not release.build.dockerfile:
156156
entrypoint = ['/runner/init']
157157
else:
158158
entrypoint = ['/bin/bash', '-c']
@@ -189,12 +189,25 @@ def _refresh_ingress(self, hosts, tls_map, ssl_redirect):
189189
whitelist = self.appsettings_set.latest().whitelist
190190
if whitelist:
191191
kwargs.update({"whitelist": whitelist})
192-
data = self._scheduler.ingress.get(namespace, ingress).json()
193-
version = data["metadata"]["resourceVersion"]
194-
self._scheduler.ingress.put(
195-
ingress, settings.INGRESS_CLASS, namespace, version, **kwargs)
192+
try:
193+
# In order to create an ingress, we must first have a namespace.
194+
if ingress == "":
195+
raise ServiceUnavailable('Empty hostname')
196+
try:
197+
data = self._scheduler.ingress.get(namespace, ingress).json()
198+
version = data["metadata"]["resourceVersion"]
199+
self._scheduler.ingress.put(
200+
ingress, settings.INGRESS_CLASS, namespace, version, **kwargs)
201+
except KubeException:
202+
self.log("creating Ingress {}".format(namespace), level=logging.INFO)
203+
self._scheduler.ingress.create(
204+
ingress, settings.INGRESS_CLASS, namespace, **kwargs)
205+
except KubeException as e:
206+
raise ServiceUnavailable('Could not create Ingress in Kubernetes') from e
196207

197-
def _refresh_ingress_and_tls(self):
208+
def refresh_ingress_and_tls(self):
209+
if not getattr(self, 'refresh_ingress_and_tls_enabled', True):
210+
return
198211
ingress = self.id
199212
hosts, tls_map = [], {}
200213

@@ -219,11 +232,6 @@ def _refresh_ingress_and_tls(self):
219232
self._refresh_ingress(hosts, tls_map, ssl_redirect)
220233
self._refresh_tls(certs_auto_enabled, hosts)
221234

222-
def refresh(self):
223-
if not getattr(self, "refresh_enabled", True):
224-
return
225-
self._refresh_ingress_and_tls()
226-
227235
def log(self, message, level=logging.INFO):
228236
"""Logs a message in the context of this application.
229237
@@ -255,7 +263,7 @@ def create(self, *args, **kwargs): # noqa
255263
)
256264

257265
# create required minimum resources in k8s for the application
258-
namespace = ingress = service = self.id
266+
namespace = service = self.id
259267
quota_name = '{}-quota'.format(self.id)
260268
try:
261269
self.log('creating Namespace {} and services'.format(namespace), level=logging.DEBUG)
@@ -290,22 +298,7 @@ def create(self, *args, **kwargs): # noqa
290298
raise ServiceUnavailable('Could not delete the Namespace in Kubernetes') from e
291299

292300
raise ServiceUnavailable('Kubernetes resources could not be created') from e
293-
294-
try:
295-
# In order to create an ingress, we must first have a namespace.
296-
if ingress == "":
297-
raise ServiceUnavailable('Empty hostname')
298-
try:
299-
self._scheduler.ingress.get(namespace, ingress)
300-
except KubeException:
301-
self.log("creating Ingress {}".format(namespace), level=logging.INFO)
302-
host = "%s.%s" % (ingress, settings.PLATFORM_DOMAIN)
303-
self._scheduler.ingress.create(
304-
ingress, settings.INGRESS_CLASS, namespace,
305-
hosts=[host, ])
306-
self.refresh_enabled = False # No refresh ingress
307-
except KubeException as e:
308-
raise ServiceUnavailable('Could not create Ingress in Kubernetes') from e
301+
setattr(self, 'refresh_ingress_and_tls_enabled', False) # do not refresh
309302
try:
310303
self.appsettings_set.latest()
311304
except AppSettings.DoesNotExist:
@@ -317,7 +310,10 @@ def create(self, *args, **kwargs): # noqa
317310
# Attach the platform specific application sub domain to the k8s service
318311
# Only attach it on first release in case a customer has remove the app domain
319312
if rel.version == 1 and not Domain.objects.filter(domain=self.id).exists():
320-
Domain(owner=self.owner, app=self, domain=self.id).save()
313+
Domain.objects.create(owner=self.owner, app=self, domain=self.id)
314+
# The default routable is true, so refresh ingress and tls
315+
setattr(self, 'refresh_ingress_and_tls_enabled', True)
316+
self.refresh_ingress_and_tls() # refresh
321317

322318
def delete(self, *args, **kwargs):
323319
"""Delete this application including all containers"""
@@ -338,7 +334,8 @@ def delete(self, *args, **kwargs):
338334
if e.response.status_code == 404:
339335
break
340336
except KubeException as e:
341-
raise ServiceUnavailable('Could not delete Kubernetes Namespace {} within 30 seconds'.format(self.id)) from e # noqa
337+
raise ServiceUnavailable(
338+
'Could not delete Kubernetes Namespace {} within 30 seconds'.format(self.id)) from e # noqa
342339
except KubeHTTPException:
343340
# it's fine if the namespace does not exist - delete app from the DB
344341
pass
@@ -563,8 +560,8 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True): # noqa
563560
self.structure = structure
564561
# if procfile structure exists then we use it
565562
if release.build.procfile and \
566-
release.build.sha and not \
567-
release.build.dockerfile:
563+
release.build.sha and not \
564+
release.build.dockerfile:
568565
self.procfile_structure = release.build.procfile
569566
self.save()
570567

@@ -638,21 +635,14 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True): # noqa
638635
# Make sure the application is routable and uses the correct port done after the fact to
639636
# let initial deploy settle before routing traffic to the application
640637
if deploys and app_type:
641-
app_settings = self.appsettings_set.latest()
642-
service_annotations = {
643-
'maintenance': app_settings.maintenance,
644-
}
645-
646638
routable = deploys[app_type].get('routable')
647639
port = deploys[app_type].get('envs', {}).get('PORT', None)
648-
self._update_application_service(self.id, app_type, port, routable, service_annotations) # noqa
649-
640+
self._update_application_service(self.id, app_type, port, routable) # noqa
650641
# Wait until application is available in the router
651642
# Only run when there is no previous build / release
652643
old = release.previous()
653644
if old is None or old.build is None:
654645
self.verify_application_health(**deploys[app_type])
655-
656646
# cleanup old release objects from kubernetes
657647
release.cleanup_old()
658648

@@ -964,42 +954,25 @@ def maintenance_mode(self, mode):
964954
self._scheduler.svc.update(self.id, self.id, data=old_service)
965955
raise ServiceUnavailable(str(e)) from e
966956

967-
# set maintenance mode for services
968-
for svc in self.service_set.all():
969-
svc.maintenance_mode(mode)
970-
971957
def routable(self, routable):
972958
"""
973959
Turn on/off if an application is publically routable
974960
"""
975-
service = self._fetch_service_config(self.id)
976-
old_service = service.copy() # in case anything fails for rollback
977-
978-
try:
979-
service['metadata']['labels']['router.drycc.cc/routable'] = str(routable).lower()
980-
self._scheduler.svc.update(self.id, self.id, data=service)
981-
except KubeException as e:
982-
self._scheduler.svc.update(self.id, self.id, data=old_service)
983-
raise ServiceUnavailable(str(e)) from e
961+
if routable:
962+
self.refresh_ingress_and_tls()
963+
else:
964+
try:
965+
namespace = ingress = self.id
966+
self._scheduler.ingress.delete(namespace, ingress)
967+
except KubeException as e:
968+
raise ServiceUnavailable(str(e)) from e
984969

985-
def _update_application_service(self, namespace, app_type, port, routable=False, annotations={}): # noqa
970+
def _update_application_service(self, namespace, app_type, port, routable=False): # noqa
986971
"""Update application service with all the various required information"""
987972
service = self._fetch_service_config(namespace)
988973
old_service = service.copy() # in case anything fails for rollback
989974

990975
try:
991-
# Update service information
992-
for key, value in annotations.items():
993-
if value is not None:
994-
service['metadata']['annotations']['router.drycc.cc/%s' % key] = str(value)
995-
else:
996-
service['metadata']['annotations'].pop('router.drycc.cc/%s' % key, None)
997-
if routable:
998-
service['metadata']['labels']['router.drycc.cc/routable'] = 'true'
999-
else:
1000-
# delete the annotation
1001-
service['metadata']['labels'].pop('router.drycc.cc/routable', None)
1002-
1003976
# Set app type selector
1004977
service['spec']['selector']['type'] = app_type
1005978

@@ -1128,10 +1101,12 @@ def _gather_app_settings(self, release, app_settings, process_type, replicas):
11281101
deploy_timeout = int(config.values.get('DRYCC_DEPLOY_TIMEOUT', settings.DRYCC_DEPLOY_TIMEOUT)) # noqa
11291102

11301103
# configures how many ReplicaSets to keep beside the latest version
1131-
deployment_history = config.values.get('KUBERNETES_DEPLOYMENTS_REVISION_HISTORY_LIMIT', settings.KUBERNETES_DEPLOYMENTS_REVISION_HISTORY_LIMIT) # noqa
1104+
deployment_history = config.values.get('KUBERNETES_DEPLOYMENTS_REVISION_HISTORY_LIMIT',
1105+
settings.KUBERNETES_DEPLOYMENTS_REVISION_HISTORY_LIMIT) # noqa
11321106

11331107
# get application level pod termination grace period
1134-
pod_termination_grace_period_seconds = int(config.values.get('KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS', settings.KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS)) # noqa
1108+
pod_termination_grace_period_seconds = int(config.values.get(
1109+
'KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS', settings.KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS)) # noqa
11351110

11361111
# set the image pull policy that is associated with the application container
11371112
image_pull_policy = config.values.get('IMAGE_PULL_POLICY', settings.IMAGE_PULL_POLICY)

rootfs/api/models/appsettings.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ class AppSettings(UuidAuditedModel):
2828

2929
class Meta:
3030
get_latest_by = 'created'
31-
unique_together = (('app', 'uuid'))
31+
unique_together = (('app', 'uuid'), )
3232
ordering = ['-created']
3333

34+
def __init__(self, *args, **kwargs):
35+
UuidAuditedModel.__init__(self, *args, **kwargs)
36+
self.summary = []
37+
3438
def __str__(self):
3539
return "{}-{}".format(self.app.id, str(self.uuid)[:7])
3640

@@ -40,12 +44,12 @@ def new(self, user, whitelist):
4044
on behalf of a user.
4145
"""
4246

43-
appSettings = AppSettings.objects.create(
47+
app_settings = AppSettings.objects.create(
4448
owner=user, app=self.app, whitelist=whitelist)
4549

46-
return appSettings
50+
return app_settings
4751

48-
def update_maintenance(self, previous_settings):
52+
def _update_maintenance(self, previous_settings):
4953
old = getattr(previous_settings, 'maintenance', None)
5054
new = getattr(self, 'maintenance', None)
5155
# If no previous settings then assume it is the first record and default to true
@@ -59,7 +63,7 @@ def update_maintenance(self, previous_settings):
5963
self.app.maintenance_mode(new)
6064
self.summary += ["{} changed maintenance mode from {} to {}".format(self.owner, old, new)] # noqa
6165

62-
def update_routable(self, previous_settings):
66+
def _update_routable(self, previous_settings):
6367
old = getattr(previous_settings, 'routable', None)
6468
new = getattr(self, 'routable', None)
6569
# If no previous settings then assume it is the first record and default to true
@@ -73,7 +77,7 @@ def update_routable(self, previous_settings):
7377
self.app.routable(new)
7478
self.summary += ["{} changed routablity from {} to {}".format(self.owner, old, new)]
7579

76-
def update_whitelist(self, previous_settings):
80+
def _update_whitelist(self, previous_settings):
7781
# If no previous settings then assume it is the first record and set as empty
7882
# to prevent from database constraint violation
7983
if not previous_settings:
@@ -94,7 +98,7 @@ def update_whitelist(self, previous_settings):
9498
self.summary += ' and '
9599
self.summary += "{} {}".format(self.owner, changes)
96100

97-
def update_autoscale(self, previous_settings):
101+
def _update_autoscale(self, previous_settings):
98102
data = getattr(previous_settings, 'autoscale', {}).copy()
99103
new = getattr(self, 'autoscale', {}).copy()
100104
# If no previous settings then do nothing
@@ -134,7 +138,7 @@ def update_autoscale(self, previous_settings):
134138
if changes:
135139
self.summary += ["{} {}".format(self.owner, changes)]
136140

137-
def update_label(self, previous_settings):
141+
def _update_label(self, previous_settings):
138142
data = getattr(previous_settings, 'label', {}).copy()
139143
new = getattr(self, 'label', {}).copy()
140144
if not previous_settings:
@@ -168,19 +172,18 @@ def update_label(self, previous_settings):
168172

169173
@transaction.atomic
170174
def save(self, *args, **kwargs):
171-
self.summary = []
172175
previous_settings = None
173176
try:
174177
previous_settings = self.app.appsettings_set.latest()
175178
except AppSettings.DoesNotExist:
176179
pass
177180

178181
try:
179-
self.update_maintenance(previous_settings)
180-
self.update_routable(previous_settings)
181-
self.update_whitelist(previous_settings)
182-
self.update_autoscale(previous_settings)
183-
self.update_label(previous_settings)
182+
self._update_maintenance(previous_settings)
183+
self._update_routable(previous_settings)
184+
self._update_whitelist(previous_settings)
185+
self._update_autoscale(previous_settings)
186+
self._update_label(previous_settings)
184187
except (UnprocessableEntity, NotFound):
185188
raise
186189
except Exception as e:
@@ -195,5 +198,5 @@ def save(self, *args, **kwargs):
195198
try:
196199
return super(AppSettings, self).save(**kwargs)
197200
finally:
198-
self.app.refresh()
201+
self.app.refresh_ingress_and_tls()
199202
self.app.log('summary of app setting changes: {}'.format(summary), logging.DEBUG)

rootfs/api/models/domain.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def save(self, *args, **kwargs):
3030
# Save to DB
3131
return super(Domain, self).save(*args, **kwargs)
3232
finally:
33-
self.app.refresh()
33+
self.app.refresh_ingress_and_tls()
3434

3535
@transaction.atomic
3636
def delete(self, *args, **kwargs):
@@ -41,7 +41,7 @@ def delete(self, *args, **kwargs):
4141
# Delete from DB
4242
return super(Domain, self).delete(*args, **kwargs)
4343
finally:
44-
self.app.refresh()
44+
self.app.refresh_ingress_and_tls()
4545

4646
def __str__(self):
4747
return self.domain

0 commit comments

Comments
 (0)