OCPI — Manuel de déploiement
Ce runbook décrit le déploiement complet de la couche OCPI sur un environnement neuf (Dev, Staging ou Production). Il couvre :
- La génération et l'application de la migration EF Core.
- Le déploiement des binaires
Chargemsi.OcpiGatewayetChargemsi.Worker. - La configuration des secrets (connection string, URLs Gireve).
- Le CSR Gireve et le handshake OCPI initial.
- La vérification post-déploiement.
- La procédure de rollback.
Hypothèse : la base SQL Server existe déjà (les migrations OCPP historiques ont été appliquées). Si ce n'est pas le cas, suivre d'abord la procédure de déploiement OCPP.
Pré-requis
| Outil | Version | Installation |
|---|---|---|
| .NET SDK | 8.0+ (10.0 pour le gateway) | https://dotnet.microsoft.com/download |
| EF Core CLI | 6+ | dotnet tool install -g dotnet-ef |
| Accès SQL Server | login avec db_ddladmin au moins le temps de la migration |
DBA |
| Identifiant certificat Gireve | un par environnement | Portail / support Gireve |
Certificat client .crt + cle .key Gireve |
un par environnement | Email CTM Gireve apres traitement du CSR |
| Whitelist firewall Gireve | inbound + outbound | Equipe reseau ChargeMSI |
Reseau — IPs Gireve a autoriser
Gireve communique sa liste d'IPs sortantes (par environnement) en reponse au CSR. Pour la Pre-Production (PP-IOP), les IPs communiquees le 2026-05-19 sont :
34.76.32.171
35.240.33.86
195.21.21.104
Regles a poser :
| Sens | Source -> Destination | Port | Notes |
|---|---|---|---|
| Inbound | 34.76.32.171, 35.240.33.86, 195.21.21.104 -> ocpi.chargemsi.com (ou ocpi-preprod.chargemsi.com) |
TCP/443 | Gireve appelle nos endpoints /ocpi/versions, /credentials, push tokens & commands. |
| Outbound | Gateway + Worker -> 34.76.32.171, 35.240.33.86, 195.21.21.104 |
TCP/443 | ChargeMSI pousse locations, sessions, cdrs vers Gireve. |
En Production reelle (
ocpi.gireve.com), redemander la liste a jour a l'equipe CTM Gireve : elle differe de la liste PP-IOP.
Certificat client mTLS Gireve
Apres reception du .crt signe par Gireve, l'installer comme decrit dans
first-registration.md / Etape 1 bis :
- empaqueter
.key+.crtdans un.p12(PKCS#12) ; - placer le
.p12dans un dossier hote, ex./etc/chargemsi/gireve/partner_key/preprod/; - monter ce dossier
:rodans les conteneurs (voirdocker-compose.ocpi.yml) ; - exposer
Ocpi__Gireve__ClientCertificatePathetOcpi__Gireve__ClientCertificatePasswordau gateway et au worker (les deux appellent Gireve en sortie).
Exemple .env pour le compose :
GIREVE_CERT_DIR=/etc/chargemsi/gireve/partner_key/preprod
GIREVE_CERT_FILE=chargemsi.gireve.com.p12
GIREVE_CERT_PASSWORD=<via Ansible Vault, jamais en clair>
Le compose monte ce dossier sous /certs/gireve :
ocpigateway:
environment:
Ocpi__Gireve__ClientCertificatePath: /certs/gireve/chargemsi.gireve.com.p12
Ocpi__Gireve__ClientCertificatePassword: ${GIREVE_CERT_PASSWORD}
volumes:
- ${GIREVE_CERT_DIR}:/certs/gireve:ro
Étape 1 — Générer la migration OCPI
À exécuter une seule fois, lors de la première version OCPI déployée (ou à chaque évolution du modèle OCPI).
# Depuis la racine de la solution
cd D:\AWorkspace\OCPPCLAUDE
dotnet ef migrations add Ocpi_Init `
--project Chargemsi.Database `
--startup-project Chargemsi.OcpiGateway `
--context OcpiDbContext `
--output-dir Ocpi/Migrations
Appliquer la migration depuis le répertoire de la solution :
dotnet ef database update --context OCPPCoreContext --project Chargemsi.Database --startup-project Chargemsi.ManagementPortal
Ce qui doit se passer
- Création de
Chargemsi.Database/Ocpi/Migrations/{timestamp}_Ocpi_Init.cs+ son.Designer.cs. - Création de
Chargemsi.Database/Ocpi/Migrations/OcpiDbContextModelSnapshot.cs.
Vérifier la migration AVANT de l'appliquer
Ouvrez le .cs généré et contrôlez que :
- Le schéma cible est bien
ocpi(migrationBuilder.EnsureSchema(name: "ocpi")en première ligne). - Les tables créées sont uniquement :
Partners,Credentials,Endpoints,Sessions,Cdrs,CommandLogs,InboundMessages,OutboundMessages. - Aucune table métier OCPP n'est touchée (pas de
ALTER TABLE [ChargePoint], etc.). - L'index unique sur
Credentials.TokenValueest présent.
Si le diff inclut des modifications inattendues, NE PAS appliquer. Régénérer avec
dotnet ef migrations remove --context OcpiDbContextaprès avoir corrigé le modèle.
Commit
git add Chargemsi.Database/Ocpi/Migrations/*
git commit -m "ef(ocpi): initial migration Ocpi_Init"
Étape 2 — Appliquer la migration à un environnement
Mode A — Direct (Dev / Staging)
$env:ASPNETCORE_ENVIRONMENT = "Staging"
$env:ConnectionStrings__OcppSqlServerProd = "Server=...;Database=OCPP_STAGING;User ID=...;Password=...;TrustServerCertificate=true;"
dotnet ef database update `
--project Chargemsi.Database `
--startup-project Chargemsi.OcpiGateway `
--context OcpiDbContext
Mode B — Script SQL idempotent (Production recommandée)
dotnet ef migrations script `
--idempotent `
--project Chargemsi.Database `
--startup-project Chargemsi.OcpiGateway `
--context OcpiDbContext `
--output deploy/ocpi-init.sql
Le DBA exécute ocpi-init.sql dans une fenêtre de maintenance. Le flag
--idempotent rend le script rejouable sans risque (il vérifie l'état de
__EFMigrationsHistory_Ocpi avant chaque opération).
Vérification post-migration
-- Le schéma a bien été créé
SELECT name FROM sys.schemas WHERE name = 'ocpi';
-- Les 8 tables OCPI sont là
SELECT name FROM sys.tables
WHERE SCHEMA_NAME(schema_id) = 'ocpi'
ORDER BY name;
-- Attendu : Cdrs, CommandLogs, Credentials, Endpoints,
-- InboundMessages, OutboundMessages, Partners, Sessions
-- La table de migration OCPI est isolée
SELECT * FROM ocpi.[__EFMigrationsHistory_Ocpi];
Étape 3 — Déployer les binaires
3.1 Gateway (Chargemsi.OcpiGateway)
dotnet publish Chargemsi.OcpiGateway `
-c Release `
-r linux-x64 `
--self-contained false `
-o publish/ocpigateway
Image Docker — la Chargemsi.OcpiGateway/Dockerfile est multi-stage et fait
les choses suivantes (voir le fichier pour le détail des commentaires) :
- Stage
base: ASP.NET 10, ajoutecurl+ca-certificates+tzdata(UTC). Écoute sur8080(HTTP). TLS est terminé par le reverse-proxy. - Stage
restore: ne copie que les.csprojpour profiter du cache Docker tant que les dépendances ne bougent pas. - Stage
build+publish:--no-restore(gain CI/CD). - Stage
final:HEALTHCHECKsur/health/live, exécution en utilisateur non-root viaUSER $APP_UID.
Build depuis la racine du repo :
docker build -t chargemsi/ocpigateway:latest \
-f Chargemsi.OcpiGateway/Dockerfile .
Run autonome :
docker run --name chargemsi-ocpigateway \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ConnectionStrings__OcppSqlServerProd="Server=...;Database=...;User ID=...;Password=...;Encrypt=True;TrustServerCertificate=True;" \
-e Ocpi__PublicBaseUrl="https://ocpi.chargemsi.com" \
-e Ocpi__Gireve__VersionsUrl="https://ocpi-pp-iop.gireve.com/ocpi/versions" \
-p 8080:8080 \
chargemsi/ocpigateway:latest
3.1 bis Déploiement docker-compose (stack OCPI complète)
Un fichier docker-compose.ocpi.yml à la racine du repo monte le stack
complet (SQL Server + Gateway + Worker) :
# 1. Copier le template et remplir les secrets
cp .env.ocpi.sample .env
$EDITOR .env # SA_PASSWORD, OCPP_DB_PASSWORD, OCPI_PUBLIC_BASE_URL, ...
# 2. Lancer le stack
docker compose -f docker-compose.ocpi.yml up -d --build
# 3. Suivre les logs
docker compose -f docker-compose.ocpi.yml logs -f ocpigateway
# 4. Smoke-test
curl -fsS http://localhost:8080/health/live
curl -fsS http://localhost:8080/health/ready
Ce que fait le compose :
ocpi-db: SQL Server 2022, volume persistantchargemsi-ocpi-db-data, healthchecksqlcmd SELECT 1.ocpigateway: démarre après que la DB soit healthy, écoute sur 8080, healthcheck/health/live.ocpiworker: démarre après le gateway, expose Hangfire sur le port 8081 de l'hôte (à ne pas exposer publiquement sans auth).- réseau Docker dédié
chargemsi-ocpi; le reverse-proxy NGINX/Traefik se branche dessus enexternal: trueou rejoint le réseau via son propre compose.
3.2 Worker (Chargemsi.Worker)
Idem. À noter : le Worker est un service à part, pas un pod du gateway.
Il a son propre cycle de vie (déployé en Deployment k8s avec replicas: 1
pour éviter les doubles dépilements de l'outbox — ou utiliser Hangfire.Pro
pour permettre N réplicas).
Étape 4 — Configurer les secrets
Variables OBLIGATOIRES
| Clé | Description | Où |
|---|---|---|
ConnectionStrings__OcppSqlServerProd |
Chaîne SQL Server (mêmes credentials que l'OCPP) | Gateway et Worker |
ConnectionStrings__HangfireDb |
Chaîne SQL Server pour Hangfire | Worker uniquement |
Ocpi__Gireve__VersionsUrl |
URL /versions de l'environnement Gireve (https://ocpi-pp-iop.gireve.com/ocpi/versions en PP-IOP, https://ocpi.gireve.com/ocpi/versions en prod) |
Gateway et Worker |
Ocpi__Gireve__ClientCertificatePath |
Chemin du .p12 (cle + cert Gireve) monte dans le conteneur, ex. /certs/gireve/chargemsi.gireve.com.p12 |
Gateway et Worker |
Ocpi__Gireve__ClientCertificatePassword |
Mot de passe du .p12 (Ansible Vault / Docker secret / Key Vault) |
Gateway et Worker |
Ocpi__PublicBaseUrl |
URL publique du gateway (utilisée dans les réponses Versions et Credentials) | Gateway |
Variables utiles au handshake initial
| Clé | Description |
|---|---|
Ocpi__Gireve__VersionsUrl |
URL /versions de l'environnement Gireve. |
Le token temporaire donne a Gireve pour son premier appel entrant n'est pas lu
depuis une variable d'environnement par le gateway. Il doit etre insere dans
ocpi.Credentials avec Direction = Inbound, comme decrit dans
first-registration.md.
En k8s (Helm)
# values.yaml (extrait)
ocpiGateway:
env:
ASPNETCORE_ENVIRONMENT: Production
envFrom:
- secretRef:
name: chargemsi-ocpi-secrets # contient les ConnectionStrings et tokens
Étape 5 — Handshake OCPI initial
Voir le runbook dédié first-registration.md.
Résumé exécutable :
# Une fois le gateway up :
# 1. generer le CSR avec le Common Name fourni par Gireve ;
# 2. generer TOKEN_TEMPORAIRE_CHARGEMSI ;
# 3. inserer ce token en base dans ocpi.Credentials ;
# 4. transmettre a Gireve l'URL /credentials et le token temporaire.
# Voir le runbook first-registration.
À l'issue du handshake, la table ocpi.Credentials doit contenir :
SELECT TokenKind, Direction, IsActive, CreatedDate
FROM ocpi.Credentials
WHERE PartnerId = (SELECT TOP 1 Id FROM ocpi.Partners WHERE PartyId = 'GIR');
-- Attendu :
-- TokenKind = 'C', Direction = 0, IsActive = 1 -- Inbound: Gireve appelle ChargeMSI
-- TokenKind = 'C', Direction = 1, IsActive = 1 -- Outbound: ChargeMSI appelle Gireve
Étape 6 — Vérification post-déploiement
Checks automatiques
# 1) Liveness — process up
Invoke-WebRequest https://ocpi-preprod.chargemsi.com/health/live
# Attendu : 200 OK, "Healthy"
# 2) Readiness — DB OCPP + DB OCPI + Gireve
Invoke-WebRequest https://ocpi-preprod.chargemsi.com/health/ready
# Attendu : 200 OK avec gireve-versions = Healthy
# 3) Endpoint Versions OCPI (auth requise)
Invoke-RestMethod https://ocpi-preprod.chargemsi.com/ocpi/versions `
-Headers @{ Authorization = "Token $tokenC_inbound" }
# Attendu : enveloppe OCPI status_code=1000
Checks SQL
-- Outbox vide ou en train de se vider
SELECT Status, COUNT(*) FROM ocpi.OutboundMessages GROUP BY Status;
-- Le Worker écrit son audit des locations
SELECT TOP 5 * FROM ocpi.InboundMessages ORDER BY ReceivedUtc DESC;
Smoke côté Gireve
Demandez à Gireve de :
- Appeler
GET /ocpi/cpo/2.2.1/locations→ réponse 200 + enveloppe OCPI. - Pousser un
Tokentest (PUT/cpo/2.2.1/tokens/FR/GIR/{uid}) → 200.
Étape 7 — Rollback
Cas 1 : la migration a tout cassé
# Annuler la migration OCPI sans toucher aux tables OCPP
dotnet ef database update <NomMigrationPrecedente> `
--project Chargemsi.Database `
--startup-project Chargemsi.OcpiGateway `
--context OcpiDbContext
Pour la toute première migration
Ocpi_Init, le rollback se fait avec--migration 0(rétablit l'état initial : tables OCPI supprimées, schémaocpienlevé).
Cas 2 : le gateway ne démarre pas
- Vérifier
kubectl logs deployment/ocpi-gateway --tail=200. - Erreurs courantes :
- Connection string manquante → contrôler les secrets.
OcpiDbContextne trouve pas la table → la migration n'a pas été appliquée.gireve-versions Degraded→ normal au démarrage si Gireve n'est pas joignable, n'empêche pas le démarrage.
- Rollback :
kubectl rollout undo deployment/ocpi-gateway.
Cas 3 : les CDRs partent en DeadLetter en masse
- Identifier la cause via :
SELECT TOP 20 LastError, COUNT(*) AS N FROM ocpi.OutboundMessages WHERE Status = 4 GROUP BY LastError ORDER BY N DESC; - Corriger le bug (mapper, payload, certificat...).
- Réinjecter :
UPDATE ocpi.OutboundMessages SET Status = 0, RetryCount = 0, NextAttemptUtc = NULL, LastError = NULL WHERE Status = 4;
Checklist de déploiement (à imprimer)
- [ ] Migration
Ocpi_Initgénérée et reviewée (Étape 1) - [ ] Script
ocpi-init.sqlexécuté par le DBA (Étape 2) - [ ] Tables OCPI présentes en base (vérification SQL)
- [ ] Image Docker du gateway buildée et poussée (Étape 3.1)
- [ ] Image Docker du worker buildée et poussée (Étape 3.2)
- [ ] Secrets k8s ou env vars configurés (Étape 4)
- [ ] IPs Gireve whitelistees inbound + outbound sur le pare-feu
(
34.76.32.171,35.240.33.86,195.21.21.104en PP-IOP) - [ ] Certificat client Gireve (
.crt+.key->.pfx) deploye etOcpi__Gireve__ClientCertificate__*injectes - [ ] Pods up et
readinessProbeOK - [ ] Handshake OCPI réussi, tokens finaux Inbound/Outbound stockés (Étape 5)
- [ ] Smoke checks externes (Gireve) validés (Étape 6)
- [ ] Procédure rollback connue de l'astreinte (Étape 7)