Frontend + Backend Integrated Deployment (K8s only) (#45)

* support running backend + frontend together on k8s
* split nginx container into separate frontend service, which uses nignx-base image and the static frontend files
* add nginx-based frontend image to docker-compose build (for building only, docker-based combined deployment not yet supported)

* backend:
- fix paths for email templates
- chart: support '--set backend_only=1' and '--set frontend_only=1' to only force deploy one or the other
- run backend from root /api in uvicorn
This commit is contained in:
Ilya Kreymer 2021-12-03 10:17:22 -08:00 committed by GitHub
parent 87bef6d967
commit 05c1129fb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 227 additions and 58 deletions

View File

@ -8,5 +8,5 @@ RUN pip install -r requirements.txt
ADD . /app
CMD uvicorn main:app --host 0.0.0.0 --reload --access-log --log-level debug
CMD uvicorn main:app --host 0.0.0.0 --root-path /api --reload --access-log --log-level debug

View File

@ -37,7 +37,7 @@ class EmailSender:
message = f"""
Please verify your registration for Browsertrix Cloud for {receiver_email}
You can verify by clicking here: {self.host}/app/verify/{token}
You can verify by clicking here: {self.host}/verify?token={token}
The verification token is: {token}"""
@ -49,7 +49,7 @@ The verification token is: {token}"""
message = f"""
You are invited by {sender} to join their archive, {archive_name} on Browsertrix Cloud!
You can join by clicking here: {self.host}/app/join/{token}
You can join by clicking here: {self.host}/join/{token}?email={receiver_email}
The invite token is: {token}"""

View File

@ -21,3 +21,4 @@
.idea/
*.tmproj
.vscode/
frontend/

View File

@ -63,8 +63,10 @@ http {
}
location / {
proxy_pass http://localhost:8000/;
proxy_set_header Host $host;
#proxy_pass http://localhost:8000/;
#proxy_set_header Host $host;
root /usr/share/nginx/html;
index index.html index.htm;
}
}
}

View File

@ -2,35 +2,28 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.name }}
name: {{ .Values.name }}-backend
namespace: {{ .Release.Namespace }}
spec:
selector:
matchLabels:
app: {{ .Values.name }}
role: backend
replicas: {{ .Values.api_num_replicas }}
template:
metadata:
labels:
app: {{ .Values.name }}
role: backend
annotations:
# force helm to update the deployment each time
{{- if not .Values.frontend_only }}
"helm.update": {{ randAlphaNum 5 | quote }}
{{- end }}
spec:
volumes:
- name: nginx-config
configMap:
name: nginx-config
items:
- key: nginx.conf
path: nginx.conf
- name: nginx-resolver
emptyDir: {}
initContainers:
{{- if .Values.minio_local }}
- name: init-bucket
@ -47,42 +40,8 @@ spec:
args: ['-c', 'mc mb --ignore-existing local/{{ .Values.minio_local_bucket_name }}' ]
{{- end }}
- name: init-nginx
image: {{ .Values.nginx_image }}
command: ["/bin/sh"]
args: ["-c", "echo resolver $(awk 'BEGIN{ORS=\" \"} $1==\"nameserver\" {print $2}' /etc/resolv.conf) valid=30s \";\" > /etc/nginx/resolvers/resolvers.conf"]
volumeMounts:
- name: nginx-resolver
mountPath: /etc/nginx/resolvers/
containers:
- name: nginx
image: {{ .Values.nginx_image }}
imagePullPolicy: {{ .Values.nginx_pull_policy }}
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
readOnly: true
- name: nginx-resolver
mountPath: /etc/nginx/resolvers/
readOnly: true
resources:
limits:
cpu: {{ .Values.nginx_limit_cpu }}
requests:
cpu: {{ .Values.nginx_requests_cpu }}
readinessProbe:
httpGet:
path: /healthz
port: 80
- name: api
image: {{ .Values.api_image }}
imagePullPolicy: {{ .Values.api_pull_policy }}
@ -101,6 +60,13 @@ spec:
cpu: {{ .Values.api_requests_cpu }}
memory: {{ .Values.api_requests_memory }}
startupProbe:
httpGet:
path: /healthz
port: 8000
failureThreshold: 30
periodSeconds: 5
readinessProbe:
httpGet:
path: /healthz
@ -112,9 +78,10 @@ kind: Service
metadata:
namespace: {{ .Release.Namespace }}
name: {{ .Values.name }}
name: {{ .Values.name }}-backend
labels:
app: {{ .Values.name }}
role: backend
{{- if .Values.service }}
{{- if .Values.service.annotations }}
@ -128,6 +95,7 @@ metadata:
spec:
selector:
app: {{ .Values.name }}
role: backend
{{- if .Values.service }}
{{- if .Values.service.type }}
@ -137,7 +105,5 @@ spec:
ports:
- protocol: TCP
port: 80
port: 8000
name: api
#externalIPs:
# - 127.0.0.1

View File

@ -45,3 +45,6 @@ metadata:
data:
{{ (.Files.Glob "*.conf").AsConfig | indent 2 }}
#{{ (.Files.Glob "frontend/*.*").AsConfig | indent 2 }}

View File

@ -0,0 +1,99 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.name }}-frontend
namespace: {{ .Release.Namespace }}
spec:
selector:
matchLabels:
app: {{ .Values.name }}
role: frontend
replicas: 1
template:
metadata:
labels:
app: {{ .Values.name }}
role: frontend
annotations:
# force helm to update the deployment each time
{{- if not .Values.backend_only }}
"helm.update": {{ randAlphaNum 5 | quote }}
{{- end }}
spec:
volumes:
- name: nginx-resolver
emptyDir: {}
initContainers:
- name: init-nginx
image: {{ .Values.nginx_image }}
command: ["/bin/sh"]
args: ["-c", "echo resolver $(awk 'BEGIN{ORS=\" \"} $1==\"nameserver\" {print $2}' /etc/resolv.conf) valid=30s \";\" > /etc/nginx/resolvers/resolvers.conf"]
volumeMounts:
- name: nginx-resolver
mountPath: /etc/nginx/resolvers/
containers:
- name: nginx
image: {{ .Values.nginx_image }}
imagePullPolicy: {{ .Values.nginx_pull_policy }}
volumeMounts:
- name: nginx-resolver
mountPath: /etc/nginx/resolvers/
readOnly: true
resources:
limits:
cpu: {{ .Values.nginx_limit_cpu }}
requests:
cpu: {{ .Values.nginx_requests_cpu }}
readinessProbe:
httpGet:
path: /healthz
port: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: {{ .Release.Namespace }}
name: {{ .Values.name }}-frontend
labels:
app: {{ .Values.name }}
role: frontend
{{- if .Values.service }}
{{- if .Values.service.annotations }}
annotations:
{{- range $key, $val := .Values.service.annotations }}
{{ $key }}: {{ $val | quote }}
{{- end }}
{{- end }}
{{- end }}
spec:
selector:
app: {{ .Values.name }}
role: frontend
{{- if .Values.service }}
{{- if .Values.service.type }}
type: {{ .Values.service.type | quote }}
{{- end }}
{{- end }}
ports:
- protocol: TCP
port: 80
name: frontend

View File

@ -40,11 +40,19 @@ spec:
number: 9000
{{- end }}
- path: /api/(.*)
pathType: Prefix
backend:
service:
name: browsertrix-cloud-backend
port:
number: 8000
- path: /(.*)
pathType: Prefix
backend:
service:
name: browsertrix-cloud
name: browsertrix-cloud-frontend
port:
number: 80

View File

@ -17,6 +17,12 @@ services:
- minio
- mongo
frontend:
build: ./frontend
image: registry.digitalocean.com/btrix/webrecorder/browsertrix-frontend
ports:
- 8010:80
redis:
image: redis
command: redis-server --appendonly yes

6
frontend/Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM nginx
COPY ./index.html /usr/share/nginx/html
COPY ./dist/main.js /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf

78
frontend/nginx.conf Normal file
View File

@ -0,0 +1,78 @@
worker_processes 1;
error_log stderr;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /dev/stdout;
sendfile on;
keepalive_timeout 65;
include ./resolvers/resolvers.conf;
server {
listen 80 default_server;
server_name _;
proxy_buffering off;
proxy_buffers 16 64k;
proxy_buffer_size 64k;
root /usr/share/nginx/html;
index index.html index.htm;
error_page 500 501 502 503 504 /50x.html;
merge_slashes off;
location = /50x.html {
root /usr/share/nginx/html;
}
# fallback to index for any page
error_page 404 /index.html;
location ~* /watch/([^/]+)/([^/]+)/ws {
set $archive $1;
set $crawl $2;
#auth_request /authcheck;
proxy_pass http://$2.crawlers.svc.cluster.local:9037/ws;
proxy_set_header Host "localhost";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
location ~* /watch/([^/]+)/([^/]+)/ {
set $archive $1;
set $crawl $2;
#auth_request /authcheck;
proxy_pass http://$2.crawlers.svc.cluster.local:9037/;
proxy_set_header Host "localhost";
}
location = /authcheck {
internal;
proxy_pass http://localhost:8000/archives/$archive/crawls/$crawl;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location /healthz {
return 200;
}
location / {
#proxy_pass http://localhost:8000/;
#proxy_set_header Host $host;
root /usr/share/nginx/html;
index index.html index.htm;
}
}
}

View File

@ -20,7 +20,7 @@ const shoelaceAssetsSrcPath = path.resolve(
__dirname,
"node_modules/@shoelace-style/shoelace/dist/assets"
);
const shoelaceAssetsPublicPath = "/shoelace/assets";
const shoelaceAssetsPublicPath = "shoelace/assets";
module.exports = {
entry: "./src/index.ts",
@ -63,7 +63,7 @@ module.exports = {
static: [
{
directory: shoelaceAssetsSrcPath,
publicPath: shoelaceAssetsPublicPath,
publicPath: "/" + shoelaceAssetsPublicPath,
},
{