Skip to content

Commit f279fc6

Browse files
committed
feat(controller): add admission webhook
1 parent 48287ea commit f279fc6

18 files changed

Lines changed: 251 additions & 50 deletions

charts/controller/templates/_helpers.tpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ env:
3232
{{- end }}
3333
- name: "TZ"
3434
value: {{ .Values.time_zone | default "UTC" | quote }}
35+
- name: "DJANGO_SETTINGS_MODULE"
36+
value: "api.settings.production"
3537
{{- if (.Values.deploy_hook_urls) }}
3638
- name: DRYCC_DEPLOY_HOOK_URLS
3739
value: "{{ .Values.deploy_hook_urls }}"

charts/controller/templates/controller-cronjob-daily.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ spec:
3232
containers:
3333
- image: {{.Values.image_registry}}/{{.Values.image_org}}/controller:{{.Values.image_tag}}
3434
imagePullPolicy: {{.Values.image_pull_policy}}
35-
name: drycc-controller-load_db_state_to_k8s
35+
name: drycc-controller-load-db-state-to-k8s
3636
command:
3737
- /bin/bash
3838
- -c
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: drycc-controller-webhook
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
component: drycc-controller-webhook
10+
template:
11+
metadata:
12+
labels:
13+
component: drycc-controller-webhook
14+
spec:
15+
initContainers:
16+
- name: drycc-controller-init
17+
image: docker.io/drycc/python-dev:latest
18+
imagePullPolicy: {{.Values.image_pull_policy}}
19+
command:
20+
- netcat
21+
- -v
22+
- -u
23+
- $(DRYCC_DATABASE_URL),$(DRYCC_RABBITMQ_URL)
24+
- -a
25+
- $(DRYCC_REDIS_ADDRS)
26+
{{- include "controller.envs" . | indent 8 }}
27+
containers:
28+
- name: drycc-controller
29+
image: {{.Values.image_registry}}/{{.Values.image_org}}/controller:{{.Values.image_tag}}
30+
imagePullPolicy: {{.Values.image_pull_policy}}
31+
livenessProbe:
32+
httpGet:
33+
path: /healthz
34+
port: 8443
35+
scheme: HTTPS
36+
initialDelaySeconds: 30
37+
timeoutSeconds: 10
38+
readinessProbe:
39+
httpGet:
40+
path: /readiness
41+
port: 8443
42+
scheme: HTTPS
43+
initialDelaySeconds: 30
44+
timeoutSeconds: 10
45+
periodSeconds: 5
46+
ports:
47+
- containerPort: 8443
48+
name: https
49+
volumeMounts:
50+
- name: drycc-controller-webhook-cert
51+
mountPath: /etc/controller/webhook/cert
52+
{{- include "controller.limits" . | indent 8 }}
53+
{{- include "controller.envs" . | indent 8 }}
54+
volumes:
55+
- name: drycc-controller-webhook-cert
56+
secret:
57+
secretName: drycc-controller-webhook-cert
58+
items:
59+
- key: token
60+
path: token
61+
- key: tls.crt
62+
path: tls.crt
63+
- key: tls.key
64+
path: tls.key
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{{- $token := randAlphaNum 128 | lower }}
2+
{{- $ca := genCA "controller-webhook-ca" 3650 }}
3+
{{- $altName1 := printf "drycc-controller-webhook.%s" .Release.Namespace }}
4+
{{- $altName2 := printf "drycc-controller-webhook.%s.svc" .Release.Namespace }}
5+
{{- $cert := genSignedCert "drycc-controller-webhook" nil (list $altName1 $altName2) 3650 $ca }}
6+
apiVersion: admissionregistration.k8s.io/v1
7+
kind: MutatingWebhookConfiguration
8+
metadata:
9+
name: drycc-controller-webhook
10+
webhooks:
11+
- name: "{{ $altName2 }}"
12+
sideEffects: None
13+
admissionReviewVersions: ["v1"]
14+
clientConfig:
15+
caBundle: {{ b64enc $ca.Cert }}
16+
service:
17+
name: drycc-controller-webhook
18+
namespace: "{{ .Release.Namespace }}"
19+
path: "{{ printf "/v2/webhooks/scale/%s/" $token }}"
20+
port: 8443
21+
failurePolicy: Fail
22+
rules:
23+
- operations: ["UPDATE"]
24+
apiGroups: ["apps"]
25+
apiVersions: ["*"]
26+
resources: ["deployments/scale"]
27+
timeoutSeconds: 30
28+
---
29+
apiVersion: v1
30+
kind: Secret
31+
metadata:
32+
name: drycc-controller-webhook-cert
33+
labels:
34+
release: "{{ .Release.Name }}"
35+
heritage: "{{ .Release.Service }}"
36+
type: Opaque
37+
data:
38+
token: {{ b64enc $token }}
39+
tls.crt: {{ b64enc $cert.Cert }}
40+
tls.key: {{ b64enc $cert.Key }}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
kind: Service
2+
apiVersion: v1
3+
metadata:
4+
name: drycc-controller-webhook
5+
spec:
6+
selector:
7+
component: drycc-controller-webhook
8+
ports:
9+
- name: https
10+
port: 8443
11+
targetPort: 8443

