diff --git a/deployment/INSTALLATION.md b/deployment/INSTALLATION.md index b5073c0..38caa69 100644 --- a/deployment/INSTALLATION.md +++ b/deployment/INSTALLATION.md @@ -21,6 +21,13 @@ migriert. Abweichende, bewusst konfigurierte Werte bleiben erhalten. `EndpointMaxCount` begrenzt nun eindeutige Socket-Ziele statt Artefakte; der neue Wert `EndpointCatalogMaxEntries=1000` begrenzt separat die Kataloggröße. +Version 2.2.2 ergänzt `IncludeEnvironmentInServiceName=false`. Damit bleibt +`EnvironmentName=ACC` als Umgebungsmetadatum erhalten, ohne die bestehenden +Services von `BizTalk ...` in `BizTalk ACC ...` umzubenennen. Das beseitigt +nach dem nächsten Agentenlauf das 2.2.1-Fehlerbild `Item not found in +monitoring data`. Nur bei bewusst auf `true` gesetztem Opt-in ist anschließend +eine Checkmk Service Discovery erforderlich. + Vor dem Stoppen des vorhandenen Tasks wird die neue Version separat getestet. Bei normalen Servicekonten prüft der Installer außerdem Kennwort und Batch-Anmelderecht vor der Umschaltung. diff --git a/docs/ACC-Endpoint-Reachability-Analyse-2026-08-10.md b/docs/ACC-Endpoint-Reachability-Analyse-2026-08-10.md new file mode 100644 index 0000000..0f602f2 --- /dev/null +++ b/docs/ACC-Endpoint-Reachability-Analyse-2026-08-10.md @@ -0,0 +1,159 @@ +# ACC-Analyse: Checkmk-Datenalter und Endpoint Reachability + +## Befund + +Die am 10.08.2026 bereitgestellten ACC-Artefakte zeigen zwei getrennte +Fehlerbilder. + +### 1. Checkmk findet die neun Services nicht mehr + +Die Checkmk-Ansicht erwartet die bisherigen Namen ohne Umgebung, zum Beispiel: + +```text +BizTalk Endpoint Reachability +BizTalk Platform +BizTalk SQL Access +``` + +Der aktuelle Snapshot enthält dagegen ausschließlich umbenannte Services: + +```text +BizTalk ACC Endpoint Reachability +BizTalk ACC Platform +BizTalk ACC SQL Access +``` + +Damit ist `Item not found in monitoring data` kein Beleg für einen veralteten +oder fehlenden Snapshot. Checkmk sucht nach einem anderen Servicenamen als der +Agent aktuell liefert. Die unterschiedlichen Alterswerte sind die Zeitpunkte, +zu denen Checkmk die jeweiligen alten Namen zuletzt gesehen hat. + +Ursache in Version 2.2.1: `EnvironmentName=ACC` wurde zugleich als +Servicenamensbestandteil verwendet. Das Installerfeld „Umgebung“ konnte damit +bei einem Update den stabilen Checkmk-Vertrag unbeabsichtigt ändern. + +### 2. Endpoint-Probe ist schnell und erfolgreich, wird aber als UNKNOWN formatiert + +Der Snapshot belegt: + +| Wert | ACC-Befund | +| --- | ---: | +| aktive Transportkandidaten laut Katalog | 135 | +| automatisch konfigurierte/prüfbare Artefakte | 64 | +| vollständig getestete Artefakte | 64 | +| erreichbare Artefakte | 64 | +| fehlgeschlagene Artefakte | 0 | +| eindeutige Host-/Port-/Protokoll-Ziele | 17 | +| nicht automatisch auflösbare externe Kandidaten | 61 | +| Probe-Laufzeit | 266 ms | + +Die Netzwerkprobe selbst ist damit weder veraltet noch langsam. Sie +dedupliziert 64 Artefakte auf 17 Netzwerkverbindungen und schließt alle Probes +in deutlich unter einer Sekunde ab. + +Version 2.2.1 behandelt jedoch jeden Wert +`biztalk_endpoints_unsupported > 0` als unvollständiges Ergebnis. Deshalb wird +der Service trotz `available=64` und `failed=0` als `UNKNOWN` ausgegeben. Die +61 Kandidaten fehlen erwartungsgemäß in `endpoints.xml`, weil für sie kein +sicherer und geheimnisfreier Host/Port ermittelt werden konnte. Das ist eine +Abdeckungsinformation, kein Fehler der 64 ausgeführten Probes. + +Aus `135 - 64 - 61` ergeben sich zusätzlich 10 lokale, dynamische oder bewusst +nicht netzwerkprüfbare Kandidaten. Version 2.2.2 weist sie explizit als +`excluded` aus. + +## Korrektur in Version 2.2.2 + +### Stabile Servicenamen + +- `EnvironmentName` bleibt Umgebungsmetadatum für Snapshot und Katalog. +- Standard `IncludeEnvironmentInServiceName=false`: Die neun Namen bleiben + `BizTalk ...` und passen wieder zu den bereits entdeckten ACC-Services. +- Nur ein bewusstes Opt-in mit `true` erzeugt Namen wie `BizTalk ACC ...` und + verlangt anschließend eine Checkmk Service Discovery. + +### Korrekte Abdeckungssemantik + +- Erfolgreich geprüfte Ziele bestimmen den Zustand der Endpoint-Probe. +- Ein fehlgeschlagenes getestetes Ziel bleibt `CRIT`. +- `unsupported` und `excluded` werden als vollständige Metriken ausgegeben, + führen neben erfolgreicher Prüfabdeckung aber nicht mehr zu `UNKNOWN`. +- Wenn externe `unsupported`-Kandidaten existieren und überhaupt kein Ziel + automatisch geprüft werden kann, bleibt der Service bewusst `UNKNOWN`. + +### Sofortige Katalogkonsistenz + +Der Provider vergleicht in jedem Minutenlauf die bereits aus WMI gelesenen +aktiven Kandidaten mit dem Katalog. Folgende Änderungen lösen sofort einen +atomaren Neuabgleich aus: + +- neuer, deaktivierter oder entfernter prüfbarer Artefaktschlüssel, +- geänderter Host, Port oder Protokoll eines vorhandenen Artefakts, +- geänderte Zahl aktiver oder nicht automatisch auflösbarer Kandidaten. + +Manuelle Overrides gelten weiterhin als beabsichtigte Abdeckung und lösen +keine Refresh-Schleife aus. Der 168-Stunden-Abgleich bleibt als zusätzliche +Frischegrenze bestehen. + +### Laufzeitgrenzen + +Die performante Implementierung bleibt unverändert: + +- Deduplizierung nach Protokoll, Host und Port, +- maximal 16 parallele Probes, +- 3 Sekunden Timeout je eindeutiges Ziel, +- maximal 100 automatisch konfigurierte Endpoint-Einträge. + +Der beobachtete ACC-Wert von 266 ms für 17 eindeutige Ziele liegt deutlich im +Minutenbudget. + +## Erwartung nach dem Update + +Der Endpoint-Service sollte bei unverändertem ACC-Zustand sinngemäß liefern: + +```text +0 "BizTalk Endpoint Reachability" ... candidates=135 ... active=64 ... +available=64 ... failed=0 ... unsupported=61 ... excluded=10 ... +unique_targets=17 ... probe_ms= +``` + +Die übrigen fachlichen Zustände aus dem gelieferten Snapshot werden danach in +Checkmk wieder sichtbar und sind getrennt zu bewerten: + +- 3 resumable Suspensions: `WARN`, +- 2 Host Instances mit unbekanntem Status: `CRIT`, +- 22 nicht allowlistete deaktivierte Receive Locations: `CRIT`, +- 17 nicht allowlistete inaktive Send Ports: `CRIT`, +- 2 Errors und 6 Warnings im Event-Log-Zeitfenster: `WARN`. + +Diese Zustände sind nicht die Ursache des Datenalters und werden durch den +Endpoint-/Namensfix bewusst nicht grüngefärbt. + +## ACC-Abnahme nach Installation + +1. Setup 2.2.2 als Update ausführen; Umgebung `ACC` kann bestehen bleiben. +2. In der installierten Config bestätigen: + + ```text + EnvironmentName=ACC + IncludeEnvironmentInServiceName=false + ``` + +3. Scheduled Task manuell starten und `LastTaskResult=0` prüfen. +4. Snapshot zweimal im Abstand von ungefähr einer Minute prüfen; `generatedUtc` + muss fortschreiten und die Namen müssen `BizTalk ...` ohne `ACC` lauten. +5. `BizTalk Endpoint Reachability` muss bei `failed=0` und vorhandener + automatischer Prüfabdeckung `OK` liefern. `unsupported`/`excluded` bleiben + als Metriken sichtbar. +6. Agent-Dump prüfen: + + ```cmd + "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump | findstr /C:"BizTalk " + ``` + +7. Checkmk-Prüfung neu planen. Die bestehenden neun Namen sollten ohne + Umbenennung wieder Daten erhalten. Nur falls Checkmk noch alte + `BizTalk ACC ...`-Services enthält, einmal Service Discovery durchführen + und die bewusst falschen Namen entfernen. +8. `biztalk_endpoint_probe_duration_ms` und `elapsed_ms` über mehrere Läufe + beobachten; der Task darf nicht in das nächste Minutenintervall laufen. diff --git a/docs/BizTalk_Checkmk_Pulse_Architekturueberblick.docx b/docs/BizTalk_Checkmk_Pulse_Architekturueberblick.docx new file mode 100644 index 0000000..37e7de3 Binary files /dev/null and b/docs/BizTalk_Checkmk_Pulse_Architekturueberblick.docx differ diff --git a/docs/architecture.png b/docs/architecture.png new file mode 100644 index 0000000..72585aa Binary files /dev/null and b/docs/architecture.png differ diff --git a/scripts/generate-architecture-overview.py b/scripts/generate-architecture-overview.py new file mode 100644 index 0000000..bce810e --- /dev/null +++ b/scripts/generate-architecture-overview.py @@ -0,0 +1,584 @@ +#!/usr/bin/env python3 +"""Generate the external BizTalk Checkmk Pulse architecture overview as DOCX.""" + +from pathlib import Path + +from docx import Document +from docx.enum.section import WD_SECTION +from docx.enum.table import WD_ALIGN_VERTICAL, WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT +from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Cm, Inches, Pt, RGBColor + + +ROOT = Path(__file__).resolve().parents[1] +DOCS = ROOT / "docs" +OUTPUT = DOCS / "BizTalk_Checkmk_Pulse_Architekturueberblick.docx" +ARCHITECTURE_IMAGE = DOCS / "architecture.png" + +NAVY = "17365D" +BLUE = "24547C" +MID_BLUE = "2B6F9F" +LIGHT_BLUE = "EEF6FB" +LIGHTER_BLUE = "F4F7FA" +GREEN = "16845B" +LIGHT_GREEN = "ECFDF5" +ORANGE = "DD6B20" +LIGHT_ORANGE = "FFF7ED" +TEXT = "1F2937" +MUTED = "5B6770" +GRID = "CBD5E1" +WHITE = "FFFFFF" + + +def set_cell_shading(cell, fill): + tc_pr = cell._tc.get_or_add_tcPr() + shd = tc_pr.find(qn("w:shd")) + if shd is None: + shd = OxmlElement("w:shd") + tc_pr.append(shd) + shd.set(qn("w:fill"), fill) + + +def set_cell_margins(cell, top=120, start=120, bottom=120, end=120): + tc = cell._tc + tc_pr = tc.get_or_add_tcPr() + tc_mar = tc_pr.first_child_found_in("w:tcMar") + if tc_mar is None: + tc_mar = OxmlElement("w:tcMar") + tc_pr.append(tc_mar) + for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)): + node = tc_mar.find(qn(f"w:{margin}")) + if node is None: + node = OxmlElement(f"w:{margin}") + tc_mar.append(node) + node.set(qn("w:w"), str(value)) + node.set(qn("w:type"), "dxa") + + +def set_repeat_table_header(row): + tr_pr = row._tr.get_or_add_trPr() + repeat = OxmlElement("w:tblHeader") + repeat.set(qn("w:val"), "true") + tr_pr.append(repeat) + + +def prevent_row_split(row): + tr_pr = row._tr.get_or_add_trPr() + cant_split = OxmlElement("w:cantSplit") + tr_pr.append(cant_split) + + +def set_table_fixed(table): + table.autofit = False + tbl_pr = table._tbl.tblPr + layout = tbl_pr.find(qn("w:tblLayout")) + if layout is None: + layout = OxmlElement("w:tblLayout") + tbl_pr.append(layout) + layout.set(qn("w:type"), "fixed") + + +def set_paragraph_shading(paragraph, fill, border=None): + p_pr = paragraph._p.get_or_add_pPr() + shd = OxmlElement("w:shd") + shd.set(qn("w:fill"), fill) + p_pr.append(shd) + if border: + p_bdr = OxmlElement("w:pBdr") + left = OxmlElement("w:left") + left.set(qn("w:val"), "single") + left.set(qn("w:sz"), "20") + left.set(qn("w:space"), "8") + left.set(qn("w:color"), border) + p_bdr.append(left) + p_pr.append(p_bdr) + + +def add_bottom_border(paragraph, color="8FB8DC"): + p_pr = paragraph._p.get_or_add_pPr() + p_bdr = OxmlElement("w:pBdr") + bottom = OxmlElement("w:bottom") + bottom.set(qn("w:val"), "single") + bottom.set(qn("w:sz"), "8") + bottom.set(qn("w:space"), "5") + bottom.set(qn("w:color"), color) + p_bdr.append(bottom) + p_pr.append(p_bdr) + + +def set_keep(paragraph, keep_next=False, keep_lines=True): + p_pr = paragraph._p.get_or_add_pPr() + if keep_next: + p_pr.append(OxmlElement("w:keepNext")) + if keep_lines: + p_pr.append(OxmlElement("w:keepLines")) + + +def add_page_field(paragraph): + run = paragraph.add_run() + fld_char_1 = OxmlElement("w:fldChar") + fld_char_1.set(qn("w:fldCharType"), "begin") + instr_text = OxmlElement("w:instrText") + instr_text.set(qn("xml:space"), "preserve") + instr_text.text = " PAGE " + fld_char_2 = OxmlElement("w:fldChar") + fld_char_2.set(qn("w:fldCharType"), "end") + run._r.extend((fld_char_1, instr_text, fld_char_2)) + + +def format_run(run, *, size=None, bold=None, color=None, italic=None, font="Liberation Sans"): + run.font.name = font + run._element.rPr.rFonts.set(qn("w:eastAsia"), font) + if size is not None: + run.font.size = Pt(size) + if bold is not None: + run.bold = bold + if color: + run.font.color.rgb = RGBColor.from_string(color) + if italic is not None: + run.italic = italic + return run + + +def add_rich_paragraph(document, parts, *, style=None, before=0, after=5, align=None): + paragraph = document.add_paragraph(style=style) + paragraph.paragraph_format.space_before = Pt(before) + paragraph.paragraph_format.space_after = Pt(after) + paragraph.paragraph_format.line_spacing = 1.12 + if align is not None: + paragraph.alignment = align + for text, options in parts: + run = paragraph.add_run(text) + format_run(run, **options) + return paragraph + + +def add_body(document, text, *, bold_lead=None, after=5): + paragraph = document.add_paragraph() + paragraph.paragraph_format.space_after = Pt(after) + paragraph.paragraph_format.line_spacing = 1.13 + if bold_lead and text.startswith(bold_lead): + format_run(paragraph.add_run(bold_lead), bold=True, color=TEXT) + format_run(paragraph.add_run(text[len(bold_lead):]), color=TEXT) + else: + format_run(paragraph.add_run(text), color=TEXT) + return paragraph + + +def add_heading(document, text, level=1): + paragraph = document.add_paragraph() + paragraph.paragraph_format.space_before = Pt(12 if level == 1 else 8) + paragraph.paragraph_format.space_after = Pt(6 if level == 1 else 4) + size = 17 if level == 1 else 12 + format_run(paragraph.add_run(text), size=size, bold=True, color=NAVY) + if level == 1: + add_bottom_border(paragraph) + set_keep(paragraph, keep_next=True) + return paragraph + + +def add_bullet(document, text, *, level=0): + paragraph = document.add_paragraph(style="List Bullet" if level == 0 else "List Bullet 2") + paragraph.paragraph_format.left_indent = Cm(0.65 + 0.45 * level) + paragraph.paragraph_format.first_line_indent = Cm(-0.25) + paragraph.paragraph_format.space_after = Pt(2.5) + paragraph.paragraph_format.line_spacing = 1.08 + format_run(paragraph.add_run(text), color=TEXT) + return paragraph + + +def add_numbered(document, text): + paragraph = document.add_paragraph(style="List Number") + paragraph.paragraph_format.left_indent = Cm(0.7) + paragraph.paragraph_format.first_line_indent = Cm(-0.3) + paragraph.paragraph_format.space_after = Pt(3) + paragraph.paragraph_format.line_spacing = 1.08 + format_run(paragraph.add_run(text), color=TEXT) + return paragraph + + +def add_callout(document, label, text, *, fill=LIGHT_BLUE, border=MID_BLUE): + table = document.add_table(rows=1, cols=1) + table.alignment = WD_TABLE_ALIGNMENT.CENTER + table.autofit = False + cell = table.cell(0, 0) + cell.width = Cm(17.2) + set_cell_shading(cell, fill) + set_cell_margins(cell, top=170, start=230, bottom=170, end=230) + paragraph = cell.paragraphs[0] + paragraph.paragraph_format.space_after = Pt(0) + paragraph.paragraph_format.line_spacing = 1.12 + format_run(paragraph.add_run(label), bold=True, color=border) + format_run(paragraph.add_run(text), color=TEXT) + table.rows[0]._tr.get_or_add_trPr().append(OxmlElement("w:cantSplit")) + after = document.add_paragraph() + after.paragraph_format.space_after = Pt(1) + return table + + +def add_table(document, headers, rows, widths, *, font_size=8.7): + table = document.add_table(rows=1, cols=len(headers)) + table.style = "Table Grid" + table.alignment = WD_TABLE_ALIGNMENT.CENTER + set_table_fixed(table) + header = table.rows[0] + set_repeat_table_header(header) + prevent_row_split(header) + for index, title in enumerate(headers): + cell = header.cells[index] + cell.width = Cm(widths[index]) + cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER + set_cell_shading(cell, BLUE) + set_cell_margins(cell, top=100, start=110, bottom=100, end=110) + p = cell.paragraphs[0] + p.paragraph_format.space_after = Pt(0) + format_run(p.add_run(title), bold=True, color=WHITE, size=font_size) + for row_index, values in enumerate(rows): + row = table.add_row() + prevent_row_split(row) + for col_index, value in enumerate(values): + cell = row.cells[col_index] + cell.width = Cm(widths[col_index]) + cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.TOP + set_cell_margins(cell, top=90, start=110, bottom=90, end=110) + if row_index % 2 == 1: + set_cell_shading(cell, LIGHTER_BLUE) + p = cell.paragraphs[0] + p.paragraph_format.space_after = Pt(0) + p.paragraph_format.line_spacing = 1.03 + format_run(p.add_run(str(value)), color=TEXT, size=font_size) + spacer = document.add_paragraph() + spacer.paragraph_format.space_after = Pt(1) + return table + + +def add_code_block(document, lines): + paragraph = document.add_paragraph() + paragraph.paragraph_format.left_indent = Cm(0.15) + paragraph.paragraph_format.right_indent = Cm(0.15) + paragraph.paragraph_format.space_before = Pt(3) + paragraph.paragraph_format.space_after = Pt(7) + paragraph.paragraph_format.line_spacing = 1.0 + set_paragraph_shading(paragraph, "F3F6F8", MID_BLUE) + for index, line in enumerate(lines): + run = paragraph.add_run(line) + format_run(run, font="Liberation Mono", size=7.8, color="0F3E5E") + if index < len(lines) - 1: + run.add_break() + set_keep(paragraph) + return paragraph + + +def configure_document(document): + section = document.sections[0] + section.page_width = Cm(21.0) + section.page_height = Cm(29.7) + section.top_margin = Cm(1.55) + section.bottom_margin = Cm(1.55) + section.left_margin = Cm(1.7) + section.right_margin = Cm(1.7) + section.header_distance = Cm(0.65) + section.footer_distance = Cm(0.65) + section.different_first_page_header_footer = True + + styles = document.styles + normal = styles["Normal"] + normal.font.name = "Liberation Sans" + normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Liberation Sans") + normal.font.size = Pt(9.6) + normal.font.color.rgb = RGBColor.from_string(TEXT) + normal.paragraph_format.space_after = Pt(4) + + for name in ("List Bullet", "List Bullet 2", "List Number"): + styles[name].font.name = "Liberation Sans" + styles[name]._element.rPr.rFonts.set(qn("w:eastAsia"), "Liberation Sans") + styles[name].font.size = Pt(9.3) + + header = section.header + p = header.paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.RIGHT + p.paragraph_format.space_after = Pt(0) + format_run(p.add_run("BizTalk Checkmk Pulse | Architektur- und Lösungsüberblick"), size=7.5, color=MUTED) + + footer = section.footer + p = footer.paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.paragraph_format.space_after = Pt(0) + format_run(p.add_run("Version 2.2.2 | Stand 10.08.2026 | Seite "), size=7.5, color=MUTED) + add_page_field(p) + + +def build_document(): + if not ARCHITECTURE_IMAGE.exists(): + raise FileNotFoundError(f"Architecture image not found: {ARCHITECTURE_IMAGE}") + + document = Document() + configure_document(document) + core = document.core_properties + core.title = "BizTalk Checkmk Pulse – Architektur- und Lösungsüberblick" + core.subject = "Gesamtarchitektur, Funktionsumfang und Datenaustausch" + core.author = "BEW" + core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.2" + + # Cover + banner = document.add_table(rows=1, cols=1) + banner.autofit = False + banner.cell(0, 0).width = Cm(17.3) + set_cell_shading(banner.cell(0, 0), NAVY) + set_cell_margins(banner.cell(0, 0), top=170, start=170, bottom=170, end=170) + p = banner.cell(0, 0).paragraphs[0] + p.paragraph_format.space_after = Pt(0) + format_run(p.add_run("LÖSUNGSÜBERSICHT · EXTERNE DARSTELLUNG"), bold=True, color=WHITE, size=9) + + p = document.add_paragraph() + p.paragraph_format.space_before = Pt(32) + p.paragraph_format.space_after = Pt(10) + format_run(p.add_run("BizTalk Checkmk Pulse"), size=28, bold=True, color=NAVY) + p = document.add_paragraph() + p.paragraph_format.space_after = Pt(26) + format_run(p.add_run("Architektur, Funktionsumfang und Datenaustausch des lokalen Checkmk-Monitorings für Microsoft BizTalk Server"), size=15, color="4B6478") + + add_callout( + document, + "", + "Die Lösung übersetzt den technischen Laufzeitzustand einer BizTalk-Umgebung in neun kompakte Checkmk-Services. Ein dediziertes Konto erfasst die benötigten Daten; der Checkmk-Agent selbst erhält keine BizTalk- oder SQL-Berechtigungen.", + fill=BLUE, + border=WHITE, + ) + # Callout helper uses dark text; force white on the cover callout. + for run in document.tables[-1].cell(0, 0).paragraphs[0].runs: + run.font.color.rgb = RGBColor.from_string(WHITE) + run.font.size = Pt(12.5) + + facts = document.add_table(rows=1, cols=4) + facts.alignment = WD_TABLE_ALIGNMENT.CENTER + set_table_fixed(facts) + for cell, value, label in zip( + facts.rows[0].cells, + ("9", "1 min", "180 s", "Read-only"), + ("stabile Services", "Sammelintervall", "Frischegrenze", "BizTalk-Zugriff"), + ): + cell.width = Cm(4.3) + set_cell_shading(cell, "EAF2F8") + set_cell_margins(cell, top=180, start=80, bottom=180, end=80) + p = cell.paragraphs[0] + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.paragraph_format.space_after = Pt(2) + format_run(p.add_run(value), size=15, bold=True, color=NAVY) + p = cell.add_paragraph() + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.paragraph_format.space_after = Pt(0) + format_run(p.add_run(label), size=8.3, color=TEXT) + + document.add_paragraph().paragraph_format.space_after = Pt(3) + add_table( + document, + ("Dokumentmerkmal", "Angabe"), + ( + ("Lösungsstand", "BizTalk Checkmk Pulse 2.2.2"), + ("Zielplattform", "Microsoft BizTalk Server 2020 · Windows Server 2019 · Checkmk 2.4"), + ("Dokumentstand", "10. August 2026"), + ("Dokumentzweck", "Architektur- und Leistungsüberblick für technische Stakeholder"), + ), + (4.2, 13.0), + font_size=8.8, + ) + add_rich_paragraph( + document, + (("Dieses Dokument beschreibt den implementierten Lösungsstand. Umgebungsspezifische Parameter und die jeweilige Betriebsfreigabe werden im Rollout bestätigt.", {"size": 8.2, "color": MUTED}),), + before=10, + after=0, + ) + document.add_page_break() + + # 1–2: Summary and Checkmk overview + add_heading(document, "1. Zusammenfassung") + add_body(document, "BizTalk Checkmk Pulse ergänzt das zentrale Monitoring um eine BizTalk-spezifische Sicht. Die Lösung erkennt typische Betriebsstörungen – zum Beispiel suspendierte Instanzen, gestoppte Host Instances, unerwartet inaktive Ports, nicht erreichbare externe Ziele oder aktuelle BizTalk-Ereignisse – und stellt sie als eigenständige Services in Checkmk dar.") + add_body(document, "Die Architektur folgt einer klaren Aufgabentrennung:") + add_numbered(document, "Ein minütlicher Windows Scheduled Task sammelt unter einem dedizierten, eingeschränkt berechtigten AD-Servicekonto die BizTalk-Betriebsdaten.") + add_numbered(document, "Die Ergebnisse werden in einen kompakten, versionierten und integritätsgeprüften lokalen Snapshot geschrieben.") + add_numbered(document, "Der Checkmk Windows Agent läuft weiterhin als LocalSystem und liest ausschließlich diesen Snapshot.") + add_numbered(document, "Checkmk übernimmt Status, Metriken und Kurztexte in das zentrale Monitoring.") + add_callout(document, "Nutzen: ", "BizTalk-Störungen werden zentral sichtbar, ohne dem Checkmk-Agenten direkte BizTalk- oder SQL-Rechte zu erteilen. Die Ausgabe bleibt kompakt, graphfähig und alarmierbar.", fill=LIGHT_GREEN, border=GREEN) + + add_heading(document, "2. Was ist Checkmk – und was ist ein Local Check?") + add_body(document, "Checkmk ist eine zentrale Monitoring-Plattform für IT-Infrastrukturen und Anwendungen. Auf überwachten Servern liefert ein Agent technische Zustände und Messwerte an die Checkmk-Instanz. Dort werden die Daten als Hosts und Services dargestellt, historisiert und für Dashboards, Schwellwerte und Benachrichtigungen verwendet.") + add_body(document, "Ein Local Check ist eine bewusst einfache Erweiterungsmöglichkeit des Checkmk-Agenten. Die Prüfung läuft auf dem Zielsystem und gibt pro Service eine Textzeile mit vier Bestandteilen aus:") + add_code_block(document, ('0 "BizTalk PRD Send Ports" biztalk_send_ports_total=31;;;0|biztalk_send_ports_started=31;;;0', 'Alle Send Ports sind aktiv.')) + add_table( + document, + ("Bestandteil", "Bedeutung"), + ( + ("0", "Status: 0 OK, 1 WARN, 2 CRIT, 3 UNKNOWN"), + ('"BizTalk …"', "Eindeutiger Servicename, der in Checkmk angezeigt wird"), + ("biztalk_…=Wert", "Metriken für Schwellwerte, Auswertungen und Zeitreihen"), + ("Kurztext", "Lesbare Zusammenfassung; bei Fehlern mit begrenzter Liste betroffener Objekte"), + ), + (4.0, 13.2), + ) + add_body(document, "Der lokale Check dieser Lösung führt im Agentenpfad keine aufwendige BizTalk-Abfrage aus. Er startet lediglich den Consumer, validiert den vorbereiteten Snapshot und schreibt die bereits erzeugten Checkmk-Zeilen auf die Standardausgabe. Damit bleibt der Agentenaufruf schnell und seine Berechtigungsfläche klein.") + add_rich_paragraph( + document, + ( + ("Referenz: ", {"size": 8.0, "bold": True, "color": MUTED}), + ("Checkmk User Guide, „Local checks“ – https://docs.checkmk.com/latest/en/localchecks.html", {"size": 8.0, "color": MID_BLUE}), + ), + after=0, + ) + document.add_page_break() + + # 3: Architecture + add_heading(document, "3. Gesamtarchitektur") + p = document.add_paragraph() + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.paragraph_format.space_after = Pt(3) + run = p.add_run() + run.add_picture(str(ARCHITECTURE_IMAGE), width=Cm(16.9)) + caption = document.add_paragraph() + caption.alignment = WD_ALIGN_PARAGRAPH.CENTER + caption.paragraph_format.space_after = Pt(6) + format_run(caption.add_run("Abbildung 1: Trennung von privilegierter Datenerfassung, lokalem Datentransport und Checkmk-Consumer"), size=7.8, color=MUTED, italic=True) + add_table( + document, + ("Baustein", "Aufgabe", "Berechtigungsprofil"), + ( + ("Scheduled Task", "Startet den Collector jede Minute und verhindert parallele Läufe.", "Dediziertes AD-Servicekonto, RunLevel Limited"), + ("Collector (--collect)", "Liest BizTalk-WMI, prüft integrierten SQL-Zugriff, Event Log und aktive Endpunkte.", "BizTalk Read-only; kein lokaler Administrator, kein SQL-sysadmin"), + ("Snapshot & Katalog", "Lokaler, ACL-geschützter Übergabepunkt zwischen Collector und Consumer.", "Collector schreibt; LocalSystem liest"), + ("Consumer (--consume)", "Prüft Format, Maschine, Alter, Zeilenanzahl und SHA-256; gibt gültige Checkmk-Zeilen aus.", "LocalSystem; kein WMI-, SQL- oder Endpoint-Zugriff"), + ("Checkmk", "Erkennt neun Services, übernimmt Metriken und löst regelbasiert Benachrichtigungen aus.", "Zentraler Monitoring-Betrieb"), + ), + (3.5, 8.1, 5.6), + font_size=7.9, + ) + add_callout(document, "Fail-safe-Verhalten: ", "Fehlt der Snapshot, ist er älter als standardmäßig 180 Sekunden oder schlägt eine Integritätsprüfung fehl, liefert der Consumer neun gültige UNKNOWN-Services. Ein Transportfehler wird sichtbar und nicht als gesunder Zustand interpretiert.") + add_heading(document, "Architekturprinzipien", level=2) + add_bullet(document, "Least Privilege: Nur das Collector-Konto erhält den erforderlichen lesenden BizTalk-Zugriff.") + add_bullet(document, "Atomare Übergabe: Der Collector ersetzt den Snapshot erst nach vollständigem Schreiben.") + add_bullet(document, "Begrenzte Laufzeit: Timeouts, Parallelität, maximale Endpunktzahl und Textlängen sind konfiguriert.") + add_bullet(document, "Stabile Schnittstelle: Die neun Basisservices bleiben auch bei Fehlern vorhanden.") + add_bullet(document, "Stabile Namen: Die Umgebungskennzeichnung ändert vorhandene Checkmk-Services standardmäßig nicht.") + document.add_page_break() + + # 4: Monitoring scope + add_heading(document, "4. Was überwacht die Lösung?") + add_body(document, "Die Lösung erzeugt standardmäßig neun kompakte Services. Die einzelnen Zustände können in Checkmk separat visualisiert, alarmiert und historisiert werden.") + add_table( + document, + ("Service", "Überwachte Aussage", "Typische Messwerte"), + ( + ("BizTalk Platform", "WMI-Zugang, BizTalk-Gruppe und zentrale Datenbankziele sind ermittelbar.", "Plattform- und Zielinformationen"), + ("BizTalk SQL Access", "Collector-Konto kann die BizTalk-Datenbanken per Windows-Authentifizierung öffnen.", "Ziele gesamt, erreichbar, fehlgeschlagen"), + ("BizTalk Suspended Instances", "Resumable/non-resumable Instanzen und Routing Failure Reports.", "Anzahlen je Kategorie"), + ("BizTalk Host Instances", "Lokale Host Instances laufen oder befinden sich in Stop-/Übergangszuständen.", "Started, stopped, pending, unknown"), + ("BizTalk Receive Locations", "Aktivierungen sowie erwartete oder unerwartete Deaktivierungen.", "Enabled, expected/unexpected disabled"), + ("BizTalk Send Ports", "Gestartete sowie erwartet oder unerwartet inaktive Send Ports.", "Started, stopped, bound, unknown"), + ("BizTalk Endpoint Reachability", "Technische Ziele aktiver Send-/Receive-Artefakte sind über Host/Port erreichbar.", "Aktiv, getestet, erreichbar, fehlerhaft, Dauer"), + ("BizTalk Orchestrations", "Gestartete, gestoppte, gebundene, ungebundene oder unbekannte Zustände.", "Anzahlen je Laufzeitstatus"), + ("BizTalk Event Log", "Aktuelle BizTalk-bezogene Fehler und Warnungen im Application Log.", "Errors und Warnings im Zeitfenster"), + ), + (4.5, 8.2, 4.5), + font_size=8.0, + ) + add_heading(document, "Endpoint-Prüfung", level=2) + add_body(document, "Die Erreichbarkeitsprüfung beschränkt sich auf aktuell gestartete Send Ports und aktivierte Receive Locations. Adressen werden auf Protokoll, Host und Port reduziert, dedupliziert und mit begrenzter Parallelität geprüft. Im gesunden Zustand erscheint nur eine Gesamtaussage; bei Fehlern werden ausschließlich die nicht erreichbaren Ziele begrenzt aufgelistet.") + add_callout(document, "Wichtige Abgrenzung: ", "Die Endpoint-Prüfung ist ein technischer Netzwerkcheck. Sie bestätigt je nach Protokoll DNS, Route, Firewall und einen annehmenden TCP-Port beziehungsweise den lokalen UDP-Versand. Sie führt keine Anmeldung durch, sendet keine BizTalk-Nachricht und bewertet nicht die fachliche Funktion des Zielsystems.", fill=LIGHT_ORANGE, border=ORANGE) + document.add_page_break() + + # 5: Data exchange + add_heading(document, "5. Welche Daten werden ausgetauscht?") + add_body(document, "Der Datenaustausch besteht aus drei klar getrennten Stufen. Verarbeitet werden Betriebs- und Konfigurationsmetadaten, keine fachlichen Nachrichteninhalte.") + add_table( + document, + ("Stufe", "Dateninhalt", "Transport und Schutz"), + ( + ("1. Quellen → Collector", "BizTalk-Gruppe und Datenbankziele; Namen und Laufzeitzustände von Host Instances, suspendierten Instanzen, Receive Locations, Send Ports und Orchestrations; Event-Log-Zähler; Host/Port aktiver Endpunkte.", "Lokale WMI-Abfragen, integrierter SQL-Verbindungstest, lokales Event Log und begrenzte TCP-/UDP-Probes."), + ("2. Collector → Snapshot", "Erzeugungszeit, Quellmaschine, Collector-Identität, Zeilenanzahl, SHA-256 und neun Checkmk-Zeilen mit Status, Metriken und Kurztext.", "Lokale Datei, atomar ersetzt, ACL-geschützt, standardmäßig maximal 1 MiB."), + ("3. Consumer → Checkmk", "Validierte Local-Check-Zeilen: Status, Zähler, Summaries sowie bei Störungen begrenzte Objekt- oder Ziellisten.", "Standardausgabe des Local Checks als Bestandteil der Checkmk-Agentenausgabe."), + ), + (3.7, 8.2, 5.3), + font_size=8.0, + ) + add_heading(document, "Beispiel des lokalen Snapshot-Vertrags", level=2) + add_code_block( + document, + ( + "BIZTALK_CHECKMK_PULSE_SNAPSHOT_V2", + "generatedUtc=2026-08-10T08:15:00.0000000Z", + "machineBase64=QlRaLVBSSC0wMQ==", + "identityBase64=RE9NQUlOXHN2Y19iaXp0YWxrX21vbml0b3Jpbmc=", + "payloadLines=9", + "payloadSha256=<64 hexadezimale Zeichen>", + "", + '0 "BizTalk PRD Suspended Instances" biztalk_suspended_total=0;;;0|... Suspended total=0.', + '0 "BizTalk PRD Endpoint Reachability" biztalk_endpoints_active=61;;;0|... Alle 61 aktiven Endpunkte sind erreichbar.', + ), + ) + add_body(document, "Die Base64-Felder verhindern problematische Trennzeichen im Header; sie sind keine Verschlüsselung. SHA-256 erkennt unvollständige oder veränderte Snapshots. Der Schutz vor unberechtigtem lokalem Zugriff erfolgt über Windows-Dateirechte.") + add_heading(document, "Enthaltene und nicht enthaltene Informationen", level=2) + add_table( + document, + ("Im Monitoring enthalten", "Nicht erhoben oder übertragen"), + ( + ("Technische Status- und Mengeninformationen", "BizTalk-Nachrichten oder fachliche Payloads"), + ("Relevante BizTalk-Anwendungs- und Artefaktnamen", "Passwörter, Tokens oder Endpoint-Zugangsdaten"), + ("DB-Server/-Namen und Ausführungsidentität zur Diagnose", "Vollständige Inhalte der BizTalk-Datenbanken"), + ("Bei Endpoint-Fehlern: Protokoll, Host und Port", "Anmeldungen oder fachliche Requests an Zielsysteme"), + ("Fehlerkategorien und begrenzte Diagnosetexte", "Dateiinhalte von UNC-/SMB-Freigaben"), + ), + (8.6, 8.6), + font_size=8.2, + ) + add_callout(document, "Dateneinordnung: ", "Die Ausgabe enthält technische Betriebsmetadaten. Artefakt-, Server- und Zielnamen können Rückschlüsse auf Integrationen zulassen und sollten innerhalb der bestehenden Zugriffs- und Aufbewahrungsregeln des Monitorings behandelt werden.") + document.add_page_break() + + # 6–7: Operation and conclusion + add_heading(document, "6. Betrieb, Sicherheit und Grenzen") + add_heading(document, "Betriebsmodell", level=2) + add_bullet(document, "Collector jede Minute; parallele Läufe werden verhindert.") + add_bullet(document, "Endpoint-Katalog übernimmt relevante Kandidatenänderungen im nächsten Minutenlauf und wird spätestens wöchentlich vollständig abgeglichen.") + add_bullet(document, "Nicht automatisch prüfbare Adapter bleiben als Abdeckungsmetriken sichtbar, ohne erfolgreiche Netzwerkprobes pauschal zu entwerten.") + add_bullet(document, "Getrennte Tageslogs für Provider und Consumer; Standardaufbewahrung 30 Tage.") + add_bullet(document, "Erwartet inaktive Receive Locations und Send Ports können exakt allowlisted werden und bleiben als Messwert sichtbar.") + add_bullet(document, "Endpoint-Prüfung standardmäßig mit maximal 100 Zielen, 16 parallelen Probes und 3 Sekunden Timeout je Ziel.") + + add_heading(document, "Sicherheitsmodell", level=2) + add_table( + document, + ("Kontrolle", "Wirkung"), + ( + ("Dediziertes Servicekonto", "BizTalk-/SQL-Zugriff ist auf den Collector begrenzt und wird nicht auf alle LocalSystem-Dienste ausgeweitet."), + ("BizTalk Read-Only-Gruppe", "Lesende BizTalk-Rolle; individuelle SQL-Rechte oder SQL-sysadmin sind nicht vorgesehen."), + ("ACL-getrennter Snapshot", "Collector schreibt, LocalSystem liest; Konfiguration und Logs werden getrennt behandelt."), + ("Integritäts- und Altersprüfung", "Falsche Maschine, fehlerhaftes Format, abweichende Prüfsumme oder veraltete Daten führen zu UNKNOWN."), + ("Begrenzte Ausgabe", "Listen und Diagnosen werden gekürzt; vollständige Zähler bleiben als Metriken erhalten."), + ), + (4.6, 12.6), + font_size=8.3, + ) + + add_heading(document, "Bewusste Grenzen", level=2) + add_bullet(document, "Die Lösung ersetzt nicht das allgemeine Checkmk-MSSQL-Plugin und ist kein vollständiges SQL-Monitoring.") + add_bullet(document, "Sie bewertet keine fachliche End-to-End-Verarbeitung einer BizTalk-Schnittstelle.") + add_bullet(document, "Ein erreichbarer Port beweist nicht, dass Anmeldung, Protokoll oder Zielanwendung fachlich funktionieren.") + add_bullet(document, "SHA-256 schützt die Übergabe vor unbemerkten Fehlern, nicht vor einem lokalen Administrator.") + add_bullet(document, "Schwellwerte, Allowlist-Einträge und Benachrichtigungen bleiben Teil der betrieblichen Konfiguration.") + + add_heading(document, "7. Fazit") + add_body(document, "BizTalk Checkmk Pulse schafft eine klar abgegrenzte Brücke zwischen BizTalk Server und Checkmk. Die Lösung liefert eine verständliche, alarmierbare Sicht auf die wichtigsten Laufzeitkomponenten, hält den Checkmk-Agenten von privilegierten Datenquellen fern und macht auch Fehler im Datentransport selbst sichtbar. Der ausgetauschte Datenumfang bleibt auf technische Betriebsmetadaten beschränkt.") + add_callout(document, "Kernaussage: ", "Ein privilegierter Read-only-Collector sammelt, ein unprivilegierter Consumer validiert und übergibt – Checkmk erhält neun stabile Services statt direkten Zugriff auf BizTalk oder SQL.", fill=LIGHT_GREEN, border=GREEN) + add_rich_paragraph( + document, + (("Dokumentbasis: Implementierung und Projektdokumentation von BizTalk Checkmk Pulse 2.2.2 sowie Checkmk User Guide „Local checks“, abgerufen am 10. August 2026.", {"size": 7.8, "color": MUTED}),), + before=8, + after=0, + ) + + document.save(OUTPUT) + print(OUTPUT) + + +if __name__ == "__main__": + build_document() diff --git a/src/BizTalkCheckmkPulse.Setup/Properties/AssemblyInfo.cs b/src/BizTalkCheckmkPulse.Setup/Properties/AssemblyInfo.cs index d147f79..5aa7b40 100644 --- a/src/BizTalkCheckmkPulse.Setup/Properties/AssemblyInfo.cs +++ b/src/BizTalkCheckmkPulse.Setup/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ using System.Runtime.CompilerServices; [assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")] [assembly: AssemblyCompany("BEW")] [assembly: AssemblyProduct("BizTalk Checkmk Pulse")] -[assembly: AssemblyVersion("2.2.1.0")] -[assembly: AssemblyFileVersion("2.2.1.0")] +[assembly: AssemblyVersion("2.2.2.0")] +[assembly: AssemblyFileVersion("2.2.2.0")] diff --git a/src/BizTalkCheckmkPulse/MonitoringOptions.cs b/src/BizTalkCheckmkPulse/MonitoringOptions.cs index 5bc6dbd..5567bde 100644 --- a/src/BizTalkCheckmkPulse/MonitoringOptions.cs +++ b/src/BizTalkCheckmkPulse/MonitoringOptions.cs @@ -15,6 +15,7 @@ namespace BizTalkCheckmkPulse public string Server { get; set; } public string ServicePrefix { get; set; } public string EnvironmentName { get; set; } + public bool IncludeEnvironmentInServiceName { get; set; } public int QueryTimeoutSeconds { get; set; } public bool ProbeSqlConnectivity { get; set; } public int SqlConnectionTimeoutSeconds { get; set; } @@ -57,6 +58,7 @@ namespace BizTalkCheckmkPulse Server = "."; ServicePrefix = "BizTalk"; EnvironmentName = string.Empty; + IncludeEnvironmentInServiceName = false; QueryTimeoutSeconds = 25; ProbeSqlConnectivity = true; SqlConnectionTimeoutSeconds = 5; @@ -92,13 +94,14 @@ namespace BizTalkCheckmkPulse } /// - /// Erzeugt den stabilen Checkmk-Service-Namen mit optionalem Umgebungsnamen. + /// Erzeugt den stabilen Checkmk-Service-Namen. Der Umgebungsname ist nur explizit opt-in, + /// damit ein Installer-Update bestehende Checkmk-Services nicht unbemerkt umbenennt. /// /// Fachlicher Suffix des Services. /// Vollstaendiger Service-Name fuer Checkmk. public string ServiceName(string suffix) { - var prefix = string.IsNullOrWhiteSpace(EnvironmentName) + var prefix = !IncludeEnvironmentInServiceName || string.IsNullOrWhiteSpace(EnvironmentName) ? ServicePrefix : ServicePrefix + " " + EnvironmentName.Trim(); return prefix.Trim() + " " + suffix; @@ -117,6 +120,10 @@ namespace BizTalkCheckmkPulse options.Server = ReadString(settings, "Server", options.Server); options.ServicePrefix = ReadString(settings, "ServicePrefix", options.ServicePrefix); options.EnvironmentName = ReadString(settings, "EnvironmentName", options.EnvironmentName); + options.IncludeEnvironmentInServiceName = ReadBool( + settings, + "IncludeEnvironmentInServiceName", + options.IncludeEnvironmentInServiceName); options.QueryTimeoutSeconds = ReadInt(settings, "QueryTimeoutSeconds", options.QueryTimeoutSeconds, 5, 120); options.ProbeSqlConnectivity = ReadBool(settings, "ProbeSqlConnectivity", options.ProbeSqlConnectivity); options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60); diff --git a/src/BizTalkCheckmkPulse/Program.cs b/src/BizTalkCheckmkPulse/Program.cs index 3934587..2ead3ae 100644 --- a/src/BizTalkCheckmkPulse/Program.cs +++ b/src/BizTalkCheckmkPulse/Program.cs @@ -98,8 +98,14 @@ namespace BizTalkCheckmkPulse + result.SendPorts.Count + " send_ports_not_started=" + result.SendPorts.Count(x => x.Status != 3) + + " endpoint_candidates_active=" + + result.EndpointConnectivity.ActiveCandidates + " endpoints_active=" + result.EndpointConnectivity.Active + + " endpoints_unsupported=" + + result.EndpointConnectivity.UnsupportedActive + + " endpoints_excluded=" + + result.EndpointConnectivity.ExcludedActive + " endpoints_failed=" + result.EndpointConnectivity.Results.Count(x => !x.Available) + " endpoints_unresolved=" diff --git a/src/BizTalkCheckmkPulse/Properties/AssemblyInfo.cs b/src/BizTalkCheckmkPulse/Properties/AssemblyInfo.cs index 1b32362..ff889fc 100644 --- a/src/BizTalkCheckmkPulse/Properties/AssemblyInfo.cs +++ b/src/BizTalkCheckmkPulse/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ using System.Reflection; [assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")] [assembly: AssemblyCompany("BEW")] [assembly: AssemblyProduct("BizTalk Checkmk Pulse")] -[assembly: AssemblyVersion("2.2.1.0")] -[assembly: AssemblyFileVersion("2.2.1.0")] +[assembly: AssemblyVersion("2.2.2.0")] +[assembly: AssemblyFileVersion("2.2.2.0")]