Skip to content

Commit d97509d

Browse files
authored
add base implementation for admission controller (#545)
1 parent f2602f8 commit d97509d

File tree

14 files changed

+775
-56
lines changed

14 files changed

+775
-56
lines changed

charts/platform-disks/Chart.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ dependencies:
77
- name: k8s-resources
88
version: "1.0.1"
99
repository: https://neuro-inc.github.io/helm-charts
10+
- name: admission-controller-lib
11+
version: "0.1.10"
12+
repository: https://neuro-inc.github.io/helm-charts

charts/platform-disks/templates/_helpers.tpl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,71 @@ heritage: {{ .Release.Service | quote }}
2626
release: {{ .Release.Name | quote }}
2727
{{- end -}}
2828

29+
{{/*
30+
Selector labels
31+
*/}}
32+
{{- define "platformDisks.selectorLabels" -}}
33+
app: {{ include "platformDisks.name" . }}
34+
release: {{ .Release.Name }}
35+
service: {{ include "platformDisks.name" . }}
36+
{{- end }}
37+
38+
{{/*
39+
Admission controller selector labels
40+
*/}}
41+
{{- define "platformDisks.admissionController.selectorLabels" -}}
42+
app: {{ include "platformDisks.name" . }}
43+
release: {{ .Release.Name }}
44+
service: {{ include "platformDisks.name" . }}-admission-controller
45+
{{- end }}
46+
2947
{{- define "platformDisks.kubeAuthMountRoot" -}}
3048
{{- printf "/var/run/secrets/kubernetes.io/serviceaccount" -}}
3149
{{- end -}}
50+
51+
{{- define "platformDisks.admissionControllerCertMountRoot" -}}
52+
{{- printf "/var/run/secrets/admission-controller/cert" -}}
53+
{{- end -}}
54+
55+
{{- define "platformDisks.env" -}}
56+
- name: NP_DISK_API_PLATFORM_AUTH_URL
57+
value: {{ .Values.platform.authUrl | quote }}
58+
- name: NP_DISK_API_PLATFORM_AUTH_TOKEN
59+
{{- if .Values.platform.token }}
60+
{{ toYaml .Values.platform.token | indent 2 }}
61+
{{- end }}
62+
- name: NP_DISK_API_K8S_API_URL
63+
value: https://kubernetes.default:443
64+
- name: NP_DISK_API_K8S_AUTH_TYPE
65+
value: token
66+
- name: NP_DISK_API_K8S_CA_PATH
67+
value: {{ include "platformDisks.kubeAuthMountRoot" . }}/ca.crt
68+
- name: NP_DISK_API_K8S_TOKEN_PATH
69+
value: {{ include "platformDisks.kubeAuthMountRoot" . }}/token
70+
- name: NP_DISK_API_K8S_NS
71+
value: {{ .Values.disks.namespace | default "default" | quote }}
72+
{{- if .Values.disks.storageClassName }}
73+
- name: NP_DISK_API_K8S_STORAGE_CLASS
74+
value: {{ .Values.disks.storageClassName }}
75+
{{- end }}
76+
- name: NP_DISK_API_STORAGE_LIMIT_PER_PROJECT
77+
value: {{ .Values.disks.limitPerProject | quote }}
78+
- name: NP_DISK_API_ENABLE_DOCS
79+
value: {{ .Values.docs.enabled | quote }}
80+
- name: NP_CLUSTER_NAME
81+
value: {{ .Values.platform.clusterName | quote }}
82+
{{- if .Values.cors.origins }}
83+
- name: NP_CORS_ORIGINS
84+
value: {{ join "," .Values.cors.origins | quote }}
85+
{{- end }}
86+
{{- if .Values.sentry }}
87+
- name: SENTRY_DSN
88+
value: {{ .Values.sentry.dsn }}
89+
- name: SENTRY_CLUSTER_NAME
90+
value: {{ .Values.sentry.clusterName }}
91+
- name: SENTRY_APP_NAME
92+
value: platform-disks
93+
- name: SENTRY_SAMPLE_RATE
94+
value: {{ .Values.sentry.sampleRate | default 0 | quote }}
95+
{{- end }}
96+
{{- end -}}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{{ include "admission-controller-lib.preinstallJob" . }}
2+
---
3+
{{ include "admission-controller-lib.postinstallJob" . }}
4+
---
5+
apiVersion: apps/v1
6+
kind: Deployment
7+
metadata:
8+
name: {{ include "platformDisks.fullname" . }}
9+
labels:
10+
{{- include "platformDisks.labels" . | nindent 4 }}
11+
spec:
12+
replicas: 1
13+
selector:
14+
matchLabels:
15+
{{- include "platformDisks.admissionController.selectorLabels" . | nindent 6 }}
16+
template:
17+
metadata:
18+
labels:
19+
{{- include "platformDisks.admissionController.selectorLabels" . | nindent 8 }}
20+
spec:
21+
containers:
22+
- name: admission-controller
23+
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
24+
imagePullPolicy: {{ .Values.image.pullPolicy }}
25+
command:
26+
- platform-disk-admission-controller
27+
ports:
28+
- containerPort: {{ .Values.admissionControllerService.port }}
29+
name: http
30+
protocol: TCP
31+
32+
{{- if .Values.resources }}
33+
resources:
34+
{{- toYaml .Values.resources | nindent 12 }}
35+
{{- end }}
36+
37+
livenessProbe:
38+
httpGet:
39+
path: /ping
40+
port: http
41+
scheme: HTTPS
42+
initialDelaySeconds: 10
43+
periodSeconds: 30
44+
timeoutSeconds: 5
45+
failureThreshold: 3
46+
47+
readinessProbe:
48+
httpGet:
49+
path: /ping
50+
port: http
51+
scheme: HTTPS
52+
initialDelaySeconds: 5
53+
periodSeconds: 10
54+
timeoutSeconds: 5
55+
failureThreshold: 3
56+
57+
env:
58+
- name: NP_DISK_API_TLS_CERT_PATH
59+
value: "{{ include "platformDisks.admissionControllerCertMountRoot" . }}/tls.crt"
60+
- name: NP_DISK_API_TLS_KEY_PATH
61+
value: "{{ include "platformDisks.admissionControllerCertMountRoot" . }}/tls.key"
62+
{{- include "platformDisks.env" . | nindent 12 }}
63+
64+
volumeMounts:
65+
- mountPath: {{ include "platformDisks.admissionControllerCertMountRoot" . }}
66+
readOnly: true
67+
name: admission-controller-cert
68+
- mountPath: {{ include "platformDisks.kubeAuthMountRoot" . }}
69+
name: kube-api-data
70+
readOnly: true
71+
72+
{{- with .Values.imagePullSecrets }}
73+
imagePullSecrets:
74+
{{- toYaml . | nindent 10 }}
75+
{{- end }}
76+
77+
volumes:
78+
- name: admission-controller-cert
79+
secret:
80+
secretName: "{{ .Values.admissionController.serviceName }}-cert"
81+
- name: kube-api-data
82+
projected:
83+
sources:
84+
- serviceAccountToken:
85+
expirationSeconds: 3600
86+
path: token
87+
- configMap:
88+
name: kube-root-ca.crt
89+
items:
90+
- key: ca.crt
91+
path: ca.crt
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: {{ .Values.admissionController.serviceName }}
5+
labels:
6+
{{- include "platformDisks.labels.standard" . | nindent 4 }}
7+
spec:
8+
selector:
9+
{{- include "platformDisks.admissionController.selectorLabels" . | nindent 4 }}
10+
ports:
11+
- name: https
12+
port: 443
13+
targetPort: {{ .Values.admissionControllerService.port }}
14+
protocol: TCP

charts/platform-disks/templates/deployment.yaml

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ spec:
88
replicas: {{ .Values.replicas }}
99
selector:
1010
matchLabels:
11-
app: {{ include "platformDisks.name" . }}
12-
release: {{ .Release.Name }}
13-
service: platform-disks
11+
{{- include "platformDisks.selectorLabels" . | nindent 6 }}
1412
strategy:
1513
rollingUpdate:
1614
maxSurge: 1
@@ -19,9 +17,7 @@ spec:
1917
template:
2018
metadata:
2119
labels:
22-
app: {{ include "platformDisks.name" . }}
23-
release: {{ .Release.Name }}
24-
service: platform-disks
20+
{{- include "platformDisks.selectorLabels" . | nindent 8 }}
2521
{{- if .Values.secrets }}
2622
annotations:
2723
checksum/secret: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
@@ -53,46 +49,7 @@ spec:
5349
resources: {{ toYaml .Values.resources | nindent 10 }}
5450
{{- end }}
5551
env:
56-
- name: NP_DISK_API_PLATFORM_AUTH_URL
57-
value: {{ .Values.platform.authUrl | quote }}
58-
- name: NP_DISK_API_PLATFORM_AUTH_TOKEN
59-
{{- if .Values.platform.token }}
60-
{{ toYaml .Values.platform.token | indent 10 }}
61-
{{- end }}
62-
- name: NP_DISK_API_K8S_API_URL
63-
value: https://kubernetes.default:443
64-
- name: NP_DISK_API_K8S_AUTH_TYPE
65-
value: token
66-
- name: NP_DISK_API_K8S_CA_PATH
67-
value: {{ include "platformDisks.kubeAuthMountRoot" . }}/ca.crt
68-
- name: NP_DISK_API_K8S_TOKEN_PATH
69-
value: {{ include "platformDisks.kubeAuthMountRoot" . }}/token
70-
- name: NP_DISK_API_K8S_NS
71-
value: {{ .Values.disks.namespace | default "default" | quote }}
72-
{{- if .Values.disks.storageClassName }}
73-
- name: NP_DISK_API_K8S_STORAGE_CLASS
74-
value: {{ .Values.disks.storageClassName }}
75-
{{- end }}
76-
- name: NP_DISK_API_STORAGE_LIMIT_PER_PROJECT
77-
value: {{ .Values.disks.limitPerProject | quote }}
78-
- name: NP_DISK_API_ENABLE_DOCS
79-
value: {{ .Values.docs.enabled | quote }}
80-
- name: NP_CLUSTER_NAME
81-
value: {{ .Values.platform.clusterName | quote }}
82-
{{- if .Values.cors.origins }}
83-
- name: NP_CORS_ORIGINS
84-
value: {{ join "," .Values.cors.origins | quote }}
85-
{{- end }}
86-
{{- if .Values.sentry }}
87-
- name: SENTRY_DSN
88-
value: {{ .Values.sentry.dsn }}
89-
- name: SENTRY_CLUSTER_NAME
90-
value: {{ .Values.sentry.clusterName }}
91-
- name: SENTRY_APP_NAME
92-
value: platform-disks
93-
- name: SENTRY_SAMPLE_RATE
94-
value: {{ .Values.sentry.sampleRate | default 0 | quote }}
95-
{{- end }}
52+
{{- include "platformDisks.env" . | nindent 10 }}
9653
volumeMounts:
9754
- mountPath: {{ include "platformDisks.kubeAuthMountRoot" . }}
9855
name: kube-api-data

charts/platform-disks/values.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,39 @@ jobProjectNamespaceMigration:
6060
limits:
6161
cpu: "200m"
6262
memory: "200Mi"
63+
64+
admissionControllerDeployment:
65+
resources:
66+
requests:
67+
cpu: "100m"
68+
memory: "100Mi"
69+
limits:
70+
cpu: "200m"
71+
memory: "200Mi"
72+
73+
admissionControllerService:
74+
port: 8080
75+
76+
admissionController:
77+
serviceName: platform-disks-admission-controller
78+
webhookPath: /mutate
79+
namespaceSelector:
80+
matchExpressions:
81+
- key: platform.apolo.us/org
82+
operator: Exists
83+
- key: platform.apolo.us/project
84+
operator: Exists
85+
objectSelector: {}
86+
rules:
87+
- apiGroups: [""]
88+
apiVersions: [v1]
89+
operations: [CREATE, UPDATE]
90+
resources: [pods]
91+
scope: '*'
92+
- apiGroups: [""]
93+
apiVersions: [v1]
94+
operations: [CREATE]
95+
resources: [persistentvolumeclaims]
96+
scope: '*'
97+
reinvocationPolicy: IfNeeded
98+
failurePolicy: Fail
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import logging
2+
import ssl
3+
4+
import aiohttp
5+
import uvloop
6+
from neuro_logging import init_logging, setup_sentry
7+
8+
from ..config import Config
9+
from ..config_factory import EnvironConfigFactory
10+
from .api import create_app
11+
12+
LOGGER = logging.getLogger(__name__)
13+
14+
15+
async def create_ssl_context(config: Config) -> ssl.SSLContext | None:
16+
context = None
17+
if config.server.tls_cert_path and config.server.tls_key_path:
18+
LOGGER.info("Loading SSL cert chain from http server config")
19+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
20+
context.load_cert_chain(
21+
certfile=config.server.tls_cert_path,
22+
keyfile=config.server.tls_key_path,
23+
)
24+
LOGGER.info("Loaded SSL cert chain")
25+
return context
26+
27+
28+
def main() -> None:
29+
init_logging(health_check_url_path="/ping")
30+
setup_sentry()
31+
32+
config = EnvironConfigFactory().create()
33+
LOGGER.info("Loaded config: %s", config)
34+
35+
loop = uvloop.new_event_loop()
36+
ssl_context = loop.run_until_complete(create_ssl_context(config))
37+
app = loop.run_until_complete(create_app(config))
38+
aiohttp.web.run_app(
39+
app,
40+
host=config.server.host,
41+
port=config.server.port,
42+
ssl_context=ssl_context,
43+
handler_cancellation=True,
44+
loop=loop,
45+
)
46+
47+
48+
if __name__ == "__main__":
49+
main()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import logging
2+
3+
import aiohttp
4+
import aiohttp.web
5+
6+
from ..config import Config
7+
8+
LOGGER = logging.getLogger(__name__)
9+
10+
11+
class AdmissionControllerHandler:
12+
def __init__(self, app: aiohttp.web.Application) -> None:
13+
self._app = app
14+
15+
def register(self) -> None:
16+
self._app.add_routes(
17+
[
18+
aiohttp.web.get("/ping", self.handle_ping),
19+
aiohttp.web.post("/mutate", self.handle_post_mutate),
20+
]
21+
)
22+
23+
async def handle_ping(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
24+
return aiohttp.web.Response(text="ok")
25+
26+
async def handle_post_mutate(
27+
self, request: aiohttp.web.Request
28+
) -> aiohttp.web.Response:
29+
return aiohttp.web.json_response(
30+
{
31+
"apiVersion": "admission.k8s.io/v1",
32+
"kind": "AdmissionReview",
33+
"status": {"allowed": True},
34+
}
35+
)
36+
37+
38+
async def create_app(config: Config) -> aiohttp.web.Application:
39+
app = aiohttp.web.Application()
40+
41+
AdmissionControllerHandler(app=app).register()
42+
43+
return app

0 commit comments

Comments
 (0)