Skip to content

Commit c43035e

Browse files
committed
feat(controller): use cncf buildpacks replace slugrunner
1 parent 2785b58 commit c43035e

8 files changed

Lines changed: 46 additions & 202 deletions

File tree

charts/controller/templates/_helpers.tpl

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -168,25 +168,4 @@ resources:
168168
memory: {{.Values.limits_memory}}
169169
{{- end }}
170170
{{- end }}
171-
{{- end }}
172-
173-
174-
{{/* Generate controller deployment volumeMounts */}}
175-
{{- define "controller.volumeMounts" }}
176-
volumeMounts:
177-
- mountPath: /etc/slugrunner
178-
name: slugrunner-config
179-
readOnly: true
180-
{{- end }}
181-
182-
183-
{{/* Generate controller deployment volumes */}}
184-
{{- define "controller.volumes" }}
185-
volumes:
186-
- name: rabbitmq-creds
187-
secret:
188-
secretName: rabbitmq-creds
189-
- name: slugrunner-config
190-
configMap:
191-
name: slugrunner-config
192-
{{- end }}
171+
{{- end }}

rootfs/api/models/app.py

Lines changed: 22 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -122,48 +122,37 @@ def types(self):
122122
def _get_command(self, container_type):
123123
"""
124124
Return the kubernetes "container arguments" to be sent off to the scheduler.
125-
126-
In reality this is the command that the user it attempting to run.
127125
"""
128-
try:
129-
# FIXME: remove slugrunner's hardcoded entrypoint
130-
release = self.release_set.filter(failed=False).latest()
126+
release = self.release_set.filter(failed=False).latest()
127+
if release is not None and release.build is not None:
128+
# dockerfile or container image
131129
if release.build.dockerfile or not release.build.sha:
132-
cmd = release.build.procfile[container_type]
133-
# if the entrypoint is `/bin/bash -c`, we want to supply the list
134-
# as a script. Otherwise, we want to send it as a list of arguments.
135-
if self._get_entrypoint(container_type) == ['/bin/bash', '-c']:
136-
return [cmd]
137-
else:
138-
return cmd.split()
139-
140-
return ['start', container_type]
141-
# if the key is not present or if a parent attribute is None
142-
except (KeyError, TypeError, AttributeError):
143-
# handle special case for Dockerfile deployments
144-
return [] if container_type == 'cmd' else ['start', container_type]
130+
# has profile
131+
if release.build.procfile and container_type in release.build.procfile:
132+
cmd = release.build.procfile[container_type]
133+
# if the entrypoint is `/bin/bash -c`, we want to supply the list
134+
# as a script. Otherwise, we want to send it as a list of arguments.
135+
if self._get_entrypoint(container_type) == ['/bin/bash', '-c']:
136+
return [cmd]
137+
else:
138+
return cmd.split()
139+
return []
145140

146141
def _get_entrypoint(self, container_type):
147142
"""
148143
Return the kubernetes "container command" to be sent off to the scheduler.
149-
150-
In this case, it is the entrypoint for the docker image. Because of Heroku compatibility,
151-
Any containers that are not from a buildpack are run under /bin/bash.
152144
"""
153-
# handle special case for Dockerfile deployments
154145
if container_type == 'cmd':
155146
return []
156-
157-
# if this is a procfile-based app, switch the entrypoint to slugrunner's default
158-
# FIXME: remove slugrunner's hardcoded entrypoint
147+
entrypoint = ['/bin/bash', '-c']
159148
release = self.release_set.filter(failed=False).latest()
160149
if release.build.procfile \
161150
and release.build.sha \
162151
and not release.build.dockerfile:
163-
entrypoint = ['/runner/init']
164-
else:
165-
entrypoint = ['/bin/bash', '-c']
166-
152+
if container_type in release.build.procfile:
153+
entrypoint = [container_type]
154+
else:
155+
entrypoint = ['launcher']
167156
return entrypoint
168157

169158
def _refresh_certificate(self, certs_auto_enabled, hosts):
@@ -576,12 +565,6 @@ def _scale_pods(self, scale_types):
576565
release = self.release_set.filter(failed=False).latest()
577566
app_settings = self.appsettings_set.latest()
578567
volumes = Volume.objects.filter(app=self, path__isnull=False)
579-
# use slugrunner image for app if buildpack app otherwise use normal image
580-
if release.build.type == 'buildpack':
581-
image = next(filter(lambda item: item['name'] == release.build.stack,
582-
settings.SLUGRUNNER_IMAGES))['image']
583-
else:
584-
image = release.image
585568

