Licences par cœur pour les logiciels serveur .NET : compter les cœurs correctement et lier le droit
Les licences par cœur vivent ou meurent selon qu'on compte correctement les cœurs. Voici pourquoi Environment.ProcessorCount induit en erreur, comment lire les cœurs physiques et les quotas CPU de conteneur en .NET, et comment lier un droit de nombre de cœurs dans une licence signée.
Les licences par cœur, c’est ainsi qu’une grande partie des logiciels serveur se vendent : un prix qui évolue avec la capacité CPU sur laquelle le client vous exécute, parce qu’un moteur de base de données ou un service d’analytique sur une machine de 64 cœurs délivre bien plus de valeur que le même binaire sur une VM de 4. Le modèle est simple à énoncer et étonnamment facile à rater, car la partie difficile n’est pas la politique — c’est de répondre, correctement et de façon résistante à la falsification, à la question « sur combien de cœurs cela tourne-t-il réellement ? ». En .NET, l’API évidente vous donne la mauvaise réponse plus souvent que la bonne.
Le piège du comptage
Attrapez Environment.ProcessorCount et vous livrerez un bug de licence. Il renvoie le nombre de processeurs logiques disponibles pour le processus courant — ce qui n’est pas la capacité que vous vendez, et ce d’au moins trois façons :
- Il compte les hyperthreads, donc une puce de 8 cœurs physiques en rapporte typiquement 16.
- Il se réduit sous l’affinité : si le processus est épinglé sur un sous-ensemble de CPU (via des masques d’affinité ou un ordonnanceur), il rapporte le sous-ensemble, pas la machine.
- Il ignore les quotas de conteneur : dans un pod Kubernetes limité à 2 CPU sur un nœud de 64 cœurs, il peut encore rapporter un grand nombre selon le runtime et la version — vous ne mesurez pas la limite que l’orchestrateur a réellement imposée.
Donc avant d’écrire la moindre vérification, décidez quel nombre est votre unité de licence : cœurs physiques, processeurs logiques ou quota CPU du conteneur. Puis mesurez celui-là exprès.
Mesurer le bon nombre
Processeurs logiques (peu coûteux, mais le moins significatif à lui seul) :
int logical = Environment.ProcessorCount;
Cœurs physiques sous Windows — faites la somme de NumberOfCores sur les sockets :
using System.Management; // Windows only
int physical = 0;
using var searcher = new ManagementObjectSearcher("SELECT NumberOfCores FROM Win32_Processor");
foreach (var mo in searcher.Get())
physical += Convert.ToInt32(mo["NumberOfCores"]);
Cœurs physiques sous Linux — comptez les paires uniques (physical id, core id) :
var pairs = new HashSet<(string socket, string core)>();
string socket = "0", core = "0";
foreach (var line in File.ReadLines("/proc/cpuinfo"))
{
if (line.StartsWith("physical id")) socket = line.Split(':')[1].Trim();
else if (line.StartsWith("core id")) core = line.Split(':')[1].Trim();
else if (line.Length == 0) pairs.Add((socket, core));
}
int physicalLinux = Math.Max(pairs.Count, 1);
Quota CPU du conteneur — le nombre qui compte réellement en Kubernetes/Docker :
// cgroup v2: /sys/fs/cgroup/cpu.max → "<quota> <period>" or "max <period>"
static double? CgroupCpuLimit()
{
const string v2 = "/sys/fs/cgroup/cpu.max";
if (File.Exists(v2))
{
var parts = File.ReadAllText(v2).Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts[0] == "max") return null; // unlimited
return double.Parse(parts[0]) / double.Parse(parts[1]);
}
// cgroup v1
const string q = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us";
const string p = "/sys/fs/cgroup/cpu/cpu.cfs_period_us";
if (File.Exists(q) && File.Exists(p))
{
double quota = double.Parse(File.ReadAllText(q).Trim());
if (quota <= 0) return null; // unlimited
return quota / double.Parse(File.ReadAllText(p).Trim());
}
return null;
}
Choisissez l’un de ces nombres comme votre capacité mesurée — la plupart des produits serveur optent pour les cœurs physiques en bare-metal/VM et pour le quota cgroup (arrondi au supérieur) pour les conteneurs.
Lier le droit dans la licence
Mesurer n’est que la moitié du travail ; le nombre mesuré doit être confronté à quelque chose que le client ne peut pas éditer. Ce quelque chose est un droit signé. La licence porte une valeur maxCores dans son payload, signée par votre clé privée, et le client vérifie la signature avant de lui faire confiance (la mécanique est dans droits de licence et feature flags et dans l’anatomie d’une clé signée). Puis, au démarrage :
var license = keyright.Validate(); // verifies signature against embedded public key
int licensedCores = license.GetInt("maxCores"); // trusted entitlement from the signed payload
int measured = MeasureLicensingCores(); // one of the numbers above, chosen per policy
if (measured > licensedCores)
{
// Fail closed — but loudly and actionably.
throw new LicenseException(
$"This license permits {licensedCores} cores; this host exposes {measured}. " +
$"Reduce the CPU allocation or upgrade the license.");
}
Deux notes de conception qui valent l’encre. D’abord, échouez en fermé, mais diagnostiquez : un opérateur qui a accidentellement planifié votre service sur un nœud plus gros devrait lire exactement ce qui s’est passé — mesuré contre sous licence — et non voir le processus mourir sur une erreur générique. Ensuite, envisagez une courte grâce ou un avertissement unique avant que l’application ne mordre, pour qu’un surprovisionnement passager pendant un déploiement ne mette pas la production à terre ; cela reflète le raisonnement sur la grâce dans pannes du serveur de licences et fenêtres de grâce. Comme le nombre de cœurs est lu localement et que le droit est vérifié localement, toute la vérification fonctionne hors ligne et à chaque lancement sans aucun appel au serveur.
Faire cela avec Keyright
Keyright vous permet de placer un droit maxCores (ou maxLogicalProcessors, ou un quota de conteneur) dans la licence signée, et son SDK .NET vérifie ce payload contre une clé publique que vous intégrez à la compilation — de sorte que la comparaison entre cœurs sous licence et mesurés s’exécute localement, hors ligne et en échec fermé, avec le message de diagnostic que vous choisissez. Vous décidez de la politique de comptage ; Keyright vous fournit le nombre résistant à la falsification pour la confronter. C’est auto-hébergé, donc votre clé de signature et les données de licence de vos clients restent sur une infrastructure que vous contrôlez.
Consultez les offres, ou partez de ajouter des licences à une app .NET et ajoutez le droit de nombre de cœurs au modèle que vous avez déjà.
Essayez Nebula.NET
Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.