Compare commits
2
Commits
26e6ea5aac
...
8fe1b57173
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fe1b57173 | ||
|
|
d408419c5f |
@@ -21,7 +21,29 @@ 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.
|
||||
|
||||
Vor dem Stoppen des vorhandenen Tasks wird die neue Version separat getestet.
|
||||
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.
|
||||
|
||||
Ab Version 2.2.3 vergleicht der Installer bei jedem Update die exakten
|
||||
Servicenamen der installierten und der vorbereiteten Version. Neue, entfernte
|
||||
oder umbenannte Services führen **vor dem Stoppen des Tasks** zu einem
|
||||
Sicherheitsstopp. Die Installation darf nur über die Checkbox
|
||||
**Service-Rename ist beabsichtigt; Checkmk Service Discovery ist eingeplant**
|
||||
fortgesetzt werden. Der Installer zeigt dabei die entfernten und neuen Namen
|
||||
an. Eine bloß geänderte Ausgabereihenfolge gilt nicht als Rename.
|
||||
|
||||
Beim korrigierenden Wechsel von 2.2.1 mit `BizTalk ACC ...` auf stabile
|
||||
`BizTalk ...`-Namen ist die Änderung beabsichtigt: Checkbox aktivieren und den
|
||||
Servicebestand danach per Discovery abgleichen. Sind in Checkmk bereits die
|
||||
unpräfixierten Services vorhanden, werden damit insbesondere eventuell
|
||||
vorhandene falsche `BizTalk ACC ...`-Services bereinigt.
|
||||
|
||||
Vor dem Stoppen des vorhandenen Tasks wird die neue Version separat getestet
|
||||
und ihr Checkmk-Servicevertrag mit der installierten Version verglichen.
|
||||
Bei normalen Servicekonten prüft der Installer außerdem Kennwort und
|
||||
Batch-Anmelderecht vor der Umschaltung.
|
||||
Der Task wird vor dem Dateitausch deaktiviert und sein Prozessende maximal zehn
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# 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.
|
||||
- Version 2.2.3 ergänzt einen versionsübergreifenden Installer-Schutz: Der
|
||||
tatsächliche Servicevertrag der vorhandenen und der neuen EXE wird vor dem
|
||||
Task-Stopp verglichen. Unbestätigte Renames, neue oder entfernte Services
|
||||
blockieren das Update; die Differenz wird vollständig angezeigt.
|
||||
|
||||
### 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.3 als Update ausführen; Umgebung `ACC` kann bestehen bleiben.
|
||||
Beim Wechsel von der fehlerhaften 2.2.1-Ausgabe `BizTalk ACC ...` auf die
|
||||
stabilen Namen die Rename-/Discovery-Checkbox bewusst aktivieren.
|
||||
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.
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
@@ -0,0 +1,585 @@
|
||||
#!/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.3 | 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.3"
|
||||
|
||||
# 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.3"),
|
||||
("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 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 Suspended Instances" biztalk_suspended_total=0;;;0|... Suspended total=0.',
|
||||
'0 "BizTalk 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_bullet(document, "Der Installer vergleicht bei Updates die exakten Checkmk-Servicenamen und stoppt unbestätigte Änderungen, bevor Task oder Programmdateien angefasst werden.")
|
||||
|
||||
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.3 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()
|
||||
@@ -27,7 +27,13 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
checkmkLocalDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "checkmk", "agent", "local");
|
||||
}
|
||||
|
||||
public void Install(string account, string password, bool isGmsa, string environmentName, Action<string> report)
|
||||
public void Install(
|
||||
string account,
|
||||
string password,
|
||||
bool isGmsa,
|
||||
string environmentName,
|
||||
bool allowServiceNameChange,
|
||||
Action<string> report)
|
||||
{
|
||||
Validate(account, password, isGmsa, environmentName);
|
||||
report = report ?? delegate { };
|
||||
@@ -72,9 +78,21 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
stagedConfig,
|
||||
File.Exists(targetConfig) ? targetConfig : null,
|
||||
environmentName);
|
||||
RunSelfTest(stagedExe);
|
||||
var stagedServiceNames = RunSelfTest(stagedExe);
|
||||
report("Update-Staging validiert. Umgebung=" + (effectiveEnvironment.Length == 0 ? "(keine)" : effectiveEnvironment) + ".");
|
||||
|
||||
if (File.Exists(targetExe))
|
||||
{
|
||||
var installedServiceNames = RunSelfTest(targetExe);
|
||||
var serviceNameChange = EnsureServiceNameCompatibility(
|
||||
installedServiceNames,
|
||||
stagedServiceNames,
|
||||
allowServiceNameChange);
|
||||
report(serviceNameChange.Length == 0
|
||||
? "Checkmk-Servicevertrag unveraendert: keine Service Discovery erforderlich."
|
||||
: "Checkmk-Service-Rename ausdruecklich bestaetigt; Service Discovery erforderlich. " + serviceNameChange);
|
||||
}
|
||||
|
||||
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
|
||||
scheduler.DeleteIfExists(TaskName);
|
||||
taskRemoved = true;
|
||||
@@ -397,7 +415,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
new DirectoryInfo(path).SetAccessControl(security);
|
||||
}
|
||||
|
||||
private static void RunSelfTest(string executable)
|
||||
private static IReadOnlyList<string> RunSelfTest(string executable)
|
||||
{
|
||||
var start = new ProcessStartInfo(executable, "--self-test")
|
||||
{
|
||||
@@ -419,7 +437,45 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
var lines = output.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (process.ExitCode != 0 || lines.Length != 9 || lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
|
||||
throw new InvalidOperationException("Self-Test fehlgeschlagen. Exitcode=" + process.ExitCode + ", Zeilen=" + lines.Length + ". " + error);
|
||||
}
|
||||
|
||||
var serviceNames = lines.Select(ExtractServiceName).ToArray();
|
||||
if (serviceNames.Distinct(StringComparer.Ordinal).Count() != serviceNames.Length)
|
||||
throw new InvalidOperationException("Self-Test fehlgeschlagen: Checkmk-Servicenamen sind nicht eindeutig.");
|
||||
return serviceNames;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ExtractServiceName(string line)
|
||||
{
|
||||
var firstQuote = line.IndexOf('"');
|
||||
var secondQuote = firstQuote < 0 ? -1 : line.IndexOf('"', firstQuote + 1);
|
||||
if (firstQuote < 0 || secondQuote <= firstQuote + 1)
|
||||
throw new InvalidOperationException("Self-Test fehlgeschlagen: Checkmk-Servicename kann nicht gelesen werden: " + line);
|
||||
return line.Substring(firstQuote + 1, secondQuote - firstQuote - 1);
|
||||
}
|
||||
|
||||
internal static string EnsureServiceNameCompatibility(
|
||||
IEnumerable<string> installedServiceNames,
|
||||
IEnumerable<string> stagedServiceNames,
|
||||
bool allowServiceNameChange)
|
||||
{
|
||||
var installed = new HashSet<string>(installedServiceNames ?? Enumerable.Empty<string>(), StringComparer.Ordinal);
|
||||
var staged = new HashSet<string>(stagedServiceNames ?? Enumerable.Empty<string>(), StringComparer.Ordinal);
|
||||
if (installed.SetEquals(staged)) return string.Empty;
|
||||
|
||||
var removed = installed.Except(staged, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||
var added = staged.Except(installed, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||
var description = "Entfernt=[" + string.Join(", ", removed) + "]; Neu=[" + string.Join(", ", added) + "].";
|
||||
if (!allowServiceNameChange)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Sicherheitsstopp: Das Update wuerde Checkmk-Servicenamen aendern. "
|
||||
+ description
|
||||
+ " Ohne Service Discovery entstehen verwaiste bzw. fehlende Services. "
|
||||
+ "Nur wenn die Aenderung beabsichtigt ist, im Setup 'Service Discovery ist eingeplant' bestaetigen und anschliessend die Discovery ausfuehren.");
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
private readonly TextBox password = new TextBox();
|
||||
private readonly ComboBox environment = new ComboBox();
|
||||
private readonly CheckBox gmsa = new CheckBox();
|
||||
private readonly CheckBox allowServiceNameChange = new CheckBox();
|
||||
private readonly Button install = new Button();
|
||||
private readonly Button uninstall = new Button();
|
||||
private readonly TextBox status = new TextBox();
|
||||
@@ -20,8 +21,8 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
{
|
||||
this.engine = engine;
|
||||
Text = "BizTalk Checkmk Pulse Setup";
|
||||
ClientSize = new Size(650, 485);
|
||||
MinimumSize = new Size(666, 524);
|
||||
ClientSize = new Size(650, 535);
|
||||
MinimumSize = new Size(666, 574);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Font = new Font("Segoe UI", 9F);
|
||||
|
||||
@@ -68,18 +69,23 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
if (gmsa.Checked) password.Clear();
|
||||
};
|
||||
|
||||
install.Location = new Point(190, 267);
|
||||
allowServiceNameChange.Location = new Point(190, 262);
|
||||
allowServiceNameChange.Size = new Size(420, 34);
|
||||
allowServiceNameChange.Text = "Service-Rename ist beabsichtigt; Checkmk Service Discovery ist eingeplant";
|
||||
allowServiceNameChange.Enabled = engine.IsInstalled;
|
||||
|
||||
install.Location = new Point(190, 306);
|
||||
install.Size = new Size(200, 34);
|
||||
install.Text = "Installieren / aktualisieren";
|
||||
install.Click += async delegate { await InstallAsync(); };
|
||||
|
||||
uninstall.Location = new Point(410, 267);
|
||||
uninstall.Location = new Point(410, 306);
|
||||
uninstall.Size = new Size(200, 34);
|
||||
uninstall.Text = "Deinstallieren";
|
||||
uninstall.Click += async delegate { await UninstallAsync(); };
|
||||
|
||||
status.Location = new Point(22, 320);
|
||||
status.Size = new Size(588, 135);
|
||||
status.Location = new Point(22, 359);
|
||||
status.Size = new Size(588, 146);
|
||||
status.Multiline = true;
|
||||
status.ReadOnly = true;
|
||||
status.ScrollBars = ScrollBars.Vertical;
|
||||
@@ -89,7 +95,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
|
||||
Controls.AddRange(new Control[]
|
||||
{
|
||||
title, description, account, password, environment, gmsa, install, uninstall, status
|
||||
title, description, account, password, environment, gmsa, allowServiceNameChange, install, uninstall, status
|
||||
});
|
||||
AcceptButton = install;
|
||||
}
|
||||
@@ -114,6 +120,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
collectorPassword,
|
||||
collectorIsGmsa,
|
||||
environmentName,
|
||||
allowServiceNameChange.Checked,
|
||||
Report));
|
||||
password.Clear();
|
||||
Report("Installation abgeschlossen. Der Provider-Task wurde einmalig gestartet.");
|
||||
@@ -166,6 +173,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
account.Enabled = !busy;
|
||||
environment.Enabled = !busy;
|
||||
gmsa.Enabled = !busy;
|
||||
allowServiceNameChange.Enabled = !busy && engine.IsInstalled;
|
||||
password.Enabled = !busy && !gmsa.Checked;
|
||||
UseWaitCursor = busy;
|
||||
if (message != null) Report(message);
|
||||
|
||||
@@ -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.3.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.3.0")]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="suffix">Fachlicher Suffix des Services.</param>
|
||||
/// <returns>Vollstaendiger Service-Name fuer Checkmk.</returns>
|
||||
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);
|
||||
|
||||
@@ -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="
|
||||
|
||||
@@ -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.3.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.3.0")]
|
||||
|
||||
@@ -37,6 +37,13 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
|
||||
Run("EndpointOutputExplainsOnlyRealResolutionGaps", EndpointOutputExplainsOnlyRealResolutionGaps);
|
||||
Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval);
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
Run("EnvironmentLabelDoesNotRenameServicesByDefault", EnvironmentLabelDoesNotRenameServicesByDefault);
|
||||
Run("DefaultServiceContractIsExact", DefaultServiceContractIsExact);
|
||||
Run("InstallerRejectsUnconfirmedServiceRename", InstallerRejectsUnconfirmedServiceRename);
|
||||
Run("InstallerAllowsConfirmedServiceRename", InstallerAllowsConfirmedServiceRename);
|
||||
>>>>>>> e694e4c (Guard Checkmk service renames during updates)
|
||||
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
|
||||
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
||||
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
||||
@@ -683,6 +690,82 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
Assert(configuredMaximum <= 30000, "configured endpoint worst-case must leave headroom in the minute interval");
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
private static void EnvironmentLabelDoesNotRenameServicesByDefault()
|
||||
{
|
||||
var options = new MonitoringOptions { EnvironmentName = "ACC" };
|
||||
var lines = new CheckmkLocalFormatter(options).FormatSelfTest().ToArray();
|
||||
Assert(lines.Any(x => x.Contains("\"BizTalk Platform\"")), "stable service name without environment missing");
|
||||
Assert(lines.All(x => x.IndexOf("\"BizTalk ACC ", StringComparison.Ordinal) < 0), "environment label must not rename services by default");
|
||||
|
||||
options.IncludeEnvironmentInServiceName = true;
|
||||
lines = new CheckmkLocalFormatter(options).FormatSelfTest().ToArray();
|
||||
Assert(lines.Any(x => x.Contains("\"BizTalk ACC Platform\"")), "explicit environment service-name opt-in missing");
|
||||
}
|
||||
|
||||
private static void DefaultServiceContractIsExact()
|
||||
{
|
||||
var actual = new CheckmkLocalFormatter(new MonitoringOptions())
|
||||
.FormatSelfTest()
|
||||
.Select(ExtractQuotedServiceName)
|
||||
.ToArray();
|
||||
var expected = new[]
|
||||
{
|
||||
"BizTalk Platform",
|
||||
"BizTalk SQL Access",
|
||||
"BizTalk Suspended Instances",
|
||||
"BizTalk Host Instances",
|
||||
"BizTalk Receive Locations",
|
||||
"BizTalk Send Ports",
|
||||
"BizTalk Endpoint Reachability",
|
||||
"BizTalk Orchestrations",
|
||||
"BizTalk Event Log"
|
||||
};
|
||||
AssertEqual(string.Join("|", expected), string.Join("|", actual), "exact default service contract");
|
||||
}
|
||||
|
||||
private static void InstallerRejectsUnconfirmedServiceRename()
|
||||
{
|
||||
try
|
||||
{
|
||||
BizTalkCheckmkPulse.Setup.InstallerEngine.EnsureServiceNameCompatibility(
|
||||
new[] { "BizTalk Platform", "BizTalk Event Log" },
|
||||
new[] { "BizTalk ACC Platform", "BizTalk ACC Event Log" },
|
||||
false);
|
||||
throw new InvalidOperationException("unconfirmed rename was accepted");
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Assert(ex.Message.Contains("Sicherheitsstopp"), "rename rejection must explain the safety stop");
|
||||
Assert(ex.Message.Contains("Service Discovery"), "rename rejection must require discovery");
|
||||
}
|
||||
}
|
||||
|
||||
private static void InstallerAllowsConfirmedServiceRename()
|
||||
{
|
||||
var unchanged = BizTalkCheckmkPulse.Setup.InstallerEngine.EnsureServiceNameCompatibility(
|
||||
new[] { "BizTalk Platform", "BizTalk Event Log" },
|
||||
new[] { "BizTalk Event Log", "BizTalk Platform" },
|
||||
false);
|
||||
AssertEqual(string.Empty, unchanged, "service ordering must not be treated as rename");
|
||||
|
||||
var change = BizTalkCheckmkPulse.Setup.InstallerEngine.EnsureServiceNameCompatibility(
|
||||
new[] { "BizTalk ACC Platform" },
|
||||
new[] { "BizTalk Platform" },
|
||||
true);
|
||||
Assert(change.Contains("BizTalk ACC Platform"), "confirmed rename must report removed service");
|
||||
Assert(change.Contains("BizTalk Platform"), "confirmed rename must report new service");
|
||||
}
|
||||
|
||||
private static string ExtractQuotedServiceName(string line)
|
||||
{
|
||||
var start = line.IndexOf('"') + 1;
|
||||
var end = line.IndexOf('"', start);
|
||||
return line.Substring(start, end - start);
|
||||
}
|
||||
|
||||
>>>>>>> e694e4c (Guard Checkmk service renames during updates)
|
||||
private static void InstallerUpdatePreservesExistingSettings()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.InstallerTests." + Guid.NewGuid().ToString("N"));
|
||||
|
||||
Reference in New Issue
Block a user