586569
tasks = []
587570
for scale_type, replicas in scale_types.items():
@@ -594,7 +577,7 @@ def _scale_pods(self, scale_types):
594577
self._scheduler.scale,
595578
namespace=self.id,
596579
name=self._get_job_id(scale_type),
597-
image=image,
580+
image=release.image,
598581
entrypoint=self._get_entrypoint(scale_type),
599582
command=self._get_command(scale_type),
600583
**data
@@ -670,27 +653,17 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True): # noqa
670653
# Check if any proc type has a Deployment in progress
671654
self._check_deployment_in_progress(deploys, force_deploy)
672655

673-
# use slugrunner image for app if buildpack app otherwise use normal image
674-
if release.build.type == 'buildpack':
675-
image = next(filter(lambda item: item['name'] == release.build.stack,
676-
settings.SLUGRUNNER_IMAGES))['image']
677-
else:
678-
image = release.image
679-
680656
try:
681657
# create the application config in k8s (secret in this case) for all deploy objects
682658
self.set_application_config(release)
683-
# only buildpack apps need access to object storage
684-
if release.build.type == 'buildpack':
685-
self.create_object_store_secret()
686659

687660
# gather all proc types to be deployed
688661
tasks = [
689662
functools.partial(
690663
self._scheduler.deploy,
691664
namespace=self.id,
692665
name=self._get_job_id(scale_type),
693-
image=image,
666+
image=release.image,
694667
entrypoint=self._get_entrypoint(scale_type),
695668
command=self._get_command(scale_type),
696669
**kwargs
@@ -903,12 +876,6 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits):
903876
raise DryccException('No build associated with this release to run this command')
904877

905878
app_settings = self.appsettings_set.latest()
906-
# use slugrunner image for app if buildpack app otherwise use normal image
907-
if release.build.type == 'buildpack':
908-
image = next(filter(lambda item: item['name'] == release.build.stack,
909-
settings.SLUGRUNNER_IMAGES))['image']
910-
else:
911-
image = release.image
912879
volume_list = []
913880
if volumes:
914881
volume_objs = Volume.objects.filter(app=release.app, name__in=volumes.keys())
@@ -928,7 +895,7 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits):
928895
exit_code, output = self._scheduler.run(
929896
self.id,
930897
name,
931-
image,
898+
release.image,
932899
self._get_entrypoint(scale_type),
933900
[command],
934901
**data
@@ -1029,16 +996,7 @@ def _build_env_vars(self, release):
1029996
settings.DRYCC_DATETIME_FORMAT))
1030997
}
1031998

1032-
# Check if it is a slug builder image.
1033-
if release.build.type == 'buildpack':
1034-
# overwrite image so slugrunner image is used in the container
1035-
default_env['SLUG_URL'] = release.image
1036-
default_env['BUILDER_STORAGE'] = settings.APP_STORAGE
1037-
default_env['DRYCC_MINIO_SERVICE_HOST'] = settings.MINIO_HOST
1038-
default_env['DRYCC_MINIO_SERVICE_PORT'] = settings.MINIO_PORT
1039-
1040-
if release.build.sha:
1041-
default_env['SOURCE_VERSION'] = release.build.sha
999+
default_env['SOURCE_VERSION'] = release.build.sha
10421000

10431001
# fetch application port and inject into ENV vars as needed
10441002
port = release.get_port()
@@ -1319,11 +1277,3 @@ def set_application_config(self, release):
13191277
self._scheduler.secret.create(self.id, secret_name, secrets_env, labels=labels)
13201278
else:
13211279
self._scheduler.secret.update(self.id, secret_name, secrets_env, labels=labels)
1322-
1323-
def create_object_store_secret(self):
1324-
try:
1325-
self._scheduler.secret.get(self.id, 'objectstorage-keyfile')
1326-
except KubeException:
1327-
secret = self._scheduler.secret.get(
1328-
settings.WORKFLOW_NAMESPACE, 'objectstorage-keyfile').json()
1329-
self._scheduler.secret.create(self.id, 'objectstorage-keyfile', secret['data'])

rootfs/api/models/release.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,12 @@ def image(self):
6060
return self.build.image
6161

6262
# Sort out image information based on build type
63-
if self.build.type == 'dockerfile':
64-
# DockerFile
63+
if self.build.type == 'dockerfile' or self.build.type == 'buildpack':
64+
# DockerFile or buildpack
6565
return '{}/{}:git-{}'.format(settings.REGISTRY_URL, self.app.id, str(self.build.sha))
6666
elif self.build.type == 'image':
6767
# Drycc Pull, docker image in local registry
6868
return self.build.image
69-
elif self.build.type == 'buildpack':
70-
# Build Pack - Registry URL not prepended since slugrunner image will download slug
71-
return self.build.image
7269

