Lizenzierung pro Kern für .NET-Serversoftware: Kerne korrekt zählen und das Recht binden
Lizenzierung pro Kern steht und fällt damit, Kerne korrekt zu zählen. Hier erfahren Sie, warum Environment.ProcessorCount in die Irre führt, wie Sie in .NET physische Kerne und Container-CPU-Quotas auslesen und wie Sie ein Kernzahl-Recht in eine signierte Lizenz binden.
Lizenzierung pro Kern ist die Art, wie viel Serversoftware verkauft wird: ein Preis, der mit der CPU-Kapazität skaliert, auf der der Kunde Sie betreibt, denn eine Datenbank-Engine oder ein Analytics-Dienst auf einer 64-Kern-Maschine liefert weit mehr Wert als dasselbe Binary auf einer 4-Kern-VM. Das Modell ist einfach zu formulieren und überraschend leicht falsch zu machen, denn der schwierige Teil ist nicht die Richtlinie — es ist, korrekt und manipulationsresistent die Frage zu beantworten: „Auf wie vielen Kernen läuft das eigentlich?” In .NET gibt Ihnen die naheliegende API öfter die falsche als die richtige Antwort.
Die Zählfalle
Greifen Sie zu Environment.ProcessorCount, und Sie liefern einen Lizenzierungsbug aus. Es gibt die Anzahl der logischen Prozessoren zurück, die dem aktuellen Prozess zur Verfügung stehen — was nicht die Kapazität ist, die Sie verkaufen, und zwar auf mindestens drei Arten:
- Es zählt Hyperthreads, sodass ein Chip mit 8 physischen Kernen typischerweise 16 meldet.
- Es schrumpft unter Affinität: Ist der Prozess auf eine Teilmenge der CPUs gepinnt (über Affinitätsmasken oder einen Scheduler), meldet es die Teilmenge, nicht die Maschine.
- Es ignoriert Container-Quotas: In einem auf 2 CPUs begrenzten Kubernetes-Pod auf einem 64-Kern-Knoten kann es je nach Laufzeit und Version weiterhin eine große Zahl melden — Sie messen nicht das Limit, das der Orchestrator tatsächlich auferlegt hat.
Bevor Sie also eine einzige Prüfung schreiben, entscheiden Sie, welche Zahl Ihre Lizenzeinheit ist: physische Kerne, logische Prozessoren oder die Container-CPU-Quota. Messen Sie dann genau diese gezielt.
Die richtige Zahl messen
Logische Prozessoren (günstig, aber für sich allein am wenigsten aussagekräftig):
int logical = Environment.ProcessorCount;
Physische Kerne unter Windows — summieren Sie NumberOfCores über die 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"]);
Physische Kerne unter Linux — zählen Sie eindeutige (physical id, core id)-Paare:
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);
Container-CPU-Quota — die Zahl, die in Kubernetes/Docker wirklich zählt:
// 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;
}
Wählen Sie eine dieser Zahlen als Ihre gemessene Kapazität — die meisten Serverprodukte legen sich bei Bare-Metal/VM auf physische Kerne und bei Containern auf die cgroup-Quota (aufgerundet) fest.
Das Recht in die Lizenz binden
Messen ist nur die halbe Arbeit; die gemessene Zahl muss gegen etwas geprüft werden, das der Kunde nicht bearbeiten kann. Dieses Etwas ist ein signiertes Recht. Die Lizenz trägt einen maxCores-Wert in ihrem Payload, signiert mit Ihrem privaten Schlüssel, und der Client verifiziert die Signatur, bevor er ihm vertraut (die Mechanik steht in Lizenzrechte und Feature-Flags und in die Anatomie eines signierten Schlüssels). Dann, beim Start:
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.");
}
Zwei Entwurfshinweise, die die Tinte wert sind. Erstens: scheitern Sie geschlossen, aber diagnostizieren Sie. Ein Operator, der Ihren Dienst versehentlich auf einem größeren Knoten eingeplant hat, sollte genau lesen, was passiert ist — gemessen gegen lizenziert — und nicht zusehen, wie der Prozess mit einem generischen Fehler stirbt. Zweitens: Erwägen Sie eine kurze Kulanz oder eine einmalige Warnung, bevor die Durchsetzung zubeißt, damit eine vorübergehende Überprovisionierung während eines Rollouts die Produktion nicht lahmlegt; das spiegelt das Kulanzdenken in Ausfallzeiten des Lizenzservers und Kulanzfenster. Weil die Kernzahl lokal gelesen und das Recht lokal verifiziert wird, funktioniert die gesamte Prüfung offline und bei jedem Start ohne einen Rückruf nach Hause.
Das mit Keyright umsetzen
Keyright lässt Sie ein maxCores-Recht (oder maxLogicalProcessors, oder eine Container-Quota) in die signierte Lizenz legen, und sein .NET-SDK verifiziert diesen Payload gegen einen öffentlichen Schlüssel, den Sie zur Bauzeit einbetten — sodass der Vergleich zwischen lizenzierten und gemessenen Kernen lokal, offline und geschlossen scheiternd läuft, mit der Diagnosemeldung, die Sie wählen. Sie entscheiden über die Zählrichtlinie; Keyright liefert Ihnen die manipulationsresistente Zahl, gegen die Sie sie prüfen. Es ist selbst gehostet, sodass Ihr Signierschlüssel und die Lizenzdaten Ihrer Kunden auf einer Infrastruktur bleiben, die Sie kontrollieren.
Sehen Sie sich die Tarife an, oder starten Sie von Lizenzierung zu einer .NET-App hinzufügen und fügen Sie das Kernzahl-Recht dem Modell hinzu, das Sie bereits haben.
Nebula.NET testen
Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.