# KodeMed GmbH — OpenShift Deployment Policy **Version**: 1.0 **Effective**: 2026-03-04 **Audience**: DevOps, Platform Engineers, IT Architects, Project Managers --- ## 1. Prérequis — Réponses aux exigences de la plateforme ### 1.1 Limites de ressources RAM/CPU Chaque pod définit des `requests` et `limits` dans ses manifests de déploiement : | Pod | CPU Request | CPU Limit | Memory Request | Memory Limit | |-----|-------------|-----------|----------------|--------------| | `kodemed-server` | 250m | 1000m | 512Mi | 1Gi | | `kodemed-dataserver` | 200m | 500m | 256Mi | 1Gi | | `kodemed-grouper` | 200m | 500m | 512Mi | 1Gi | | `kodemed-ui` (nginx) | 50m | 200m | 32Mi | 64Mi | **Notes** : - La JVM utilise `-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC`, ce qui lui permet de respecter automatiquement le `memory limit` du container. - Ces valeurs sont des minimums recommandés. En production, les limites peuvent être ajustées selon la charge observée. --- ### 1.2 Namespace dédié Un namespace spécifique sera créé pour le projet : ``` kodemed-test → environnement de test kodemed-prod → environnement de production ``` Tous les objets Kubernetes (Deployments, Services, Routes, Secrets, ConfigMaps, PVCs) sont déployés dans le namespace correspondant. --- ### 1.3 Haute disponibilité — Minimum 2 pods par service | Service | Replicas (min) | Stratégie de rolling update | Contraintes | |---------|----------------|-----------------------------|-------------| | `kodemed-server` | 2 | `maxUnavailable: 0`, `maxSurge: 1` | **Sticky sessions obligatoires** (voir §1.3.1) | | `kodemed-dataserver` | 2 | `maxUnavailable: 1`, `maxSurge: 1` | Stateless — aucune contrainte | | `kodemed-grouper` | 2 | `maxUnavailable: 1`, `maxSurge: 1` | Stateless — aucune contrainte | | `kodemed-ui` | 2 | `maxUnavailable: 1`, `maxSurge: 1` | Stateless — aucune contrainte | **Il est possible de redémarrer un pod sans impact sur le service** grâce à la stratégie `RollingUpdate` avec `maxUnavailable: 0` (le nouveau pod est prêt avant l'ancien qui s'arrête). #### 1.3.1 Sticky Sessions pour kodemed-server Le `kodemed-server` maintient des connexions WebSocket actives en mémoire JVM (état des instances DLL dans un `ConcurrentHashMap`). La Route OpenShift **doit** configurer l'affinité de session : ```yaml metadata: annotations: haproxy.router.openshift.io/balance: source haproxy.router.openshift.io/disable_cookies: "false" haproxy.router.openshift.io/timeout: 86400s ``` **Impact d'un redémarrage de pod `kodemed-server`** : - Les clients WebSocket connectés à ce pod seront déconnectés. - Le `CodingClient` Windows se reconnecte automatiquement en 60 secondes au pod restant. - Les sessions de codage en cours ne sont **pas perdues** — elles sont persistées en base de données PostgreSQL. - Seule la connexion WebSocket est interrompue temporairement. --- ### 1.4 Readiness Probes Tous les pods disposent de readiness probes via Spring Boot Actuator : ```yaml # Java services (server, dataserver, grouper) readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 # (8081 pour dataserver, 8082 pour grouper) initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 90 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 startupProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 12 ``` > **Note** : Les endpoints spécifiques `/actuator/health/liveness` et `/actuator/health/readiness` (Spring Boot 2.3+) sont utilisés au lieu du endpoint générique `/actuator/health`, car ils sont conçus pour distinguer les états de liveness et readiness du pod. ```yaml # kodemed-ui (nginx) readinessProbe: httpGet: path: / port: 3000 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 livenessProbe: httpGet: path: / port: 3000 initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 3 failureThreshold: 3 ``` --- ### 1.5 Base de données — Pas de pod DB autorisé **Conforme.** PostgreSQL 16 ne sera **pas** déployé en tant que pod dans OpenShift. Options recommandées : 1. **Base de données externe managée** (recommandé) — Azure Database for PostgreSQL, AWS RDS, ou instance PostgreSQL gérée par l'équipe infrastructure du client. 2. **Instance PostgreSQL on-premise** existante du client. **Chiffrement in-transit** : La connexion JDBC utilise SSL/TLS : ``` SPRING_DATASOURCE_URL=jdbc:postgresql://db-host:5432/kodemed?sslmode=require&sslrootcert=/certs/ca.crt ``` Le certificat SSL racine est monté dans le pod via un Secret OpenShift : ```yaml volumes: - name: db-ssl-cert secret: secretName: kodemed-db-ssl-cert volumeMounts: - name: db-ssl-cert mountPath: /certs readOnly: true ``` --- ### 1.6 Mise à jour régulière des images Conforme avec la [Update Policy](UPDATE_POLICY.md) existante : - **Images de base** : `eclipse-temurin:21-jre-alpine` (Java) et `nginxinc/nginx-unprivileged:alpine` (UI) — mises à jour à chaque release. - **Scan de vulnérabilités** : Trivy intégré au pipeline CI. Les images avec des CVE critiques sont bloquées. - **Signature d'images** : Cosign pour la vérification de l'intégrité de la chaîne d'approvisionnement. - **Cadence** : Releases mensuelles (maintenance) + trimestrielles (features). Voir la Update Policy pour les détails. --- ### 1.7 Images immutables **Conforme.** Les images KodeMed sont immutables par conception : - Pas de volume en écriture dans le container (sauf les volumes explicitement montés pour les données). - Pas de `apt-get install`, `npm install`, ou téléchargement de fichiers au runtime. - La configuration est injectée uniquement via des variables d'environnement et des ConfigMaps/Secrets. - Le filesystem du container est en `readOnlyRootFilesystem: true` (sauf `/tmp` pour les fichiers temporaires JVM). ```yaml securityContext: readOnlyRootFilesystem: true allowPrivilegeEscalation: false runAsNonRoot: true runAsUser: 1001 volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {} ``` --- ### 1.8 Pas de nested containers **Conforme.** Aucun des pods KodeMed ne fait tourner Docker-in-Docker ou un autre runtime de containers. Tous les pods exécutent un seul processus principal (JVM ou nginx). --- ### 1.9 Contraintes OpenShift (SCC) Les images KodeMed sont compatibles avec les SCC OpenShift : | Image | UID | SCC requis | Notes | |-------|-----|------------|-------| | `kodemed-server` | 1001 | `restricted` | OK — non-root, pas de capabilities | | `kodemed-dataserver` | 1001 | `restricted` | OK — non-root, pas de capabilities | | `kodemed-grouper` | 1001 | `restricted` | OK — non-root, pas de capabilities | | `kodemed-ui` | 101 | `restricted` | ✅ OK — utilise `nginxinc/nginx-unprivileged:alpine` (UID 101), écoute sur port 3000 | ✅ **Fait** : L'image `kodemed-ui` utilise désormais `nginxinc/nginx-unprivileged:alpine`, compatible avec la SCC `restricted` d'OpenShift. --- ## 2. Questions ouvertes — Réponses ### 2.1 Utilisation de PDB (PodDisruptionBudget) ? **Oui, recommandé.** Des PDB seront créés pour garantir la disponibilité lors des opérations de maintenance du cluster (drain de nœud, mises à jour) : ```yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: kodemed-server-pdb spec: minAvailable: 1 selector: matchLabels: app: kodemed-server --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: kodemed-dataserver-pdb spec: minAvailable: 1 selector: matchLabels: app: kodemed-dataserver --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: kodemed-grouper-pdb spec: minAvailable: 1 selector: matchLabels: app: kodemed-grouper --- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: kodemed-ui-pdb spec: minAvailable: 1 selector: matchLabels: app: kodemed-ui ``` Avec `minAvailable: 1` et 2 replicas, le cluster peut évacuer au maximum 1 pod à la fois par service. --- ### 2.2 Utilisation d'un opérateur ? **Non requis.** KodeMed n'a pas besoin d'un opérateur Kubernetes custom : - L'application est déployable avec des ressources Kubernetes standard (Deployment, Service, Route, ConfigMap, Secret). - Pas de logique de réconciliation complexe nécessaire. - Pas de CRD (Custom Resource Definition) spécifique. Si le client utilise déjà un opérateur PostgreSQL (ex: CloudNativePG, Crunchy Postgres Operator), celui-ci peut être utilisé pour gérer la base de données externe, mais ce n'est pas une responsabilité de KodeMed. --- ### 2.3 Application Stateless vs Stateful ? | Service | Classification | Détails | |---------|---------------|---------| | `kodemed-server` | **Stateful au niveau WebSocket** | Les connexions WebSocket des instances DLL sont stockées en mémoire JVM (`ConcurrentHashMap`). Les données métier (sessions, audit) sont persistées en PostgreSQL. Type Kubernetes : **Deployment** avec sticky sessions (pas StatefulSet). | | `kodemed-dataserver` | **Stateless** (après démarrage) | Lit les catalogues depuis PostgreSQL. Peut nécessiter un PVC pour le dossier d'import hot-folder (`/app/import`), mais ce volume est facultatif. Type Kubernetes : **Deployment**. | | `kodemed-grouper` | **Stateless** | Aucune base de données. Charge les fichiers de spécification SwissDRG depuis un volume monté au démarrage. Type Kubernetes : **Deployment**. | | `kodemed-ui` | **Stateless** | Serveur de fichiers statiques nginx. Type Kubernetes : **Deployment**. | **En résumé** : Tous les services sont déployés en tant que `Deployment` (pas `StatefulSet`). Le `kodemed-server` nécessite une affinité de session au niveau Route, mais ce n'est pas un StatefulSet car il ne nécessite pas de stockage persistant par pod ni d'identité réseau stable. --- ### 2.4 Type de déploiement ? **Recommandation : Helm charts** avec support GitOps. | Méthode | Recommandation | Justification | |---------|---------------|---------------| | **Helm** | **Recommandé** | Paramétrable (values.yaml par environnement), versionnable, rollback facile, compatible avec ArgoCD/FluxCD. | | Manuel (kubectl apply) | Déconseillé | Pas reproductible, risque d'erreurs, pas de versioning. | | Opérateur | Non nécessaire | Voir §2.2 — la complexité n'est pas justifiée. | **Structure du Helm chart proposée** : ``` charts/kodemed/ ├── Chart.yaml ├── values.yaml # Valeurs par défaut ├── values-test.yaml # Surcharges pour environnement test ├── values-prod.yaml # Surcharges pour environnement prod └── templates/ ├── _helpers.tpl ├── server-deployment.yaml ├── server-service.yaml ├── server-route.yaml ├── server-pdb.yaml ├── dataserver-deployment.yaml ├── dataserver-service.yaml ├── dataserver-route.yaml ├── dataserver-pdb.yaml ├── grouper-deployment.yaml ├── grouper-service.yaml ├── grouper-route.yaml ├── grouper-pdb.yaml ├── ui-deployment.yaml ├── ui-service.yaml ├── ui-route.yaml ├── ui-pdb.yaml ├── configmap.yaml ├── secret.yaml └── NOTES.txt ``` --- ### 2.5 GitOps compatible ? **Oui.** L'architecture KodeMed est pleinement compatible avec GitOps : - **Images immutables** avec tags versionnés (`YYYY.M.D.BUILD`). - **Configuration déclarative** : toute la configuration est injectée via variables d'environnement / ConfigMaps / Secrets. - **Helm charts versionnés** stockés dans le JFrog registry du client. - **Pas d'état local** dans les images (sauf volumes montés explicitement). **Workflow GitOps proposé** : ``` 1. Push image → JFrog Registry (client) 2. Update values.yaml (image tag) → Git repo 3. ArgoCD/FluxCD détecte le changement 4. Sync automatique → OpenShift applique le nouveau déploiement 5. Rolling update sans downtime ``` --- ## 3. Responsabilités — Matrice RASCI | Activité | Mieres IT (KodeMed) | Client (Platform Team) | Client (IT Project) | |----------|---------------------|----------------------|---------------------| | **Développement et build des images** | **R** (Responsible) | I (Informed) | I (Informed) | | **Scan de vulnérabilités des images** | **R** | C (Consulted) | I | | **Publication des images sur JFrog** | **R** | S (Supportive) | I | | **Création du namespace OpenShift** | C | **R** | A (Accountable) | | **Configuration des quotas/limites** | C | **R** | A | | **Création et gestion des Helm charts** | **R** | C | I | | **Publication des Helm charts sur JFrog** | **R** | S | I | | **Déploiement initial (test)** | S | **R** | A | | **Déploiement initial (production)** | S | **R** | A | | **Configuration des Routes/Ingress** | C | **R** | I | | **Configuration SSL/TLS (routes)** | I | **R** | A | | **Gestion certificats SSL (DB in-transit)** | C | **R** | A | | **Provision de la base de données PostgreSQL** | C | **R** | A | | **Configuration DB (schéma, migrations)** | **R** (automatique) | S | I | | **Monitoring et alerting** | C | **R** | I | | **Gestion des Secrets OpenShift** | C | **R** | A | | **Mises à jour des images (rolling updates)** | **R** (fournit les images) | **R** (applique en OpenShift) | A | | **Support applicatif niveau 2/3** | **R** | S | I | | **Support infrastructure OpenShift** | I | **R** | I | | **Sauvegarde et restauration DB** | C | **R** | A | | **Tests d'acceptation post-déploiement** | S | C | **R** | | **Gestion des incidents applicatifs** | **R** | S | A | | **Gestion des incidents infrastructure** | I | **R** | A | **Légende RASCI** : - **R** = Responsible (exécute la tâche) - **A** = Accountable (valide/approuve, un seul par ligne) - **S** = Supportive (aide à l'exécution) - **C** = Consulted (donne un avis avant décision) - **I** = Informed (est notifié après exécution) --- ## 4. Registry JFrog — Transfert des images ### 4.1 Images à transférer Les images actuellement publiées sur `harbor.mieresit.com/kodemed/` devront être re-publiées (ou mirrored) sur le JFrog du client : | Image source (Harbor) | Image cible (JFrog) | |----------------------|---------------------| | `harbor.mieresit.com/kodemed/kodemed-server:` | `/kodemed/kodemed-server:` | | `harbor.mieresit.com/kodemed/kodemed-dataserver:` | `/kodemed/kodemed-dataserver:` | | `harbor.mieresit.com/kodemed/kodemed-grouper-server:` | `/kodemed/kodemed-grouper-server:` | | `harbor.mieresit.com/kodemed/kodemed-ui:` | `/kodemed/kodemed-ui:` | ### 4.2 Processus de publication **Option A — Push direct** (CI/CD de Mieres IT push vers JFrog client) : ```bash # Tag et push vers JFrog client docker tag harbor.mieresit.com/kodemed/kodemed-server:$TAG $JFROG_REGISTRY/kodemed/kodemed-server:$TAG docker push $JFROG_REGISTRY/kodemed/kodemed-server:$TAG ``` **Option B — Remote repository** (JFrog proxie Harbor) : - Configurer un remote Docker repository dans JFrog pointant vers `harbor.mieresit.com/kodemed/`. - Les images sont automatiquement cachées localement au premier pull. ### 4.3 Helm charts sur JFrog Les Helm charts seront publiés dans un Helm repository sur JFrog : ```bash # Publication helm package charts/kodemed/ curl -u $JFROG_USER:$JFROG_TOKEN -T kodemed-*.tgz \ "$JFROG_URL/kodemed-helm/kodemed-*.tgz" # Utilisation helm repo add kodemed $JFROG_URL/kodemed-helm helm install kodemed kodemed/kodemed -f values-prod.yaml ``` --- ## 5. Ports et Routes OpenShift | Service | Port container | Route OpenShift | TLS | WebSocket | |---------|---------------|-----------------|-----|-----------| | `kodemed-server` | 8080 | `kodemed-api.` | Edge / Re-encrypt | **Oui** (timeout 86400s) | | `kodemed-dataserver` | 8081 | `kodemed-data.` | Edge / Re-encrypt | Non | | `kodemed-grouper` | 8082 | `kodemed-grouper.` | Edge / Re-encrypt | Non | | `kodemed-ui` | 3000 | `kodemed.` | Edge | Non | **Note WebSocket** : La Route pour `kodemed-server` nécessite les annotations suivantes : ```yaml haproxy.router.openshift.io/timeout: 86400s haproxy.router.openshift.io/balance: source ``` --- ## 6. Secrets et Configuration ### Secrets OpenShift (à créer par le client) | Secret | Clés | Description | |--------|------|-------------| | `kodemed-db-credentials` | `SPRING_DATASOURCE_URL`, `SPRING_DATASOURCE_USERNAME`, `SPRING_DATASOURCE_PASSWORD` | Credentials PostgreSQL | | `kodemed-db-ssl-cert` | `ca.crt` | Certificat SSL racine pour la connexion DB in-transit | | `kodemed-encryption-key` | `KODEMED_ENCRYPTION_KEY` | Clé AES-256 (base64, 32 bytes) pour le chiffrement des données sensibles | | `kodemed-oidc` | `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI`, `..._JWK_SET_URI` | Configuration OIDC pour la validation des tokens JWT (Keycloak, Azure AD, etc.) | | `kodemed-admin-api-key` | `KODEMED_ADMIN_API_KEY` | Clé API pour l'accès admin au DataServer (import/reimport scripts) — optionnel | | `kodemed-license` | `kodemed.license` (fichier) | Fichier de licence KodeMed (monté en volume dans tous les services Java) | ### ConfigMaps | ConfigMap | Description | |-----------|-------------| | `kodemed-config` | Variables d'environnement non sensibles (URLs inter-services, CORS, feature flags) | | `kodemed-grouper-specs` | Fichiers de spécification SwissDRG (.sgs) — montés en volume ou PVC si taille > 1Mi | | `kodemed-grouper-catalogues` | Fichiers de catalogue SwissDRG (.csv) — montés en volume ou PVC | --- ## 7. Actions requises avant déploiement | # | Action | Responsable | Priorité | |---|--------|-------------|----------| | 1 | Créer le namespace `kodemed-test` sur OpenShift | Client (Platform) | Haute | | 2 | Provisionner PostgreSQL 16 avec SSL activé | Client (Platform) | Haute | | 3 | Configurer le repository Docker dans JFrog pour les images KodeMed | Client (Platform) | Haute | | 4 | Configurer le repository Helm dans JFrog | Client (Platform) | Haute | | 5 | Fournir les credentials JFrog à Mieres IT pour le push d'images | Client (Platform) | Haute | | 6 | ~~Adapter l'image `kodemed-ui` pour nginx non-root (SCC restricted)~~ | Mieres IT | ✅ Fait | | 7 | ~~Créer les Helm charts~~ | Mieres IT | ✅ Fait | | 8 | Créer les Secrets OpenShift (DB, encryption, OIDC, license, admin-api-key) | Client (Platform) | Haute | | 9 | Configurer les Routes avec support WebSocket et sticky sessions | Client (Platform) | Haute | | 10 | Valider la matrice RASCI avec toutes les parties prenantes | Client (IT Project) | Moyenne | --- *KodeMed GmbH · Switzerland* *Ce document est sujet à modification. La dernière version est toujours disponible dans le dépôt Git du projet.*