7370
def new(self, user, config, build, summary=None, source_version='latest'):
7471
"""

rootfs/api/settings/production.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -272,21 +272,6 @@
272272

273273
PLATFORM_DOMAIN = os.environ.get('DRYCC_PLATFORM_DOMAIN', 'local.drycc.cc')
274274

275-
# k8s image policies
276-
if os.path.exists('/etc/slugrunner/images.json'):
277-
with open('/etc/slugrunner/images.json') as fb:
278-
SLUGRUNNER_IMAGES = json.load(fb)
279-
else:
280-
SLUGRUNNER_IMAGES = [
281-
{
282-
"name": 'heroku-18',
283-
"image": 'drycc/slugrunner:canary.heroku-18',
284-
},
285-
{
286-
"name": 'heroku-20',
287-
"image": 'drycc/slugrunner:canary.heroku-20',
288-
},
289-
]
290275
IMAGE_PULL_POLICY = os.environ.get('IMAGE_PULL_POLICY', "IfNotPresent") # noqa
291276

292277
# True, true, yes, y and more evaluate to True

rootfs/api/tests/test_app.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ def test_build_env_vars(self, mock_requests):
656656
'DRYCC_APP': app.id,
657657
'WORKFLOW_RELEASE': 'v2',
658658
'PORT': 5000,
659+
'SOURCE_VERSION': '',
659660
'WORKFLOW_RELEASE_SUMMARY': 'autotest deployed autotest/example',
660661
'WORKFLOW_RELEASE_CREATED_AT': str(time_created.strftime(
661662
settings.DRYCC_DATETIME_FORMAT))
@@ -670,10 +671,6 @@ def test_build_env_vars(self, mock_requests):
670671
'DRYCC_APP': app.id,
671672
'WORKFLOW_RELEASE': 'v3',
672673
'PORT': 5000,
673-
'SLUG_URL': 'autotest/example',
674-
'BUILDER_STORAGE': None,
675-
'DRYCC_MINIO_SERVICE_HOST': '127.0.0.1',
676-
'DRYCC_MINIO_SERVICE_PORT': 80,
677674
'SOURCE_VERSION': 'abc1234',
678675
'WORKFLOW_RELEASE_SUMMARY': 'autotest deployed abc1234',
679676
'WORKFLOW_RELEASE_CREATED_AT': str(time_created.strftime(

rootfs/api/tests/test_pods.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ def test_command_good(self, mock_requests):
492492
app = App.objects.get(id=app_id)
493493
user = User.objects.get(username='autotest')
494494

495-
# Heroku Buildpack app
495+
# CNCF Buildpack app
496496
build = Build.objects.create(
497497
owner=user,
498498
app=app,
@@ -514,9 +514,9 @@ def test_command_good(self, mock_requests):
514514
build=build
515515
)
516516

517-
# use `start web` for backwards compatibility with slugrunner
518-
self.assertEqual(app._get_command('web'), ['start', 'web'])
519-
self.assertEqual(app._get_command('worker'), ['start', 'worker'])
517+
# use `start web` for backwards compatibility with buildpacks
518+
self.assertEqual(app._get_command('web'), [])
519+
self.assertEqual(app._get_command('worker'), [])
520520

521521
# switch to docker image app
522522
build.sha = ''
@@ -541,7 +541,7 @@ def test_command_good(self, mock_requests):
541541
# for backwards compatibility if no Procfile is supplied
542542
build.procfile = {}
543543
build.save()
544-
self.assertEqual(app._get_command('worker'), ['start', 'worker'])
544+
self.assertEqual(app._get_command('worker'), [])
545545

546546
def test_run_command_good(self, mock_requests):
547547
"""Test the run command for each container workflow"""
@@ -598,7 +598,7 @@ def test_run_command_good(self, mock_requests):
598598
response = self.client.post(url, body)
599599
self.assertEqual(response.status_code, 200, response.data)
600600
app = App.objects.get(id=app_id)
601-
self.assertEqual(app._get_entrypoint('web'), ['/runner/init'])
601+
self.assertEqual(app._get_entrypoint('web'), ['web'])
602602

603603
def test_run_not_fail_on_debug(self, mock_requests):
604604
"""

rootfs/scheduler/resources/pod.py

Lines changed: 5 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ def state(self, pod):
9898

