AI overhaul
This commit is contained in:
Executable
+71
@@ -0,0 +1,71 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: membership-config
|
||||
namespace: {{ www_namespace }}
|
||||
data:
|
||||
{% for key, value in www_membership_environment | dictsort %}
|
||||
{{ key }}: {{ value | string | to_json }}
|
||||
{% endfor %}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: membership-tiers
|
||||
namespace: {{ www_namespace }}
|
||||
data:
|
||||
tiers.json: {{ www_membership_tiers | to_json | to_json }}
|
||||
---
|
||||
# Only Apache may assert the trusted proxy headers to the membership service.
|
||||
# Requires a CNI which actually enforces NetworkPolicy.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: membership-web
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: membership
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: httpd
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8000
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: membership-worker
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: membership-worker
|
||||
policyTypes: [Ingress]
|
||||
ingress: []
|
||||
{% if www_membership_postgres_managed %}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: membership-db
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: membership-db
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
www-membership-client: "true"
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
{% endif %}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
{% if www_membership_enabled %}
|
||||
ProxyRequests Off
|
||||
ProxyPreserveHost On
|
||||
ProxyPass "/members/" "http://membership:8000/members/" connectiontimeout=5 timeout=120
|
||||
ProxyPassReverse "/members/" "http://membership:8000/members/"
|
||||
<Location "/members/">
|
||||
# NetworkPolicy permits only Apache to assert these headers.
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
RequestHeader set X-Membership-Client-IP "expr=%{REMOTE_ADDR}"
|
||||
LimitRequestBody 262144
|
||||
# The Tor/public static mirror remains available, but account/billing flows
|
||||
# use only the canonical HTTPS origin registered in Dex and Stripe.
|
||||
<If "%{HTTP_HOST} != '{{ www_hostname }}'">
|
||||
Require all denied
|
||||
</If>
|
||||
</Location>
|
||||
{% else %}
|
||||
# Membership proxy disabled. Existing stateful workloads are not deleted.
|
||||
<Location "/members/">
|
||||
Require all denied
|
||||
</Location>
|
||||
{% endif %}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
{% set component = 'migrate' %}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: membership-migrate-{{ _www_membership_revision[:16] }}
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: {{ www_wait_timeout }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: membership-migrate
|
||||
www-membership-client: "true"
|
||||
spec:
|
||||
{% filter indent(6, true) %}{% include 'membership-pod.yml.j2' %}{% endfilter %}
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
# Shared pod spec for web, worker and migration Job.
|
||||
automountServiceAccountToken: false
|
||||
restartPolicy: {{ 'OnFailure' if component == 'migrate' else 'Always' }}
|
||||
{% if www_image_pull_secrets | length %}
|
||||
imagePullSecrets:
|
||||
{{ www_image_pull_secrets | to_nice_yaml | indent(2, true) }}
|
||||
{% endif %}
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10001
|
||||
runAsGroup: 10001
|
||||
fsGroup: 10001
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: {{ component }}
|
||||
image: {{ www_membership_image | to_json }}
|
||||
imagePullPolicy: {{ www_membership_image_pull_policy }}
|
||||
{% if component == 'migrate' %}
|
||||
command: [python, manage.py, migrate, --noinput]
|
||||
{% elif component == 'worker' %}
|
||||
command: [python, manage.py, sync_memberships, --loop, --interval, {{ www_membership_worker_interval | string | to_json }}]
|
||||
{% endif %}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: membership-config
|
||||
- secretRef:
|
||||
name: {{ www_membership_secret_name }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
resources:
|
||||
{{ (www_membership_worker_resources if component == 'worker' else www_membership_resources) | to_nice_yaml | indent(6, true) }}
|
||||
{% if component == 'web' %}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
startupProbe:
|
||||
tcpSocket: {port: http}
|
||||
periodSeconds: 5
|
||||
failureThreshold: 24
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- python
|
||||
- -c
|
||||
- {{ ("import http.client; c=http.client.HTTPConnection('127.0.0.1',8000,timeout=3); c.request('GET','/members/',headers={'Host': '" ~ www_hostname ~ "', 'X-Forwarded-Proto': 'https'}); r=c.getresponse(); assert r.status==200; c.close()") | to_json }}
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket: {port: http}
|
||||
periodSeconds: 20
|
||||
{% endif %}
|
||||
volumeMounts:
|
||||
- name: tiers
|
||||
mountPath: /etc/membership/tiers
|
||||
readOnly: true
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{% if www_membership_ca_secret_name | length %}
|
||||
- name: ca
|
||||
mountPath: /etc/membership/ca
|
||||
readOnly: true
|
||||
{% endif %}
|
||||
volumes:
|
||||
- name: tiers
|
||||
configMap:
|
||||
name: membership-tiers
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
sizeLimit: 64Mi
|
||||
{% if www_membership_ca_secret_name | length %}
|
||||
- name: ca
|
||||
secret:
|
||||
secretName: {{ www_membership_ca_secret_name }}
|
||||
defaultMode: 0444
|
||||
{% endif %}
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: membership-db
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
selector:
|
||||
app: membership-db
|
||||
ports:
|
||||
- name: postgres
|
||||
port: 5432
|
||||
targetPort: postgres
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: membership-db
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
serviceName: membership-db
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: membership-db
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: membership-db
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
{% if www_image_pull_secrets | length %}
|
||||
imagePullSecrets:
|
||||
{{ www_image_pull_secrets | to_nice_yaml | indent(8, true) }}
|
||||
{% endif %}
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: postgres
|
||||
image: {{ www_membership_postgres_image | to_json }}
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
value: {{ www_membership_environment.POSTGRES_DB | to_json }}
|
||||
- name: POSTGRES_USER
|
||||
value: {{ www_membership_environment.POSTGRES_USER | to_json }}
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ www_membership_secret_name }}
|
||||
key: POSTGRES_PASSWORD
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
ports:
|
||||
- name: postgres
|
||||
containerPort: 5432
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: [sh, -c, 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"']
|
||||
periodSeconds: 5
|
||||
startupProbe:
|
||||
exec:
|
||||
command: [sh, -c, 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"']
|
||||
failureThreshold: 60
|
||||
periodSeconds: 5
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
resources:
|
||||
{{ www_membership_postgres_resources | to_nice_yaml | indent(12, true) }}
|
||||
volumeMounts:
|
||||
- name: database
|
||||
mountPath: /var/lib/postgresql/data
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: database
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
{% if www_membership_postgres_storage_class | length %}
|
||||
storageClassName: {{ www_membership_postgres_storage_class | to_json }}
|
||||
{% endif %}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ www_membership_postgres_storage_size }}
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
{% for component in ['web', 'worker'] %}
|
||||
{% set deployment = 'membership' if component == 'web' else 'membership-worker' %}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ deployment }}
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
replicas: {{ www_membership_replicas if component == 'web' else 1 }}
|
||||
{% if component == 'worker' %}
|
||||
strategy:
|
||||
type: Recreate
|
||||
{% endif %}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ deployment }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ deployment }}
|
||||
www-membership-client: "true"
|
||||
annotations:
|
||||
membership/config-revision: {{ _www_membership_revision | to_json }}
|
||||
spec:
|
||||
{% filter indent(6, true) %}{% include 'membership-pod.yml.j2' %}{% endfilter %}
|
||||
{% endfor %}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: membership
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: membership
|
||||
ports:
|
||||
- name: http
|
||||
port: 8000
|
||||
targetPort: http
|
||||
Executable
+172
@@ -0,0 +1,172 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: data
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
accessModes: [ReadWriteMany]
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ www_archive_storage_size }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: {{ www_namespace }}
|
||||
data:
|
||||
httpd.conf: {{ lookup('ansible.builtin.file', role_path ~ '/files/httpd.conf') | to_json }}
|
||||
membership.conf: {{ _www_proxy_config | to_json }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: httpd
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: httpd
|
||||
annotations:
|
||||
www/config-revision: {{ (lookup('ansible.builtin.file', role_path ~ '/files/httpd.conf') ~ _www_proxy_config ~ www_rollout_revision ~ www_repo_url ~ www_repo_version) | hash('sha256') | to_json }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
{% if www_image_pull_secrets | length %}
|
||||
imagePullSecrets:
|
||||
{{ www_image_pull_secrets | to_nice_yaml | indent(8, true) }}
|
||||
{% endif %}
|
||||
initContainers:
|
||||
- name: init
|
||||
image: {{ www_source_image | to_json }}
|
||||
command:
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y git ca-certificates
|
||||
git clone --depth 1 -- "$WWW_REPO_URL" /tmp/www
|
||||
git -C /tmp/www fetch --depth 1 origin "$WWW_REPO_VERSION"
|
||||
git -C /tmp/www checkout --detach FETCH_HEAD
|
||||
# Never delete /data: it contains persistent archives, not just
|
||||
# this repository's static site. Copy hidden site assets too.
|
||||
cp -a /tmp/www/htdocs/. /data/
|
||||
env:
|
||||
- name: WWW_REPO_URL
|
||||
value: {{ www_repo_url | to_json }}
|
||||
- name: WWW_REPO_VERSION
|
||||
value: {{ www_repo_version | to_json }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: httpd
|
||||
image: {{ www_httpd_image | to_json }}
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
- name: metrics
|
||||
containerPort: 9117
|
||||
readinessProbe:
|
||||
tcpSocket: {port: http}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /usr/local/apache2/htdocs
|
||||
- name: httpd
|
||||
mountPath: /usr/local/apache2/conf/httpd.conf
|
||||
subPath: httpd.conf
|
||||
readOnly: true
|
||||
- name: httpd
|
||||
mountPath: /usr/local/apache2/conf/extra/membership.conf
|
||||
subPath: membership.conf
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: data
|
||||
- name: httpd
|
||||
configMap:
|
||||
name: httpd
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: prometheus
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: httpd
|
||||
ports:
|
||||
- name: http
|
||||
port: 9117
|
||||
targetPort: metrics
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: httpd
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
{% if www_onion_enabled %}
|
||||
---
|
||||
apiVersion: tor.k8s.torproject.org/v1alpha2
|
||||
kind: OnionService
|
||||
metadata:
|
||||
name: httpd
|
||||
namespace: {{ www_namespace }}
|
||||
spec:
|
||||
version: 3
|
||||
rules:
|
||||
- port:
|
||||
number: 80
|
||||
backend:
|
||||
service:
|
||||
name: httpd
|
||||
port:
|
||||
number: 80
|
||||
{% endif %}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: www
|
||||
namespace: {{ www_namespace }}
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ www_cluster_issuer | to_json }}
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
# Redact email verification tokens and OIDC codes at the edge, not only in
|
||||
# Apache/Gunicorn. Do not enable request-body/error tracing for /members/.
|
||||
nginx.ingress.kubernetes.io/enable-access-log: "false"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "256k"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
|
||||
spec:
|
||||
ingressClassName: {{ www_ingress_class }}
|
||||
rules:
|
||||
- host: {{ www_hostname }}
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: /
|
||||
backend:
|
||||
service:
|
||||
name: httpd
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts: [{{ www_hostname }}]
|
||||
secretName: www
|
||||
Reference in New Issue
Block a user