rootfs/api/models/app.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -473,18 +473,16 @@ def scale(self, user, structure): # noqa
473473
new_structure.update(structure)
474474

475475
if new_structure != self.structure:
476-
# save new structure to the database
477-
self.structure = new_structure
478-
self.procfile_structure = release.build.procfile
479-
self.save()
480-
481476
try:
482477
self._scale_pods(structure)
483478
except ServiceUnavailable:
484479
# scaling failed, go back to old scaling numbers
485480
self._scale_pods(old_structure)
486481
raise
487-
482+
# save new structure to the database
483+
self.structure = new_structure
484+
self.procfile_structure = release.build.procfile
485+
self.save()
488486
msg = '{} scaled pods '.format(user.username) + ' '.join(
489487
"{}={}".format(k, v) for k, v in list(structure.items()))
490488
self.log(msg)

rootfs/api/settings/celery.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class Config:
2222
task_default_queue = 'priority.low'
2323

2424

25-
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings.production')
2625
app = Celery('drycc')
2726
app.config_from_object(Config)
2827
app.conf.update(

rootfs/api/settings/production.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
# https://docs.djangoproject.com/en/1.11/ref/checks/#security
2525
SILENCED_SYSTEM_CHECKS = [
2626
'security.W004',
27-
'security.W008'
27+
'security.W008',
28+
'security.W012',
2829
]
2930

3031
CONN_MAX_AGE = 60 * 3
@@ -81,7 +82,7 @@
8182
'corsheaders.middleware.CorsMiddleware',
8283
'django.middleware.security.SecurityMiddleware',
8384
'django.middleware.clickjacking.XFrameOptionsMiddleware',
84-
# 'django.middleware.csrf.CsrfViewMiddleware',
85+
'django.middleware.csrf.CsrfViewMiddleware',
8586
'django.contrib.sessions.middleware.SessionMiddleware',
8687
'django.middleware.common.CommonMiddleware',
8788
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -138,9 +139,10 @@
138139
)
139140

140141
X_FRAME_OPTIONS = 'DENY'
141-
# CSRF_COOKIE_SECURE = True
142-
CSRF_COOKIE_HTTPONLY = True
143-
# SESSION_COOKIE_SECURE = True
142+
CSRF_COOKIE_SECURE = True
143+
CSRF_COOKIE_HTTPONLY = False
144+
CSRF_COOKIE_SAMESITE = None
145+
SESSION_COOKIE_SECURE = False
144146
SECURE_CONTENT_TYPE_NOSNIFF = True
145147
SECURE_BROWSER_XSS_FILTER = True
146148

@@ -478,4 +480,11 @@
478480
# Workflow-manager Configuration Options
479481
WORKFLOW_MANAGER_URL = os.environ.get('DRYCC_WORKFLOW_MANAGER_URL', None)
480482
WORKFLOW_MANAGER_ACCESS_KEY = os.environ.get('WORKFLOW_MANAGER_ACCESS_KEY', None)
481-
WORKFLOW_MANAGER_SECRET_KEY = os.environ.get('WORKFLOW_MANAGER_SECRET_KEY', None)
483+
WORKFLOW_MANAGER_SECRET_KEY = os.environ.get('WORKFLOW_MANAGER_SECRET_KEY', None)
484+
485+
# Drycc admission webhook token
486+
if os.path.exists("/etc/controller/webhook/cert"):
487+
with open("/etc/controller/webhook/cert/token") as f:
488+
DRYCC_ADMISSION_WEBHOOK_TOKEN = f.read()
489+
else:
490+
DRYCC_ADMISSION_WEBHOOK_TOKEN = None

rootfs/api/settings/testing.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import random
22
import string
33
import os
4-
4+
from django.core import signals
5+
from api.settings.celery import app
56
from api.settings.production import DATABASES
67
from api.settings.production import * # noqa
78

9+
# Monkey patch celery
10+
app.conf.update(task_always_eager=True)
11+
signals.request_started.send = lambda sender, **named: []
12+
signals.request_finished.send = lambda sender, **named: []
13+
814
# A boolean that turns on/off debug mode.
915
# https://docs.djangoproject.com/en/1.11/ref/settings/#debug
1016
DEBUG = True

rootfs/api/tasks.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from celery import shared_task
88

99
from api import manager
10+
from api.models import ServiceUnavailable
1011
from api.models.resource import Resource
1112
logger = logging.getLogger(__name__)
1213

@@ -46,3 +47,17 @@ def send_measurements(measurements: List[Dict[str, str]]):
4647
measurement.post(measurements)
4748
finally:
4849
signals.request_finished.send(sender=task_id)
50+
51+
52+
@shared_task(
53+
autoretry_for=(ServiceUnavailable, ),
54+
retry_jitter=True,
55+
retry_kwargs={'max_retries': 3}
56+
)
57+
def scale_app(app, user, structure):
58+
task_id = uuid.uuid4().hex
59+
signals.request_started.send(sender=task_id)
60+
try:
61+
app.scale(user, structure)
62+
finally:
63+
signals.request_finished.send(sender=task_id)

0 commit comments

Comments
 (0)