forked from allenai/pawls
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwebapp.jsonnet
357 lines (338 loc) · 14.7 KB
/
webapp.jsonnet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/**
* This is a template that's compiled down to a definition of the
* infrastructural resources required for running your application.
*
* For more information on the JSONNET language, see:
* https://jsonnet.org/learning/getting_started.html
*/
// This file is generated once at template creation time and unlikely to change
// from that point forward.
local config = import '../skiff.json';
function(
apiImage, proxyImage, cause, sha, env='prod', branch='', repo='',
buildId=''
)
// We only allow registration of hostnames attached to '*.apps.allenai.org'
// at this point. If you need a custom domain, contact us: [email protected].
local topLevelDomain = '.apps.allenai.org';
local hosts = [
if env == 'prod' then
config.appName + topLevelDomain
else
config.appName + '.' + env + topLevelDomain
];
// In production we run two versions of your application, as to ensure that
// if one instance goes down or is busy, end users can still use the application.
// In all other environments we run a single instance to save money.
local replicas = (
if env == 'prod' then
2
else
1
);
// Each app gets it's own namespace.
local namespaceName = config.appName;
// Since we deploy resources for different environments in the same namespace,
// we need to give things a fully qualified name that includes the environment
// as to avoid unintentional collission / redefinition.
local fullyQualifiedName = config.appName + '-' + env;
// Every resource is tagged with the same set of labels. These labels serve the
// following purposes:
// - They make it easier to query the resources, i.e.
// kubectl get pod -l app=my-app,env=staging
// - The service definition uses them to find the pods it directs traffic to.
local namespaceLabels = {
app: config.appName,
contact: config.contact,
team: config.team
};
local labels = namespaceLabels + {
env: env
};
local selectorLabels = {
app: config.appName,
env: env
};
// By default multiple instances of your application could get scheduled
// to the same node. This means if that node goes down your application
// does too. We use the label below to avoid that.
local antiAffinityLabels = {
onlyOneOfPerNode: config.appName + '-' + env
};
local podLabels = labels + antiAffinityLabels;
// Annotations carry additional information about your deployment that
// we use for auditing, debugging and administrative purposes
local annotations = {
"apps.allenai.org/sha": sha,
"apps.allenai.org/branch": branch,
"apps.allenai.org/repo": repo,
"apps.allenai.org/build": buildId
};
// The port the NGINX proxy is bound to.
local proxyPort = 80;
// The port the API (Python Flask application) is bound to.
local apiPort = 8000;
// This is used to verify that the proxy (and thereby the UI portion of the
// application) is healthy. If this fails the application won't receive traffic,
// and may be restarted.
local proxyHealthCheck = {
port: proxyPort,
scheme: 'HTTP'
};
// This is used to verify that the API is funtional. We simply check for
// whether the socket is open and available.
local apiHealthCheck = {
port: apiPort,
scheme: 'HTTP'
};
local namespace = {
apiVersion: 'v1',
kind: 'Namespace',
metadata: {
name: namespaceName,
labels: namespaceLabels
}
};
local ingress = {
apiVersion: 'extensions/v1beta1',
kind: 'Ingress',
metadata: {
name: fullyQualifiedName,
namespace: namespaceName,
labels: labels,
annotations: annotations + {
'cert-manager.io/cluster-issuer': 'letsencrypt-prod',
'kubernetes.io/ingress.class': 'nginx',
'nginx.ingress.kubernetes.io/ssl-redirect': 'true',
'nginx.ingress.kubernetes.io/auth-url': 'https://google.login.apps.allenai.org/oauth2/auth',
'nginx.ingress.kubernetes.io/auth-signin': 'https://google.login.apps.allenai.org/oauth2/start?rd=https://$host$request_uri',
'nginx.ingress.kubernetes.io/auth-response-headers': 'X-Auth-Request-User, X-Auth-Request-Email'
}
},
spec: {
tls: [
{
secretName: fullyQualifiedName + '-tls',
hosts: hosts
}
],
rules: [
{
host: host,
http: {
paths: [
{
backend: {
serviceName: fullyQualifiedName,
servicePort: proxyPort
}
}
]
}
} for host in hosts
]
}
};
local deployment = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
labels: labels,
name: fullyQualifiedName,
namespace: namespaceName,
annotations: annotations + {
'kubernetes.io/change-cause': cause
}
},
spec: {
revisionHistoryLimit: 3,
replicas: replicas,
selector: {
matchLabels: selectorLabels
},
template: {
metadata: {
name: fullyQualifiedName,
namespace: namespaceName,
labels: podLabels,
annotations: annotations
},
spec: {
# This block tells the cluster that we'd like to make sure
# each instance of your application is on a different node. This
# way if a node goes down, your application doesn't:
# See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-isolation-restriction
affinity: {
podAntiAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: [
{
labelSelector: {
matchExpressions: [
{
key: labelName,
operator: "In",
values: [ antiAffinityLabels[labelName], ],
} for labelName in std.objectFields(antiAffinityLabels)
],
},
topologyKey: "kubernetes.io/hostname"
},
]
},
},
volumes: [
{
name: 'skiff-files',
persistentVolumeClaim: {
claimName: 'skiff-files-server-pawls'
}
},
{
name: "users",
secret: {
secretName: "users"
}
}
],
containers: [
{
name: fullyQualifiedName + '-api',
image: apiImage,
env: [ { name: "IN_PRODUCTION", value: "prod" }],
volumeMounts: [
{
mountPath: '/skiff_files/apps/pawls',
name: 'skiff-files',
readOnly: false
},
{
name: 'users',
mountPath: '/users',
readOnly: true
}
],
# The "probes" below allow Kubernetes to determine
# if your application is working properly.
#
# The readiness probe is used to determine if
# an instance of your application can accept live
# requests. The configuration below tells Kubernetes
# to stop sending live requests to your application
# if it returns 3 non 2XX responses over 30 seconds.
# When this happens the application instance will
# be taken out of rotation and given time to "catch-up".
# Once it returns a single 2XX, Kubernetes will put
# it back in rotation.
#
# The liveness probe is used to determine if an
# instance needs to be restarted. The configuration
# below tells Kubernetes to restart the application
# if it's unhealthy for 90 seconds. You can increase
# the `failureThreshold` if your API is slow.
#
# The route that's used by these probes should not
# depend on any external services, it should purely
# assess the health of your local application.
#
# Lastly, the `initialDelaySeconds` instructs
# Kubernetes to wait 30 seconds before starting the
# liveness probe. This is to give your application
# time to start. If your application needs more time
# you should increase this value and give things
# a little headroom, things are always a little slower
# in the cloud :).
readinessProbe: {
httpGet: apiHealthCheck + {
path: '/?check=readiness_probe'
},
periodSeconds: 10,
failureThreshold: 3
},
livenessProbe: {
httpGet: apiHealthCheck + {
path: '/?check=liveness_probe'
},
periodSeconds: 10,
failureThreshold: 9,
initialDelaySeconds: 30
},
# This tells Kubernetes what CPU and memory resources your API needs.
# We set these values low by default, as most applications receive
# bursts of activity and accordingly don't need dedicated resources
# at all times.
#
# Your application will be allowed to use more resources than what's
# specified below. That said your application might be killed if it
# uses more than what's requested. If you know you need more memory
# or that your workload is CPU intensive, consider increasing the
# values below.
#
# Currently all pods (the collection of containers defined here)
# cannot request more than 23.9 GB of RAM or 3.92 (3920m) CPU
# cycles. If you need more resources reach out to [email protected],
# we can provide accomodations on a case by case basis.
#
# If your application is of high criticality and you'd like to ensure
# maximum availability, reach out to [email protected] for help modifying
# these settings.
#
# See the following for more information on these values:
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#how-pods-with-resource-requests-are-scheduled
resources: {
requests: {
cpu: '50m',
memory: '500Mi'
}
},
},
{
name: fullyQualifiedName + '-proxy',
image: proxyImage,
readinessProbe: {
httpGet: proxyHealthCheck + {
path: '/?check=rdy'
}
},
livenessProbe: {
failureThreshold: 6,
httpGet: proxyHealthCheck + {
path: '/?check=live'
}
},
resources: {
requests: {
cpu: '50m',
memory: '100Mi'
}
}
}
]
}
}
}
};
local service = {
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: fullyQualifiedName,
namespace: namespaceName,
labels: labels,
annotations: annotations
},
spec: {
selector: selectorLabels,
ports: [
{
port: proxyPort,
name: 'http'
}
]
}
};
[
namespace,
ingress,
deployment,
service
]