(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 15:35:51 +02:00
parent 2aff9da569
commit ec002aebfc
33 changed files with 1476 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
+40 -4
View File
@@ -143,6 +143,24 @@ 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 generates the backend and the
collaboration server keys with `openssl`, hands them to a secret both mount
read-only, and sets the `*_FILE` variables pointing at them. No key is
templated into a manifest or kept in a values file, and the job is the only
thing granted a write: its role may create a secret and read whether that one
exists, nothing else, and the services never call the kubernetes API.
Idempotent — an existing secret is left alone, so it re-runs on every sync,
and rolling the keys is deleting the secret and letting the next run create
it again. `jwtKeys.existingSecret` points at keys of your own instead, and
skips both the job and its rights
- ✨(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 — next to the backend migrate job, retrying while the
postgres server does not answer, since nothing in the chart creates it.
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
@@ -204,13 +222,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
+54 -1
View File
@@ -1,12 +1,65 @@
# 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 creates the two keys, once, in a secret every service mounts read-only, and points the backend and yhub at them. It generates them with `openssl` in a pod-local volume and hands them to `kubectl create secret`, so they never touch a disk, a manifest or a values file. The secret is left alone when it is already there, so the job is safe to re-run — it runs on every sync — and rolling the keys is deleting the secret and letting the next run create it again. Both sides follow: they pick the verification key by its `kid` and fetch the set again when they meet one they do not know.
The job is the only thing allowed near that secret: the chart gives it a service account whose role can `create` a secret and read whether that one exists, nothing more. The services never call the kubernetes API — they read a mounted file. The secret is not part of the release either, so uninstalling keeps the same identities; delete the secret to start over.
Deployments already holding their keys in a secret of their own point the chart at it instead, and the job and its rights are not created at all:
```yaml
jwtKeys:
enabled: true
existingSecret: my-jwt-keys # holding private.pem and yhub-private.pem
```
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
+46 -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:
@@ -159,11 +162,6 @@ frontend:
runAsNonRoot: false
yProvider:
converter:
enabled: true
replicas: 2
replicas: 1
image:
@@ -192,6 +190,47 @@ 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 into a secret both mount
# read-only.
jwtKeys:
enabled: true
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
@@ -218,7 +257,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:
@@ -137,11 +140,6 @@ frontend:
tag: *tag
yProvider:
converter:
enabled: true
replicas: 1
replicas: 1
image:
@@ -155,6 +153,30 @@ 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 into a secret both mount
# read-only.
jwtKeys:
enabled: true
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
@@ -182,7 +204,7 @@ ingressCollaborationWS:
host: {{ .Values.feature }}-docs.{{ .Values.domain }}
ingressCollaborationApi:
enabled: true
enabled: false
host: {{ .Values.feature }}-docs.{{ .Values.domain }}
ingressAdmin:
+111 -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,122 @@
| `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.existingSecret` | Secret already holding the keys, generated in a secret of the chart's own when empty | `nil` |
| `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.rbac.create` | Create the service account and the role the job needs to create the secret | `true` |
| `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.kubectlImage.repository` | Repository to use to pull the image handing the keys to the secret | `dtzar/helm-kubectl` |
| `jwtKeys.kubectlImage.tag` | Tag of the image handing the keys to the secret | `3.16.2` |
| `jwtKeys.kubectlImage.pullPolicy` | Pull policy of the image handing the keys to the secret | `IfNotPresent` |
| `jwtKeys.job.podSecurityContext` | Pod security context of the generating job | `{}` |
| `jwtKeys.job.securityContext.allowPrivilegeEscalation` | Whether to allow privilege escalation for the job containers | `false` |
| `jwtKeys.job.securityContext.capabilities.drop` | List of capabilities to drop for the job containers | `["ALL"]` |
| `jwtKeys.job.securityContext.runAsNonRoot` | Whether to run the job containers as a non-root user | `true` |
| `jwtKeys.job.securityContext.runAsUser` | User the job containers run as, their images declaring none | `1000` |
| `jwtKeys.job.securityContext.runAsGroup` | Group the job containers run as | `1000` |
| `jwtKeys.job.securityContext.seccompProfile.type` | Seccomp profile type for the job containers | `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.generateCommand` | Override the command generating the keys | `[]` |
| `jwtKeys.job.publishCommand` | Override the command creating the secret from the generated 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 job containers | `{}` |
| `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` | Service account of the generating job Pod, the one created above when empty | `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.runAsUser` | User the yhub container runs as | `1000` |
| `yhub.securityContext.runAsGroup` | Group the yhub container runs as | `1000` |
| `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` | Override the command creating and upgrading the yhub schema | `[]` |
| `yhub.initDb.retries` | How many times the schema script is retried while the postgres server does not answer | `60` |
| `yhub.initDb.retryDelaySeconds` | Seconds between two attempts | `5` |
| `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 | `[]` |
+75 -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,81 @@ 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. The jwt-keys job generates them once into a secret every service
mounts read-only, so no key is ever templated into a manifest, written in a
values file, or kept anywhere the services themselves can write.
Requires top level scope
*/}}
{{- define "impress.jwtKeys.secretName" -}}
{{- .Values.jwtKeys.existingSecret | default (printf "%s-jwt-keys" (include "impress.fullname" .)) -}}
{{- end }}
{{- define "impress.jwtKeys.serviceAccountName" -}}
{{- .Values.jwtKeys.job.serviceAccountName | 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. A pod referencing a secret that does not exist yet
stays in ContainerCreating and mounts it as soon as the job creates it, so
nothing else is needed to order the two.
Requires top level scope
*/}}
{{- define "impress.jwtKeys.volume" -}}
- name: jwt-keys
secret:
secretName: {{ include "impress.jwtKeys.secretName" . }}
# read-only for everyone, as the files the job generates are
defaultMode: 0444
{{- end }}
{{- define "impress.jwtKeys.volumeMount" -}}
- name: jwt-keys
mountPath: {{ .Values.jwtKeys.mountPath }}
readOnly: true
{{- 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
@@ -38,9 +38,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 +58,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 +78,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:
@@ -49,9 +49,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 +86,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 +122,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:
+10 -1
View File
@@ -44,9 +44,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 +64,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 +96,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:
@@ -48,9 +48,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 +68,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 +100,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:
@@ -48,9 +48,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 +68,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 +100,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:
@@ -49,9 +49,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 +86,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 +122,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,153 @@
{{- if and .Values.jwtKeys.enabled (not .Values.jwtKeys.existingSecret) -}}
{{- $fullName := include "impress.fullname" . -}}
{{- $component := "jwt-keys" -}}
{{- $secretName := include "impress.jwtKeys.secretName" . -}}
# 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.
#
# Two steps, two images: openssl writes the keys in a volume the pod throws
# away, then kubectl hands them to the secret the services mount. They only
# ever exist in that pod and in the secret.
#
# Idempotent, and deliberately so: the secret is left alone when it is already
# there, which is what makes it safe to re-run on every sync. Rolling the keys
# is deleting the secret and letting the next run create it again — both
# services follow, they pick the verification key by its "kid" and re-fetch the
# set when they meet one they do not know.
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 service account it runs as, before anything mounting the secret
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}}
serviceAccountName: {{ include "impress.jwtKeys.serviceAccountName" . }}
{{- with .Values.jwtKeys.job.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
restartPolicy: {{ .Values.jwtKeys.job.restartPolicy }}
initContainers:
- name: generate
image: "{{ .Values.jwtKeys.image.repository }}:{{ .Values.jwtKeys.image.tag }}"
imagePullPolicy: {{ .Values.jwtKeys.image.pullPolicy }}
{{- if .Values.jwtKeys.job.generateCommand }}
command:
{{- toYaml .Values.jwtKeys.job.generateCommand | nindent 12 }}
{{- else }}
# `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 name in {{ .Values.jwtKeys.backendKeyFilename | quote }} {{ .Values.jwtKeys.yhubKeyFilename | quote }}; do
openssl genpkey -algorithm RSA \
-pkeyopt rsa_keygen_bits:{{ .Values.jwtKeys.keySize }} \
-out "/keys/$name"
echo "$name generated"
done
{{- end }}
{{- with .Values.jwtKeys.job.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.jwtKeys.job.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: keys
mountPath: /keys
containers:
- name: publish
image: "{{ .Values.jwtKeys.kubectlImage.repository }}:{{ .Values.jwtKeys.kubectlImage.tag }}"
imagePullPolicy: {{ .Values.jwtKeys.kubectlImage.pullPolicy }}
{{- if .Values.jwtKeys.job.publishCommand }}
command:
{{- toYaml .Values.jwtKeys.job.publishCommand | nindent 12 }}
{{- else }}
command:
- /bin/sh
- -c
- |
set -eu
# the keys generated above are dropped on the floor when the
# secret is already there: they are new ones, and replacing the
# live pair is a decision, never a side effect of a sync
if kubectl get secret {{ $secretName | quote }} >/dev/null 2>&1; then
echo "secret {{ $secretName }} already exists, keeping the keys it holds"
exit 0
fi
kubectl create secret generic {{ $secretName | quote }} \
--from-file={{ .Values.jwtKeys.backendKeyFilename }}=/keys/{{ .Values.jwtKeys.backendKeyFilename }} \
--from-file={{ .Values.jwtKeys.yhubKeyFilename }}=/keys/{{ .Values.jwtKeys.yhubKeyFilename }}
echo "secret {{ $secretName }} created"
{{- 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:
- name: keys
mountPath: /keys
readOnly: true
{{- 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:
# the keys live here for the lifetime of this pod and nowhere else
- name: keys
emptyDir:
medium: Memory
{{- end }}
@@ -0,0 +1,63 @@
{{- if and .Values.jwtKeys.enabled .Values.jwtKeys.rbac.create (not .Values.jwtKeys.existingSecret) -}}
{{- $component := "jwt-keys" -}}
{{- $name := include "impress.jwtKeys.serviceAccountName" . -}}
# The generating job is the only thing in this release allowed to touch the
# secret holding the keys, and all it is allowed to do is read whether it
# exists and create it — not read its content back, not replace it, not delete
# it. The services themselves get the keys through a volume, so they need no
# access to the kubernetes API at all.
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ $name }}
namespace: {{ .Release.Namespace | quote }}
annotations:
# The same wave as the job that runs under it. An earlier wave looks safer
# and is not: argocd only moves to the next wave once the current one is
# healthy, and a ServiceAccount has no health of its own to report. Within
# a wave it applies by kind, and accounts, roles and bindings all come
# before jobs — which is the ordering actually needed here.
argocd.argoproj.io/sync-wave: "-2"
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ $name }}
namespace: {{ .Release.Namespace | quote }}
annotations:
argocd.argoproj.io/sync-wave: "-2"
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
rules:
# creating cannot be restricted to a name, kubernetes has no such rule
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create"]
# reading can, and is restricted to the one secret — to its existence really,
# the job never looks at what it holds
- apiGroups: [""]
resources: ["secrets"]
resourceNames:
- {{ include "impress.jwtKeys.secretName" . | quote }}
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ $name }}
namespace: {{ .Release.Namespace | quote }}
annotations:
argocd.argoproj.io/sync-wave: "-2"
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ $name }}
subjects:
- kind: ServiceAccount
name: {{ $name }}
namespace: {{ .Release.Namespace | quote }}
{{- end }}
@@ -0,0 +1,178 @@
{{- 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 }}
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,148 @@
{{- 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.
#
# No sync wave, again like the backend migrate job: it runs in the default one,
# alongside it, and waits for its database the way that one waits for Django's.
# An earlier wave only moved it ahead of the postgres it needs.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ $fullName }}-init-db
namespace: {{ .Release.Namespace | quote }}
annotations:
argocd.argoproj.io/sync-options: Replace=true,Force=true
{{- 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 }}
{{- if .Values.yhub.initDb.command }}
command:
{{- toYaml .Values.yhub.initDb.command | nindent 12 }}
{{- else }}
# The script yhub ships, wrapped as `npm run init-db`, retried until
# the postgres server answers: nothing here creates it, and a chart
# sync does not wait for whatever does. Retrying the whole script
# rather than probing the port first — it is idempotent, so a run
# against a database that is up but incomplete is a no-op, and no
# postgres client has to be present in the image to ask.
command:
- /bin/sh
- -c
- |
set -u
attempt=1
until node node_modules/@y/hub/bin/init-db.js; do
if [ "$attempt" -ge {{ .Values.yhub.initDb.retries }} ]; then
echo "database still unreachable after $attempt attempts, giving up"
exit 1
fi
echo "database not ready, retrying in {{ .Values.yhub.initDb.retryDelaySeconds }}s ($attempt/{{ .Values.yhub.initDb.retries }})"
attempt=$((attempt + 1))
sleep {{ .Values.yhub.initDb.retryDelaySeconds }}
done
{{- 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 -}}
+332 -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
@@ -643,7 +661,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
@@ -653,77 +675,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: []
@@ -834,6 +785,310 @@ 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 creates them once in a secret every service mounts
## read-only, and leaves them alone on the next run — and points the backend
## and the collaboration server at them. No key is ever templated into a
## manifest or written in a values file, and only that job may create the
## secret: nothing in the release can read it back through the api.
##
## 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.existingSecret Secret already holding the keys, generated in a secret of the chart's own when empty
##
## It has to hold the two filenames below. Naming one skips the job and the
## rights it needs, the services only mount what is there.
existingSecret: null
## @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.rbac.create Create the service account and the role the job needs to create the secret
##
## Turning it off means providing `jwtKeys.job.serviceAccountName` with an
## account allowed to `create` secrets and to `get` the one named above.
rbac:
create: true
## @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.kubectlImage.repository Repository to use to pull the image handing the keys to the secret
## @param jwtKeys.kubectlImage.tag Tag of the image handing the keys to the secret
## @param jwtKeys.kubectlImage.pullPolicy Pull policy of the image handing the keys to the secret
##
## A second image because the openssl one carries no kubectl, and reaching
## the api with what it does carry (busybox wget, which cannot be told about
## the cluster ca) would mean sending the token over an unverified
## connection.
kubectlImage:
repository: dtzar/helm-kubectl
pullPolicy: IfNotPresent
tag: "3.16.2"
## @param jwtKeys.job.podSecurityContext Pod security context of the generating job
## @param jwtKeys.job.securityContext.allowPrivilegeEscalation Whether to allow privilege escalation for the job containers
## @param jwtKeys.job.securityContext.capabilities.drop List of capabilities to drop for the job containers
## @param jwtKeys.job.securityContext.runAsNonRoot Whether to run the job containers as a non-root user
## @param jwtKeys.job.securityContext.runAsUser User the job containers run as, their images declaring none
## @param jwtKeys.job.securityContext.runAsGroup Group the job containers run as
## @param jwtKeys.job.securityContext.seccompProfile.type Seccomp profile type for the job containers
## @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.generateCommand Override the command generating the keys
## @param jwtKeys.job.publishCommand Override the command creating the secret from the generated 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 job containers
## @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 Service account of the generating job Pod, the one created above when empty
## @skip jwtKeys.job.env
job:
podSecurityContext: {}
# neither image declares a user of its own, and 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
generateCommand: []
publishCommand: []
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.runAsUser User the yhub container runs as
## @param yhub.securityContext.runAsGroup Group the yhub container runs as
## @param yhub.securityContext.seccompProfile.type Seccomp profile type for the yhub container
##
## The user is named rather than left to the image: asking for runAsNonRoot
## without it is refused outright by kubernetes ("container has runAsNonRoot
## and image will run as root") on any image that declares none — which every
## yhub image built before the un-privileged user was added to its Dockerfile
## does. 1000 is the `node` user the base image already carries.
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
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 Override the command creating and upgrading the yhub schema
## @param yhub.initDb.retries How many times the schema script is retried while the postgres server does not answer
## @param yhub.initDb.retryDelaySeconds Seconds between two attempts
## @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
##
## The job runs in the default sync wave, next to the backend migrate job,
## and waits for its database the same way that one waits for Django's:
## nothing in this chart creates the postgres server, so it has to be given
## the time whatever does takes. The defaults below wait five minutes.
initDb:
enabled: true
command: []
retries: 60
retryDelaySeconds: 5
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