Fix ACC endpoint reachability and stable service names

This commit is contained in:
2026-08-10 18:03:53 +02:00
parent 26e6ea5aac
commit d408419c5f
9 changed files with 769 additions and 6 deletions
+7
View File
@@ -21,6 +21,13 @@ migriert. Abweichende, bewusst konfigurierte Werte bleiben erhalten.
`EndpointMaxCount` begrenzt nun eindeutige Socket-Ziele statt Artefakte; der `EndpointMaxCount` begrenzt nun eindeutige Socket-Ziele statt Artefakte; der
neue Wert `EndpointCatalogMaxEntries=1000` begrenzt separat die Kataloggröße. 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. Vor dem Stoppen des vorhandenen Tasks wird die neue Version separat getestet.
Bei normalen Servicekonten prüft der Installer außerdem Kennwort und Bei normalen Servicekonten prüft der Installer außerdem Kennwort und
Batch-Anmelderecht vor der Umschaltung. Batch-Anmelderecht vor der Umschaltung.
@@ -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=<Laufzeit>
```
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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

+584
View File
@@ -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()
# 12: 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()
# 67: 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()
@@ -6,5 +6,5 @@ using System.Runtime.CompilerServices;
[assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")] [assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")]
[assembly: AssemblyCompany("BEW")] [assembly: AssemblyCompany("BEW")]
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")] [assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
[assembly: AssemblyVersion("2.2.1.0")] [assembly: AssemblyVersion("2.2.2.0")]
[assembly: AssemblyFileVersion("2.2.1.0")] [assembly: AssemblyFileVersion("2.2.2.0")]
+9 -2
View File
@@ -15,6 +15,7 @@ namespace BizTalkCheckmkPulse
public string Server { get; set; } public string Server { get; set; }
public string ServicePrefix { get; set; } public string ServicePrefix { get; set; }
public string EnvironmentName { get; set; } public string EnvironmentName { get; set; }
public bool IncludeEnvironmentInServiceName { get; set; }
public int QueryTimeoutSeconds { get; set; } public int QueryTimeoutSeconds { get; set; }
public bool ProbeSqlConnectivity { get; set; } public bool ProbeSqlConnectivity { get; set; }
public int SqlConnectionTimeoutSeconds { get; set; } public int SqlConnectionTimeoutSeconds { get; set; }
@@ -57,6 +58,7 @@ namespace BizTalkCheckmkPulse
Server = "."; Server = ".";
ServicePrefix = "BizTalk"; ServicePrefix = "BizTalk";
EnvironmentName = string.Empty; EnvironmentName = string.Empty;
IncludeEnvironmentInServiceName = false;
QueryTimeoutSeconds = 25; QueryTimeoutSeconds = 25;
ProbeSqlConnectivity = true; ProbeSqlConnectivity = true;
SqlConnectionTimeoutSeconds = 5; SqlConnectionTimeoutSeconds = 5;
@@ -92,13 +94,14 @@ namespace BizTalkCheckmkPulse
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="suffix">Fachlicher Suffix des Services.</param> /// <param name="suffix">Fachlicher Suffix des Services.</param>
/// <returns>Vollstaendiger Service-Name fuer Checkmk.</returns> /// <returns>Vollstaendiger Service-Name fuer Checkmk.</returns>
public string ServiceName(string suffix) public string ServiceName(string suffix)
{ {
var prefix = string.IsNullOrWhiteSpace(EnvironmentName) var prefix = !IncludeEnvironmentInServiceName || string.IsNullOrWhiteSpace(EnvironmentName)
? ServicePrefix ? ServicePrefix
: ServicePrefix + " " + EnvironmentName.Trim(); : ServicePrefix + " " + EnvironmentName.Trim();
return prefix.Trim() + " " + suffix; return prefix.Trim() + " " + suffix;
@@ -117,6 +120,10 @@ namespace BizTalkCheckmkPulse
options.Server = ReadString(settings, "Server", options.Server); options.Server = ReadString(settings, "Server", options.Server);
options.ServicePrefix = ReadString(settings, "ServicePrefix", options.ServicePrefix); options.ServicePrefix = ReadString(settings, "ServicePrefix", options.ServicePrefix);
options.EnvironmentName = ReadString(settings, "EnvironmentName", options.EnvironmentName); options.EnvironmentName = ReadString(settings, "EnvironmentName", options.EnvironmentName);
options.IncludeEnvironmentInServiceName = ReadBool(
settings,
"IncludeEnvironmentInServiceName",
options.IncludeEnvironmentInServiceName);
options.QueryTimeoutSeconds = ReadInt(settings, "QueryTimeoutSeconds", options.QueryTimeoutSeconds, 5, 120); options.QueryTimeoutSeconds = ReadInt(settings, "QueryTimeoutSeconds", options.QueryTimeoutSeconds, 5, 120);
options.ProbeSqlConnectivity = ReadBool(settings, "ProbeSqlConnectivity", options.ProbeSqlConnectivity); options.ProbeSqlConnectivity = ReadBool(settings, "ProbeSqlConnectivity", options.ProbeSqlConnectivity);
options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60); options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60);
+6
View File
@@ -98,8 +98,14 @@ namespace BizTalkCheckmkPulse
+ result.SendPorts.Count + result.SendPorts.Count
+ " send_ports_not_started=" + " send_ports_not_started="
+ result.SendPorts.Count(x => x.Status != 3) + result.SendPorts.Count(x => x.Status != 3)
+ " endpoint_candidates_active="
+ result.EndpointConnectivity.ActiveCandidates
+ " endpoints_active=" + " endpoints_active="
+ result.EndpointConnectivity.Active + result.EndpointConnectivity.Active
+ " endpoints_unsupported="
+ result.EndpointConnectivity.UnsupportedActive
+ " endpoints_excluded="
+ result.EndpointConnectivity.ExcludedActive
+ " endpoints_failed=" + " endpoints_failed="
+ result.EndpointConnectivity.Results.Count(x => !x.Available) + result.EndpointConnectivity.Results.Count(x => !x.Available)
+ " endpoints_unresolved=" + " endpoints_unresolved="
@@ -6,5 +6,5 @@ using System.Reflection;
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")] [assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
[assembly: AssemblyCompany("BEW")] [assembly: AssemblyCompany("BEW")]
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")] [assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
[assembly: AssemblyVersion("2.2.1.0")] [assembly: AssemblyVersion("2.2.2.0")]
[assembly: AssemblyFileVersion("2.2.1.0")] [assembly: AssemblyFileVersion("2.2.2.0")]