TP6.1 live: NAS-Sync weicht zurueck -- 40,8 auf 11,0 ms unter Last

Christophs Einwand beim TP6-Abschluss war berechtigt, und er kippt die Erklaerung,
die ich einen Tag lang vertreten habe.

ABNAHME (gleiche NAS-Last, gleicher Messclient, Kontrolle inklusive):
  Priorisierung aus    40,8 ms avg / 53,4 max
  Priorisierung an     11,0 ms avg / 14,6 max     -73 %

Die Last war echt, nicht behauptet: wan-up-total fuhr 9,0 Mbit (max-limit greift
exakt), wan-up-bulk 8,87 Mbit mit 1581 Drops, vlan100 tx 9,0 Mbps. Die Kontrolle
mit abgeschalteter Markierung war noetig, um auszuschliessen, dass die Verbesserung
bloss daher kommt, dass Bulk und Ping jetzt von verschiedenen Hosts stammen.

KORREKTUR AN TP6: dort steht, die 30 ms entstuenden im DOCSIS-Uplink und seien
unabaenderlich. Das ist widerlegt. Der Fehler lag in der METHODE dieser Messung:
Bulk und Messverkehr kamen vom SELBEN Host, also gab es fuer CAKEs triple-isolate
nichts zu trennen -- alles lag in derselben Klasse, und Shapen allein aendert keine
Reihenfolge. Was CAKE nicht schaffte, schafft explizite Klassentrennung.
TP6 traegt jetzt einen Warnhinweis und ist als Messprotokoll zu lesen, nicht als
Ursachenanalyse. Weiter gueltig bleibt: CAKE als EINE Queue ueber allem brachte
nichts (dreimal gemessen), und Interface-HTB ist auf diesem Geraet tot.

GEBAUT (Template [11a], via GC):
  2x mangle postrouting (VOR src-nat -- nur dort ist die LAN-Quelle sichtbar):
     NAS 10.40.20.10 -> bulkup, alles andere -> restup
  wan-up-total (parent=global, max-limit=9M)
     wan-up-rest  priority=1 limit-at=6M
     wan-up-bulk  priority=8 limit-at=1M

Drei Entscheidungen, begruendet im Template:
  - Prioritaet statt Cap: ein Limit haette die NAS auch nachts gebremst und
    Bandbreite verschenkt (DSM drosselt nicht, User-bestaetigt). limit-at=1M
    sorgt dafuer, dass der Sync nie ganz verhungert.
  - src-address=10.40.20.10, NICHT VLAN20: dort haengen auch HA, Nautobot, pve.
  - parent=global, nicht vlan100: Interface-HTB ist hier wirkungslos.

GOVERNANCE: ComplianceRule ip-firewall-mangle angelegt (ingest-mangle-rule.py),
config_ordered=True -- die NAS-Regel muss vor dem Catch-all stehen, sonst waere die
Priorisierung STILL wirkungslos. 26 Rules x 4 Geraete = 104 Checks, 104/104 gruen.

Formatfalle nebenbei: priority=8 ist Default und wird von /export weggelassen --
explizit im Template erzeugt es Dauer-Drift, bei dem das Geraet recht hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 06:38:39 +02:00
parent f15dea6d16
commit 890c9f4869
3 changed files with 215 additions and 42 deletions

