Files
awoooi/scripts/ops/deploy-alertmanager-runtime-scrape.sh
ogt 4af6487ba6
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 2s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m31s
CD Pipeline / build-and-deploy (push) Successful in 15m18s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 1s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 22s
CD Pipeline / post-deploy-checks (push) Successful in 5m35s
fix(alerting): align Alertmanager metric schema
2026-07-16 18:01:47 +08:00

420 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
# Bounded Prometheus scrape-source convergence for Alertmanager delivery truth.
set -euo pipefail
TRACE_ID="${TRACE_ID:?TRACE_ID is required}"
RUN_ID="${RUN_ID:?RUN_ID is required}"
WORK_ITEM_ID="${WORK_ITEM_ID:?WORK_ITEM_ID is required}"
TARGET_HOST="${TARGET_HOST:-wooo@192.168.0.110}"
PROMETHEUS_URL="${PROMETHEUS_URL:-http://192.168.0.110:9090}"
ALERTMANAGER_METRICS_URL="${ALERTMANAGER_METRICS_URL:-http://127.0.0.1:9093/metrics}"
PROMETHEUS_CONTAINER="${PROMETHEUS_CONTAINER:-prometheus}"
REMOTE_CONFIG="${REMOTE_CONFIG:-/home/wooo/monitoring/prometheus.yml}"
MODE="${1:---dry-run}"
case "${MODE}" in
apply|--dry-run) ;;
*) printf 'usage: %s [--dry-run|apply]\n' "$0" >&2; exit 64 ;;
esac
for value in "${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}"; do
case "${value}" in
*[!A-Za-z0-9._-]*|'')
printf 'trace/run/work item identifiers must be non-empty and shell-safe\n' >&2
exit 64
;;
esac
done
ssh -o BatchMode=yes -o ConnectTimeout=8 "${TARGET_HOST}" \
bash -s -- "${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${MODE}" \
"${PROMETHEUS_URL}" "${ALERTMANAGER_METRICS_URL}" \
"${PROMETHEUS_CONTAINER}" "${REMOTE_CONFIG}" <<'REMOTE'
set -euo pipefail
TRACE_ID="$1"
RUN_ID="$2"
WORK_ITEM_ID="$3"
MODE="$4"
PROMETHEUS_URL="$5"
ALERTMANAGER_METRICS_URL="$6"
PROMETHEUS_CONTAINER="$7"
REMOTE_CONFIG="$8"
REMOTE_CANDIDATE="/tmp/awoooi-prometheus-alertmanager-runtime.${RUN_ID}.yml"
CONTAINER_CANDIDATE="/etc/prometheus/.awoooi-alertmanager-runtime.${RUN_ID}.yml"
BACKUP_CONFIG="${REMOTE_CONFIG}.bak.${RUN_ID}"
APPLY_STARTED=0
DEPLOY_VERIFIED=0
SOURCE_SHA=""
CANDIDATE_SHA=""
exec 9>/tmp/awoooi-prometheus-config.lock
if ! flock -n 9; then
printf 'terminal trace_id=%s run_id=%s work_item_id=%s status=blocked_with_safe_next_action reason=prometheus_config_apply_in_progress\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" >&2
exit 75
fi
cleanup() {
rm -f "${REMOTE_CANDIDATE}"
docker exec -u 0 "${PROMETHEUS_CONTAINER}" \
rm -f "${CONTAINER_CANDIDATE}" >/dev/null 2>&1 || true
}
rollback() {
local rollback_rc=0
local current_sha backup_sha restored_sha
current_sha="$(sha256sum "${REMOTE_CONFIG}" | awk '{print $1}')" || rollback_rc=1
backup_sha="$(sha256sum "${BACKUP_CONFIG}" | awk '{print $1}')" || rollback_rc=1
if [ "${rollback_rc}" -eq 0 ] && [ "${current_sha}" = "${SOURCE_SHA}" ]; then
printf 'rollback_receipt trace_id=%s run_id=%s work_item_id=%s terminal=no_write_source_intact source_sha=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${SOURCE_SHA}"
return 0
fi
if [ "${rollback_rc}" -ne 0 ] \
|| [ "${current_sha}" != "${CANDIDATE_SHA}" ] \
|| [ "${backup_sha}" != "${SOURCE_SHA}" ]; then
rollback_rc=1
else
cp "${BACKUP_CONFIG}" "${REMOTE_CONFIG}" || rollback_rc=1
restored_sha="$(sha256sum "${REMOTE_CONFIG}" | awk '{print $1}')" || rollback_rc=1
[ "${restored_sha}" = "${SOURCE_SHA}" ] || rollback_rc=1
fi
if [ "${rollback_rc}" -eq 0 ]; then
curl -fsS -X POST "${PROMETHEUS_URL}/-/reload" >/dev/null \
|| rollback_rc=1
fi
if [ "${rollback_rc}" -eq 0 ]; then
curl -fsS "${PROMETHEUS_URL}/-/ready" >/dev/null || rollback_rc=1
fi
if [ "${rollback_rc}" -eq 0 ]; then
printf 'rollback_receipt trace_id=%s run_id=%s work_item_id=%s terminal=rolled_back ready=true backup=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${BACKUP_CONFIG}"
return 0
fi
printf 'rollback_receipt trace_id=%s run_id=%s work_item_id=%s terminal=rollback_failed ready=false backup=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${BACKUP_CONFIG}" >&2
return 1
}
on_exit() {
rc=$?
trap - EXIT
set +e
cleanup
rollback_rc=0
if [ "${rc}" -ne 0 ] && [ "${APPLY_STARTED}" -eq 1 ] \
&& [ "${DEPLOY_VERIFIED}" -eq 0 ]; then
rollback || rollback_rc=$?
fi
if [ "${rollback_rc}" -ne 0 ]; then
exit 70
fi
exit "${rc}"
}
trap on_exit EXIT
test -f "${REMOTE_CONFIG}"
test "$(docker inspect --format '{{.State.Running}}' "${PROMETHEUS_CONTAINER}")" = true
curl -fsS "${PROMETHEUS_URL}/-/ready" >/dev/null
METRICS_SNAPSHOT="$(curl -fsS "${ALERTMANAGER_METRICS_URL}")"
grep -Eq '^alertmanager_notification_requests_total\{[^}]*integration="webhook"' <<<"${METRICS_SNAPSHOT}"
grep -Eq '^alertmanager_notification_requests_failed_total\{[^}]*integration="webhook"' <<<"${METRICS_SNAPSHOT}"
SOURCE_SHA="$(sha256sum "${REMOTE_CONFIG}" | awk '{print $1}')"
python3 - "${REMOTE_CONFIG}" "${REMOTE_CANDIDATE}" <<'PY'
from pathlib import Path
import re
import sys
source = Path(sys.argv[1])
candidate = Path(sys.argv[2])
text = source.read_text(encoding="utf-8")
begin = " # BEGIN AWOOOI ALERTMANAGER RUNTIME DELIVERY TRUTH\n"
end = " # END AWOOOI ALERTMANAGER RUNTIME DELIVERY TRUTH\n"
block = """ # BEGIN AWOOOI ALERTMANAGER RUNTIME DELIVERY TRUTH
- job_name: 'alertmanager-runtime'
scrape_interval: 30s
metrics_path: /metrics
static_configs:
- targets: ['alertmanager:9093']
labels:
host: '110'
service: 'alertmanager'
env: 'prod'
metric_relabel_configs:
- source_labels: [__name__, integration]
separator: ';'
regex: 'alertmanager_notification_requests(_failed)?_total;webhook'
target_label: receiver_contract
replacement: 'awoooi-webhook'
# END AWOOOI ALERTMANAGER RUNTIME DELIVERY TRUTH
"""
if text.count(begin) != text.count(end) or text.count(begin) > 1:
raise SystemExit("alertmanager runtime managed block is ambiguous")
if begin in text:
pattern = re.escape(begin) + r".*?" + re.escape(end) + r"\n?"
text, count = re.subn(pattern, block, text, count=1, flags=re.S)
if count != 1:
raise SystemExit("managed block replacement failed")
else:
legacy_pattern = (
r"(?ms)^ - job_name: ['\"]alertmanager-runtime['\"]\n"
r".*?(?=^ - job_name:|\Z)"
)
legacy_matches = list(re.finditer(legacy_pattern, text))
if len(legacy_matches) == 1:
text = re.sub(legacy_pattern, block, text, count=1)
elif legacy_matches:
raise SystemExit("legacy alertmanager runtime job is ambiguous")
else:
anchors = (
" # === AWOOOI API Metrics",
" - job_name: 'awoooi-api'\n",
)
anchor = next((value for value in anchors if value in text), None)
if anchor is None or text.count(anchor) != 1:
raise SystemExit("awoooi-api insertion anchor missing or ambiguous")
text = text.replace(anchor, block + anchor, 1)
provider_begin = " # BEGIN AWOOOI OLLAMA CLOUD EDGE PROBES\n"
provider_end = " # END AWOOOI OLLAMA CLOUD EDGE PROBES\n"
provider_block = """ # BEGIN AWOOOI OLLAMA CLOUD EDGE PROBES
- targets:
- http://34.143.170.20:11434/api/tags
labels:
service: ollama-cloud-edge
provider: ollama_gcp_a
probe_origin: host110
boundary: cloud_edge_availability_only
- targets:
- http://34.21.145.224:11434/api/tags
labels:
service: ollama-cloud-edge
provider: ollama_gcp_b
probe_origin: host110
boundary: cloud_edge_availability_only
# END AWOOOI OLLAMA CLOUD EDGE PROBES
"""
if (
text.count(provider_begin) != text.count(provider_end)
or text.count(provider_begin) > 1
):
raise SystemExit("Ollama cloud edge managed block is ambiguous")
if provider_begin in text:
provider_pattern = (
re.escape(provider_begin)
+ r".*?"
+ re.escape(provider_end)
)
text, count = re.subn(
provider_pattern,
provider_block,
text,
count=1,
flags=re.S,
)
if count != 1:
raise SystemExit("Ollama cloud edge managed block replacement failed")
else:
blackbox_match = re.search(
r"(?ms)^ - job_name: ['\"]blackbox-http['\"]\n"
r".*?(?=^ - job_name:|\Z)",
text,
)
if blackbox_match is None:
raise SystemExit("blackbox-http job missing")
blackbox_job = blackbox_match.group(0)
if "provider: ollama_gcp_" in blackbox_job:
raise SystemExit("unmanaged Ollama cloud edge targets are ambiguous")
relabel_anchor = " relabel_configs:\n"
if blackbox_job.count(relabel_anchor) != 1:
raise SystemExit("blackbox-http relabel anchor missing or ambiguous")
patched_job = blackbox_job.replace(
relabel_anchor,
provider_block + relabel_anchor,
1,
)
text = text[: blackbox_match.start()] + patched_job + text[blackbox_match.end() :]
if text.count("job_name: 'alertmanager-runtime'") != 1:
raise SystemExit("alertmanager runtime job must exist exactly once")
if "targets: ['alertmanager:9093']" not in text:
raise SystemExit("canonical Alertmanager target missing")
if text.count("provider: ollama_gcp_a") != 1:
raise SystemExit("canonical GCP-A cloud edge target missing or ambiguous")
if text.count("provider: ollama_gcp_b") != 1:
raise SystemExit("canonical GCP-B cloud edge target missing or ambiguous")
if "192.168.0.111:11434" in text:
raise SystemExit("host111 must not be probed from retired host110 boundary")
candidate.write_text(text, encoding="utf-8")
PY
docker cp "${REMOTE_CANDIDATE}" \
"${PROMETHEUS_CONTAINER}:${CONTAINER_CANDIDATE}" >/dev/null
docker exec "${PROMETHEUS_CONTAINER}" promtool check config "${CONTAINER_CANDIDATE}"
CANDIDATE_SHA="$(sha256sum "${REMOTE_CANDIDATE}" | awk '{print $1}')"
test "$(sha256sum "${REMOTE_CONFIG}" | awk '{print $1}')" = "${SOURCE_SHA}"
printf 'check_receipt trace_id=%s run_id=%s work_item_id=%s mode=%s source_sha=%s candidate_sha=%s promtool=pass persistent_writes_performed=0 temporary_validation_artifact=true\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${MODE}" \
"${SOURCE_SHA}" "${CANDIDATE_SHA}"
if [ "${MODE}" = "--dry-run" ]; then
printf 'terminal trace_id=%s run_id=%s work_item_id=%s status=check_pass_no_persistent_write\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}"
exit 0
fi
cp -p "${REMOTE_CONFIG}" "${BACKUP_CONFIG}"
test "$(sha256sum "${BACKUP_CONFIG}" | awk '{print $1}')" = "${SOURCE_SHA}"
test "$(sha256sum "${REMOTE_CONFIG}" | awk '{print $1}')" = "${SOURCE_SHA}"
APPLY_STARTED=1
cp "${REMOTE_CANDIDATE}" "${REMOTE_CONFIG}"
test "$(sha256sum "${REMOTE_CONFIG}" | awk '{print $1}')" = "${CANDIDATE_SHA}"
curl -fsS -X POST "${PROMETHEUS_URL}/-/reload" >/dev/null
curl -fsS "${PROMETHEUS_URL}/-/ready" >/dev/null
printf 'execution_receipt trace_id=%s run_id=%s work_item_id=%s action=alertmanager_runtime_scrape_converged active_sha=%s backup=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${CANDIDATE_SHA}" \
"${BACKUP_CONFIG}"
target_readback() {
curl -fsS "${PROMETHEUS_URL}/api/v1/targets?state=active" | python3 -c '
import json
import sys
targets = json.load(sys.stdin)["data"]["activeTargets"]
matches = [
target for target in targets
if target.get("labels", {}).get("job") == "alertmanager-runtime"
]
if len(matches) != 1:
print(f"{len(matches)}|missing|never")
else:
target = matches[0]
print("{}|{}|{}".format(
len(matches),
target.get("health", "unknown"),
target.get("lastScrape", "never"),
))
'
}
provider_target_readback() {
curl -fsS "${PROMETHEUS_URL}/api/v1/targets?state=active" | python3 -c '
import json
import sys
targets = json.load(sys.stdin)["data"]["activeTargets"]
providers = ("ollama_gcp_a", "ollama_gcp_b")
matches = {
provider: [
target
for target in targets
if target.get("labels", {}).get("job") == "blackbox-http"
and target.get("labels", {}).get("provider") == provider
and target.get("labels", {}).get("probe_origin") == "host110"
]
for provider in providers
}
ready = all(
len(matches[provider]) == 1
and matches[provider][0].get("lastScrape", "never") != "never"
for provider in providers
)
values = []
for provider in providers:
target = matches[provider][0] if len(matches[provider]) == 1 else {}
values.extend([
target.get("lastScrape", "never"),
target.get("health", "missing"),
])
print("|".join(["1" if ready else "0", *values]))
'
}
FIRST_SCRAPE=""
for attempt in $(seq 1 9); do
IFS='|' read -r count health last_scrape <<<"$(target_readback)"
printf 'target_probe trace_id=%s run_id=%s attempt=%s count=%s health=%s last_scrape=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${attempt}" "${count}" "${health}" \
"${last_scrape}"
if [ "${count}" = 1 ] && [ "${health}" = up ] \
&& [ "${last_scrape}" != never ]; then
FIRST_SCRAPE="${last_scrape}"
break
fi
sleep 10
done
test -n "${FIRST_SCRAPE}"
FIRST_GCP_A_SCRAPE=""
FIRST_GCP_B_SCRAPE=""
for attempt in $(seq 1 9); do
IFS='|' read -r providers_ready gcp_a_scrape gcp_a_health \
gcp_b_scrape gcp_b_health <<<"$(provider_target_readback)"
printf 'provider_target_probe trace_id=%s run_id=%s attempt=%s ready=%s gcp_a_health=%s gcp_b_health=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${attempt}" "${providers_ready}" \
"${gcp_a_health}" "${gcp_b_health}"
if [ "${providers_ready}" = 1 ]; then
FIRST_GCP_A_SCRAPE="${gcp_a_scrape}"
FIRST_GCP_B_SCRAPE="${gcp_b_scrape}"
break
fi
sleep 10
done
test -n "${FIRST_GCP_A_SCRAPE}"
test -n "${FIRST_GCP_B_SCRAPE}"
sleep 31
IFS='|' read -r count health SECOND_SCRAPE <<<"$(target_readback)"
test "${count}" = 1
test "${health}" = up
test "${SECOND_SCRAPE}" != "${FIRST_SCRAPE}"
IFS='|' read -r providers_ready SECOND_GCP_A_SCRAPE gcp_a_health \
SECOND_GCP_B_SCRAPE gcp_b_health <<<"$(provider_target_readback)"
test "${providers_ready}" = 1
test "${SECOND_GCP_A_SCRAPE}" != "${FIRST_GCP_A_SCRAPE}"
test "${SECOND_GCP_B_SCRAPE}" != "${FIRST_GCP_B_SCRAPE}"
SERIES_COUNT="$(curl -fsS --get \
--data-urlencode 'query=alertmanager_notification_requests_total{job="alertmanager-runtime",integration="webhook",receiver_contract="awoooi-webhook"}' \
"${PROMETHEUS_URL}/api/v1/query" | python3 -c '
import json
import sys
print(len(json.load(sys.stdin)["data"]["result"]))
')"
test "${SERIES_COUNT}" = 1
FAILED_SERIES_COUNT="$(curl -fsS --get \
--data-urlencode 'query=alertmanager_notification_requests_failed_total{job="alertmanager-runtime",integration="webhook",receiver_contract="awoooi-webhook"}' \
"${PROMETHEUS_URL}/api/v1/query" | python3 -c '
import json
import sys
print(len(json.load(sys.stdin)["data"]["result"]))
')"
test "${FAILED_SERIES_COUNT}" = 1
PROVIDER_SERIES_COUNT="$(curl -fsS --get \
--data-urlencode 'query=probe_success{job="blackbox-http",provider=~"ollama_gcp_a|ollama_gcp_b",probe_origin="host110"}' \
"${PROMETHEUS_URL}/api/v1/query" | python3 -c '
import json
import sys
print(len(json.load(sys.stdin)["data"]["result"]))
')"
test "${PROVIDER_SERIES_COUNT}" = 2
test "$(sha256sum "${REMOTE_CONFIG}" | awk '{print $1}')" = "${CANDIDATE_SHA}"
DEPLOY_VERIFIED=1
printf 'post_verifier trace_id=%s run_id=%s work_item_id=%s terminal=two_scrapes_up first=%s second=%s delivery_series_count=%s failed_series_count=%s provider_series_count=%s gcp_a_health=%s gcp_b_health=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${FIRST_SCRAPE}" \
"${SECOND_SCRAPE}" "${SERIES_COUNT}" "${FAILED_SERIES_COUNT}" \
"${PROVIDER_SERIES_COUNT}" "${gcp_a_health}" "${gcp_b_health}"
printf 'terminal trace_id=%s run_id=%s work_item_id=%s status=completed backup=%s\n' \
"${TRACE_ID}" "${RUN_ID}" "${WORK_ITEM_ID}" "${BACKUP_CONFIG}"
REMOTE