From f82749c902f1e6008aace7d99ed4f5a8bac8839d Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Sat, 4 Apr 2026 00:39:32 +0300 Subject: [PATCH 1/8] refactor(kubernetes): simplify chart configuration --- deployment/kubernetes/README.md | 35 ++++------- deployment/kubernetes/templates/_helpers.tpl | 48 --------------- .../kubernetes/templates/mip-config.yaml | 42 ------------- .../templates/platform-backend.yaml | 61 +++++++------------ .../kubernetes/templates/platform-ui.yaml | 16 +---- deployment/kubernetes/values.yaml | 40 +++--------- 6 files changed, 44 insertions(+), 198 deletions(-) delete mode 100644 deployment/kubernetes/templates/_helpers.tpl delete mode 100644 deployment/kubernetes/templates/mip-config.yaml diff --git a/deployment/kubernetes/README.md b/deployment/kubernetes/README.md index 62b7935..8ef8dc3 100644 --- a/deployment/kubernetes/README.md +++ b/deployment/kubernetes/README.md @@ -6,7 +6,7 @@ MIP now supports two Kubernetes infrastructure options: 1. **VM-based / microk8s clusters** – the remainder of this document (starting in the Requirements section) walks through preparing Ubuntu virtual machines and installing the stack on top of microk8s. 2. **Managed clusters** – for cloud-managed Kubernetes (AKS/EKS/GKE, etc.) follow the [mip-infra getting started guide](https://github.com/Medical-Informatics-Platform/mip-infra?tab=readme-ov-file#-getting-started) to provision the cluster and its base services. Once the cluster is available, return here for component configuration details as needed. -Choose between these modes via the Helm values: set `managed_cluster: true` (managed) or `false` (microk8s/VM). The templates react to this flag to deploy the components with the right assumptions for networking, storage, and access. +Choose between these modes via the Helm values: set `cluster.managed: true` (managed) or `false` (microk8s/VM). The templates react to this flag to deploy the components with the right assumptions for networking, storage, and access. ## Requirements ### Hardware @@ -53,18 +53,16 @@ Afterward, The dataset CSV files should be placed in their proper pathology fold ## Configuration Prior to deploying it (on a microk8s K8s cluster of one or more nodes), there are a few adjustments to make in `values.yaml`. Each top-level section controls a part of the stack: -* `cluster`: namespace, storage classes and whether the cluster provisions persistent volumes dynamically (`managed: true`). -* `network`: public hostname, protocols, and whether the UI is exposed directly or through a reverse proxy (`link`). +* `cluster`: storage classes and whether the cluster provisions persistent volumes dynamically (`managed: true`). +* `global`: shared public hostname used by the ingress and backend redirects. * `platform-ui`, `platform-backend`, `platformBackendDatabase`: container images and component specific options (including the platform-ui ingress/tls settings). -* `keycloak`: toggles the connection parameters to the external Keycloak instance (`enabled`, `host`, `protocol`, `realm`, `clientId`). +* `keycloak`: toggles the connection parameters to the external Keycloak instance (`enabled`, `host`, `protocol`, `realm`). Copy `values.yaml` to a new file (for example `my-values.yaml`) and edit it in-place. A few important knobs: ```yaml -network: - link: proxied # use "direct" when exposing the UI publicly +global: publicHost: mip.example.org - publicProtocol: https platform-ui: backend: @@ -72,35 +70,23 @@ platform-ui: port: 8080 context: services ingress: - redirectRootTo: /home # optional 302 redirect for the landing page tlsSecretName: platform-ui-tls keycloak: enabled: true host: iam.example.org - protocol: https - realm: MIP - clientId: mipfed ``` -The reachability diagram from the legacy profiles is still valid as a reference for deciding the correct `network.*` settings: +The reachability diagram from the legacy profiles is still valid as a reference for deciding the correct public URL: ![MIP Reachability Scheme](../docs/MIP_Configuration.png) -### MACHINE_MAIN_IP -This is the machine's main IP address. Generally, it's the IP address of the first NIC after the local one. -If the MIP is running on top of a VPN, you may want to put the VPN interface's IP address. -If you reach the machine through a public IP, if this IP is **NOT** directly assigned on the machine, but is using static NAT, you still **MUST** set the **INTERNAL** IP of the machine itself! +`global.publicHost` defaults to `hbpmip.link`. Override it in your custom values file or with `--set-string global.publicHost=` whenever a deployment needs a different public hostname. -### MACHINE_PUBLIC_FQDN -This is the public, fully qualified domain name of the MIP, the main URL on which you want to reach the MIP from the Internet. This may point: -* Directly on the public IP of the MIP, for a **direct** use case. It may be assigned on the machine or used in front as a static NAT -* On the public IP of the reverse-proxy server, for a **proxied** use case +`global.publicHost` must be the bare hostname served by the ingress, without `http://` or `https://`. -### MACHINE_PRIVATE_FQDN_OR_IP -This is **ONLY** used in a **proxied** use case situation. -It's actually the internal IP or address from which the reverse-proxy server "sees" (reaches) the MIP machine. +`keycloak.protocol` defaults to `https` and `keycloak.realm` defaults to `MIP`. Override them only when your external Keycloak differs from those defaults. -These three settings map directly to the `network` section in `values.yaml` (`publicHost`, `link`, `publicProtocol`). When running behind a reverse proxy also set `externalProtocol` to describe the protocol used between the proxy and the MIP pods. +If you deploy behind a reverse proxy or load balancer, forward the standard `X-Forwarded-*` headers to the backend so Spring can reconstruct the public request URL correctly. **WARNING!**: In **ANY** case, when you use an **EXTERNAL** KeyCloak service (i.e. iam.ebrains.eu), make sure that you use the correct *CLIENT_ID* and *CLIENT_SECRET* to match the MIP instance you're deploying! @@ -185,6 +171,7 @@ For a more in-depth guide on deploying Exaflow, please refer to the documentatio * Deploy (or upgrade) the Helm release with your customised values ``` microk8s helm3 upgrade --install mip \ + --namespace \ -f /opt/mip-deployment/kubernetes/my-values.yaml \ /opt/mip-deployment/kubernetes ``` diff --git a/deployment/kubernetes/templates/_helpers.tpl b/deployment/kubernetes/templates/_helpers.tpl deleted file mode 100644 index d908586..0000000 --- a/deployment/kubernetes/templates/_helpers.tpl +++ /dev/null @@ -1,48 +0,0 @@ -{{- define "mip.namespace" -}} -{{- default "default" .Values.cluster.namespace -}} -{{- end -}} - -{{- define "mip.instanceName" -}} -{{- printf "%s %s" (default "MIP" .Values.mip.displayName) (default "latest" .Values.mip.version) -}} -{{- end -}} - -{{- define "mip.instanceVersion" -}} -{{- $platformUi := default "" (index .Values "platform-ui").image.tag -}} -{{- $platformBackend := default "" (index .Values "platform-backend").image.tag -}} -{{- $exaflow := default "" .Values.engines.exaflow.image.tag -}} -{{- printf "Platform-ui: %s, Backend: %s, Exaflow: %s" $platformUi $platformBackend $exaflow -}} -{{- end -}} - -{{- define "mip.keycloak.enabled" -}} -{{- ternary true false (default true .Values.keycloak.enabled) -}} -{{- end -}} - -{{- define "mip.keycloak.protocol" -}} -{{- default "https" .Values.keycloak.protocol -}} -{{- end -}} - -{{- define "mip.keycloak.host" -}} -{{- default "" .Values.keycloak.host -}} -{{- end -}} - -{{- define "mip.keycloak.realm" -}} -{{- default "MIP" .Values.keycloak.realm -}} -{{- end -}} - -{{- define "mip.keycloak.clientId" -}} -{{- default "" .Values.keycloak.clientId -}} -{{- end -}} - -{{- define "mip.keycloak.sslRequired" -}} -{{- default "external" .Values.keycloak.sslRequired -}} -{{- end -}} - -{{- define "mip.keycloak.authUrl" -}} -{{- $protocol := include "mip.keycloak.protocol" . -}} -{{- $host := include "mip.keycloak.host" . -}} -{{- if and $protocol $host -}} -{{- printf "%s://%s/auth/" $protocol $host -}} -{{- else -}} -{{- "" -}} -{{- end -}} -{{- end -}} diff --git a/deployment/kubernetes/templates/mip-config.yaml b/deployment/kubernetes/templates/mip-config.yaml deleted file mode 100644 index 1905752..0000000 --- a/deployment/kubernetes/templates/mip-config.yaml +++ /dev/null @@ -1,42 +0,0 @@ -{{- $namespace := include "mip.namespace" . -}} -{{- $platformUi := index .Values "platform-ui" -}} -{{- $platformBackend := index .Values "platform-backend" -}} -{{- $platformUiDatacatalogProtocol := default .Values.datacatalog.protocol $platformUi.config.datacatalog.protocol -}} -{{- $platformUiDatacatalogHost := default .Values.datacatalog.host $platformUi.config.datacatalog.host -}} -{{- $matomoEnabled := ternary "true" "false" (default false $platformUi.config.matomo.enabled) -}} -{{- $proxyForwarding := ternary "true" "false" (default true .Values.keycloak.proxyAddressForwarding) -}} -{{- $authEnabled := default true .Values.keycloak.enabled -}} -{{- $authFlag := ternary "1" "0" $authEnabled -}} -apiVersion: v1 -kind: ConfigMap -metadata: - name: mip-config - namespace: {{ $namespace }} -data: - engines.exaflow.URL: {{ default "" .Values.engines.exaflow.url | quote }} - - mip.INSTANCE_NAME: {{ include "mip.instanceName" . | quote }} - mip.INSTANCE_VERSION: {{ include "mip.instanceVersion" . | quote }} - mip.LINK: {{ .Values.network.link | quote }} - mip.EXTERNAL_PROTOCOL: {{ .Values.network.externalProtocol | quote }} - mip.PUBLIC_PROTOCOL: {{ .Values.network.publicProtocol | quote }} - mip.PUBLIC_HOST: {{ .Values.network.publicHost | quote }} - mip.PLATFORM_UI_URL: {{ printf "%s://%s" .Values.network.publicProtocol .Values.network.publicHost | quote }} - - platform-backend.LOG_LEVEL: {{ $platformBackend.config.logLevel | quote }} - platform-backend.LOG_LEVEL_FRAMEWORK: {{ $platformBackend.config.logLevelFramework | quote }} - platform-backend.ALGORITHM_UPDATE_INTERVAL: {{ $platformBackend.config.algorithmUpdateInterval | quote }} - - platform-ui.ERROR_LOG_LEVEL: {{ $platformUi.config.errorLogLevel | quote }} - platform-ui.DATACATALOG_SERVER: {{ printf "%s://%s" $platformUiDatacatalogProtocol $platformUiDatacatalogHost | quote }} - platform-ui.MATOMO_ENABLED: {{ $matomoEnabled | quote }} - platform-ui.MATOMO_URL: {{ default "" $platformUi.config.matomo.url | quote }} - platform-ui.MATOMO_SITE_ID: {{ default "" $platformUi.config.matomo.siteId | quote }} - - keycloak.AUTHENTICATION: {{ $authFlag | quote }} - keycloak.HOSTNAME: {{ include "mip.keycloak.host" . | quote }} - keycloak.AUTH_URL: {{ include "mip.keycloak.authUrl" . | quote }} - keycloak.REALM: {{ include "mip.keycloak.realm" . | quote }} - keycloak.CLIENT_ID: {{ include "mip.keycloak.clientId" . | quote }} - keycloak.SSL_REQUIRED: {{ include "mip.keycloak.sslRequired" . | quote }} - keycloak.PROXY_ADDRESS_FORWARDING: {{ $proxyForwarding | quote }} diff --git a/deployment/kubernetes/templates/platform-backend.yaml b/deployment/kubernetes/templates/platform-backend.yaml index 7c9791f..752118a 100644 --- a/deployment/kubernetes/templates/platform-backend.yaml +++ b/deployment/kubernetes/templates/platform-backend.yaml @@ -1,5 +1,19 @@ -{{- $namespace := include "mip.namespace" . -}} +{{- $namespace := default "default" .Release.Namespace -}} {{- $platformBackend := index .Values "platform-backend" -}} +{{- $authEnabled := default true .Values.keycloak.enabled -}} +{{- $authFlag := ternary "1" "0" $authEnabled -}} +{{- $publicHost := default "" .Values.global.publicHost -}} +{{- $platformUiUrl := "" -}} +{{- if $publicHost -}} +{{- $platformUiUrl = printf "https://%s" $publicHost -}} +{{- end -}} +{{- $keycloakProtocol := default "https" .Values.keycloak.protocol -}} +{{- $keycloakHost := default "" .Values.keycloak.host -}} +{{- $keycloakRealm := default "MIP" .Values.keycloak.realm -}} +{{- $keycloakAuthUrl := "" -}} +{{- if and $keycloakProtocol $keycloakHost -}} +{{- $keycloakAuthUrl = printf "%s://%s/auth/" $keycloakProtocol $keycloakHost -}} +{{- end -}} {{- $localStorageClass := .Values.cluster.storageClasses.local -}} {{- $managedStorageClass := .Values.cluster.storageClasses.managed -}} {{- if not .Values.cluster.managed }} @@ -302,20 +316,11 @@ spec: mountPath: /var/log/platform-backend env: - name: LOG_LEVEL - valueFrom: - configMapKeyRef: - name: mip-config - key: platform-backend.LOG_LEVEL + value: {{ $platformBackend.config.logLevel | quote }} - name: LOG_LEVEL_FRAMEWORK - valueFrom: - configMapKeyRef: - name: mip-config - key: platform-backend.LOG_LEVEL_FRAMEWORK + value: {{ $platformBackend.config.logLevelFramework | quote }} - name: AUTHENTICATION - valueFrom: - configMapKeyRef: - name: mip-config - key: keycloak.AUTHENTICATION + value: {{ $authFlag | quote }} - name: PLATFORM_DB_URL value: jdbc:postgresql://localhost:5432/portal - name: PLATFORM_DB_SERVER @@ -331,25 +336,13 @@ spec: name: mip-secret key: platform-backend-db.PLATFORM_DB_PASSWORD - name: EXAFLOW_URL - valueFrom: - configMapKeyRef: - name: mip-config - key: engines.exaflow.URL + value: {{ default "" .Values.engines.exaflow.url | quote }} - name: PLATFORM_UI_BASE_URL - valueFrom: - configMapKeyRef: - name: mip-config - key: mip.PLATFORM_UI_URL + value: {{ $platformUiUrl | quote }} - name: KEYCLOAK_AUTH_URL - valueFrom: - configMapKeyRef: - name: mip-config - key: keycloak.AUTH_URL + value: {{ $keycloakAuthUrl | quote }} - name: KEYCLOAK_REALM - valueFrom: - configMapKeyRef: - name: mip-config - key: keycloak.REALM + value: {{ $keycloakRealm | quote }} - name: KEYCLOAK_CLIENT_ID valueFrom: secretKeyRef: @@ -360,16 +353,8 @@ spec: secretKeyRef: name: keycloak-credentials key: client-secret - # - name: KEYCLOAK_SSL_REQUIRED - # valueFrom: - # configMapKeyRef: - # name: mip-config - # key: keycloak.SSL_REQUIRED - name: ALGORITHM_UPDATE_INTERVAL - valueFrom: - configMapKeyRef: - name: mip-config - key: platform-backend.ALGORITHM_UPDATE_INTERVAL + value: {{ $platformBackend.config.algorithmUpdateInterval | quote }} --- apiVersion: v1 diff --git a/deployment/kubernetes/templates/platform-ui.yaml b/deployment/kubernetes/templates/platform-ui.yaml index c7dd3af..429faf0 100644 --- a/deployment/kubernetes/templates/platform-ui.yaml +++ b/deployment/kubernetes/templates/platform-ui.yaml @@ -1,15 +1,12 @@ -{{- $namespace := include "mip.namespace" . -}} +{{- $namespace := default "default" .Release.Namespace -}} {{- $platformUi := index .Values "platform-ui" -}} {{- $platformBackend := index .Values "platform-backend" -}} {{- $ingress := default (dict) $platformUi.ingress -}} {{- $backendHost := default "" $platformUi.backend.host -}} {{- $backendPort := default 8080 $platformUi.backend.port -}} {{- $backendContext := default "services" $platformUi.backend.context -}} -{{- $ingressEnabled := true -}} -{{- if hasKey $ingress "enabled" -}} -{{- $ingressEnabled = $ingress.enabled -}} -{{- end -}} -{{- $publicHost := default "" .Values.network.publicHost -}} +{{- $ingressEnabled := default true $ingress.enabled -}} +{{- $publicHost := default "" .Values.global.publicHost -}} --- apiVersion: apps/v1 kind: Deployment @@ -32,12 +29,6 @@ spec: ports: - containerPort: 80 env: - - name: FRONTEND_VERSION - value: {{ default "" $platformUi.image.tag | quote }} - - name: BACKEND_VERSION - value: {{ default "" $platformBackend.image.tag | quote }} - - name: EXAFLOW_VERSION - value: {{ default "" .Values.engines.exaflow.image.tag | quote }} - name: MIP_VERSION value: {{ default "" .Values.mip.version | quote }} {{- if $backendHost }} @@ -73,7 +64,6 @@ metadata: name: platform-ui-ingress namespace: {{ $namespace }} annotations: - kubernetes.io/ingress.class: {{ default "nginx" $ingress.className | quote }} {{- if $ingress.certManagerClusterIssuer }} cert-manager.io/cluster-issuer: {{ $ingress.certManagerClusterIssuer | quote }} {{- end }} diff --git a/deployment/kubernetes/values.yaml b/deployment/kubernetes/values.yaml index 6923a64..601e188 100644 --- a/deployment/kubernetes/values.yaml +++ b/deployment/kubernetes/values.yaml @@ -1,56 +1,35 @@ cluster: - namespace: default managed: true storageClasses: local: k8s-local-storage managed: ceph-corbo-cephfs -network: - externalIP: "" - link: proxied - externalProtocol: https - publicProtocol: https - publicHost: default +global: + publicHost: hbpmip.link engines: exaflow: url: "http://exaflow-controller-service:5000" - image: - repository: madgik/exaflow - tag: 1.0.0 mip: - displayName: MIP - version: 9.0.2 -datacatalog: - protocol: https - host: "" + version: 9.0.3 platform-ui: image: repository: hbpmip/platform-ui - tag: 1.0.1 + tag: 1.0.2 service: nodePort: 30080 ingress: enabled: true - className: nginx-public + className: haproxy-public certManagerClusterIssuer: letsencrypt-public tlsSecretName: platform-ui-tls - redirectRootTo: /home + redirectRootTo: '' backend: host: platform-backend-service port: 8080 context: services - config: - errorLogLevel: ERROR - datacatalog: - protocol: https - host: "" - matomo: - enabled: false - url: "" - siteId: "" platform-backend: image: repository: hbpmip/platform-backend - tag: 9.1.1 + tag: 9.1.2 config: logLevel: INFO logLevelFramework: INFO @@ -74,8 +53,3 @@ platformBackendDatabase: keycloak: enabled: true host: iam.ebrains.eu - protocol: https - realm: MIP - clientId: mipfedqa - sslRequired: external - proxyAddressForwarding: true From e0d0883d4fdef1e9a4e73d9617189ae20282148c Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Sat, 4 Apr 2026 00:40:05 +0300 Subject: [PATCH 2/8] chore(dev): align compose image versions --- .gitignore | 3 ++- deployment/dev/.env.example | 6 +++--- deployment/dev/docker-compose.yml | 3 --- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 7bb6000..2b42cca 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ __pycache__ **/.fuse* dev/.env .venv -.env \ No newline at end of file +.env +/deployment/dev/data/stroke \ No newline at end of file diff --git a/deployment/dev/.env.example b/deployment/dev/.env.example index 8456a6a..98e9698 100644 --- a/deployment/dev/.env.example +++ b/deployment/dev/.env.example @@ -1,8 +1,8 @@ # Versions EXAFLOW=1.0.0 -PLATFORM_BACKEND=9.1.1 -PLATFORM_UI=1.0.1 -MIP=9.0.2 +PLATFORM_BACKEND=9.1.2 +PLATFORM_UI=1.0.2 +MIP=9.0.3 # Toggle authentication AUTHENTICATION=0 diff --git a/deployment/dev/docker-compose.yml b/deployment/dev/docker-compose.yml index e3a037e..8528d5d 100644 --- a/deployment/dev/docker-compose.yml +++ b/deployment/dev/docker-compose.yml @@ -180,9 +180,6 @@ services: PLATFORM_BACKEND_SERVER: platform-backend:8080 PLATFORM_BACKEND_CONTEXT: services NOTEBOOK_ENABLED: 0 - FRONTEND_VERSION: "${PLATFORM_UI}" - BACKEND_VERSION: "${PLATFORM_BACKEND}" - EXAFLOW_VERSION: "${EXAFLOW}" MIP_VERSION: "${MIP}" depends_on: - platform-backend From 98ee346b48184285363dbfd492fda30d372c98b9 Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Mon, 6 Apr 2026 11:15:16 +0300 Subject: [PATCH 3/8] refactor(kubernetes): remove root redirect setting --- deployment/kubernetes/templates/platform-ui.yaml | 7 ------- deployment/kubernetes/values.yaml | 1 - 2 files changed, 8 deletions(-) diff --git a/deployment/kubernetes/templates/platform-ui.yaml b/deployment/kubernetes/templates/platform-ui.yaml index 429faf0..0a6eb57 100644 --- a/deployment/kubernetes/templates/platform-ui.yaml +++ b/deployment/kubernetes/templates/platform-ui.yaml @@ -68,13 +68,6 @@ metadata: cert-manager.io/cluster-issuer: {{ $ingress.certManagerClusterIssuer | quote }} {{- end }} nginx.ingress.kubernetes.io/rewrite-target: "/" -{{- $redirect := default "" $ingress.redirectRootTo -}} -{{- if $redirect }} - nginx.ingress.kubernetes.io/configuration-snippet: | - if ($request_uri = "/") { - return 302 {{ $redirect }}; - } -{{- end }} spec: ingressClassName: {{ default "nginx" $ingress.className | quote }} {{- if and $ingress.tlsSecretName (not (empty $publicHost)) }} diff --git a/deployment/kubernetes/values.yaml b/deployment/kubernetes/values.yaml index 601e188..5eae029 100644 --- a/deployment/kubernetes/values.yaml +++ b/deployment/kubernetes/values.yaml @@ -21,7 +21,6 @@ platform-ui: className: haproxy-public certManagerClusterIssuer: letsencrypt-public tlsSecretName: platform-ui-tls - redirectRootTo: '' backend: host: platform-backend-service port: 8080 From a60615037993a6697460c07a3482d2bb62502991 Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Mon, 6 Apr 2026 12:13:14 +0300 Subject: [PATCH 4/8] chore: remove nginx rewrite-target annotation from platform-ui ingress template --- deployment/kubernetes/templates/platform-ui.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/deployment/kubernetes/templates/platform-ui.yaml b/deployment/kubernetes/templates/platform-ui.yaml index 0a6eb57..679d4c7 100644 --- a/deployment/kubernetes/templates/platform-ui.yaml +++ b/deployment/kubernetes/templates/platform-ui.yaml @@ -67,7 +67,6 @@ metadata: {{- if $ingress.certManagerClusterIssuer }} cert-manager.io/cluster-issuer: {{ $ingress.certManagerClusterIssuer | quote }} {{- end }} - nginx.ingress.kubernetes.io/rewrite-target: "/" spec: ingressClassName: {{ default "nginx" $ingress.className | quote }} {{- if and $ingress.tlsSecretName (not (empty $publicHost)) }} From b45563da182e6d53f934d03f4831bfa9ef2e571e Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Mon, 6 Apr 2026 13:00:06 +0300 Subject: [PATCH 5/8] refactor(kubernetes): align microk8s runtime behavior --- deployment/kubernetes/README.md | 11 +- .../templates/platform-backend.yaml | 155 +----------------- .../kubernetes/templates/platform-ui.yaml | 7 - deployment/kubernetes/values.yaml | 5 - 4 files changed, 14 insertions(+), 164 deletions(-) diff --git a/deployment/kubernetes/README.md b/deployment/kubernetes/README.md index 8ef8dc3..3189592 100644 --- a/deployment/kubernetes/README.md +++ b/deployment/kubernetes/README.md @@ -6,7 +6,7 @@ MIP now supports two Kubernetes infrastructure options: 1. **VM-based / microk8s clusters** – the remainder of this document (starting in the Requirements section) walks through preparing Ubuntu virtual machines and installing the stack on top of microk8s. 2. **Managed clusters** – for cloud-managed Kubernetes (AKS/EKS/GKE, etc.) follow the [mip-infra getting started guide](https://github.com/Medical-Informatics-Platform/mip-infra?tab=readme-ov-file#-getting-started) to provision the cluster and its base services. Once the cluster is available, return here for component configuration details as needed. -Choose between these modes via the Helm values: set `cluster.managed: true` (managed) or `false` (microk8s/VM). The templates react to this flag to deploy the components with the right assumptions for networking, storage, and access. +Choose between these modes via the Helm values: set `cluster.managed: true` (managed) or `false` (microk8s/VM). The chart now keeps the same workload shape in both modes and uses the flag only to select the storage class and, for microk8s, bootstrap the local `StorageClass`. ## Requirements ### Hardware @@ -53,9 +53,9 @@ Afterward, The dataset CSV files should be placed in their proper pathology fold ## Configuration Prior to deploying it (on a microk8s K8s cluster of one or more nodes), there are a few adjustments to make in `values.yaml`. Each top-level section controls a part of the stack: -* `cluster`: storage classes and whether the cluster provisions persistent volumes dynamically (`managed: true`). +* `cluster`: storage classes and whether the chart should use the managed storage class or the microk8s local storage class. * `global`: shared public hostname used by the ingress and backend redirects. -* `platform-ui`, `platform-backend`, `platformBackendDatabase`: container images and component specific options (including the platform-ui ingress/tls settings). +* `platform-ui`, `platform-backend`, `platformBackendDatabase`: container images and component specific options (including the shared ingress/tls settings and PVC sizes). * `keycloak`: toggles the connection parameters to the external Keycloak instance (`enabled`, `host`, `protocol`, `realm`). Copy `values.yaml` to a new file (for example `my-values.yaml`) and edit it in-place. A few important knobs: @@ -110,7 +110,7 @@ sudo adduser mipadmin microk8s As *mipadmin* user: ``` -microk8s enable dns helm3 ingress +microk8s enable dns helm3 ingress storage ``` ``` sudo mkdir -p /data/ @@ -119,6 +119,8 @@ sudo mkdir -p /data/ sudo chown -R mipadmin.mipadmin /data ``` +The web-app chart uses dynamically provisioned PVCs on microk8s too. The `storage` addon provides the `k8s.io/microk8s-hostpath` provisioner, and the chart bootstraps a local `StorageClass` on top of it for the MIP PVCs. + For a "federated" deployment, you may want to add nodes to your cluster. "microk8s add-node" will give you a **one-time usage** token, which you can use on a worker node to actually "join" the cluster. This process must be repeated on all the worker nodes. ### Exaflow Deployment @@ -168,6 +170,7 @@ For a more in-depth guide on deploying Exaflow, please refer to the documentatio ``` * Copy `values.yaml` to `/opt/mip-deployment/kubernetes/my-values.yaml` and tailor it to your environment. + * On microk8s, keep `cluster.managed: false` so the chart uses the local storage class it bootstraps. * Deploy (or upgrade) the Helm release with your customised values ``` microk8s helm3 upgrade --install mip \ diff --git a/deployment/kubernetes/templates/platform-backend.yaml b/deployment/kubernetes/templates/platform-backend.yaml index 752118a..fd17835 100644 --- a/deployment/kubernetes/templates/platform-backend.yaml +++ b/deployment/kubernetes/templates/platform-backend.yaml @@ -16,147 +16,9 @@ {{- end -}} {{- $localStorageClass := .Values.cluster.storageClasses.local -}} {{- $managedStorageClass := .Values.cluster.storageClasses.managed -}} -{{- if not .Values.cluster.managed }} ---- -# 1) platform-backend DB data on hostPath -apiVersion: v1 -kind: PersistentVolume -metadata: - name: platform-backend-db-vol0 - labels: - storage: platform-backend-db-storage0 -spec: - capacity: - storage: {{ .Values.platformBackendDatabase.persistence.size }} - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Delete - storageClassName: {{ $localStorageClass }} - hostPath: - path: {{ .Values.platformBackendDatabase.persistence.localPath }} - type: DirectoryOrCreate - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: master - operator: In - values: - - "true" ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: platform-backend-db-claim0 - namespace: {{ $namespace }} - labels: - storage: platform-backend-db-storage0 -spec: - selector: - matchLabels: - storage: platform-backend-db-storage0 - storageClassName: {{ $localStorageClass }} - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.platformBackendDatabase.persistence.size }} - ---- -# 2) platform-backend config/storage on hostPath -apiVersion: v1 -kind: PersistentVolume -metadata: - name: platform-backend-storage-vol0 - labels: - storage: platform-backend-storage0 -spec: - capacity: - storage: {{ $platformBackend.persistence.data.size }} - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Delete - storageClassName: {{ $localStorageClass }} - hostPath: - path: {{ $platformBackend.persistence.data.localPath }} - type: DirectoryOrCreate - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: master - operator: In - values: - - "true" ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: platform-backend-claim0 - namespace: {{ $namespace }} - labels: - storage: platform-backend-storage0 -spec: - selector: - matchLabels: - storage: platform-backend-storage0 - storageClassName: {{ $localStorageClass }} - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ $platformBackend.persistence.data.size }} - ---- -# 3) platform-backend logs on hostPath -apiVersion: v1 -kind: PersistentVolume -metadata: - name: platform-backend-logs-vol0 - labels: - storage: platform-backend-logs-storage0 -spec: - capacity: - storage: {{ $platformBackend.persistence.logs.size }} - volumeMode: Filesystem - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: {{ $localStorageClass }} - hostPath: - path: {{ $platformBackend.persistence.logs.localPath }} - type: DirectoryOrCreate - nodeAffinity: - required: - nodeSelectorTerms: - - matchExpressions: - - key: master - operator: In - values: - - "true" +{{- $backendStorageClass := ternary $managedStorageClass $localStorageClass .Values.cluster.managed -}} --- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: platform-backend-logs-claim0 - namespace: {{ $namespace }} - labels: - storage: platform-backend-logs-storage0 -spec: - selector: - matchLabels: - storage: platform-backend-logs-storage0 - storageClassName: {{ $localStorageClass }} - accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ $platformBackend.persistence.logs.size }} -{{- else }} ---- -# managed cluster: only dynamic PVCs on CephFS +# platform-backend PVCs apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -166,12 +28,13 @@ metadata: # tells Argo CD: do not prune (delete) this resource argocd.argoproj.io/sync-options: Delete=false,Prune=false spec: - storageClassName: {{ $managedStorageClass }} + storageClassName: {{ $backendStorageClass }} accessModes: - ReadWriteOnce resources: requests: storage: {{ .Values.platformBackendDatabase.persistence.size }} + --- apiVersion: v1 kind: PersistentVolumeClaim @@ -182,12 +45,13 @@ metadata: # tells Argo CD: do not prune (delete) this resource argocd.argoproj.io/sync-options: Delete=false,Prune=false spec: - storageClassName: {{ $managedStorageClass }} + storageClassName: {{ $backendStorageClass }} accessModes: - ReadWriteOnce resources: requests: storage: {{ $platformBackend.persistence.data.size }} + --- apiVersion: v1 kind: PersistentVolumeClaim @@ -198,13 +62,12 @@ metadata: # tells Argo CD: do not prune (delete) this resource argocd.argoproj.io/sync-options: Delete=false,Prune=false spec: - storageClassName: {{ $managedStorageClass }} + storageClassName: {{ $backendStorageClass }} accessModes: - ReadWriteOnce resources: requests: storage: {{ $platformBackend.persistence.logs.size }} -{{- end }} --- @@ -238,10 +101,6 @@ spec: labels: app: platform-backend spec: -{{- if not .Values.cluster.managed }} - nodeSelector: - master: "true" -{{- end }} hostname: platform-backend volumes: - name: platform-backend-db-claim0 diff --git a/deployment/kubernetes/templates/platform-ui.yaml b/deployment/kubernetes/templates/platform-ui.yaml index 679d4c7..e460fd4 100644 --- a/deployment/kubernetes/templates/platform-ui.yaml +++ b/deployment/kubernetes/templates/platform-ui.yaml @@ -1,6 +1,5 @@ {{- $namespace := default "default" .Release.Namespace -}} {{- $platformUi := index .Values "platform-ui" -}} -{{- $platformBackend := index .Values "platform-backend" -}} {{- $ingress := default (dict) $platformUi.ingress -}} {{- $backendHost := default "" $platformUi.backend.host -}} {{- $backendPort := default 8080 $platformUi.backend.port -}} @@ -50,12 +49,6 @@ spec: - protocol: TCP port: 80 targetPort: 80 -{{- if not .Values.cluster.managed }} - type: NodePort -{{- if $platformUi.service.nodePort }} - nodePort: {{ $platformUi.service.nodePort }} -{{- end }} -{{- end }} {{- if and $ingressEnabled (not (empty $publicHost)) }} --- apiVersion: networking.k8s.io/v1 diff --git a/deployment/kubernetes/values.yaml b/deployment/kubernetes/values.yaml index 5eae029..ff65ed3 100644 --- a/deployment/kubernetes/values.yaml +++ b/deployment/kubernetes/values.yaml @@ -14,8 +14,6 @@ platform-ui: image: repository: hbpmip/platform-ui tag: 1.0.2 - service: - nodePort: 30080 ingress: enabled: true className: haproxy-public @@ -35,10 +33,8 @@ platform-backend: algorithmUpdateInterval: 60 persistence: data: - localPath: /opt/mip-deployment/config size: 100Mi logs: - localPath: /opt/mip-deployment/logs size: 1Gi platformBackendDatabase: @@ -46,7 +42,6 @@ platformBackendDatabase: repository: postgres tag: 18.3-alpine persistence: - localPath: /opt/mip-deployment/.stored_data/platform_backenddb size: 1Gi keycloak: From 2baf90c619589160bf038dadfa57acacfc8eee5f Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Mon, 6 Apr 2026 13:16:48 +0300 Subject: [PATCH 6/8] refactor(kubernetes): delay microk8s volume binding --- deployment/kubernetes/README.md | 2 +- .../kubernetes/templates/microk8s-local-storageclass.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/kubernetes/README.md b/deployment/kubernetes/README.md index 3189592..4857717 100644 --- a/deployment/kubernetes/README.md +++ b/deployment/kubernetes/README.md @@ -119,7 +119,7 @@ sudo mkdir -p /data/ sudo chown -R mipadmin.mipadmin /data ``` -The web-app chart uses dynamically provisioned PVCs on microk8s too. The `storage` addon provides the `k8s.io/microk8s-hostpath` provisioner, and the chart bootstraps a local `StorageClass` on top of it for the MIP PVCs. +The web-app chart uses dynamically provisioned PVCs on microk8s too. The `storage` addon provides the `k8s.io/microk8s-hostpath` provisioner, and the chart bootstraps a local `StorageClass` on top of it for the MIP PVCs. That `StorageClass` uses `WaitForFirstConsumer` binding so PVC placement follows pod scheduling more safely on multi-node microk8s clusters. For a "federated" deployment, you may want to add nodes to your cluster. "microk8s add-node" will give you a **one-time usage** token, which you can use on a worker node to actually "join" the cluster. This process must be repeated on all the worker nodes. diff --git a/deployment/kubernetes/templates/microk8s-local-storageclass.yaml b/deployment/kubernetes/templates/microk8s-local-storageclass.yaml index 9e55d89..3e5ffef 100644 --- a/deployment/kubernetes/templates/microk8s-local-storageclass.yaml +++ b/deployment/kubernetes/templates/microk8s-local-storageclass.yaml @@ -7,5 +7,5 @@ metadata: labels: app: {{ $localStorageClass }} provisioner: k8s.io/microk8s-hostpath -volumeBindingMode: Immediate +volumeBindingMode: WaitForFirstConsumer {{- end }} From d7fbfade3ec3a37685c41f8dfef9437aff6a6400 Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Mon, 6 Apr 2026 13:31:51 +0300 Subject: [PATCH 7/8] refactor(kubernetes): pin microk8s workloads to master --- deployment/kubernetes/README.md | 2 ++ deployment/kubernetes/templates/platform-backend.yaml | 4 ++++ deployment/kubernetes/templates/platform-ui.yaml | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/deployment/kubernetes/README.md b/deployment/kubernetes/README.md index 4857717..6f8318c 100644 --- a/deployment/kubernetes/README.md +++ b/deployment/kubernetes/README.md @@ -121,6 +121,8 @@ sudo chown -R mipadmin.mipadmin /data The web-app chart uses dynamically provisioned PVCs on microk8s too. The `storage` addon provides the `k8s.io/microk8s-hostpath` provisioner, and the chart bootstraps a local `StorageClass` on top of it for the MIP PVCs. That `StorageClass` uses `WaitForFirstConsumer` binding so PVC placement follows pod scheduling more safely on multi-node microk8s clusters. +On microk8s, the MIP web-app chart schedules both `platform-backend` and `platform-ui` onto nodes labeled `master=true`, so make sure the node intended to host the stack carries that label. + For a "federated" deployment, you may want to add nodes to your cluster. "microk8s add-node" will give you a **one-time usage** token, which you can use on a worker node to actually "join" the cluster. This process must be repeated on all the worker nodes. ### Exaflow Deployment diff --git a/deployment/kubernetes/templates/platform-backend.yaml b/deployment/kubernetes/templates/platform-backend.yaml index fd17835..7d24fff 100644 --- a/deployment/kubernetes/templates/platform-backend.yaml +++ b/deployment/kubernetes/templates/platform-backend.yaml @@ -101,6 +101,10 @@ spec: labels: app: platform-backend spec: +{{- if not .Values.cluster.managed }} + nodeSelector: + master: "true" +{{- end }} hostname: platform-backend volumes: - name: platform-backend-db-claim0 diff --git a/deployment/kubernetes/templates/platform-ui.yaml b/deployment/kubernetes/templates/platform-ui.yaml index e460fd4..35b6646 100644 --- a/deployment/kubernetes/templates/platform-ui.yaml +++ b/deployment/kubernetes/templates/platform-ui.yaml @@ -22,6 +22,10 @@ spec: labels: app: platform-ui spec: +{{- if not .Values.cluster.managed }} + nodeSelector: + master: "true" +{{- end }} containers: - name: platform-ui image: "{{ $platformUi.image.repository }}:{{ $platformUi.image.tag }}" From 3e5071e99653bc968c0dc983c0c8e481707cde42 Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Mon, 6 Apr 2026 13:52:33 +0300 Subject: [PATCH 8/8] chore: bump platform-backend to 9.2.0 and platform-ui to 1.1.0 --- deployment/dev/.env.example | 4 ++-- deployment/kubernetes/values.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment/dev/.env.example b/deployment/dev/.env.example index 98e9698..5a2407e 100644 --- a/deployment/dev/.env.example +++ b/deployment/dev/.env.example @@ -1,7 +1,7 @@ # Versions EXAFLOW=1.0.0 -PLATFORM_BACKEND=9.1.2 -PLATFORM_UI=1.0.2 +PLATFORM_BACKEND=9.2.0 +PLATFORM_UI=1.1.0 MIP=9.0.3 # Toggle authentication diff --git a/deployment/kubernetes/values.yaml b/deployment/kubernetes/values.yaml index ff65ed3..87bde9c 100644 --- a/deployment/kubernetes/values.yaml +++ b/deployment/kubernetes/values.yaml @@ -13,7 +13,7 @@ mip: platform-ui: image: repository: hbpmip/platform-ui - tag: 1.0.2 + tag: 1.1.0 ingress: enabled: true className: haproxy-public @@ -26,7 +26,7 @@ platform-ui: platform-backend: image: repository: hbpmip/platform-backend - tag: 9.1.2 + tag: 9.2.0 config: logLevel: INFO logLevelFramework: INFO