122
nautobot/ingest-mangle-rule.py Executable file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""
ComplianceRule `ip-firewall-mangle` anlegen (idempotent).
Die Luecke, die das schliesst
-----------------------------
`/ip firewall mangle` stand unter **keiner** ComplianceRule. Fuer TP6.1 ist das nicht
egal, sondern zentral: die Upload-Priorisierung besteht aus **zwei** Teilen, und der
Queue-Tree allein ist wirkungslos. Faellt die Markierungsregel weg oder driftet sie,
landet aller Verkehr in derselben Klasse -- und **nichts** meldet das, waehrend die
Queue weiter zufrieden aussieht.
Dieselbe blinde Stelle, die TP26 fuer `/ip firewall filter` und TP6 fuer `/queue`
geschlossen hat.
⚠️ `config_ordered=True`, anders als bei `queue`: Mangle ist eine Kette, die von oben
nach unten ausgewertet wird. Die spezifische NAS-Regel MUSS vor dem Catch-all stehen --
sonst bekaeme der Sync die Rest-Prioritaet und die ganze Konstruktion waere still
wirkungslos. Genau wie bei `ip-firewall-filter`.
export NAUTOBOT_TOKEN=... # aus docs/nas-vlan20/stacks/210-nautobot.yml
python3 nautobot/ingest-mangle-rule.py # Dry-Run
python3 nautobot/ingest-mangle-rule.py --apply
"""
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
BASE = "http://10.40.20.30:8080/api"
TOKEN = os.environ.get("NAUTOBOT_TOKEN", "")
APPLY = "--apply" in sys.argv
FEATURE = "ip-firewall-mangle"
BESCHREIBUNG = ("TP6.1: packet-marks fuer die Upload-Priorisierung. Ohne Rule waere die "
"Markierung unsichtbarer Drift -- und ohne sie ist der Queue-Tree wirkungslos.")
MATCH = "/ip firewall mangle"
PLATTFORM = "RouterOS"
def req(method, path, data=None):
url = path if path.startswith("http") else BASE + path
body = json.dumps(data).encode() if data is not None else None
r = urllib.request.Request(url, data=body, method=method)
r.add_header("Authorization", "Token " + TOKEN)
r.add_header("Content-Type", "application/json")
r.add_header("Accept", "application/json")
try:
with urllib.request.urlopen(r, timeout=60) as resp:
raw = resp.read()
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
# 🔴 Fehlerkoerper ausgeben: Nautobot begruendet 400er praezise, und ohne den
# Text raet man an Feldnamen herum (TP22-Erfahrung).
sys.exit("!! %s %s -> %s\n %s" % (method, url, e.code, e.read().decode()[:600]))
def eins(pfad, **filter):
q = urllib.parse.urlencode(filter)
res = req("GET", "%s?%s" % (pfad, q)).get("results", [])
if len(res) > 1:
sys.exit("FEHLER: %s?%s liefert %d Treffer, erwartet 0 oder 1" % (pfad, q, len(res)))
return res[0] if res else None
def main():
if not TOKEN:
sys.exit("NAUTOBOT_TOKEN fehlt.")
print("Modus:", "APPLY" if APPLY else "DRY-RUN (nichts wird geschrieben)")
plat = eins("/dcim/platforms/", name=PLATTFORM)
if not plat:
sys.exit("Plattform %r nicht gefunden." % PLATTFORM)
print("Plattform : %s (%s)" % (plat["display"], plat["id"]))
feat = eins("/plugins/golden-config/compliance-feature/", name=FEATURE)
if feat:
print("Feature : existiert bereits (%s)" % feat["id"])
else:
print("Feature : anlegen -> %s" % FEATURE)
if APPLY:
feat = req("POST", "/plugins/golden-config/compliance-feature/",
{"name": FEATURE, "slug": FEATURE, "description": BESCHREIBUNG})
print(" angelegt (%s)" % feat["id"])
if not feat:
print("\nDry-Run: ohne Feature kann die Rule nicht geprueft werden. Mit --apply erneut.")
return
rule = eins("/plugins/golden-config/compliance-rule/",
feature=feat["id"], platform=plat["id"])
if rule:
print("Rule : existiert bereits (%s), match_config=%r"
% (rule["id"], rule.get("match_config")))
if rule.get("match_config") != MATCH:
print(" ⚠️ match_config weicht ab -- erwartet %r" % MATCH)
return
nutzlast = {
"feature": feat["id"],
"platform": plat["id"],
"description": BESCHREIBUNG,
# Reihenfolge ist SEMANTISCH: die spezifische NAS-Regel muss vor dem
# Catch-all stehen, sonst ist die Priorisierung still wirkungslos.
"config_ordered": True,
"config_type": "cli",
"match_config": MATCH,
"custom_compliance": False,
"config_remediation": False,
}
print("Rule : anlegen -> %s auf %s, match_config=%r" % (FEATURE, PLATTFORM, MATCH))
if APPLY:
r = req("POST", "/plugins/golden-config/compliance-rule/", nutzlast)
print(" angelegt (%s)" % r["id"])
else:
print(" Nutzlast: %s" % json.dumps(nutzlast))
if __name__ == "__main__":
main()

View File

@@ -312,7 +312,46 @@
# ⚖️ Was hier sehr wohl ginge und offen ist: nicht Bufferbloat bekaempfen, sondern
# Saettigung VERMEIDEN (Bulk-Uploads zuruecknehmen, damit ein Call gar nicht erst in
# die Saettigung geraet). Das ist ein anderes Ziel und steht als TP6.1.
# Die ComplianceRule `queue` BLEIBT — sie bewacht diesen Abschnitt weiter als leer.
# Die ComplianceRule `queue` bewacht diesen Abschnitt weiter — jetzt mit dem Inhalt aus [11a].
# [11a] UPLOAD-PRIORISIERUNG (TP6.1, 2026-08-23) — das, was TP6 NICHT konnte.
#
# Der Unterschied in einem Satz: TP6 wollte das Warten ertraeglicher machen und
# scheiterte am DOCSIS-Puffer. Das hier verhindert, dass ueberhaupt gewartet wird.
# Die 42 ms treten NUR unter Saettigung auf — im Leerlauf misst dieselbe Strecke
# 12,9 ms. Ein NAS-Sync, der bei Last zurueckweicht, haelt die Leitung da raus.
#
# 🔴 PRIORITAET, KEIN CAP. Ein festes Limit fuer die NAS wuerde sie auch nachts
# bremsen, wenn die Leitung leer ist — genau die ungenutzte Bandbreite, die
# Christoph zu Recht nicht will (DSM drosselt von sich aus nicht). HTB nimmt dem
# Bulk die Bandbreite nur, solange jemand anders sie braucht.
#
# 🔴 `src-address=10.40.20.10` — die NAS-HOST-IP, bewusst NICHT ganz VLAN20.
# In INFRA haengen auch HA, Nautobot und pve; die gehoeren nicht pauschal nach
# hinten. Wer das je auf 10.40.20.0/24 erweitert, verlangsamt seine Werkzeuge.
#
# 🔴 `parent=global`, NICHT `parent=vlan100`. Interface-HTB ist auf diesem Geraet
# wirkungslos (hw-offloadetes out-interface, s. [11]). Der Global-Weg ist in TP6
# als wirksam BEWIESEN: 9,3 -> 7,78 Mbit bei gesetztem Limit.
#
# ⚠️ Die Markierung MUSS im postrouting stehen und die NAS-Regel VOR dem Catch-all:
# mangle postrouting laeuft VOR src-nat, nur dort ist die LAN-Quelle noch sichtbar
# (nach dem Masquerade ist alles die WAN-IP). Deshalb steht `ip-firewall-mangle`
# auf `config_ordered` — eine vertauschte Reihenfolge waere STILL wirkungslos.
#
# max-limit=9M auf dem Parent: knapp unter der gemessenen Linerate von 9,6 Mbit.
# HTB kann nur verteilen, was es selbst begrenzt — ohne dieses Limit gibt es keine
# Knappheit zu priorisieren. Preis: ~0,6 Mbit Spitze.
# limit-at sind Garantien, keine Grenzen: der Sync verhungert nie ganz (1M), und
# der Rest hat 6M sicher, auch wenn der Sync laeuft.
/ip firewall mangle add action=mark-packet chain=postrouting comment="TP6.1: NAS-Bulk" new-packet-mark=bulkup out-interface=vlan100 passthrough=no src-address=10.40.20.10
/ip firewall mangle add action=mark-packet chain=postrouting comment="TP6.1: Rest (interaktiv)" new-packet-mark=restup out-interface=vlan100 passthrough=no
/queue tree add max-limit=9M name=wan-up-total parent=global
/queue tree add limit-at=6M max-limit=9M name=wan-up-rest packet-mark=restup parent=wan-up-total priority=1
# ⚠️ Bei wan-up-bulk steht KEIN priority=8, obwohl genau das gemeint ist: 8 ist der
# Default, und /export laesst Defaults weg. Ein explizites priority=8 im Template
# erzeugt deshalb einen Dauer-Drift, bei dem das Geraet recht hat.
/queue tree add limit-at=1M max-limit=9M name=wan-up-bulk packet-mark=bulkup parent=wan-up-total
{% elif obj.role.name == "Switch" %}
# =====================================================================================
# L2-Switch (crs326-branch) — Sollzustand des Mgmt-Layers.