(helm) deploy new infra using helm

The new infra we have must be configured in the helm chart. This commit
all the missing templates to deploy yhub, it also automate the creation
of the private keys needed by all services.
This commit is contained in:
Manuel Raynaud
2026-08-13 11:45:49 +02:00
parent 3ec2b5c134
commit 39a0b665a3
33 changed files with 1465 additions and 374 deletions
+7
View File
@@ -5,6 +5,10 @@ __pycache__
**/*.pyc
venv
.venv
# the pattern above only matches at the root, and every image is built from
# there: without this one, the backend virtualenv travels to the daemon on
# every build
**/.venv
# System-specific files
.DS_Store
@@ -34,4 +38,7 @@ db.sqlite3
# Frontend
node_modules
# same as .venv above: nested ones are not matched by the pattern above, and no
# image copies them — every one of them runs its own install
**/node_modules
**/.next
+15
View File
@@ -60,11 +60,26 @@ jobs:
should_push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
docker_user: 1001:127
build-and-push-yhub:
uses: ./.github/workflows/docker-publish.yml
permissions:
contents: read
secrets: inherit
with:
image_name: lasuite/impress-yhub
context: .
file: src/yhub-server/Dockerfile
target: yhub
should_push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
# no docker_user: the image defaults to uid 1000, the `node` user the
# base image already declares in /etc/passwd
notify-argocd:
needs:
- build-and-push-backend
- build-and-push-frontend
- build-and-push-y-provider
- build-and-push-yhub
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview')
steps:
+47
View File
@@ -158,3 +158,50 @@ jobs:
run: |
docker system prune -af
docker volume prune -f
build-and-push-yhub:
runs-on: ubuntu-latest
if: github.event.repository.fork == true
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Docker meta
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}/yhub
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with:
context: .
file: ./src/yhub-server/Dockerfile
target: yhub
platforms: linux/amd64,linux/arm64
build-args: DOCKER_USER=${{ env.DOCKER_USER }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Cleanup Docker after build
if: always()
run: |
docker system prune -af
docker volume prune -f
+38 -4
View File
@@ -143,6 +143,22 @@ and this project adheres to
on its own
- 🔧(dev) generate the JWT signing key of the collaboration server when
bootstrapping the dev stack, alongside the backend one
- ✨(helm) generate the JWT signing keys of the services on the cluster
(`jwtKeys.enabled`, off by default): a job writes the backend and the
collaboration server keys, once, into a volume both mount read-only, and sets
the `*_FILE` variables pointing at them. No key is templated into a manifest,
kept in a values file or read back from the kubernetes API, and nothing but
that job is granted a write to the volume. Idempotent — an existing key is
kept, so it re-runs on every sync, and rolling a key is deleting it and
letting the next run write a new one. The volume is mounted by every backend
pod and by the collaboration server, so its storage class must support
`ReadWriteMany` unless the cluster has a single node
- ✨(helm) deploy the collaboration server: the chart gains a `yhub` deployment,
its service, and the job running the `init-db` script that creates and
upgrades its schema (an ArgoCD sync wave before the deployment, since a
document read fails until it has run). Configured under the `yhub` values
key, where `REDIS` and `POSTGRES` are required and have no default; the image
is published as `lasuite/impress-yhub`
- ✨(backend) serve `documents/{id}/formatted-content/` from yhub
- ✨(backend) duplicate a document through the collaboration server
- ✨(backend) call YHubService to seed initial document content
@@ -197,13 +213,31 @@ and this project adheres to
- 💥(backend) remove the `documents/{id}/content/` endpoint
- 💥(backend) remove the `documents/{id}/can-edit/` endpoint
- 💥(y-provider) the published `lasuite/impress-y-provider` image becomes
converter-only and no longer serves `/collaboration/ws/`; deployments using
the existing helm values lose collaboration until the helm chart routes
collaboration to yhub (follow-up)
converter-only and no longer serves `/collaboration/ws/`
- 💥(helm) route `/collaboration/` to yhub instead of the y-provider: both
collaboration ingresses now point at the yhub service, and
`ingressCollaborationApi` serves the routes yhub exposes to browsers
(`ingressCollaborationApi.paths`, one ingress rule each) instead of the
single `/collaboration/api/` path — what is not listed stays in-cluster, so
`create-ydoc`, `reset-connections`, `migrate`, `restore-ydoc` and
`reset-ydoc` are not published. The `upstream-hash-by: $arg_room` annotation
is dropped: yhub replicas exchange updates through redis, so a room needs no
sticky upstream — and hashing on a query argument its urls do not carry would
pin every connection to a single pod
- 💥(helm) drop the `yProvider.converter` values, its deployment and its
service: the y-provider serves nothing but the conversion API since the
collaboration moved to yhub, so the `yProvider` release *is* the converter
and there is no second one to enable. Deployments that had it on lose the
`-converter` suffix on the url the backend calls —
`Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/` — and
`yProvider.converter.*` values are now ignored, their `yProvider.*`
counterparts taking over
- 🔧(collaboration) split the yhub image into a development and a production
stage, like the other services: the dev stack now bind-mounts
`src/yhub-server` and runs the server through nodemon, so editing a source
file restarts it instead of needing `make build-yhub`
file restarts it instead of needing `make build-yhub`. The production stage
gains the un-privileged user and the entrypoint the other images have, and
both are now built from the repository root like the rest of them
## [v5.4.1] - 2026-07-09
+13
View File
@@ -42,8 +42,21 @@ docker_build(
]
)
docker_build(
'localhost:5001/impress-yhub:latest',
context='..',
dockerfile='../src/yhub-server/Dockerfile',
only=['./src/yhub-server', './docker', './.dockerignore'],
target = 'yhub',
build_args={'DOCKER_USER': '1000:1000'},
live_update=[
sync('../src/yhub-server', '/app'),
]
)
k8s_resource('impress-docs-backend-migrate', resource_deps=['dev-backend-postgres'])
k8s_resource('impress-docs-backend-createsuperuser', resource_deps=['impress-docs-backend-migrate'])
k8s_resource('impress-docs-yhub-init-db', resource_deps=['dev-backend-postgres'])
k8s_resource('dev-backend-keycloak', resource_deps=['dev-backend-keycloak-pg'])
k8s_resource('impress-docs-backend', resource_deps=['impress-docs-backend-migrate', 'dev-backend-redis', 'dev-backend-keycloak', 'dev-backend-postgres', 'dev-backend-minio:statefulset'])
k8s_yaml(local('cd ../src/helm && helmfile -n impress -e dev template .'))
+7 -8
View File
@@ -235,8 +235,8 @@ services:
yhub:
user: ${DOCKER_USER:-1000}
build:
context: ./src/yhub-server
dockerfile: Dockerfile
context: .
dockerfile: ./src/yhub-server/Dockerfile
target: yhub-development
image: impress:yhub-development
environment:
@@ -255,6 +255,11 @@ services:
- env.d/development/common.local
volumes:
- ./data/jwt:/data/jwt:ro
# editing a source file restarts the server (nodemon), no rebuild
- ./src/yhub-server:/app
# node_modules is installed in the image, not in the source tree: keep
# the bind mount above from hiding it
- /app/node_modules
restart: unless-stopped
ports:
- "3002:3002"
@@ -267,12 +272,6 @@ services:
# starting before minio would cache 401s for the first accessed docs
minio:
condition: service_healthy
volumes:
# editing a source file restarts the server (nodemon), no rebuild
- ./src/yhub-server:/app
# node_modules is installed in the image, not in the source tree: keep
# the bind mount above from hiding it
- /app/node_modules
kc_postgresql:
image: postgres:14.3
+46 -1
View File
@@ -1,12 +1,57 @@
# Collaboration
By default with Docs, collaboration is enabled. To allow the collaboration between users, a connection to a websocket server is made (the y-provider service), you only have to configure the Django backend URL and the allowed origin in your y-provider service:
By default with Docs, collaboration is enabled. To allow the collaboration between users, a connection to a websocket server is made (the yhub service), you only have to configure the Django backend URL and the allowed origin in your yhub service:
```yaml
COLLABORATION_BACKEND_BASE_URL: https://{yourdocsdomain.tld}
COLLABORATION_SERVER_ORIGIN: https://{yourdocsdomain.tld}
```
The collaboration server keeps the live state of a document in Redis and persists it to a PostgreSQL database of its own, so it needs both:
```yaml
REDIS: redis://{redis-host}:6379/0
POSTGRES: postgres://{user}:{password}@{postgres-host}:5432/yhub
```
Nothing creates that schema at startup: the server never runs DDL. Run the script yhub ships (`npm run init-db`, which the helm chart runs as a job) once before starting it, and again after every upgrade that adds a table. It creates the database when it is missing, it is idempotent, and until it has run every document read fails with `relation "..." does not exist`.
The Django backend reads and writes document content there too, so point it at the service:
```yaml
YHUB_API_BASE_URL: http://{yhub-service}:443
```
Prefer the internal service url: the routes the backend calls are not meant to be reachable from the outside. Route `/collaboration/ws/` to the service publicly — that is the one the browsers open — plus the document routes (`/collaboration/ydoc/`, `rollback`, `prune`, `changeset`, `activity`) and `/collaboration/jwks/`, which carries public keys and nothing else. Keep `reset-connections`, `migrate`, `restore-ydoc`, `reset-ydoc` and `create-ydoc` in-cluster.
Both directions are authenticated with short-lived RS256 JWTs rather than a shared secret, and each side verifies the other against the JWKS it publishes — so both need a signing key of their own, and neither needs a copy of the other's:
```yaml
# Django
JWT_PRIVATE_KEY_FILE: /path/to/backend-private.pem
# yhub
YHUB_JWT_PRIVATE_KEY_FILE: /path/to/yhub-private.pem
```
They are ordinary PKCS#8 RSA keys (`openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`), and rolling one needs no change on the other side. Without them the documents still open and edit, but the backend cannot create, delete or restore a document's content, and yhub cannot tell it that a document changed — its `updated_at` stops following the edits.
### Generating them on the cluster
The helm chart generates both for you, so that no key has to be created by hand, put in a values file or in a secret:
```yaml
jwtKeys:
enabled: true
```
A job then writes the two keys, once, into a volume every service mounts read-only, and points the backend and yhub at them. It keeps the keys it finds, so it is safe to re-run — it runs on every sync — and rolling a key is deleting it from the volume and letting the next run write a new one. Both sides follow: they pick the verification key by its `kid` and fetch the set again when they meet one they do not know.
Every backend pod and the collaboration server mount that volume, which puts them on several nodes as soon as they have replicas, so the storage class behind it has to support `ReadWriteMany`. On a single node cluster, `jwtKeys.persistence.accessModes: [ReadWriteOnce]` does just as well. The volume outlives the release (`helm.sh/resource-policy: keep`), so uninstalling and installing again keeps the same identities; delete the claim to start over.
Setting `JWT_PRIVATE_KEY_FILE` or `YHUB_JWT_PRIVATE_KEY_FILE` yourself keeps priority over what the job provides, so a deployment holding its keys in a secret of its own can leave `jwtKeys` disabled and mount them where it wants.
Several replicas can serve the same document: they exchange updates through Redis, so no sticky routing is needed on the websocket ingress.
## What happens when connection to the websocket is not allowed?
When multiple users access a Docs and the connection to the websocket is not allowed, then they will be in a situation where they can lose data.
@@ -67,7 +67,9 @@ backend:
AWS_STORAGE_BUCKET_NAME: docs-media-storage
STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage
USER_RECONCILIATION_FORM_URL: https://docs.127.0.0.1.nip.io
Y_PROVIDER_API_BASE_URL: http://impress-y-provider:443/api/
# the collaboration server, reached in-cluster
YHUB_API_BASE_URL: http://impress-docs-yhub:443
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/
Y_PROVIDER_API_KEY: my-secret
CACHES_KEY_PREFIX: "{{ now | unixEpoch }}"
migrate:
@@ -135,6 +137,26 @@ yProvider:
COLLABORATION_LOGGING: true
COLLABORATION_SERVER_ORIGIN: https://docs.127.0.0.1.nip.io
# The collaboration server: it serves everything under /collaboration/, the
# websocket included. It keeps the live state of a document in redis and
# persists it to a PostgreSQL database of its own, created by the init-db job
# the chart ships — give the user in POSTGRES the right to create it, or create
# the database yourself beforehand.
yhub:
replicas: 1
image:
repository: lasuite/impress-yhub
pullPolicy: Always
tag: "latest"
envVars:
POSTGRES: postgres://dinum:pass@postgresql-dev-backend-postgres:5432/yhub
REDIS: redis://user:pass@redis-dev-backend-redis:6379/2
REDIS_PREFIX: yhub
COLLABORATION_BACKEND_BASE_URL: https://docs.127.0.0.1.nip.io
COLLABORATION_SERVER_ORIGIN: https://docs.127.0.0.1.nip.io
ingress:
enabled: true
host: docs.127.0.0.1.nip.io
+6 -22
View File
@@ -26,33 +26,17 @@ COLLABORATION_BACKEND_BASE_URL: http://{django-service}:8000
The JWKS url defaults to `{COLLABORATION_BACKEND_BASE_URL}/api/v1.0/jwks`; override it with `JWKS_URL` if Django is not reachable at that base url from the y-provider service.
### Splitting conversion service
### One service, not two anymore
The conversion service is present in the `y-provider` server. The same server used to manage websockets. You can split in one side the websocket server and in an other side the converter service.
This feature is only available in our helm chart, if you are deploying an other way you can take example of what is made to implement it.
The idea is to deploy twice the `y-provider` server, one dedicated for websockets and one dedicated to the conversion.
The `y-provider` server used to serve the websockets as well, which is why it could be deployed twice — one release for the collaboration, one for the conversion (`yProvider.converter`). The collaboration is served by [yhub](collaboration.md) now, so the conversion is all that is left: the `y-provider` service **is** the converter, and the `yProvider.converter` values are gone.
In the helm chart, you can use this value that will do the job for you:
```yaml
yProvider:
converter:
enabled: true
```
Every parameter in the `yProvider` key can be overridden in the `yProvider.converter` key.
Once enabled, you have to enable the `Y_PROVIDER_API_BASE_URL` with the url of the newly created service, it is the same as before with `-converter` at the end.
If before it was
```yaml
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/
```
now it is
A deployment coming from a chart older than this one has one thing to change, the url the backend calls, which loses its suffix:
```yaml
# before
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider-converter:443/api/
# now
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/
```
## Docspec configuration
+1
View File
@@ -226,6 +226,7 @@ impress-docs-backend-8494fb797d-8k8wt 1/1 Running 0 6m45s
impress-docs-celery-worker-764b5dd98f-9qd6v 1/1 Running 0 6m45s
impress-docs-frontend-5b69b65cc4-s8pps 1/1 Running 0 6m45s
impress-docs-y-provider-5fc7ccd8cc-6ttrf 1/1 Running 0 6m45s
impress-docs-yhub-6d84f9b7c5-2xqzp 1/1 Running 0 6m45s
keycloak-dev-backend-keycloak-0 1/1 Running 0 24m
keycloak-dev-backend-keycloak-pg-0 1/1 Running 0 24m
minio-dev-backend-minio-0 1/1 Running 0 8m24s
+49 -7
View File
@@ -71,7 +71,10 @@ backend:
STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage
DOCSPEC_API_URL: http://impress-docs-docspec:4000/conversion
USER_RECONCILIATION_FORM_URL: https://docs.127.0.0.1.nip.io
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider-converter:443/api/
# the collaboration server, reached in-cluster: the backend reads and
# writes document content there, and fetches its JWKS from the same host
YHUB_API_BASE_URL: http://impress-docs-yhub:443
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/
Y_PROVIDER_API_KEY: my-secret
CACHES_KEY_PREFIX: "{{ now | unixEpoch }}"
django:
@@ -153,11 +156,6 @@ frontend:
runAsNonRoot: false
yProvider:
converter:
enabled: true
replicas: 2
replicas: 1
image:
@@ -186,6 +184,50 @@ yProvider:
- key: cacert.pem
path: cacert.pem
# The keys the backend and the collaboration server sign the calls they make to
# each other with, generated on the cluster by a job and mounted read-only in
# both. ReadWriteOnce because the local cluster has a single node.
jwtKeys:
enabled: true
persistence:
accessModes:
- ReadWriteOnce
yhub:
replicas: 1
image:
repository: localhost:5001/impress-yhub
pullPolicy: Always
tag: "latest"
envVars:
# its own logical database on the dev-backend postgres: the init-db job
# creates it, the backend never touches it
POSTGRES: postgres://dinum:pass@dev-backend-postgres:5432/yhub
# a redis database of its own too — the backend cache and celery live in /1
REDIS: redis://user:pass@dev-backend-redis:6379/2
REDIS_PREFIX: yhub
COLLABORATION_BACKEND_BASE_URL: https://docs.127.0.0.1.nip.io
COLLABORATION_SERVER_ORIGIN: https://docs.127.0.0.1.nip.io
NODE_EXTRA_CA_CERTS: /cert/cacert.pem
# YHUB_JWT_PRIVATE_KEY_FILE comes from the jwtKeys job below
# Extra volume mounts to manage our local custom CA and avoid to set ssl_verify: false
extraVolumeMounts:
- name: certs
mountPath: /cert/cacert.pem
subPath: cacert.pem
# Extra volumes to manage our local custom CA and avoid to set ssl_verify: false
extraVolumes:
- name: certs
configMap:
name: certifi
items:
- key: cacert.pem
path: cacert.pem
docSpec:
enabled: true
replicas: 1
@@ -212,7 +254,7 @@ ingressCollaborationWS:
host: docs.127.0.0.1.nip.io
ingressCollaborationApi:
enabled: true
enabled: false
host: docs.127.0.0.1.nip.io
ingressAdmin:
@@ -71,7 +71,10 @@ backend:
STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage
DOCSPEC_API_URL: http://impress-docs-docspec:4000/conversion
USER_RECONCILIATION_FORM_URL: https://{{ .Values.feature }}-docs.{{ .Values.domain }}
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider-converter:443/api/
# the collaboration server, reached in-cluster: the backend reads and
# writes document content there, and fetches its JWKS from the same host
YHUB_API_BASE_URL: http://impress-docs-yhub:443
Y_PROVIDER_API_BASE_URL: http://impress-docs-y-provider:443/api/
Y_PROVIDER_API_KEY: my-secret
CACHES_KEY_PREFIX: "{{ now | unixEpoch }}"
migrate:
@@ -131,11 +134,6 @@ frontend:
tag: *tag
yProvider:
converter:
enabled: true
replicas: 1
replicas: 1
image:
@@ -149,6 +147,33 @@ yProvider:
COLLABORATION_SERVER_ORIGIN: https://{{ .Values.feature }}-docs.{{ .Values.domain }}
NODE_OPTIONS: "--max-old-space-size=1024"
# The keys the backend and the collaboration server sign the calls they make to
# each other with, generated on the cluster by a job and mounted read-only in
# both. ReadWriteOnce because the preview cluster has a single node.
jwtKeys:
enabled: true
persistence:
accessModes:
- ReadWriteOnce
yhub:
replicas: 1
image:
repository: lasuite/impress-yhub
pullPolicy: Always
tag: *tag
envVars:
# its own logical database on the dev-backend postgres, created by the
# init-db job; redis /2, the backend cache and celery live in /1
POSTGRES: postgres://dinum:pass@dev-backend-postgres:5432/yhub
REDIS: redis://user:pass@dev-backend-redis:6379/2
REDIS_PREFIX: yhub
COLLABORATION_BACKEND_BASE_URL: https://{{ .Values.feature }}-docs.{{ .Values.domain }}
COLLABORATION_SERVER_ORIGIN: https://{{ .Values.feature }}-docs.{{ .Values.domain }}
NODE_OPTIONS: "--max-old-space-size=1024"
docSpec:
enabled: true
replicas: 1
@@ -176,7 +201,7 @@ ingressCollaborationWS:
host: {{ .Values.feature }}-docs.{{ .Values.domain }}
ingressCollaborationApi:
enabled: true
enabled: false
host: {{ .Values.feature }}-docs.{{ .Values.domain }}
ingressAdmin:
+110 -3
View File
@@ -38,7 +38,6 @@
| `ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/enable-websocket` | | `true` |
| `ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/proxy-read-timeout` | | `86400` |
| `ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/proxy-send-timeout` | | `86400` |
| `ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/upstream-hash-by` | | `$arg_room` |
| `ingressRedirects.enabled` | whether to enable the Ingress Redirects or not | `false` |
| `ingressRedirects.className` | IngressClass to use for the Ingress Redirects | `nil` |
| `ingressRedirects.host` | Host for the Ingress Redirects | `impress.example.com` |
@@ -51,13 +50,13 @@
| `ingressCollaborationApi.className` | IngressClass to use for the Ingress | `nil` |
| `ingressCollaborationApi.host` | Host for the Ingress | `impress.example.com` |
| `ingressCollaborationApi.path` | Path to use for the Ingress | `/collaboration/api/` |
| `ingressCollaborationApi.paths` | Paths to route to the collaboration server, one rule each | `["/collaboration/ydoc/","/collaboration/jwks/"]` |
| `ingressCollaborationApi.hosts` | Additional host to configure for the Ingress | `[]` |
| `ingressCollaborationApi.tls.enabled` | Whether to enable TLS for the Ingress | `true` |
| `ingressCollaborationApi.tls.secretName` | Secret name for TLS config | `nil` |
| `ingressCollaborationApi.tls.additional[].secretName` | Secret name for additional TLS config | |
| `ingressCollaborationApi.tls.additional[].hosts[]` | Hosts for additional TLS config | |
| `ingressCollaborationApi.customBackends` | Add custom backends to ingress | `[]` |
| `ingressCollaborationApi.annotations.nginx.ingress.kubernetes.io/upstream-hash-by` | | `$arg_room` |
| `ingressAdmin.enabled` | whether to enable the Ingress or not | `false` |
| `ingressAdmin.className` | IngressClass to use for the Ingress | `nil` |
| `ingressAdmin.host` | Host for the Ingress | `impress.example.com` |
@@ -301,13 +300,121 @@
| `yProvider.pdb.enabled` | Enable pdb on yProvider | `true` |
| `yProvider.serviceAccountName` | Optional service account name to use for yProvider pods | `nil` |
### JWT signing keys
| Name | Description | Value |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------- | ------------------- |
| `jwtKeys.enabled` | Generate the JWT signing keys of the services on the cluster | `false` |
| `jwtKeys.mountPath` | Path the keys are mounted at, in every service reading them | `/data/jwt` |
| `jwtKeys.backendKeyFilename` | Name of the key signing the tokens the backend issues | `private.pem` |
| `jwtKeys.yhubKeyFilename` | Name of the key signing the calls the collaboration server makes to the backend | `yhub-private.pem` |
| `jwtKeys.keySize` | Size, in bits, of the generated RSA keys | `2048` |
| `jwtKeys.image.repository` | Repository to use to pull the image generating the keys | `alpine/openssl` |
| `jwtKeys.image.tag` | Tag of the image generating the keys | `3.5.7` |
| `jwtKeys.image.pullPolicy` | Pull policy of the image generating the keys | `IfNotPresent` |
| `jwtKeys.securityContext.allowPrivilegeEscalation` | Whether to allow privilege escalation for the containers waiting for the keys | `false` |
| `jwtKeys.securityContext.capabilities.drop` | List of capabilities to drop for the containers waiting for the keys | `["ALL"]` |
| `jwtKeys.securityContext.runAsNonRoot` | Whether to run the containers waiting for the keys as a non-root user | `true` |
| `jwtKeys.securityContext.seccompProfile.type` | Seccomp profile type for the containers waiting for the keys | `RuntimeDefault` |
| `jwtKeys.persistence.existingClaim` | Claim holding the keys, generated in a claim of the chart's own when empty | `nil` |
| `jwtKeys.persistence.size` | Size of the claim holding the keys, two RSA keys and nothing else | `8Mi` |
| `jwtKeys.persistence.storageClass` | Storage class of the claim holding the keys, the cluster default when empty | `nil` |
| `jwtKeys.persistence.accessModes` | Access modes of the claim holding the keys | `["ReadWriteMany"]` |
| `jwtKeys.persistence.annotations` | Annotations to add to the claim holding the keys | `{}` |
| `jwtKeys.job.podSecurityContext.fsGroup` | Group given ownership of the volume, so the generating container can write the keys in it | `1000` |
| `jwtKeys.job.securityContext.allowPrivilegeEscalation` | Whether to allow privilege escalation for the generating container | `false` |
| `jwtKeys.job.securityContext.capabilities.drop` | List of capabilities to drop for the generating container | `["ALL"]` |
| `jwtKeys.job.securityContext.runAsNonRoot` | Whether to run the generating container as a non-root user | `true` |
| `jwtKeys.job.securityContext.runAsUser` | User the generating container runs as, the openssl image declaring none | `1000` |
| `jwtKeys.job.securityContext.runAsGroup` | Group the generating container runs as | `1000` |
| `jwtKeys.job.securityContext.seccompProfile.type` | Seccomp profile type for the generating container | `RuntimeDefault` |
| `jwtKeys.job.restartPolicy` | Restart policy of the generating job | `Never` |
| `jwtKeys.job.backoffLimit` | Numbers of generating job retries | `2` |
| `jwtKeys.job.ttlSecondsAfterFinished` | Period to wait before removing the generating job | `30` |
| `jwtKeys.job.command` | Override the command generating the keys | `[]` |
| `jwtKeys.job.annotations` | Annotations to add to the generating job | `{}` |
| `jwtKeys.job.podAnnotations` | Annotations to add to the generating job Pod | `{}` |
| `jwtKeys.job.resources` | Resource requirements for the generating container | `{}` |
| `jwtKeys.job.nodeSelector` | Node selector for the generating job Pod | `{}` |
| `jwtKeys.job.tolerations` | Tolerations for the generating job Pod | `[]` |
| `jwtKeys.job.affinity` | Affinity for the generating job Pod | `{}` |
| `jwtKeys.job.serviceAccountName` | Optional service account name to use for the generating job Pod | `nil` |
### yhub
| Name | Description | Value |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `yhub.enabled` | Enable the yhub collaboration server, its service and its init-db job | `true` |
| `yhub.image.repository` | Repository to use to pull the yhub container image | `lasuite/impress-yhub` |
| `yhub.image.tag` | yhub container tag | `latest` |
| `yhub.image.pullPolicy` | yhub container image pull policy | `IfNotPresent` |
| `yhub.command` | Override the yhub container command | `[]` |
| `yhub.args` | Override the yhub container args | `[]` |
| `yhub.replicas` | Amount of yhub replicas | `3` |
| `yhub.shareProcessNamespace` | Enable share process namespace between containers | `false` |
| `yhub.sidecars` | Add sidecars containers to yhub deployment | `[]` |
| `yhub.terminationGracePeriodSeconds` | Grace period given to a yhub pod to drain before it is killed | `60` |
| `yhub.securityContext.allowPrivilegeEscalation` | Whether to allow privilege escalation for the yhub container | `false` |
| `yhub.securityContext.capabilities.drop` | List of capabilities to drop for the yhub container | `["ALL"]` |
| `yhub.securityContext.runAsNonRoot` | Whether to run the yhub container as a non-root user | `true` |
| `yhub.securityContext.seccompProfile.type` | Seccomp profile type for the yhub container | `RuntimeDefault` |
| `yhub.envVars` | Configure yhub container environment variables | `undefined` |
| `yhub.envVars.REDIS` | Required, redis/valkey url holding the live document state (e.g. redis://valkey:6379/0) | |
| `yhub.envVars.POSTGRES` | Required, url of the yhub database, created by the init-db job (e.g. postgres://user:pass@postgres:5432/yhub) | |
| `yhub.envVars.REDIS_PREFIX` | Namespace of the redis keys, when the instance is shared (default: yhub) | |
| `yhub.envVars.COLLABORATION_BACKEND_BASE_URL` | Base url of the Docs backend, which yhub asks about users and document access rights | |
| `yhub.envVars.COLLABORATION_SERVER_ORIGIN` | Comma separated list of the origins allowed to open a websocket | |
| `yhub.envVars.YHUB_JWT_PRIVATE_KEY_FILE` | Path to the RSA private key (PEM) yhub signs its calls to the backend with, mounted from a secret | |
| `yhub.envVars.SOFT_MIGRATION` | Set to "true" to seed rooms from the legacy Django/S3 document store on first access | |
| `yhub.envVars.BY_VALUE` | Example environment variable by setting value directly | |
| `yhub.envVars.FROM_CONFIGMAP.configMapKeyRef.name` | Name of a ConfigMap when configuring env vars from a ConfigMap | |
| `yhub.envVars.FROM_CONFIGMAP.configMapKeyRef.key` | Key within a ConfigMap when configuring env vars from a ConfigMap | |
| `yhub.envVars.FROM_SECRET.secretKeyRef.name` | Name of a Secret when configuring env vars from a Secret | |
| `yhub.envVars.FROM_SECRET.secretKeyRef.key` | Key within a Secret when configuring env vars from a Secret | |
| `yhub.podAnnotations` | Annotations to add to the yhub Pod | `{}` |
| `yhub.dpAnnotations` | Annotations to add to the yhub Deployment | `{}` |
| `yhub.initDbJobAnnotations` | Annotations for the yhub init-db job | `{}` |
| `yhub.jobs.ttlSecondsAfterFinished` | Period to wait before removing the init-db job | `30` |
| `yhub.jobs.backoffLimit` | Numbers of init-db job retries | `2` |
| `yhub.initDb.enabled` | Run the job creating and upgrading the yhub schema | `true` |
| `yhub.initDb.command` | Command creating and upgrading the yhub schema, what `npm run init-db` runs | `["node","node_modules/@y/hub/bin/init-db.js"]` |
| `yhub.initDb.restartPolicy` | Restart policy of the init-db job | `Never` |
| `yhub.service.type` | yhub Service type | `ClusterIP` |
| `yhub.service.port` | yhub Service listening port | `443` |
| `yhub.service.targetPort` | yhub container listening port | `3002` |
| `yhub.service.annotations` | Annotations to add to the yhub Service | `{}` |
| `yhub.probes.liveness.path` | Configure path for yhub HTTP liveness probe | `/collaboration/jwks/v1` |
| `yhub.probes.liveness.initialDelaySeconds` | Configure initial delay for yhub liveness probe | `10` |
| `yhub.probes.readiness.path` | Configure path for yhub HTTP readiness probe | `/collaboration/jwks/v1` |
| `yhub.probes.readiness.initialDelaySeconds` | Configure initial delay for yhub readiness probe | `5` |
| `yhub.probes.liveness.targetPort` | Configure port for yhub HTTP liveness probe | |
| `yhub.probes.liveness.timeoutSeconds` | Configure timeout for yhub liveness probe | |
| `yhub.probes.readiness.targetPort` | Configure port for yhub HTTP readiness probe | |
| `yhub.probes.readiness.timeoutSeconds` | Configure timeout for yhub readiness probe | |
| `yhub.probes.startup.path` | Configure path for yhub HTTP startup probe | |
| `yhub.probes.startup.targetPort` | Configure port for yhub HTTP startup probe | |
| `yhub.probes.startup.initialDelaySeconds` | Configure initial delay for yhub startup probe | |
| `yhub.probes.startup.timeoutSeconds` | Configure timeout for yhub startup probe | |
| `yhub.resources` | Resource requirements for the yhub container | `{}` |
| `yhub.nodeSelector` | Node selector for the yhub Pod | `{}` |
| `yhub.tolerations` | Tolerations for the yhub Pod | `[]` |
| `yhub.affinity` | Affinity for the yhub Pod | `{}` |
| `yhub.persistence` | Additional volumes to create and mount on the yhub. Used for debugging purposes | `{}` |
| `yhub.persistence.volume-name.size` | Size of the additional volume | |
| `yhub.persistence.volume-name.type` | Type of the additional volume, persistentVolumeClaim or emptyDir | |
| `yhub.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `yhub.extraVolumeMounts` | Additional volumes to mount on the yhub. Mounted on the init-db job too | `[]` |
| `yhub.extraVolumes` | Additional volumes to mount on the yhub. Mounted on the init-db job too | `[]` |
| `yhub.pdb.enabled` | Enable pdb on yhub | `true` |
| `yhub.serviceAccountName` | Optional service account name to use for yhub pods | `nil` |
### docSpec
| Name | Description | Value |
| -------------------------------------------------- | --------------------------------------------------------------- | ----------------------- |
| `docSpec.enabled` | Enable docSpec deployment | `false` |
| `docSpec.image.repository` | Repository to use to pull docSpec container image | `ghcr.io/docspecio/api` |
| `docSpec.image.tag` | docSpec container tag | `2.6.3` |
| `docSpec.image.tag` | docSpec container tag | `3.0.1` |
| `docSpec.image.pullPolicy` | docSpec container image pull policy | `IfNotPresent` |
| `docSpec.command` | Override the docSpec container command | `[]` |
| `docSpec.args` | Override the docSpec container args | `[]` |
+98 -10
View File
@@ -186,16 +186,6 @@ Requires top level scope
{{ include "impress.fullname" . }}-y-provider
{{- end }}
{{/*
Full name for the yProvider converter
Requires top level scope
*/}}
{{- define "impress.yProvider.converter.fullname" -}}
{{ include "impress.yProvider.fullname" . }}-converter
{{- end }}
{{/*
Full name for the docSpec
@@ -205,6 +195,104 @@ Requires top level scope
{{ include "impress.fullname" . }}-docspec
{{- end }}
{{/*
Full name for the yhub collaboration server
Requires top level scope
*/}}
{{- define "impress.yhub.fullname" -}}
{{ include "impress.fullname" . }}-yhub
{{- end }}
{{/*
JWT signing keys the RSA keys the services sign the calls they make to each
other with. They are generated once by the jwt-keys job into a volume every
service mounts read-only, so no key is ever templated, stored in a values file
or read from the kubernetes API.
Requires top level scope
*/}}
{{- define "impress.jwtKeys.claimName" -}}
{{- .Values.jwtKeys.persistence.existingClaim | default (printf "%s-jwt-keys" (include "impress.fullname" .)) -}}
{{- end }}
{{- define "impress.jwtKeys.backendPath" -}}
{{ .Values.jwtKeys.mountPath }}/{{ .Values.jwtKeys.backendKeyFilename }}
{{- end }}
{{- define "impress.jwtKeys.yhubPath" -}}
{{ .Values.jwtKeys.mountPath }}/{{ .Values.jwtKeys.yhubKeyFilename }}
{{- end }}
{{/*
The volume holding the keys. readOnly on the volume itself, not only on the
mount: nothing but the generating job is allowed to write there.
Requires top level scope
*/}}
{{- define "impress.jwtKeys.volume" -}}
- name: jwt-keys
persistentVolumeClaim:
claimName: {{ include "impress.jwtKeys.claimName" . }}
readOnly: true
{{- end }}
{{- define "impress.jwtKeys.volumeMount" -}}
- name: jwt-keys
mountPath: {{ .Values.jwtKeys.mountPath }}
readOnly: true
{{- end }}
{{/*
Init container waiting for the keys to be there. The generating job runs in an
earlier sync wave, but nothing orders the two under a plain `helm install`
waiting here beats crash-looping, and it fails loudly (Init state) rather than
silently when the job never ran.
Usage: {{ include "impress.jwtKeys.initContainer" (dict "root" $ "image" $image "pullPolicy" $pullPolicy "files" (list "/data/jwt/private.pem")) }}
*/}}
{{- define "impress.jwtKeys.initContainer" -}}
{{- $root := .root -}}
- name: wait-for-jwt-keys
image: {{ .image | quote }}
imagePullPolicy: {{ .pullPolicy }}
command:
- /bin/sh
- -c
- |
until {{ range $index, $file := .files }}{{ if $index }} && {{ end }}[ -r "{{ $file }}" ]{{ end }}; do
echo "waiting for the JWT signing keys to be generated..."
sleep 2
done
{{- with $root.Values.jwtKeys.securityContext }}
securityContext:
{{- toYaml . | nindent 4 }}
{{- end }}
volumeMounts:
{{- include "impress.jwtKeys.volumeMount" $root | nindent 4 }}
{{- end }}
{{/*
`*_FILE` environment variables pointing at the keys, added only when the
deployment did not set them by hand configuring a key of your own stays
possible, and wins.
Requires top level scope
*/}}
{{- define "impress.jwtKeys.backendEnv" -}}
{{- if not (hasKey (.Values.backend.envVars | default dict) "JWT_PRIVATE_KEY_FILE") }}
- name: "JWT_PRIVATE_KEY_FILE"
value: {{ include "impress.jwtKeys.backendPath" . | quote }}
{{- end }}
{{- end }}
{{- define "impress.jwtKeys.yhubEnv" -}}
{{- if not (hasKey (.Values.yhub.envVars | default dict) "YHUB_JWT_PRIVATE_KEY_FILE") }}
- name: "YHUB_JWT_PRIVATE_KEY_FILE"
value: {{ include "impress.jwtKeys.yhubPath" . | quote }}
{{- end }}
{{- end }}
{{/*
Full name for the Celery Worker
@@ -29,6 +29,15 @@ items:
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end}}
shareProcessNamespace: {{ $.Values.backend.shareProcessNamespace }}
{{- if $.Values.jwtKeys.enabled }}
initContainers:
{{- include "impress.jwtKeys.initContainer" (dict
"root" $
"image" (printf "%s:%s" (($.Values.backend.image | default dict).repository | default $.Values.image.repository) (($.Values.backend.image | default dict).tag | default $.Values.image.tag))
"pullPolicy" (($.Values.backend.image | default dict).pullPolicy | default $.Values.image.pullPolicy)
"files" (list (include "impress.jwtKeys.backendPath" $))
) | nindent 16 }}
{{- end }}
containers:
{{- with $.Values.backend.sidecars }}
{{- toYaml . | nindent 18 }}
@@ -38,9 +47,12 @@ items:
imagePullPolicy: {{ ($.Values.backend.image | default dict).pullPolicy | default $.Values.image.pullPolicy }}
args:
{{- toYaml .command | nindent 22 }}
{{- if $envVars}}
{{- if or $envVars $.Values.jwtKeys.enabled }}
env:
{{- $envVars | indent 22 }}
{{- if $.Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.backendEnv" $ | nindent 22 }}
{{- end }}
{{- end }}
{{- if $.Values.backend.envFrom }}
envFrom:
@@ -55,6 +67,9 @@ items:
{{- toYaml . | nindent 22 }}
{{- end }}
volumeMounts:
{{- if $.Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volumeMount" $ | nindent 20 }}
{{- end }}
{{- range $index, $value := $.Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
@@ -72,6 +87,9 @@ items:
{{- end }}
restartPolicy: {{ .restartPolicy | default "Never" }}
volumes:
{{- if $.Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volume" $ | nindent 16 }}
{{- end }}
{{- range $index, $value := $.Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
@@ -34,6 +34,15 @@ spec:
serviceAccountName: {{ .Values.backend.serviceAccountName }}
{{- end }}
shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }}
{{- if .Values.jwtKeys.enabled }}
initContainers:
{{- include "impress.jwtKeys.initContainer" (dict
"root" .
"image" (printf "%s:%s" ((.Values.backend.image | default dict).repository | default .Values.image.repository) ((.Values.backend.image | default dict).tag | default .Values.image.tag))
"pullPolicy" ((.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy)
"files" (list (include "impress.jwtKeys.backendPath" .))
) | nindent 8 }}
{{- end }}
containers:
{{- with .Values.backend.sidecars }}
{{- toYaml . | nindent 8 }}
@@ -49,9 +58,12 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
{{- if or $envVars .Values.jwtKeys.enabled }}
env:
{{- $envVars | indent 12 }}
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.backendEnv" . | nindent 12 }}
{{- end }}
{{- end }}
{{- $envFrom := concat (.Values.backend.envFrom | default list) ((.Values.backend.django | default dict).envFrom | default list) }}
{{- if $envFrom }}
@@ -83,6 +95,9 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volumeMount" . | nindent 12 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
@@ -116,6 +131,9 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volume" . | nindent 8 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
+19 -1
View File
@@ -29,6 +29,15 @@ spec:
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end}}
shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }}
{{- if .Values.jwtKeys.enabled }}
initContainers:
{{- include "impress.jwtKeys.initContainer" (dict
"root" .
"image" (printf "%s:%s" ((.Values.backend.image | default dict).repository | default .Values.image.repository) ((.Values.backend.image | default dict).tag | default .Values.image.tag))
"pullPolicy" ((.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy)
"files" (list (include "impress.jwtKeys.backendPath" .))
) | nindent 8 }}
{{- end }}
containers:
{{- with .Values.backend.sidecars }}
{{- toYaml . | nindent 8 }}
@@ -44,9 +53,12 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
{{- if or $envVars .Values.jwtKeys.enabled }}
env:
{{- $envVars | indent 12 }}
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.backendEnv" . | nindent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
@@ -61,6 +73,9 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volumeMount" . | nindent 12 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
@@ -90,6 +105,9 @@ spec:
{{- end }}
restartPolicy: {{ .Values.backend.job.restartPolicy }}
volumes:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volume" . | nindent 8 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
@@ -33,6 +33,15 @@ spec:
serviceAccountName: {{ .Values.backend.serviceAccountName }}
{{- end }}
shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }}
{{- if .Values.jwtKeys.enabled }}
initContainers:
{{- include "impress.jwtKeys.initContainer" (dict
"root" .
"image" (printf "%s:%s" ((.Values.backend.image | default dict).repository | default .Values.image.repository) ((.Values.backend.image | default dict).tag | default .Values.image.tag))
"pullPolicy" ((.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy)
"files" (list (include "impress.jwtKeys.backendPath" .))
) | nindent 8 }}
{{- end }}
containers:
{{- with .Values.backend.sidecars }}
{{- toYaml . | nindent 8 }}
@@ -48,9 +57,12 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
{{- if or $envVars .Values.jwtKeys.enabled }}
env:
{{- $envVars | indent 12 }}
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.backendEnv" . | nindent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
@@ -65,6 +77,9 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volumeMount" . | nindent 12 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
@@ -94,6 +109,9 @@ spec:
{{- end }}
restartPolicy: {{ .Values.backend.createsuperuser.restartPolicy }}
volumes:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volume" . | nindent 8 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
@@ -33,6 +33,15 @@ spec:
serviceAccountName: {{ .Values.backend.serviceAccountName }}
{{- end }}
shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }}
{{- if .Values.jwtKeys.enabled }}
initContainers:
{{- include "impress.jwtKeys.initContainer" (dict
"root" .
"image" (printf "%s:%s" ((.Values.backend.image | default dict).repository | default .Values.image.repository) ((.Values.backend.image | default dict).tag | default .Values.image.tag))
"pullPolicy" ((.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy)
"files" (list (include "impress.jwtKeys.backendPath" .))
) | nindent 8 }}
{{- end }}
containers:
{{- with .Values.backend.sidecars }}
{{- toYaml . | nindent 8 }}
@@ -48,9 +57,12 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
{{- if or $envVars .Values.jwtKeys.enabled }}
env:
{{- $envVars | indent 12 }}
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.backendEnv" . | nindent 12 }}
{{- end }}
{{- end }}
{{- if .Values.backend.envFrom }}
envFrom:
@@ -65,6 +77,9 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volumeMount" . | nindent 12 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
@@ -94,6 +109,9 @@ spec:
{{- end }}
restartPolicy: {{ .Values.backend.migrate.restartPolicy }}
volumes:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volume" . | nindent 8 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
@@ -34,6 +34,15 @@ spec:
serviceAccountName: {{ .Values.backend.serviceAccountName }}
{{- end }}
shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }}
{{- if .Values.jwtKeys.enabled }}
initContainers:
{{- include "impress.jwtKeys.initContainer" (dict
"root" .
"image" (printf "%s:%s" ((.Values.backend.image | default dict).repository | default .Values.image.repository) ((.Values.backend.image | default dict).tag | default .Values.image.tag))
"pullPolicy" ((.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy)
"files" (list (include "impress.jwtKeys.backendPath" .))
) | nindent 8 }}
{{- end }}
containers:
{{- with .Values.backend.sidecars }}
{{- toYaml . | nindent 8 }}
@@ -49,9 +58,12 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
{{- if or $envVars .Values.jwtKeys.enabled }}
env:
{{- $envVars | indent 12 }}
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.backendEnv" . | nindent 12 }}
{{- end }}
{{- end }}
{{- $envFrom := concat (.Values.backend.envFrom | default list) (.Values.backend.celery.envFrom | default list) }}
{{- if $envFrom }}
@@ -83,6 +95,9 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volumeMount" . | nindent 12 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
@@ -116,6 +131,9 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volume" . | nindent 8 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
@@ -46,20 +46,26 @@ spec:
- host: {{ .Values.ingressCollaborationApi.host | quote }}
http:
paths:
- path: {{ .Values.ingressCollaborationApi.path | quote }}
{{- /* one rule per route: what is not listed here is not reachable
from the outside, which is how the backend-internal routes
(reset-connections, migrate, restore-ydoc, reset-ydoc) stay
in-cluster */}}
{{- range .Values.ingressCollaborationApi.paths | default (list .Values.ingressCollaborationApi.path) }}
- path: {{ . | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: ImplementationSpecific
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ include "impress.yProvider.fullname" . }}
name: {{ include "impress.yhub.fullname" $ }}
port:
number: {{ .Values.yProvider.service.port }}
number: {{ $.Values.yhub.service.port }}
{{- else }}
serviceName: {{ include "impress.yProvider.fullname" . }}
servicePort: {{ .Values.yProvider.service.port }}
serviceName: {{ include "impress.yhub.fullname" $ }}
servicePort: {{ $.Values.yhub.service.port }}
{{- end }}
{{- end }}
{{- with .Values.ingressCollaborationApi.customBackends }}
{{- toYaml . | nindent 10 }}
{{- end }}
@@ -53,12 +53,12 @@ spec:
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ include "impress.yProvider.fullname" . }}
name: {{ include "impress.yhub.fullname" . }}
port:
number: {{ .Values.yProvider.service.port }}
number: {{ .Values.yhub.service.port }}
{{- else }}
serviceName: {{ include "impress.yProvider.fullname" . }}
servicePort: {{ .Values.yProvider.service.port }}
serviceName: {{ include "impress.yhub.fullname" . }}
servicePort: {{ .Values.yhub.service.port }}
{{- end }}
{{- with .Values.ingressCollaborationWS.customBackends }}
{{- toYaml . | nindent 10 }}
@@ -0,0 +1,124 @@
{{- if .Values.jwtKeys.enabled -}}
{{- $fullName := include "impress.fullname" . -}}
{{- $component := "jwt-keys" -}}
# Generates the RSA keys the services sign the calls they make to each other
# with: one for the backend (the tokens Django issues to the collaboration
# server and the converter), one for the collaboration server (the calls it
# makes back to Django). Only the private halves are written — each service
# publishes the public half of its own key on its JWKS endpoint, where the
# other one reads it, so no key is ever copied from one side to the other.
#
# Idempotent, and deliberately so: an existing key is kept, which is what makes
# it safe to re-run on every sync. Rolling a key is deleting it from the volume
# and letting the next run generate a new one — both services follow, they pick
# the verification key by its "kid" and re-fetch the set when they meet a new
# one.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ $fullName }}-jwt-keys
namespace: {{ .Release.Namespace | quote }}
annotations:
argocd.argoproj.io/sync-options: Replace=true,Force=true
# after the claim, before anything reading the keys
argocd.argoproj.io/sync-wave: "-2"
{{- with .Values.jwtKeys.job.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
spec:
ttlSecondsAfterFinished: {{ .Values.jwtKeys.job.ttlSecondsAfterFinished }}
backoffLimit: {{ .Values.jwtKeys.job.backoffLimit }}
template:
metadata:
{{- with .Values.jwtKeys.job.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }}
spec:
{{- if $.Values.image.credentials }}
imagePullSecrets:
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end}}
{{- if .Values.jwtKeys.job.serviceAccountName }}
serviceAccountName: {{ .Values.jwtKeys.job.serviceAccountName }}
{{- end }}
# the volume of a freshly provisioned claim belongs to root:root, and the
# container is run un-privileged below: without an fsGroup it has nowhere
# to write the keys
{{- with .Values.jwtKeys.job.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
restartPolicy: {{ .Values.jwtKeys.job.restartPolicy }}
containers:
- name: jwt-keys
image: "{{ (.Values.jwtKeys.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.jwtKeys.image | default dict).tag | default .Values.image.tag }}"
imagePullPolicy: {{ (.Values.jwtKeys.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
{{- with .Values.jwtKeys.job.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if not .Values.jwtKeys.job.command }}
# `command` rather than `args`: the image entrypoint is openssl itself.
# The keys are the ones `bin/generate-jwt-private-key.sh` writes for
# the compose stack, same command — PKCS#8 RSA, the format both the
# backend and the collaboration server read.
command:
- /bin/sh
- -c
- |
set -eu
for path in {{ include "impress.jwtKeys.backendPath" . | quote }} {{ include "impress.jwtKeys.yhubPath" . | quote }}; do
if [ -f "$path" ]; then
echo "$path already exists, keeping it"
continue
fi
openssl genpkey -algorithm RSA \
-pkeyopt rsa_keygen_bits:{{ .Values.jwtKeys.keySize }} \
-out "$path.tmp"
# read-only for everyone: the services read it under user ids
# of their own, and nothing is ever to write it again
chmod 444 "$path.tmp"
# a rename, so a reader never sees a half written key
mv "$path.tmp" "$path"
echo "$path generated"
done
{{- end }}
{{- with .Values.jwtKeys.job.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.jwtKeys.job.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.jwtKeys.job.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
# the one place the volume is mounted writable
- name: jwt-keys
mountPath: {{ .Values.jwtKeys.mountPath }}
{{- with .Values.jwtKeys.job.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.jwtKeys.job.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.jwtKeys.job.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: jwt-keys
persistentVolumeClaim:
claimName: {{ include "impress.jwtKeys.claimName" . }}
{{- end }}
@@ -0,0 +1,32 @@
{{- if and .Values.jwtKeys.enabled (not .Values.jwtKeys.persistence.existingClaim) -}}
{{- $component := "jwt-keys" -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "impress.jwtKeys.claimName" . }}
namespace: {{ .Release.Namespace | quote }}
annotations:
# the keys outlive the release: uninstalling and installing again keeps the
# identity of the services rather than silently rolling both keys. Delete
# the claim by hand to roll them.
helm.sh/resource-policy: keep
# before everything else, the generating job included
argocd.argoproj.io/sync-wave: "-3"
{{- with .Values.jwtKeys.persistence.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
spec:
# every service mounts it read-only, so all but the generating job read it at
# the same time — on more than one node as soon as a deployment has replicas
# spread around, which is what makes ReadWriteMany the default here
accessModes:
{{- toYaml .Values.jwtKeys.persistence.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.jwtKeys.persistence.size | quote }}
{{- if .Values.jwtKeys.persistence.storageClass }}
storageClassName: {{ .Values.jwtKeys.persistence.storageClass | quote }}
{{- end }}
{{- end }}
@@ -0,0 +1,187 @@
{{- if .Values.yhub.enabled -}}
{{- $envVars := include "impress.common.env" (list . .Values.yhub) -}}
{{- $fullName := include "impress.yhub.fullname" . -}}
{{- $component := "yhub" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
annotations:
{{- with .Values.yhub.dpAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
spec:
replicas: {{ .Values.yhub.replicas }}
selector:
matchLabels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.yhub.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }}
spec:
{{- if $.Values.image.credentials }}
imagePullSecrets:
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end}}
{{- if .Values.yhub.serviceAccountName }}
serviceAccountName: {{ .Values.yhub.serviceAccountName }}
{{- end }}
shareProcessNamespace: {{ .Values.yhub.shareProcessNamespace }}
# a websocket connection is dropped when the pod goes away, and the client
# reconnects to another one — but the updates it sent are only in redis
# until a worker persists them, so leave the embedded worker time to drain
terminationGracePeriodSeconds: {{ .Values.yhub.terminationGracePeriodSeconds }}
{{- if .Values.jwtKeys.enabled }}
initContainers:
{{- include "impress.jwtKeys.initContainer" (dict
"root" .
"image" (printf "%s:%s" ((.Values.yhub.image | default dict).repository | default .Values.image.repository) ((.Values.yhub.image | default dict).tag | default .Values.image.tag))
"pullPolicy" ((.Values.yhub.image | default dict).pullPolicy | default .Values.image.pullPolicy)
"files" (list (include "impress.jwtKeys.yhubPath" .))
) | nindent 8 }}
{{- end }}
containers:
{{- with .Values.yhub.sidecars }}
{{- toYaml . | nindent 8 }}
{{- end }}
- name: {{ .Chart.Name }}
image: "{{ (.Values.yhub.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.yhub.image | default dict).tag | default .Values.image.tag }}"
imagePullPolicy: {{ (.Values.yhub.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
{{- with .Values.yhub.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.yhub.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $envVars .Values.jwtKeys.enabled }}
env:
{{- $envVars | indent 12 }}
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.yhubEnv" . | nindent 12 }}
{{- end }}
{{- end }}
{{- if .Values.yhub.envFrom }}
envFrom:
{{- toYaml .Values.yhub.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.yhub.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.yhub.service.targetPort }}
protocol: TCP
{{- if .Values.yhub.probes.liveness }}
livenessProbe:
{{- include "impress.probes.abstract" (merge .Values.yhub.probes.liveness (dict "targetPort" .Values.yhub.service.targetPort )) | nindent 12 }}
{{- end }}
{{- if .Values.yhub.probes.readiness }}
readinessProbe:
{{- include "impress.probes.abstract" (merge .Values.yhub.probes.readiness (dict "targetPort" .Values.yhub.service.targetPort )) | nindent 12 }}
{{- end }}
{{- if .Values.yhub.probes.startup }}
startupProbe:
{{- include "impress.probes.abstract" (merge .Values.yhub.probes.startup (dict "targetPort" .Values.yhub.service.targetPort )) | nindent 12 }}
{{- end }}
{{- with .Values.yhub.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volumeMount" . | nindent 12 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
subPath: content
{{- end }}
{{- range $name, $volume := .Values.yhub.persistence }}
- name: "{{ $name }}"
mountPath: "{{ $volume.mountPath }}"
{{- end }}
{{- range .Values.yhub.extraVolumeMounts }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
subPath: {{ .subPath | default "" }}
readOnly: {{ .readOnly }}
{{- end }}
{{- with .Values.yhub.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.yhub.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.yhub.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.jwtKeys.enabled }}
{{- include "impress.jwtKeys.volume" . | nindent 8 }}
{{- end }}
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
name: "{{ include "impress.fullname" $ }}-files-{{ $index }}"
{{- end }}
{{- range $name, $volume := .Values.yhub.persistence }}
- name: "{{ $name }}"
{{- if eq $volume.type "emptyDir" }}
emptyDir: {}
{{- else }}
persistentVolumeClaim:
claimName: "{{ $fullName }}-{{ $name }}"
{{- end }}
{{- end }}
{{- range .Values.yhub.extraVolumes }}
- name: {{ .name }}
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .secret }}
secret:
{{ toYaml .secret | nindent 12 }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
{{- else if .csi }}
csi:
{{- toYaml .csi | nindent 12 }}
{{- else if .configMap }}
configMap:
{{- toYaml .configMap | nindent 12 }}
{{- else if .emptyDir }}
emptyDir:
{{- toYaml .emptyDir | nindent 12 }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.yhub.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
{{- end }}
@@ -0,0 +1,124 @@
{{- if and .Values.yhub.enabled .Values.yhub.initDb.enabled -}}
{{- $envVars := include "impress.common.env" (list . .Values.yhub) -}}
{{- $fullName := include "impress.yhub.fullname" . -}}
{{- $component := "yhub" -}}
# yhub never runs DDL from the server or the worker: the schema is created by
# the script it ships, which creates the database when it is missing and every
# table the installed version needs. It is idempotent, and it has to run again
# on every yhub upgrade that adds a table — the counterpart of the backend
# migrate job, hence the same Replace=true so a re-sync re-runs it.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ $fullName }}-init-db
namespace: {{ .Release.Namespace | quote }}
annotations:
argocd.argoproj.io/sync-options: Replace=true,Force=true
# the server answers "relation ... does not exist" on every document read
# until this has run, so it goes in an earlier wave than the deployment
argocd.argoproj.io/sync-wave: "-1"
{{- with .Values.yhub.initDbJobAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
spec:
ttlSecondsAfterFinished: {{ .Values.yhub.jobs.ttlSecondsAfterFinished }}
backoffLimit: {{ .Values.yhub.jobs.backoffLimit }}
template:
metadata:
annotations:
{{- with .Values.yhub.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }}
spec:
{{- if $.Values.image.credentials }}
imagePullSecrets:
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end}}
{{- if .Values.yhub.serviceAccountName }}
serviceAccountName: {{ .Values.yhub.serviceAccountName }}
{{- end }}
shareProcessNamespace: {{ .Values.yhub.shareProcessNamespace }}
containers:
- name: {{ .Chart.Name }}
image: "{{ (.Values.yhub.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.yhub.image | default dict).tag | default .Values.image.tag }}"
imagePullPolicy: {{ (.Values.yhub.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
{{- with .Values.yhub.initDb.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- $envVars | indent 12 }}
{{- end }}
{{- if .Values.yhub.envFrom }}
envFrom:
{{- toYaml .Values.yhub.envFrom | nindent 12 }}
{{- end }}
{{- with .Values.yhub.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.yhub.initDb.resources | default .Values.yhub.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
subPath: content
{{- end }}
{{- range .Values.yhub.extraVolumeMounts }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
subPath: {{ .subPath | default "" }}
readOnly: {{ .readOnly }}
{{- end }}
{{- with .Values.yhub.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.yhub.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.yhub.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
restartPolicy: {{ .Values.yhub.initDb.restartPolicy }}
volumes:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
name: "{{ include "impress.fullname" $ }}-files-{{ $index }}"
{{- end }}
{{- range .Values.yhub.extraVolumes }}
- name: {{ .name }}
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .secret }}
secret:
{{ toYaml .secret | nindent 12 }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
{{- else if .csi }}
csi:
{{- toYaml .csi | nindent 12 }}
{{- else if .configMap }}
configMap:
{{- toYaml .configMap | nindent 12 }}
{{- else if .emptyDir }}
emptyDir:
{{- toYaml .emptyDir | nindent 12 }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}
{{- end }}
+22
View File
@@ -0,0 +1,22 @@
{{- if .Values.yhub.enabled -}}
{{- $fullName := include "impress.yhub.fullname" . -}}
{{- $component := "yhub" -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
annotations:
{{- toYaml $.Values.yhub.service.annotations | nindent 4 }}
spec:
type: {{ .Values.yhub.service.type }}
ports:
- port: {{ .Values.yhub.service.port }}
targetPort: {{ .Values.yhub.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }}
{{- end }}
@@ -7,7 +7,7 @@ metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
annotations:
{{- with .Values.backend.dpAnnotations }}
{{- with .Values.yProvider.dpAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
@@ -1,189 +0,0 @@
{{ if .Values.yProvider.converter.enabled -}}
{{- $yProvider := .Values.yProvider -}}
{{- $converter := .Values.yProvider.converter -}}
{{- $service := mergeOverwrite (dict) (default dict $yProvider.service) (default dict $converter.service) -}}
{{- $image := mergeOverwrite (dict) (default dict $yProvider.image) (default dict $converter.image) -}}
{{- $probes := mergeOverwrite (dict) (default dict $yProvider.probes) (default dict $converter.probes) -}}
{{- $pdb := mergeOverwrite (dict) (default dict $yProvider.pdb) (default dict $converter.pdb) -}}
{{- $dpAnnotations := mergeOverwrite (dict) (default dict $yProvider.dpAnnotations) (default dict $converter.dpAnnotations) -}}
{{- $podAnnotations := mergeOverwrite (dict) (default dict $yProvider.podAnnotations) (default dict $converter.podAnnotations) -}}
{{- $replicas := default $yProvider.replicas $converter.replicas -}}
{{- $serviceAccountName := default $yProvider.serviceAccountName $converter.serviceAccountName -}}
{{- $shareProcessNamespace := default $yProvider.shareProcessNamespace $converter.shareProcessNamespace -}}
{{- $sidecars := default $yProvider.sidecars $converter.sidecars -}}
{{- $command := default $yProvider.command $converter.command -}}
{{- $args := default $yProvider.args $converter.args -}}
{{- $envFrom := default $yProvider.envFrom $converter.envFrom -}}
{{- $securityContext := mergeOverwrite (dict) (default dict $yProvider.securityContext) (default dict $converter.securityContext) -}}
{{- $resources := mergeOverwrite (dict) (default dict $yProvider.resources) (default dict $converter.resources) -}}
{{- $nodeSelector := mergeOverwrite (dict) (default dict $yProvider.nodeSelector) (default dict $converter.nodeSelector) -}}
{{- $affinity := mergeOverwrite (dict) (default dict $yProvider.affinity) (default dict $converter.affinity) -}}
{{- $tolerations := default $yProvider.tolerations $converter.tolerations -}}
{{- $persistence := mergeOverwrite (dict) (default dict $yProvider.persistence) (default dict $converter.persistence) -}}
{{- $extraVolumeMounts := default $yProvider.extraVolumeMounts $converter.extraVolumeMounts -}}
{{- $extraVolumes := default $yProvider.extraVolumes $converter.extraVolumes -}}
{{- $envVarsScope := dict "envVars" (mergeOverwrite (dict) (default dict $yProvider.envVars) (default dict $converter.envVars)) -}}
{{- $envVars := include "impress.common.env" (list . $envVarsScope) -}}
{{- $fullName := include "impress.yProvider.converter.fullname" . -}}
{{- $component := "yProvider-converter" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
annotations:
{{- with $dpAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
spec:
replicas: {{ $replicas }}
selector:
matchLabels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }}
template:
metadata:
annotations:
{{- with $podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }}
spec:
{{- if $.Values.image.credentials }}
imagePullSecrets:
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end}}
{{- if $serviceAccountName }}
serviceAccountName: {{ $serviceAccountName }}
{{- end }}
shareProcessNamespace: {{ $shareProcessNamespace }}
containers:
{{- with $sidecars }}
{{- toYaml . | nindent 8 }}
{{- end }}
- name: {{ .Chart.Name }}
image: "{{ $image.repository | default $.Values.image.repository }}:{{ $image.tag | default $.Values.image.tag }}"
imagePullPolicy: {{ $image.pullPolicy | default $.Values.image.pullPolicy }}
{{- with $command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if $envVars}}
env:
{{- $envVars | indent 12 }}
{{- end }}
{{- if $envFrom }}
envFrom:
{{- toYaml $envFrom | nindent 12 }}
{{- end }}
{{- with $securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ $service.targetPort }}
protocol: TCP
{{- if $probes.liveness }}
livenessProbe:
{{- include "impress.probes.abstract" (merge $probes.liveness (dict "targetPort" $service.targetPort )) | nindent 12 }}
{{- end }}
{{- if $probes.readiness }}
readinessProbe:
{{- include "impress.probes.abstract" (merge $probes.readiness (dict "targetPort" $service.targetPort )) | nindent 12 }}
{{- end }}
{{- if $probes.startup }}
startupProbe:
{{- include "impress.probes.abstract" (merge $probes.startup (dict "targetPort" $service.targetPort )) | nindent 12 }}
{{- end }}
{{- with $resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
subPath: content
{{- end }}
{{- range $name, $volume := $persistence }}
- name: "{{ $name }}"
mountPath: "{{ $volume.mountPath }}"
{{- end }}
{{- range $extraVolumeMounts }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
subPath: {{ .subPath | default "" }}
readOnly: {{ .readOnly }}
{{- end }}
{{- with $nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
name: "{{ include "impress.fullname" $ }}-files-{{ $index }}"
{{- end }}
{{- range $name, $volume := $persistence }}
- name: "{{ $name }}"
{{- if eq $volume.type "emptyDir" }}
emptyDir: {}
{{- else }}
persistentVolumeClaim:
claimName: "{{ $fullName }}-{{ $name }}"
{{- end }}
{{- end }}
{{- range $extraVolumes }}
- name: {{ .name }}
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .secret }}
secret:
{{ toYaml .secret | nindent 12 }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
{{- else if .csi }}
csi:
{{- toYaml .csi | nindent 12 }}
{{- else if .configMap }}
configMap:
{{- toYaml .configMap | nindent 12 }}
{{- else if .emptyDir }}
emptyDir:
{{- toYaml .emptyDir | nindent 12 }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if $pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
{{ end }}
@@ -1,25 +0,0 @@
{{ if .Values.yProvider.converter.enabled -}}
{{- $yProvider := .Values.yProvider -}}
{{- $converter := .Values.yProvider.converter -}}
{{- $service := mergeOverwrite (dict) (default dict $yProvider.service) (default dict $converter.service) -}}
{{- $fullName := include "impress.yProvider.converter.fullname" . -}}
{{- $component := "yProvider-converter" -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
annotations:
{{- toYaml $service.annotations | nindent 4 }}
spec:
type: {{ $service.type }}
ports:
- port: {{ $service.port }}
targetPort: {{ $service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }}
{{ end -}}
+324 -77
View File
@@ -78,12 +78,14 @@ ingressCollaborationWS:
## @param ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/enable-websocket
## @param ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/proxy-read-timeout
## @param ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/proxy-send-timeout
## @param ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/upstream-hash-by
##
## No upstream-hash-by: yhub passes updates between its replicas through
## redis, so two clients editing the same document may land on different
## pods — where the y-provider it replaces needed a room to stay on one.
annotations:
nginx.ingress.kubernetes.io/enable-websocket: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "86400"
nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"
nginx.ingress.kubernetes.io/upstream-hash-by: $arg_room
## @param ingressRedirects.enabled whether to enable the Ingress Redirects or not
## @param ingressRedirects.className IngressClass to use for the Ingress Redirects
@@ -112,7 +114,22 @@ ingressCollaborationApi:
enabled: false
className: null
host: impress.example.com
## Only used when `paths` below is empty
path: /collaboration/api/
## @param ingressCollaborationApi.paths Paths to route to the collaboration server, one rule each
##
## The routes yhub serves to browsers, guarded by the same document
## authorization as the websocket. Everything it serves that is not listed
## here stays in-cluster — `create-ydoc`, `reset-connections`, `migrate`,
## `restore-ydoc` and `reset-ydoc` are called by the backend only, and
## publishing them would put document deletion and the legacy migration one
## request away from the internet.
##
## `jwks` is public on purpose: it carries the public halves of the keys
## yhub signs with, and nothing else.
paths:
- /collaboration/ydoc/
- /collaboration/jwks/
## @param ingressCollaborationApi.hosts Additional host to configure for the Ingress
hosts: []
# - chart-example.local
@@ -129,9 +146,10 @@ ingressCollaborationApi:
## @param ingressCollaborationApi.customBackends Add custom backends to ingress
customBackends: []
## @param ingressCollaborationApi.annotations.nginx.ingress.kubernetes.io/upstream-hash-by
annotations:
nginx.ingress.kubernetes.io/upstream-hash-by: $arg_room
## @skip ingressCollaborationApi.annotations
## Same as ingressCollaborationWS: no upstream-hash-by, any yhub replica
## answers for any document.
annotations: {}
## @param ingressAdmin.enabled whether to enable the Ingress or not
## @param ingressAdmin.className IngressClass to use for the Ingress
@@ -637,7 +655,11 @@ posthog:
annotations: {}
## @section yProvider
##
## The conversion service, and nothing else since the collaboration moved to
## yhub: this deployment *is* the converter the backend calls on
## `Y_PROVIDER_API_BASE_URL`, so there is no separate converter release to
## enable anymore.
yProvider:
## @param yProvider.image.repository Repository to use to pull impress's yProvider container image
## @param yProvider.image.tag impress's yProvider container tag
@@ -647,77 +669,6 @@ yProvider:
pullPolicy: IfNotPresent
tag: "latest"
converter:
## @param yProvider.converter.enabled Enable the yProvider converter deployment and service
enabled: false
## @param yProvider.converter.replicas Amount of yProvider replicas
replicas: 3
## @param yProvider.converter.resources Resource requirements for the yProvider container
resources: {}
## @param yProvider.converter.service.type yProvider converter Service type
## @param yProvider.converter.service.port yProvider converter Service listening port
## @param yProvider.converter.service.targetPort yProvider converter container listening port
## @param yProvider.converter.service.annotations Annotations to add to the yProvider converter Service
service: {}
## @param yProvider.converter.command Override the yProvider converter container command
command: []
## @param yProvider.converter.args Override the yProvider converter container args
args: []
## @param yProvider.converter.shareProcessNamespace Enable share process namespace between containers
shareProcessNamespace: false
## @param yProvider.converter.sidecars Add sidecars containers to yProvider converter deployment
sidecars: []
## @skip yProvider.converter.securityContext
securityContext: {}
## @skip yProvider.converter.envVars
envVars: {}
## @skip yProvider.converter.envFrom
envFrom: []
## @param yProvider.converter.podAnnotations Annotations to add to the yProvider converter Pod
podAnnotations: {}
## @param yProvider.converter.dpAnnotations Annotations to add to the yProvider converter Deployment
dpAnnotations: {}
## @skip yProvider.converter.probes
probes: {}
## @param yProvider.converter.nodeSelector Node selector for the yProvider converter Pod
nodeSelector: {}
## @param yProvider.converter.tolerations Tolerations for the yProvider converter Pod
tolerations: []
## @param yProvider.converter.affinity Affinity for the yProvider converter Pod
affinity: {}
## @param yProvider.converter.persistence Additional volumes to create and mount on the yProvider converter
persistence: {}
## @param yProvider.converter.extraVolumeMounts Additional volumes to mount on the yProvider converter
extraVolumeMounts: []
## @param yProvider.converter.extraVolumes Additional volumes to mount on the yProvider converter
extraVolumes: []
## @param yProvider.converter.pdb.enabled Enable pdb on yProvider converter
pdb:
enabled: true
## @param yProvider.converter.serviceAccountName Optional service account name to use for yProvider converter pods
serviceAccountName: null
## @param yProvider.command Override the yProvider container command
command: []
@@ -828,6 +779,302 @@ yProvider:
## @param yProvider.serviceAccountName Optional service account name to use for yProvider pods
serviceAccountName: null
## @section JWT signing keys
##
## The services do not share a secret: each signs the calls it makes to the
## others with an RSA key of its own and publishes the public half on its JWKS
## endpoint, where the others read it. Enabling this generates those keys on
## the cluster — a job writes them once into a volume every service mounts
## read-only, and keeps them on the next run — and points the backend and the
## collaboration server at them. Nothing is templated into a manifest, stored
## in a values file or read back from the kubernetes API.
##
## The volume is mounted by every backend pod and by the collaboration server,
## which are spread over several nodes as soon as they have replicas: the
## storage class backing it must support ReadWriteMany. On a single node
## cluster, `accessModes: [ReadWriteOnce]` does just as well.
##
## Leave it disabled to keep providing the keys yourself, through
## `backend.envVars.JWT_PRIVATE_KEY_FILE` and
## `yhub.envVars.YHUB_JWT_PRIVATE_KEY_FILE` and volumes of your own — both are
## left untouched when they are set by hand, enabled or not.
jwtKeys:
## @param jwtKeys.enabled Generate the JWT signing keys of the services on the cluster
enabled: false
## @param jwtKeys.mountPath Path the keys are mounted at, in every service reading them
mountPath: /data/jwt
## @param jwtKeys.backendKeyFilename Name of the key signing the tokens the backend issues
backendKeyFilename: private.pem
## @param jwtKeys.yhubKeyFilename Name of the key signing the calls the collaboration server makes to the backend
yhubKeyFilename: yhub-private.pem
## @param jwtKeys.keySize Size, in bits, of the generated RSA keys
keySize: 2048
## @param jwtKeys.image.repository Repository to use to pull the image generating the keys
## @param jwtKeys.image.tag Tag of the image generating the keys
## @param jwtKeys.image.pullPolicy Pull policy of the image generating the keys
##
## openssl and a shell, nothing else. Its entrypoint is openssl itself, which
## the job replaces by the script generating both keys.
image:
repository: alpine/openssl
pullPolicy: IfNotPresent
tag: "3.5.7"
## @param jwtKeys.securityContext.allowPrivilegeEscalation Whether to allow privilege escalation for the containers waiting for the keys
## @param jwtKeys.securityContext.capabilities.drop List of capabilities to drop for the containers waiting for the keys
## @param jwtKeys.securityContext.runAsNonRoot Whether to run the containers waiting for the keys as a non-root user
## @param jwtKeys.securityContext.seccompProfile.type Seccomp profile type for the containers waiting for the keys
##
## Applies to the init containers waiting for the keys, which run the image of
## the service they belong to, under the user that image declares.
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
## @param jwtKeys.persistence.existingClaim Claim holding the keys, generated in a claim of the chart's own when empty
## @param jwtKeys.persistence.size Size of the claim holding the keys, two RSA keys and nothing else
## @param jwtKeys.persistence.storageClass Storage class of the claim holding the keys, the cluster default when empty
## @param jwtKeys.persistence.accessModes Access modes of the claim holding the keys
## @param jwtKeys.persistence.annotations Annotations to add to the claim holding the keys
persistence:
existingClaim: null
size: 8Mi
storageClass: null
accessModes:
- ReadWriteMany
annotations: {}
## @param jwtKeys.job.podSecurityContext.fsGroup Group given ownership of the volume, so the generating container can write the keys in it
## @param jwtKeys.job.securityContext.allowPrivilegeEscalation Whether to allow privilege escalation for the generating container
## @param jwtKeys.job.securityContext.capabilities.drop List of capabilities to drop for the generating container
## @param jwtKeys.job.securityContext.runAsNonRoot Whether to run the generating container as a non-root user
## @param jwtKeys.job.securityContext.runAsUser User the generating container runs as, the openssl image declaring none
## @param jwtKeys.job.securityContext.runAsGroup Group the generating container runs as
## @param jwtKeys.job.securityContext.seccompProfile.type Seccomp profile type for the generating container
## @param jwtKeys.job.restartPolicy Restart policy of the generating job
## @param jwtKeys.job.backoffLimit Numbers of generating job retries
## @param jwtKeys.job.ttlSecondsAfterFinished Period to wait before removing the generating job
## @param jwtKeys.job.command Override the command generating the keys
## @param jwtKeys.job.annotations Annotations to add to the generating job
## @param jwtKeys.job.podAnnotations Annotations to add to the generating job Pod
## @param jwtKeys.job.resources Resource requirements for the generating container
## @param jwtKeys.job.nodeSelector Node selector for the generating job Pod
## @param jwtKeys.job.tolerations Tolerations for the generating job Pod
## @param jwtKeys.job.affinity Affinity for the generating job Pod
## @param jwtKeys.job.serviceAccountName Optional service account name to use for the generating job Pod
## @skip jwtKeys.job.env
job:
# the volume of a freshly provisioned claim belongs to root:root, so the
# group below is what lets the un-privileged user underneath write the keys
podSecurityContext:
fsGroup: 1000
# the openssl image runs as root and declares no user of its own, so this
# one is named here rather than inherited — kubernetes refuses to start a
# container asking for runAsNonRoot without knowing which user to run as
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
seccompProfile:
type: RuntimeDefault
restartPolicy: Never
backoffLimit: 2
ttlSecondsAfterFinished: 30
command: []
annotations: {}
podAnnotations: {}
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}
serviceAccountName: null
env: []
## @section yhub
##
## The collaboration server: it serves the whole /collaboration/ prefix, the
## websocket included, and replaces the y-provider on that role. It keeps the
## live state of a document in redis/valkey and persists it to its own
## PostgreSQL database, so it needs both — set `yhub.envVars.REDIS` and
## `yhub.envVars.POSTGRES`, there is nothing sensible to default them to.
## Disabling it sends the /collaboration/ ingresses back to the y-provider.
yhub:
## @param yhub.enabled Enable the yhub collaboration server, its service and its init-db job
enabled: true
## @param yhub.image.repository Repository to use to pull the yhub container image
## @param yhub.image.tag yhub container tag
## @param yhub.image.pullPolicy yhub container image pull policy
image:
repository: lasuite/impress-yhub
pullPolicy: IfNotPresent
tag: "latest"
## @param yhub.command Override the yhub container command
command: []
## @param yhub.args Override the yhub container args
args: []
## @param yhub.replicas Amount of yhub replicas
## Every replica also runs a worker (redis consumer groups hand each task to
## one of them), and clients editing the same document need not land on the
## same pod: updates travel through redis.
replicas: 3
## @param yhub.shareProcessNamespace Enable share process namespace between containers
shareProcessNamespace: false
## @param yhub.sidecars Add sidecars containers to yhub deployment
sidecars: []
## @param yhub.terminationGracePeriodSeconds Grace period given to a yhub pod to drain before it is killed
terminationGracePeriodSeconds: 60
## @param yhub.securityContext.allowPrivilegeEscalation Whether to allow privilege escalation for the yhub container
## @param yhub.securityContext.capabilities.drop List of capabilities to drop for the yhub container
## @param yhub.securityContext.runAsNonRoot Whether to run the yhub container as a non-root user
## @param yhub.securityContext.seccompProfile.type Seccomp profile type for the yhub container
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
## @param yhub.envVars Configure yhub container environment variables
## @extra yhub.envVars.REDIS Required, redis/valkey url holding the live document state (e.g. redis://valkey:6379/0)
## @extra yhub.envVars.POSTGRES Required, url of the yhub database, created by the init-db job (e.g. postgres://user:pass@postgres:5432/yhub)
## @extra yhub.envVars.REDIS_PREFIX Namespace of the redis keys, when the instance is shared (default: yhub)
## @extra yhub.envVars.COLLABORATION_BACKEND_BASE_URL Base url of the Docs backend, which yhub asks about users and document access rights
## @extra yhub.envVars.COLLABORATION_SERVER_ORIGIN Comma separated list of the origins allowed to open a websocket
## @extra yhub.envVars.YHUB_JWT_PRIVATE_KEY_FILE Path to the RSA private key (PEM) yhub signs its calls to the backend with, mounted from a secret
## @extra yhub.envVars.SOFT_MIGRATION Set to "true" to seed rooms from the legacy Django/S3 document store on first access
## @extra yhub.envVars.BY_VALUE Example environment variable by setting value directly
## @extra yhub.envVars.FROM_CONFIGMAP.configMapKeyRef.name Name of a ConfigMap when configuring env vars from a ConfigMap
## @extra yhub.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap
## @extra yhub.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret
## @extra yhub.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret
## @skip yhub.envVars
envVars:
<<: *commonEnvVars
## @skip yhub.envFrom List of environment variables taken from Secrets or configMaps
envFrom: []
# envFrom:
# - secret:
# name: super-secret-user-credentials
# - configMapRef:
# name: my-environment-variables
## @param yhub.podAnnotations Annotations to add to the yhub Pod
podAnnotations: {}
## @param yhub.dpAnnotations Annotations to add to the yhub Deployment
dpAnnotations: {}
## @param yhub.initDbJobAnnotations Annotations for the yhub init-db job
initDbJobAnnotations: {}
## @param yhub.jobs.ttlSecondsAfterFinished Period to wait before removing the init-db job
## @param yhub.jobs.backoffLimit Numbers of init-db job retries
jobs:
ttlSecondsAfterFinished: 30
backoffLimit: 2
## @param yhub.initDb.enabled Run the job creating and upgrading the yhub schema
## @param yhub.initDb.command Command creating and upgrading the yhub schema, what `npm run init-db` runs
## @param yhub.initDb.restartPolicy Restart policy of the init-db job
## @skip yhub.initDb.resources Resource requirements for the init-db container, defaults to yhub.resources
initDb:
enabled: true
command:
- "node"
- "node_modules/@y/hub/bin/init-db.js"
restartPolicy: Never
resources: {}
## @param yhub.service.type yhub Service type
## @param yhub.service.port yhub Service listening port
## @param yhub.service.targetPort yhub container listening port
## @param yhub.service.annotations Annotations to add to the yhub Service
service:
type: ClusterIP
port: 443
targetPort: 3002
annotations: {}
## @param yhub.probes.liveness.path Configure path for yhub HTTP liveness probe
## @param yhub.probes.liveness.initialDelaySeconds Configure initial delay for yhub liveness probe
## @param yhub.probes.readiness.path Configure path for yhub HTTP readiness probe
## @param yhub.probes.readiness.initialDelaySeconds Configure initial delay for yhub readiness probe
## @extra yhub.probes.liveness.targetPort Configure port for yhub HTTP liveness probe
## @extra yhub.probes.liveness.timeoutSeconds Configure timeout for yhub liveness probe
## @extra yhub.probes.readiness.targetPort Configure port for yhub HTTP readiness probe
## @extra yhub.probes.readiness.timeoutSeconds Configure timeout for yhub readiness probe
## @extra yhub.probes.startup.path Configure path for yhub HTTP startup probe
## @extra yhub.probes.startup.targetPort Configure port for yhub HTTP startup probe
## @extra yhub.probes.startup.initialDelaySeconds Configure initial delay for yhub startup probe
## @extra yhub.probes.startup.timeoutSeconds Configure timeout for yhub startup probe
##
## The JWKS route is the only one yhub serves unauthenticated: it answers 200
## with the public keys it signs its calls to the backend with (an empty set
## when no key is configured), and reads neither redis nor postgres.
probes:
liveness:
path: /collaboration/jwks/v1
initialDelaySeconds: 10
readiness:
path: /collaboration/jwks/v1
initialDelaySeconds: 5
## @param yhub.resources Resource requirements for the yhub container
resources: {}
## @param yhub.nodeSelector Node selector for the yhub Pod
nodeSelector: {}
## @param yhub.tolerations Tolerations for the yhub Pod
tolerations: []
## @param yhub.affinity Affinity for the yhub Pod
affinity: {}
## @param yhub.persistence Additional volumes to create and mount on the yhub. Used for debugging purposes
## @extra yhub.persistence.volume-name.size Size of the additional volume
## @extra yhub.persistence.volume-name.type Type of the additional volume, persistentVolumeClaim or emptyDir
## @extra yhub.persistence.volume-name.mountPath Path where the volume should be mounted to
persistence: {}
## @param yhub.extraVolumeMounts Additional volumes to mount on the yhub. Mounted on the init-db job too
extraVolumeMounts: []
## @param yhub.extraVolumes Additional volumes to mount on the yhub. Mounted on the init-db job too
extraVolumes: []
## @param yhub.pdb.enabled Enable pdb on yhub
pdb:
enabled: true
## @param yhub.serviceAccountName Optional service account name to use for yhub pods
serviceAccountName: null
## @section docSpec
docSpec:
## @param docSpec.enabled Enable docSpec deployment
+21 -3
View File
@@ -3,7 +3,9 @@ FROM node:22-trixie AS base
WORKDIR /app
COPY package.json package-lock.json ./
# built from the repository root, like every other image here — the entrypoint
# below lives outside this directory
COPY ./src/yhub-server/package.json ./src/yhub-server/package-lock.json ./
# ---- Development image ----
@@ -16,7 +18,7 @@ RUN npm ci
# server.js, migration.js, env.js — glob so a new module cannot be forgotten.
# compose bind-mounts the sources over /app on top of this copy, so an edit on
# the host is seen immediately; the copy keeps the image usable on its own.
COPY *.js ./
COPY ./src/yhub-server/*.js ./
EXPOSE 3002
@@ -32,8 +34,24 @@ FROM base AS yhub
RUN npm ci --omit=dev
COPY *.js ./
COPY ./src/yhub-server/*.js ./
EXPOSE 3002
# Same entrypoint as the other services: it gives the container user an entry in
# /etc/passwd, which an arbitrary uid (kubernetes runAsUser) does not have. The
# group needs the same rights as the owner on /etc/passwd for it to write there.
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
RUN chmod g=u /etc/passwd
# Un-privileged user running the application. The server writes nothing outside
# stdout, so it needs no home and no writable path. Defaulted, unlike the other
# images of this repository: the helm chart runs the pod with runAsNonRoot, and
# a build that forgot the argument would produce an image kubernetes refuses to
# start.
ARG DOCKER_USER=1000
USER ${DOCKER_USER}
ENTRYPOINT [ "/usr/local/bin/entrypoint" ]
CMD ["node", "server.js"]
+9 -1
View File
@@ -85,7 +85,15 @@ repository:
`app crashed - waiting for file changes` and the next save starts the server
again,
- `yhub` — the production image: production dependencies only, `node
server.js`, sources baked in.
server.js`, sources baked in, and the un-privileged user and the entrypoint
the other services use (kubernetes runs the pod with `runAsNonRoot`).
Both are built **from the repository root**, like every other image here — the
entrypoint they share lives outside this directory:
```
docker build -f src/yhub-server/Dockerfile --target yhub .
```
nodemon rather than node's own `--watch`: the latter watches inodes, so it
stops seeing a file as soon as it is replaced by a rename — which is what `git