9999
def manifest(self, namespace, name, image, **kwargs):
100100
app_type = kwargs.get('app_type')
101-
build_type = kwargs.get('build_type')
102101
volumes = kwargs.get('volumes')
103102

104103
# labels that represent the pod(s)
@@ -132,22 +131,6 @@ def manifest(self, namespace, name, image, **kwargs):
132131
# How long until a pod is forcefully terminated. 30 is kubernetes default
133132
spec['terminationGracePeriodSeconds'] = self._get_termination_grace_period(kwargs) # noqa
134133

135-
# Check if it is a slug builder image.
136-
if build_type == "buildpack":
137-
# add the required volume to the top level pod spec
138-
spec['volumes'] = [{
139-
'name': 'objectstorage-keyfile',
140-
'secret': {
141-
'secretName': 'objectstorage-keyfile'
142-
}
143-
}]
144-
145-
# added to kwargs to send to the container function
146-
kwargs['volumeMounts'] = [{
147-
'name': 'objectstorage-keyfile',
148-
'mountPath': '/var/run/secrets/drycc/objectstore/creds',
149-
'readOnly': True
150-
}]
151134
if volumes:
152135
exist_volumes = spec.get('volumes', [])
153136
for volume in volumes:
@@ -304,7 +287,7 @@ def _set_health_checks(self, container, env, **kwargs):
304287
healthchecks['livenessProbe']['httpGet']['port'] = env['PORT']
305288
container.update(healthchecks)
306289
elif kwargs.get('routable', False):
307-
self._default_readiness_probe(container, kwargs.get('build_type'), env.get('PORT', None)) # noqa
290+
self._default_readiness_probe(container, kwargs.get('build_type'), env.get('PORT', 5000)) # noqa
308291

309292
@staticmethod
310293
def _set_lifecycle_hooks(container, env, **kwargs):
@@ -338,53 +321,15 @@ def _set_lifecycle_hooks(container, env, **kwargs):
338321
}
339322
container["lifecycle"] = dict(lifecycle)
340323

341-
def _default_readiness_probe(self, container, build_type, port=None):
324+
def _default_readiness_probe(self, container, build_type, port=5000):
342325
# Update only the application container with the health check
343326
if build_type == "buildpack":
344-
container.update(self._default_buildpack_readiness_probe())
327+
container.update(self._default_container_readiness_probe(port))
345328
elif port:
346-
container.update(self._default_dockerapp_readiness_probe(port))
347-
348-
"""
349-
Applies exec readiness probe to the slugrunner container.
350-
http://kubernetes.io/docs/user-guide/pod-states/#container-probes
351-
352-
/runner/init is the entry point of the slugrunner.
353-
https://github.com/drycc/slugrunner/blob/01eac53f1c5f1d1dfa7570bbd6b9e45c00441fea/rootfs/Dockerfile#L20
354-
Once it downloads the slug it starts running using `exec` which means the pid 1
355-
will point to the slug/application command instead of entry point once the application has
356-
started.
357-
https://github.com/drycc/slugrunner/blob/01eac53f1c5f1d1dfa7570bbd6b9e45c00441fea/rootfs/runner/init#L90
358-
359-
This should be added only for the build pack apps when a custom liveness probe is not set to
360-
make sure that the pod is ready only when the slug is downloaded and started running.
361-
"""
362-
@staticmethod
363-
def _default_buildpack_readiness_probe(delay=30, timeout=5, period_seconds=5,
364-
success_threshold=1, failure_threshold=1):
365-
readinessprobe = {
366-
'readinessProbe': {
367-
# an exec probe
368-
'exec': {
369-
"command": [
370-
"bash",
371-
"-c",
372-
"[[ '$(ps -p 1 -o args)' != *'bash /runner/init'* ]]"
373-
]
374-
},
375-
# length of time to wait for a pod to initialize
376-
# after pod startup, before applying health checking
377-
'initialDelaySeconds': delay,
378-
'timeoutSeconds': timeout,
379-
'periodSeconds': period_seconds,
380-
'successThreshold': success_threshold,
381-
'failureThreshold': failure_threshold,
382-
},
383-
}
384-
return readinessprobe
329+
container.update(self._default_container_readiness_probe(port))
385330

386331
@staticmethod
387-
def _default_dockerapp_readiness_probe(port, delay=5, timeout=5, period_seconds=5,
332+
def _default_container_readiness_probe(port, delay=5, timeout=5, period_seconds=5,
388333
success_threshold=1, failure_threshold=1):
389334
"""
390335
Applies tcp socket readiness probe to the docker app container only if some port is exposed

0 commit comments

Comments
 (0)