6 Commits

Author SHA1 Message Date
252461c642 [#37] 优化client构建 2025-11-03 14:53:12 +08:00
629aa88550 [#37] 优化client安装包 2025-10-31 15:23:43 +08:00
c0c90a526d [#37] 测试NixOS部署server通过 2025-10-31 14:18:58 +08:00
af7cfbe74b [#37] 构建安装包 2025-10-30 11:21:05 +08:00
3e38770473 [#30] ftp容器增加动态检测并更新dns.conf到share目录 2025-10-29 17:15:48 +08:00
d130924b1a [#30] wsl部署测试;更新README 2025-10-29 16:33:38 +08:00
32 changed files with 1308 additions and 23 deletions

1
deployment/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
artifact/

View File

@@ -0,0 +1,16 @@
# Deployment Build Toolkit
This folder provides scripts to produce offline server/client packages and publish the client package to FTP.
Commands
- build_server_package.sh [--version YYYYMMDD]
- build_client_package.sh [--version YYYYMMDD]
- publish_client.sh --version YYYYMMDD --server <host> --user ftpuser --password <pass> [--port 21]
Outputs
- deployment/artifact/server/<YYYYMMDD>/
- deployment/artifact/client/<YYYYMMDD>/
Notes
- Server package contains docker images (single all-images.tar.gz), compose/, scripts/, docs/, private/ skeleton.
- Client package reuses all-in-one-full artifact, repacked as argus-metric_<YYYYMMDD>.tar.gz (compatible with setup.sh).

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BUILD_DIR="$ROOT_DIR/deployment/build"
ART_ROOT="$ROOT_DIR/deployment/artifact"
. "$BUILD_DIR/common.sh"
usage() { cat <<'EOF'
Build Argus Client Offline Package
Usage: build_client_package.sh [--version YYYYMMDD] [--out DIR]
Produces: deployment/artifact/client/<YYYYMMDD>/argus-metric_<YYYYMMDD>.tar.gz
EOF
}
VERSION="$(today_version)"
OUT_DIR=""
while [[ $# -gt 0 ]]; do
case "$1" in
--version) VERSION="$2"; shift 2;;
--out) OUT_DIR="$2"; shift 2;;
-h|--help) usage; exit 0;;
*) err "unknown arg: $1"; usage; exit 1;;
esac
done
PKG_DIR="${OUT_DIR:-$ART_ROOT/client/$VERSION}"
make_dir "$PKG_DIR"
log "Packaging client from all-in-one-full artifact"
PLUGIN_DIR="$ROOT_DIR/src/metric/client-plugins/all-in-one-full"
require_cmd bash tar gzip
(cd "$PLUGIN_DIR" && bash scripts/package_artifact.sh --force)
# pick latest artifact dir
ART_BASE="$PLUGIN_DIR/artifact"
latest_dir=$(ls -1dt "$ART_BASE"/*/ 2>/dev/null | head -n1 || true)
[[ -n "$latest_dir" ]] || { err "no client artifact found in $ART_BASE"; exit 1; }
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
# Filter-only copy: keep install_order files + scripts + deps + version.json
mkdir -p "$tmpdir/src"
cp -f "$latest_dir/version.json" "$tmpdir/src/version.json"
if command -v jq >/dev/null 2>&1; then
mapfile -t files < <(jq -r '.install_order[]' "$latest_dir/version.json")
else
files=( $(grep -E '"install_order"' -A10 "$latest_dir/version.json" | sed -n 's/\s*"\(.*\.tar\.gz\)".*/\1/p') )
fi
for f in "${files[@]}"; do
[[ -f "$latest_dir/$f" ]] && cp -f "$latest_dir/$f" "$tmpdir/src/$f"
done
for aux in install.sh uninstall.sh check_health.sh check_version.sh sync_dns.sh restart_unhealthy.sh config.env; do
[[ -f "$latest_dir/$aux" ]] && cp -f "$latest_dir/$aux" "$tmpdir/src/$aux";
done
if [[ -d "$latest_dir/deps" ]]; then
mkdir -p "$tmpdir/src/deps" && rsync -a "$latest_dir/deps/" "$tmpdir/src/deps/" >/dev/null 2>&1 || cp -r "$latest_dir/deps/." "$tmpdir/src/deps/";
fi
out_name="argus-metric_$(echo "$VERSION" | sed 's/\./_/g').tar.gz"
(cd "$tmpdir/src" && tar -czf "$PKG_DIR/$out_name" .)
log "Client package ready: $PKG_DIR/$out_name"
echo "$VERSION" > "$PKG_DIR/LATEST_VERSION"
# include publish helper and setup.sh for convenience
PUBLISH_TPL="$BUILD_DIR/templates/client/publish.sh"
if [[ -f "$PUBLISH_TPL" ]]; then
cp "$PUBLISH_TPL" "$PKG_DIR/publish.sh" && chmod +x "$PKG_DIR/publish.sh"
fi
# also place a copy of setup.sh alongside
SETUP_SRC="$ROOT_DIR/src/metric/client-plugins/all-in-one-full/scripts/setup.sh"
[[ -f "$SETUP_SRC" ]] && cp "$SETUP_SRC" "$PKG_DIR/setup.sh" || true
# docs for end users
CLIENT_DOC_DIR="$BUILD_DIR/templates/client"
if [[ -d "$CLIENT_DOC_DIR" ]]; then
rsync -a "$CLIENT_DOC_DIR/" "$PKG_DIR/" >/dev/null 2>&1 || cp -r "$CLIENT_DOC_DIR/." "$PKG_DIR/"
fi
exit 0

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BUILD_DIR="$ROOT_DIR/deployment/build"
ART_ROOT="$ROOT_DIR/deployment/artifact"
. "$BUILD_DIR/common.sh"
usage() { cat <<'EOF'
Build Argus Server Offline Package
Usage: build_server_package.sh [--version YYYYMMDD] [--out DIR] [--resave-image]
Outputs into deployment/artifact/server/<YYYYMMDD>/ by default.
EOF
}
VERSION="$(today_version)"
OUT_DIR=""
RESAVE_IMAGE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--version) VERSION="$2"; shift 2;;
--out) OUT_DIR="$2"; shift 2;;
--resave-image) RESAVE_IMAGE=true; shift;;
-h|--help) usage; exit 0;;
*) err "unknown arg: $1"; usage; exit 1;;
esac
done
PKG_DIR="${OUT_DIR:-$ART_ROOT/server/$VERSION}"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
log "Version: $VERSION"
log "Staging: $STAGE"
# 1) Layout
make_dir "$STAGE/images"
make_dir "$STAGE/compose"
make_dir "$STAGE/scripts"
make_dir "$STAGE/docs"
make_dir "$STAGE/private/argus"
# 2) Compose: derive from sys/tests by removing test-only services
SRC_COMPOSE="$ROOT_DIR/src/sys/tests/docker-compose.yml"
[[ -f "$SRC_COMPOSE" ]] || { err "missing $SRC_COMPOSE"; exit 1; }
awk -f "$BUILD_DIR/templates/docker-compose.filter.awk" -v remove="node-a,node-b,test-node,test-gpu-node" "$SRC_COMPOSE" > "$STAGE/compose/docker-compose.yml"
cp "$BUILD_DIR/templates/.env.example" "$STAGE/compose/.env.example"
# fix relative private path to match package layout (compose/ and private/ are siblings)
sed -i 's#\./private/#../private/#g' "$STAGE/compose/docker-compose.yml"
# also handle bind mount form without trailing slash
sed -i -E 's#- \./private:/private#- ../private:/private#g' "$STAGE/compose/docker-compose.yml"
# drop timezone file bind which may not exist on target distros (e.g. NixOS)
sed -i '/\/etc\/timezone:\/etc\/timezone:ro/d' "$STAGE/compose/docker-compose.yml"
# sanity-check: ensure test services are absent
if grep -E '^(\s{2})(node-a|node-b|test-node|test-gpu-node):\s*$' -n "$STAGE/compose/docker-compose.yml" >/dev/null; then
err "compose filter failed: test services still present"; exit 1;
fi
# 3) Images (reuse if already exported unless --resave-image)
existing_images_tar="$PKG_DIR/images/all-images.tar.gz"
if [[ "$RESAVE_IMAGE" == false && -f "$existing_images_tar" ]]; then
log "Reusing existing images tar: $existing_images_tar"
cp "$existing_images_tar" "$STAGE/images/"
else
require_cmd docker gzip
images=(
argus-bind9:latest
argus-master:latest
argus-elasticsearch:latest
argus-kibana:latest
argus-metric-ftp:latest
argus-metric-prometheus:latest
argus-metric-grafana:latest
argus-alertmanager:latest
argus-web-frontend:latest
argus-web-proxy:latest
)
log "Saving images: ${#images[@]}"
tarfile="$STAGE/images/all-images.tar"
docker save -o "$tarfile" "${images[@]}"
gzip -f "$tarfile"
fi
# 4) Scripts & Docs
copy_tree "$BUILD_DIR/templates/scripts" "$STAGE/scripts"
copy_tree "$BUILD_DIR/templates/docs" "$STAGE/docs"
# 5) Manifests
gen_manifest "$STAGE" "$STAGE/manifest.txt"
checksum_dir "$STAGE" "$STAGE/checksums.txt"
# 6) Move to artifact
make_dir "$PKG_DIR"
rsync -a "$STAGE/" "$PKG_DIR/" 2>/dev/null || cp -r "$STAGE/." "$PKG_DIR/"
log "Server package ready: $PKG_DIR"
echo "$VERSION" > "$PKG_DIR/version.json"
# 7) Create distributable tarball
OUT_TAR_DIR="$(dirname "$PKG_DIR")"
OUT_TAR="$OUT_TAR_DIR/server_${VERSION}.tar.gz"
log "Creating tarball: $OUT_TAR"
(cd "$PKG_DIR/.." && tar -czf "$OUT_TAR" "$(basename "$PKG_DIR")")
log "Tarball ready: $OUT_TAR"
exit 0

33
deployment/build/common.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
log() { echo -e "\033[0;34m[INFO]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
err() { echo -e "\033[0;31m[ERR ]\033[0m $*" >&2; }
require_cmd() {
for c in "$@"; do
command -v "$c" >/dev/null 2>&1 || { err "missing command: $c"; exit 1; }
done
}
today_version() {
date +%Y%m%d
}
checksum_dir() {
local dir="$1"; local out="$2"; : > "$out";
(cd "$dir" && find . -type f -print0 | sort -z | xargs -0 sha256sum) >> "$out"
}
make_dir() { mkdir -p "$1"; }
copy_tree() {
local src="$1" dst="$2"; rsync -a --delete "$src/" "$dst/" 2>/dev/null || cp -r "$src/." "$dst/";
}
gen_manifest() {
local root="$1"; local out="$2"; : > "$out";
(cd "$root" && find . -maxdepth 3 -type f -printf "%p\n" | sort) >> "$out"
}

View File

@@ -0,0 +1,29 @@
# UID/GID for service processes
ARGUS_BUILD_UID=1000
ARGUS_BUILD_GID=1000
# Host ports (adjust if occupied)
MASTER_PORT=32300
ES_HTTP_PORT=9200
KIBANA_PORT=5601
NODE_A_PORT=2020
NODE_B_PORT=2021
PROMETHEUS_PORT=9090
GRAFANA_PORT=3000
ALERTMANAGER_PORT=9093
WEB_PROXY_PORT_8080=8080
WEB_PROXY_PORT_8081=8081
WEB_PROXY_PORT_8082=8082
WEB_PROXY_PORT_8083=8083
WEB_PROXY_PORT_8084=8084
WEB_PROXY_PORT_8085=8085
# FTP
FTP_PORT=21
FTP_DATA_PORT=20
FTP_PASSIVE_HOST_RANGE=21100-21110
FTP_PASSWORD=ZGClab1234!
FTP_DOMAIN=ftp.metric.argus.com
# GPU profile disabled by default
ENABLE_GPU=false

View File

@@ -0,0 +1,37 @@
# Argus Metric 客户端安装指南(容器内普通用户场景)
## 准备与连通性检查
- FTP 连接(需要账号密码,默认 `ftpuser / ZGClab1234!`)
- `curl -u ftpuser:ZGClab1234! -I ftp://<FTP_IP>:21/LATEST_VERSION`
- `curl -u ftpuser:ZGClab1234! -s ftp://<FTP_IP>:21/ | head`
- 下载安装脚本
- `curl -u ftpuser:ZGClab1234! -fsSL ftp://<FTP_IP>:21/setup.sh -o /tmp/setup.sh`
- `chmod +x /tmp/setup.sh`
## 元数据与主机名
- Agent 需要元数据(env/user/instance)与 Master 地址:
- 方式A:hostname 形如 `env-user-instance-xxx`(推荐)
- 方式B:导出环境变量:
- `export AGENT_ENV=dev`
- `export AGENT_USER=<your_user>`
- `export AGENT_INSTANCE=<node_id>`
- Master 地址:
- `export MASTER_ENDPOINT=http://master.argus.com:3000`
> 安装脚本会在开头检查上述条件,缺失时会终止并给出修复提示。
## 执行安装
- 以 root 运行(容器内如为非 root 用户请切换为 root):
- `sudo /tmp/setup.sh --server <FTP_IP> --user ftpuser --password 'ZGClab1234!' --port 21`
- 如需自定义安装根目录:`--install-dir /opt/argus-metric`
## 安装后自检(setup 自动执行)
- setup 会等待最多 5 分钟,确认以下条件后才报告完成:
- `/private/argus/agent/<hostname>/node.json` 已生成;
- `last_report` 在持续更新;
- `health.metric-argus-agent|metric-node-exporter|metric-fluent-bit|metric-dcgm-exporter` 均为 `healthy` 且 `error` 为空。
## 手工验证(可选)
- `cat /private/argus/agent/$(hostname)/node.json | jq '.'`
- `curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9100/metrics` 应为 200
- 查看日志:`/var/log/argus-agent.log`、`/opt/argus-metric/versions/*/.install.log`

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
usage() { cat <<'EOF'
Publish Argus client package to FTP
Usage:
./publish.sh --server HOST --user USER --password PASS [--port 21]
Notes:
- This script expects to run inside the built client artifact directory.
- It reads LATEST_VERSION and uploads setup.sh, argus-metric_<ver>.tar.gz, and LATEST_VERSION.
EOF
}
HOST=""; USERNAME=""; PASSWORD=""; PORT=21
while [[ $# -gt 0 ]]; do
case "$1" in
--server) HOST="$2"; shift 2;;
--user) USERNAME="$2"; shift 2;;
--password) PASSWORD="$2"; shift 2;;
--port) PORT="$2"; shift 2;;
-h|--help) usage; exit 0;;
*) echo "unknown arg: $1" >&2; usage; exit 1;;
esac
done
[[ -n "$HOST" && -n "$USERNAME" && -n "$PASSWORD" ]] || { usage; exit 1; }
here="$(pwd)"
if [[ ! -f "$here/LATEST_VERSION" ]]; then
echo "LATEST_VERSION not found in $(pwd)" >&2; exit 1;
fi
VER=$(cat "$here/LATEST_VERSION" | tr -d '\n')
PKG="argus-metric_${VER}.tar.gz"
if [[ ! -f "$here/$PKG" ]]; then
echo "client tar not found: $PKG" >&2; exit 1
fi
# locate setup.sh (prefer colocated, fallback to bundled path if provided)
SETUP="${here}/setup.sh"
if [[ ! -f "$SETUP" ]]; then
echo "setup.sh not found in $(pwd)" >&2; exit 1
fi
echo "[PUBLISH] server=$HOST port=$PORT version=$VER"
curl -u "$USERNAME:$PASSWORD" -sfT "$SETUP" "ftp://$HOST:$PORT/setup.sh"
curl -u "$USERNAME:$PASSWORD" -sfT "$PKG" "ftp://$HOST:$PORT/$PKG"
printf "%s" "$VER" | curl -u "$USERNAME:$PASSWORD" -sfT - "ftp://$HOST:$PORT/LATEST_VERSION"
echo "[OK] publish completed"

View File

@@ -0,0 +1,41 @@
#!/usr/bin/awk -f
# Remove specific service blocks from a docker-compose.yml by service name.
# Usage: awk -f docker-compose.filter.awk -v remove="node-a,node-b,test-node,test-gpu-node" input.yml > output.yml
BEGIN{
split(remove, rm, ",");
for(i in rm){
gsub(/^\s+|\s+$/,"",rm[i]);
if (rm[i] != "") skipname[rm[i]] = 1;
}
in_services=0; skipping=0;
}
function service_header(line, m) {
# match exactly two leading spaces followed by name:
if (match(line, /^ ([A-Za-z0-9_-]+):[ ]*$/, m)) return m[1];
return "";
}
{
# Track top-level sections (no indentation)
if ($0 ~ /^[A-Za-z0-9_-]+:[ ]*$/ && $0 !~ /^\s/) {
in_services = ($0 ~ /^services:[ ]*$/) ? 1 : 0;
}
if (skipping) {
# Stop skipping at next service header or another top-level section
if (service_header($0) != "" || ($0 ~ /^(networks|volumes):[ ]*$/ && $0 !~ /^\s/)) {
skipping=0;
} else {
next;
}
}
if (in_services) {
name = service_header($0);
if (name != "" && (name in skipname)) { skipping=1; next; }
}
print;
}

View File

@@ -0,0 +1,48 @@
# Argus Server Offline Installation
## Prerequisites
- Linux x86_64 (Ubuntu 22.04 recommended; see OS compatibility for NixOS)
- Docker & Docker Compose installed
- Open ports: 32300, 9200, 5601, 9090, 9093, 8080..8085, 21, 20, 21100–21110 (or auto-fallback to high ports)
## Quick Start
1. Extract to a target dir, e.g. `/opt/argus-deploy/versions/<YYYYMMDD>`
2. `cd scripts && sudo ./server-prepare-dirs.sh`
3. `./server-install.sh`
4. `./server-status.sh`
5. `./server-selfcheck.sh` (on failure it auto-runs diagnose and writes logs under `logs/`)
6. `./server-uninstall.sh` to tear down
## What the Installer Does
- Loads local images (`images/all-images.tar.gz`)
- Generates OS-compat override (`security_opt: ["label=disable"]`, `userns_mode: host`, bind `tmpfs:/run/named`)
- Starts server-only services: bind/master/es/kibana/ftp/prometheus/grafana/alertmanager/web-frontend/web-proxy
- DNS Bootstrap:
- Ensure `/private/argus/etc/dns.conf` exists (write `172.31.0.2` if missing);
- Run `/private/argus/etc/update-dns.sh` in dependent containers so `/etc/resolv.conf` points to bind;
- Wait for `*.argus.com` hint files, then reload bind;
- Restart web‑proxy to re-render nginx resolver from `dns.conf`;
- Writes `logs/selfcheck.json` as final summary
## OS Compatibility
- NixOS / non-xattr FS: containers run with `security_opt: ["label=disable"]` and `userns_mode: host`; ensure data dirs are pre-created via `sudo ./server-prepare-dirs.sh` and owned by runtime UID:GID (default 1000:1000).
## Files & Layout
- `compose/` (docker-compose.yml, .env)
- `private/` (data mounts)
- `scripts/` (install/uninstall/status/selfcheck/diagnose)
- `logs/` (selfcheck + diagnose outputs)
## Troubleshooting (Quick)
- Run `./server-selfcheck.sh` → see `logs/selfcheck.json`
- Run `./server-diagnose.sh` → produces timestamped logs:
- `logs/diagnose_details_YYYYMMDD-HHMMSSZ.log`
- `logs/diagnose_error_YYYYMMDD-HHMMSSZ.log`
And updates `diagnose_details.log`/`diagnose_error.log` to the latest
- Error lines are tagged `[service][source]`, e.g. `[kibana][http] /api/status=503`
Common issues:
- Kibana 503: wait cold start or fix DNS so `es.log.argus.com` resolves
- web‑proxy 504: check nginx `resolver` includes `172.31.0.2 127.0.0.11`
- EACCES/locks: ensure `sudo ./server-prepare-dirs.sh` ran and ownership matches UID:GID

View File

@@ -0,0 +1,28 @@
# Argus 服务端离线安装指南
## 先决条件
- Linux x86_64(推荐 Ubuntu 22.04;NixOS 见“兼容说明”)
- 已安装 Docker 与 Docker Compose
- 端口:32300, 9200, 5601, 9090, 9093, 8080..8085, 21, 20, 21100–21110
## 快速开始
1. 解压到目标目录(例如 `/opt/argus-deploy/versions/<YYYYMMDD>`)
2. 进入 `scripts/`:`sudo ./server-prepare-dirs.sh`
3. 安装:`./server-install.sh`
4. 状态:`./server-status.sh`
5. 自检:`./server-selfcheck.sh`(失败会自动采集诊断)
6. 卸载:`./server-uninstall.sh`
## 安装流程要点
- 仅启动 10 个服务端组件(不包含测试节点);
- DNS Bootstrap:补齐首次部署 DNS 依赖(生成/确认 `dns.conf`、统一容器 resolv.conf、写入 `*.argus.com`、reload bind、重启 web‑proxy);
- 输出自检结果到 `logs/selfcheck.json`。
## 兼容说明(NixOS 等)
- 使用 `security_opt: ["label=disable"]` 与 `userns_mode: host`;
- 先运行 `sudo ./server-prepare-dirs.sh` 创建/授权目录为 `1000:1000`;
## 故障排查(见下文 Troubleshooting_zh)
- `./server-selfcheck.sh` → `logs/selfcheck.json`
- `./server-diagnose.sh` → `logs/diagnose_error_*.log` / `logs/diagnose_details_*.log`

View File

@@ -0,0 +1,21 @@
# Troubleshooting
- Status: `scripts/server-status.sh`
- Selfcheck: `scripts/server-selfcheck.sh`
- Diagnose: `scripts/server-diagnose.sh`
Outputs:
- `logs/selfcheck.json`
- `logs/diagnose_details_*.log` (full details)
- `logs/diagnose_error_*.log` (tagged errors)
Web‑Proxy:
- 8083 expects 200/302/403; 8084/8085 must include CORS header
- nginx resolver should be `172.31.0.2 127.0.0.11`
Kibana/ES:
- Verify `es.log.argus.com` resolves inside Kibana
Permissions:
- Ensure `sudo ./server-prepare-dirs.sh` ran and ownership matches runtime UID:GID

View File

@@ -0,0 +1,15 @@
# 故障排查
- 状态:`scripts/server-status.sh`
- 自检:`scripts/server-selfcheck.sh`
- 诊断:`scripts/server-diagnose.sh`
输出:
- `logs/selfcheck.json`
- `logs/diagnose_error_*.log`(错误摘要)
- `logs/diagnose_details_*.log`(详细信息)
Web‑Proxy:8083=200/302/403;8084/8085 需包含 CORS
Kibana:确认可解析 `es.log.argus.com`
权限:先运行 `sudo ./server-prepare-dirs.sh`

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ENV_FILE="$ROOT/compose/.env"; [[ -f "$ENV_FILE" ]] && set -a && source "$ENV_FILE" && set +a
mkdir -p "$ROOT/logs"
ts="$(date -u +%Y%m%d-%H%M%SZ)"
DETAILS="$ROOT/logs/diagnose_details_${ts}.log"
ERRORS="$ROOT/logs/diagnose_error_${ts}.log"
: > "$DETAILS"; : > "$ERRORS"
logd() { echo "$(date '+%F %T') $*" >> "$DETAILS"; }
append_err() { echo "$*" >> "$ERRORS"; }
http_code() { curl -s -o /dev/null -w "%{http_code}" "$1" || echo 000; }
http_body_head() { curl -s --max-time 3 "$1" 2>/dev/null | sed -n '1,5p' || true; }
header_val() { curl -s -D - -o /dev/null "$@" | awk -F': ' 'BEGIN{IGNORECASE=1}$1=="Access-Control-Allow-Origin"{gsub("\r","",$2);print $2}'; }
section() {
local name="$1"; logd "===== [$name] ====="; }
svc() {
local svc_name="$1"; local cname="$2"; shift 2
section "$svc_name ($cname)"
logd "docker ps:"; docker ps -a --format '{{.Names}}\t{{.Status}}\t{{.Image}}' | awk -v n="$cname" '$1==n' >> "$DETAILS" || true
logd "docker inspect (state/restartcount):"; docker inspect -f '{{.State.Status}} rc={{.RestartCount}} started={{.State.StartedAt}}' "$cname" >> "$DETAILS" 2>&1 || true
logd "last 200 container logs:"; docker logs --tail 200 "$cname" >> "$DETAILS" 2>&1 || true
# extract error lines from container logs
docker logs --tail 200 "$cname" 2>&1 | \
grep -Ei '\b(error|failed|fail|exception|panic|fatal|critical|unhealthy|permission denied|forbidden|refused|traceback|错误|失败)\b' | \
sed "s/^/[${svc_name}][container] /" >> "$ERRORS" || true
# supervisor status and logs
if docker exec "$cname" sh -lc 'command -v supervisorctl >/dev/null 2>&1' >/dev/null 2>&1; then
logd "supervisorctl status:"; docker exec "$cname" sh -lc 'supervisorctl status' >> "$DETAILS" 2>&1 || true
# iterate supervisor logs and collect tails + errors per file
local files
files=$(docker exec "$cname" sh -lc 'ls /var/log/supervisor/*.log 2>/dev/null' || true)
for f in $files; do
logd "tail -n 80 $f:"; docker exec "$cname" sh -lc "tail -n 80 $f 2>/dev/null || true" >> "$DETAILS" 2>&1 || true
docker exec "$cname" sh -lc "tail -n 200 $f 2>/dev/null" 2>/dev/null | \
grep -Ei '\b(error|failed|fail|exception|panic|fatal|critical|unhealthy|permission denied|forbidden|refused|traceback|错误|失败)\b' | \
sed "s/^/[${svc_name}][supervisor:$(basename $f)] /" >> "$ERRORS" || true
done
fi
}
# Core services
svc bind argus-bind-sys
svc master argus-master-sys
svc es argus-es-sys
svc kibana argus-kibana-sys
svc ftp argus-ftp
svc prometheus argus-prometheus
svc grafana argus-grafana
svc alertmanager argus-alertmanager
svc web-frontend argus-web-frontend
svc web-proxy argus-web-proxy
# HTTP checks (host side)
section HTTP
logd "ES: $(http_code "http://localhost:${ES_HTTP_PORT:-9200}/_cluster/health")"
http_body_head "http://localhost:${ES_HTTP_PORT:-9200}/_cluster/health" >> "$DETAILS" 2>&1 || true
logd "Kibana: $(http_code "http://localhost:${KIBANA_PORT:-5601}/api/status")"
http_body_head "http://localhost:${KIBANA_PORT:-5601}/api/status" >> "$DETAILS" 2>&1 || true
logd "Master readyz: $(http_code "http://localhost:${MASTER_PORT:-32300}/readyz")"
logd "Prometheus: $(http_code "http://localhost:${PROMETHEUS_PORT:-9090}/-/ready")"
logd "Grafana: $(http_code "http://localhost:${GRAFANA_PORT:-3000}/api/health")"
http_body_head "http://localhost:${GRAFANA_PORT:-3000}/api/health" >> "$DETAILS" 2>&1 || true
logd "Alertmanager: $(http_code "http://localhost:${ALERTMANAGER_PORT:-9093}/api/v2/status")"
cors8084=$(header_val -H "Origin: http://localhost:${WEB_PROXY_PORT_8080:-8080}" "http://localhost:${WEB_PROXY_PORT_8084:-8084}/api/v2/status" || true)
cors8085=$(header_val -H "Origin: http://localhost:${WEB_PROXY_PORT_8080:-8080}" "http://localhost:${WEB_PROXY_PORT_8085:-8085}/api/v1/master/nodes" || true)
logd "Web-Proxy 8080: $(http_code "http://localhost:${WEB_PROXY_PORT_8080:-8080}/")"
logd "Web-Proxy 8083: $(http_code "http://localhost:${WEB_PROXY_PORT_8083:-8083}/")"
logd "Web-Proxy 8084 CORS: ${cors8084}"
logd "Web-Proxy 8085 CORS: ${cors8085}"
# FTP share writability (container perspective)
section FTP-SHARE
docker exec argus-ftp sh -lc 'ls -ld /private/argus/ftp /private/argus/ftp/share; test -w /private/argus/ftp/share && echo "write:OK" || echo "write:FAIL"' >> "$DETAILS" 2>&1 || true
# Collect system info for context
section SYSTEM
logd "uname -a:"; uname -a >> "$DETAILS"
logd "docker version:"; docker version --format '{{.Server.Version}}' >> "$DETAILS" 2>&1 || true
logd "compose ps:"; (cd "$ROOT/compose" && if docker compose version >/dev/null 2>&1; then docker compose -p argus-sys ps; else docker-compose -p argus-sys ps; fi) >> "$DETAILS" 2>&1 || true
section SUMMARY
# Add HTTP failures and CORS problems to error log with tags
[[ $(http_code "http://localhost:${ES_HTTP_PORT:-9200}/_cluster/health") != 200 ]] && echo "[es][http] health not 200" >> "$ERRORS"
kbcode=$(http_code "http://localhost:${KIBANA_PORT:-5601}/api/status"); [[ "$kbcode" != 200 ]] && echo "[kibana][http] /api/status=$kbcode" >> "$ERRORS"
[[ $(http_code "http://localhost:${MASTER_PORT:-32300}/readyz") != 200 ]] && echo "[master][http] /readyz not 200" >> "$ERRORS"
[[ $(http_code "http://localhost:${PROMETHEUS_PORT:-9090}/-/ready") != 200 ]] && echo "[prometheus][http] /-/ready not 200" >> "$ERRORS"
gfcode=$(http_code "http://localhost:${GRAFANA_PORT:-3000}/api/health"); [[ "$gfcode" != 200 ]] && echo "[grafana][http] /api/health=$gfcode" >> "$ERRORS"
[[ $(http_code "http://localhost:${ALERTMANAGER_PORT:-9093}/api/v2/status") != 200 ]] && echo "[alertmanager][http] /api/v2/status not 200" >> "$ERRORS"
[[ -z "$cors8084" ]] && echo "[web-proxy][cors] 8084 missing Access-Control-Allow-Origin" >> "$ERRORS"
[[ -z "$cors8085" ]] && echo "[web-proxy][cors] 8085 missing Access-Control-Allow-Origin" >> "$ERRORS"
# Deduplicate errors
sort -u -o "$ERRORS" "$ERRORS"
echo "Diagnostic details -> $DETAILS"
echo "Detected errors -> $ERRORS"
# maintain latest symlinks for convenience
ln -sfn "$(basename "$DETAILS")" "$ROOT/logs/diagnose_details.log" 2>/dev/null || cp "$DETAILS" "$ROOT/logs/diagnose_details.log" 2>/dev/null || true
ln -sfn "$(basename "$ERRORS")" "$ROOT/logs/diagnose_error.log" 2>/dev/null || cp "$ERRORS" "$ROOT/logs/diagnose_error.log" 2>/dev/null || true
exit 0

View File

@@ -0,0 +1,194 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PKG_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # version root
PROJECT_NAME="argus-sys"
log() { echo -e "\033[0;34m[INSTALL]\033[0m $*"; }
err() { echo -e "\033[0;31m[ERROR ]\033[0m $*" >&2; }
require() { command -v "$1" >/dev/null 2>&1 || { err "missing command: $1"; exit 1; }; }
require docker
if docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose); else require docker-compose; COMPOSE=(docker-compose); fi
ENV_FILE="$PKG_ROOT/compose/.env"
ENV_TEMPLATE="$PKG_ROOT/compose/.env.example"
find_free_port() {
local prefer="$1"; local start=${2:-20000}; local max=${3:-65000};
if ! ss -ltnH 2>/dev/null | awk -v pat=":"$prefer"$" '$4 ~ pat{f=1} END{exit f?0:1}'; then echo "$prefer"; return; fi
for ((p=start; p<=max; p++)); do
if ! ss -ltnH 2>/dev/null | awk -v pat=":"$p"$" '$4 ~ pat{f=1} END{exit f?0:1}'; then echo "$p"; return; fi
done
return 1
}
prepare_env() {
if [[ -f "$ENV_FILE" ]]; then log ".env exists, keep as-is"; return; fi
[[ -f "$ENV_TEMPLATE" ]] || { err "missing $ENV_TEMPLATE"; exit 1; }
cp "$ENV_TEMPLATE" "$ENV_FILE"
# auto-assign ports if busy
for key in MASTER_PORT ES_HTTP_PORT KIBANA_PORT NODE_A_PORT NODE_B_PORT PROMETHEUS_PORT GRAFANA_PORT ALERTMANAGER_PORT \
WEB_PROXY_PORT_8080 WEB_PROXY_PORT_8081 WEB_PROXY_PORT_8082 WEB_PROXY_PORT_8083 WEB_PROXY_PORT_8084 WEB_PROXY_PORT_8085 \
FTP_PORT FTP_DATA_PORT; do
val=$(grep -E "^${key}=" "$ENV_FILE" | tail -1 | cut -d= -f2)
new=$(find_free_port "$val") || true
if [[ -n "${new:-}" && "$new" != "$val" ]]; then
sed -i "s/^${key}=.*/${key}=${new}/" "$ENV_FILE"
log "port ${key} busy -> ${new}"
fi
done
}
prepare_data_dirs() {
if [[ $EUID -ne 0 ]]; then
echo -e "\033[1;33m[WARN]\033[0m running as non-root: will not chown data dirs."
echo -e "\033[1;33m[WARN]\033[0m If you hit Permission denied, run: sudo $SCRIPT_DIR/server-prepare-dirs.sh"
# still ensure basic directories exist (no chown)
mkdir -p \
"$PKG_ROOT/private/argus/etc" \
"$PKG_ROOT/private/argus/metric/prometheus" \
"$PKG_ROOT/private/argus/metric/prometheus/data" \
"$PKG_ROOT/private/argus/metric/prometheus/rules" \
"$PKG_ROOT/private/argus/metric/grafana" \
"$PKG_ROOT/private/argus/metric/grafana/data" \
"$PKG_ROOT/private/argus/metric/grafana/logs" \
"$PKG_ROOT/private/argus/metric/grafana/plugins" \
"$PKG_ROOT/private/argus/metric/grafana/provisioning/datasources" \
"$PKG_ROOT/private/argus/metric/grafana/provisioning/dashboards" \
"$PKG_ROOT/private/argus/metric/grafana/data/sessions" \
"$PKG_ROOT/private/argus/metric/grafana/data/dashboards" \
"$PKG_ROOT/private/argus/alert/alertmanager" \
"$PKG_ROOT/private/argus/metric/ftp/share"
fi
}
load_images() {
local tar="$PKG_ROOT/images/all-images.tar.gz"
[[ -f "$tar" ]] || { err "missing images tar: $tar"; exit 1; }
log "loading images from $(basename "$tar") (may take minutes)"
gunzip -c "$tar" | docker load >/dev/null
}
bring_up() {
log "starting services via compose"
local ov="$PKG_ROOT/compose/docker-compose.os-compat.override.yml"
if [[ ! -f "$ov" ]]; then
cat > "$ov" <<'YAML'
services:
bind:
security_opt: ["label=disable"]
userns_mode: "host"
tmpfs:
- /run/named
master:
security_opt: ["label=disable"]
userns_mode: "host"
es:
security_opt: ["label=disable"]
userns_mode: "host"
kibana:
security_opt: ["label=disable"]
userns_mode: "host"
ftp:
security_opt: ["label=disable"]
userns_mode: "host"
prometheus:
security_opt: ["label=disable"]
userns_mode: "host"
grafana:
security_opt: ["label=disable"]
userns_mode: "host"
alertmanager:
security_opt: ["label=disable"]
userns_mode: "host"
# ensure runtime path matches container expectation
volumes:
- ../private/argus/etc:/private/argus/etc
- ../private/argus/alert/alertmanager:/alertmanager
web-frontend:
security_opt: ["label=disable"]
userns_mode: "host"
web-proxy:
security_opt: ["label=disable"]
userns_mode: "host"
YAML
log "generated OS-compat override: $(basename "$ov")"
fi
# 仅启动服务端组件,避免误起测试节点(node-a/node-b/test-node/test-gpu-node)
local services=(bind master es kibana ftp prometheus grafana alertmanager web-frontend web-proxy)
log "services: ${services[*]}"
(cd "$PKG_ROOT/compose" && "${COMPOSE[@]}" -p "$PROJECT_NAME" -f docker-compose.yml -f $(basename "$ov") up -d "${services[@]}")
}
dns_bootstrap() {
log "DNS bootstrap: initializing shared dns.conf and container resolv.conf"
local etc_dir="$PKG_ROOT/private/argus/etc"
mkdir -p "$etc_dir"
# 1) ensure dns.conf exists (fallback to bind IP 172.31.0.2)
if [[ ! -s "$etc_dir/dns.conf" ]]; then
if echo "172.31.0.2" > "$etc_dir/dns.conf" 2>/dev/null; then
log "wrote fallback dns.conf with 172.31.0.2"
else
# host-side write denied (ownership 1000:1000); write via bind container instead
if docker ps --format '{{.Names}}' | grep -q '^argus-bind-sys$'; then
docker exec argus-bind-sys sh -lc 'echo 172.31.0.2 > /private/argus/etc/dns.conf && chmod 644 /private/argus/etc/dns.conf' || true
log "fallback dns.conf written via bind container"
else
log "bind not ready; skip writing fallback dns.conf"
fi
fi
fi
# 2) wait briefly for bind to copy update-dns.sh into shared etc (bind startup.sh does this)
local i=0
while [[ ! -x "$etc_dir/update-dns.sh" && $i -lt 20 ]]; do
sleep 0.5; ((i++));
done
if [[ ! -x "$etc_dir/update-dns.sh" ]]; then
log "update-dns.sh not present yet; continuing with existing resolv.conf"
fi
# 3) run update-dns.sh inside key containers so /etc/resolv.conf points to bind
local c
for c in argus-master-sys argus-es-sys argus-kibana-sys argus-grafana argus-prometheus argus-ftp argus-web-frontend argus-web-proxy argus-alertmanager; do
if docker ps --format '{{.Names}}' | grep -q "^${c}$"; then
docker exec "$c" sh -lc 'test -x /private/argus/etc/update-dns.sh && /private/argus/etc/update-dns.sh || true' >/dev/null 2>&1 || true
fi
done
# 4) wait for service A-record hint files generated by services (best-effort)
local need=( es.log.argus.com kibana.log.argus.com master.argus.com grafana.metric.argus.com prom.metric.argus.com alertmanager.alert.argus.com )
local waited=0; local missing=1
while (( waited < 15 )); do
missing=0
for f in "${need[@]}"; do [[ -s "$etc_dir/$f" ]] || { missing=1; break; }; done
[[ $missing -eq 0 ]] && break
sleep 1; ((waited++))
done
# 5) reload bind zone (script uses supervisor to restart bind9)
if docker ps --format '{{.Names}}' | grep -q '^argus-bind-sys$'; then
docker exec argus-bind-sys sh -lc '/usr/local/bin/reload-bind9.sh' >/dev/null 2>&1 || true
fi
# 6) restart web-proxy once to re-render nginx resolver with latest dns.conf
if docker ps --format '{{.Names}}' | grep -q '^argus-web-proxy$'; then
docker restart argus-web-proxy >/dev/null 2>&1 || true
fi
}
selfcheck() {
log "running selfcheck"
bash "$PKG_ROOT/scripts/server-selfcheck.sh" || { err "selfcheck failed"; exit 1; }
}
main() {
prepare_env
prepare_data_dirs
load_images
bring_up
dns_bootstrap
selfcheck
log "install completed. See logs in $PKG_ROOT/logs/"
}
main "$@"

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PKG_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
if [[ $EUID -ne 0 ]]; then
echo "[PREPARE] This script requires root (sudo)." >&2
echo " Try: sudo $0" >&2
exit 1
fi
ENV_FILE="$PKG_ROOT/compose/.env"
[[ -f "$ENV_FILE" ]] && set -a && source "$ENV_FILE" && set +a
UIDV="${ARGUS_BUILD_UID:-1000}"; GIDV="${ARGUS_BUILD_GID:-1000}"
echo "[PREPARE] Using owner ${UIDV}:${GIDV}"
# Core etc and service data dirs (aligned with src/sys/tests/scripts/01_bootstrap.sh)
mkdir -p \
"$PKG_ROOT/private/argus/etc" \
"$PKG_ROOT/private/argus/bind" \
"$PKG_ROOT/private/argus/master" \
"$PKG_ROOT/private/argus/agent" \
"$PKG_ROOT/private/argus/log/elasticsearch" \
"$PKG_ROOT/private/argus/log/kibana"
# Prometheus
mkdir -p \
"$PKG_ROOT/private/argus/metric/prometheus" \
"$PKG_ROOT/private/argus/metric/prometheus/data" \
"$PKG_ROOT/private/argus/metric/prometheus/rules" \
"$PKG_ROOT/private/argus/metric/prometheus/targets"
# Grafana
mkdir -p \
"$PKG_ROOT/private/argus/metric/grafana" \
"$PKG_ROOT/private/argus/metric/grafana/data" \
"$PKG_ROOT/private/argus/metric/grafana/logs" \
"$PKG_ROOT/private/argus/metric/grafana/plugins" \
"$PKG_ROOT/private/argus/metric/grafana/provisioning/datasources" \
"$PKG_ROOT/private/argus/metric/grafana/provisioning/dashboards" \
"$PKG_ROOT/private/argus/metric/grafana/data/sessions" \
"$PKG_ROOT/private/argus/metric/grafana/data/dashboards" \
"$PKG_ROOT/private/argus/metric/grafana/config"
# FTP
mkdir -p "$PKG_ROOT/private/argus/metric/ftp/share"
# Alertmanager
mkdir -p "$PKG_ROOT/private/argus/alert/alertmanager"
chown -R "$UIDV":"$GIDV" \
"$PKG_ROOT/private/argus/etc" \
"$PKG_ROOT/private/argus/bind" \
"$PKG_ROOT/private/argus/master" \
"$PKG_ROOT/private/argus/agent" \
"$PKG_ROOT/private/argus/log/elasticsearch" \
"$PKG_ROOT/private/argus/log/kibana" \
"$PKG_ROOT/private/argus/metric/prometheus" \
"$PKG_ROOT/private/argus/metric/grafana" \
"$PKG_ROOT/private/argus/metric/ftp" \
"$PKG_ROOT/private/argus/alert"
chmod -R g+w "$PKG_ROOT/private/argus/alert" "$PKG_ROOT/private/argus/etc" || true
# Ensure parent directories also owned by runtime user for consistency
chown "$UIDV":"$GIDV" \
"$PKG_ROOT/private/argus" \
"$PKG_ROOT/private/argus/log" \
"$PKG_ROOT/private/argus/metric" || true
echo "[PREPARE] Done. You can now run server-install.sh"

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
log() { echo -e "\033[0;34m[CHECK]\033[0m $*"; }
err() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
ENV_FILE="$ROOT/compose/.env"; [[ -f "$ENV_FILE" ]] && set -a && source "$ENV_FILE" && set +a
wait_http() { local url="$1"; local attempts=${2:-120}; local i=1; while ((i<=attempts)); do curl -fsS "$url" >/dev/null 2>&1 && return 0; echo "[..] waiting $url ($i/$attempts)"; sleep 5; ((i++)); done; return 1; }
code_for() { curl -s -o /dev/null -w "%{http_code}" "$1" || echo 000; }
header_val() { curl -s -D - -o /dev/null "$@" | awk -F': ' 'BEGIN{IGNORECASE=1}$1=="Access-Control-Allow-Origin"{gsub("\r","",$2);print $2}'; }
mkdir -p "$ROOT/logs"
OUT_JSON="$ROOT/logs/selfcheck.json"
tmp=$(mktemp)
ok=1
log "checking Elasticsearch"
if curl -fsS "http://localhost:${ES_HTTP_PORT:-9200}/_cluster/health?wait_for_status=yellow&timeout=1s" >/dev/null 2>&1; then es_ok=true; else es_ok=false; ok=0; fi
log "checking Kibana"
kb_code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${KIBANA_PORT:-5601}/api/status" || echo 000)
kb_ok=false
if [[ "$kb_code" == "200" ]]; then body=$(curl -sS "http://localhost:${KIBANA_PORT:-5601}/api/status"); echo "$body" | grep -q '"level":"available"' && kb_ok=true; fi
[[ "$kb_ok" == true ]] || ok=0
log "checking Master"
wait_http "http://localhost:${MASTER_PORT:-32300}/readyz" 60 || ok=0
log "checking FTP"
if docker ps --format '{{.Names}}' | grep -q '^argus-ftp$'; then
if docker exec argus-ftp sh -lc 'test -w /private/argus/ftp/share'; then ftp_ok=true; else ftp_ok=false; ok=0; fi
else
ftp_ok=false; ok=0;
fi
log "checking Prometheus"
wait_http "http://localhost:${PROMETHEUS_PORT:-9090}/-/ready" 60 || ok=0
log "checking Grafana"
gf_code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${GRAFANA_PORT:-3000}/api/health" || echo 000)
gf_ok=false; if [[ "$gf_code" == "200" ]]; then body=$(curl -sS "http://localhost:${GRAFANA_PORT:-3000}/api/health"); echo "$body" | grep -q '"database"\s*:\s*"ok"' && gf_ok=true; fi
[[ "$gf_ok" == true ]] || ok=0
log "checking Alertmanager"
wait_http "http://localhost:${ALERTMANAGER_PORT:-9093}/api/v2/status" 60 || ok=0
log "checking Web-Proxy"
p8080=$(code_for "http://localhost:${WEB_PROXY_PORT_8080:-8080}/")
p8083=$(code_for "http://localhost:${WEB_PROXY_PORT_8083:-8083}/")
cors8084=$(header_val -H "Origin: http://localhost:${WEB_PROXY_PORT_8080:-8080}" "http://localhost:${WEB_PROXY_PORT_8084:-8084}/api/v2/status" || true)
cors8085=$(header_val -H "Origin: http://localhost:${WEB_PROXY_PORT_8080:-8080}" "http://localhost:${WEB_PROXY_PORT_8085:-8085}/api/v1/master/nodes" || true)
wp_ok=true
# 有些环境首页可能 403,此处接受 200/403
([[ "$p8080" == 200 || "$p8080" == 403 ]]) || wp_ok=false
([[ "$p8083" == 200 || "$p8083" == 302 || "$p8083" == 403 ]]) || wp_ok=false
[[ -n "$cors8084" && -n "$cors8085" ]] || wp_ok=false
[[ "$wp_ok" == true ]] || ok=0
cat > "$tmp" <<JSON
{
"es": $es_ok,
"kibana": $kb_ok,
"master_readyz": true,
"ftp_share_writable": $ftp_ok,
"prometheus": true,
"grafana": $gf_ok,
"alertmanager": true,
"web_proxy": $wp_ok,
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
JSON
mv "$tmp" "$OUT_JSON"
if [[ "$ok" == 1 ]]; then
log "selfcheck OK"
exit 0
else
err "selfcheck FAILED (see $OUT_JSON)"
# If diagnose script exists, run it to collect more details
if [[ -x "$SCRIPT_DIR/server-diagnose.sh" ]]; then
# run diagnose; it will print the actual timestamped file paths and update 'latest' symlinks
"$SCRIPT_DIR/server-diagnose.sh" || true
fi
exit 1
fi

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PROJECT_NAME="argus-sys"
if docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose); else COMPOSE=(docker-compose); fi
echo "== Containers =="
(cd "$ROOT/compose" && "${COMPOSE[@]}" -p "$PROJECT_NAME" ps)
echo
echo "== Key Endpoints =="
ENV_FILE="$ROOT/compose/.env"; [[ -f "$ENV_FILE" ]] && set -a && source "$ENV_FILE" && set +a
printf "master http://localhost:%s/readyz\n" "${MASTER_PORT:-32300}"
printf "es http://localhost:%s/_cluster/health\n" "${ES_HTTP_PORT:-9200}"
printf "kibana http://localhost:%s/api/status\n" "${KIBANA_PORT:-5601}"
printf "prom http://localhost:%s/-/ready\n" "${PROMETHEUS_PORT:-9090}"
printf "grafana http://localhost:%s/api/health\n" "${GRAFANA_PORT:-3000}"
printf "alert http://localhost:%s/api/v2/status\n" "${ALERTMANAGER_PORT:-9093}"
printf "web http://localhost:%s/ (8080)\n" "${WEB_PROXY_PORT_8080:-8080}"
echo
echo "== Selfcheck result =="
cat "$ROOT/logs/selfcheck.json" 2>/dev/null || echo "(no selfcheck yet)"

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PKG_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PROJECT_NAME="argus-sys"
log() { echo -e "\033[0;34m[UNINSTALL]\033[0m $*"; }
if docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose); else COMPOSE=(docker-compose); fi
(cd "$PKG_ROOT/compose" && "${COMPOSE[@]}" -p "$PROJECT_NAME" down -v || true)
log "compose stack removed"
log "you may remove data under $PKG_ROOT/private if you want a clean slate"

BIN
doc/metric_lists.xlsx Normal file

Binary file not shown.

View File

@@ -1 +1 @@
1.35.0 1.40.0

View File

@@ -48,6 +48,15 @@ if [[ ${#missing_files[@]} -gt 0 ]]; then
exit 1 exit 1
fi fi
# 防御:阻止将 Git LFS 指针文件打包
for f in bin/dcgm-exporter bin/datacenter-gpu-manager_3.3.9_amd64.deb; do
if head -n1 "$f" 2>/dev/null | grep -q '^version https://git-lfs.github.com/spec/v1$'; then
echo "[ERROR] $f 是 Git LFS 指针文件,未还原为真实制品"
echo " 请在仓库根目录执行: git lfs fetch --all && git lfs checkout"
exit 1
fi
done
log_success "所有必要文件检查完成" log_success "所有必要文件检查完成"
# 创建临时目录 # 创建临时目录

View File

@@ -47,6 +47,13 @@ if [[ ${#missing_files[@]} -gt 0 ]]; then
exit 1 exit 1
fi fi
# 防御:阻止将 Git LFS 指针文件打包
if head -n1 bin/node_exporter 2>/dev/null | grep -q '^version https://git-lfs.github.com/spec/v1$'; then
echo "[ERROR] bin/node_exporter 是 Git LFS 指针文件,未还原为真实二进制"
echo " 请在仓库根目录执行: git lfs fetch --all && git lfs checkout"
exit 1
fi
log_success "所有必要文件检查完成" log_success "所有必要文件检查完成"
# 创建临时目录 # 创建临时目录

View File

@@ -216,6 +216,36 @@ if [[ ${#missing_components[@]} -gt 0 ]]; then
exit 1 exit 1
fi fi
# 额外校验:阻止将 Git LFS 指针文件打进安装包
# 仅检查各组件目录下的 bin/ 内文件(常见为二进制或 .deb/.tar.gz 制品)
is_lfs_pointer() {
local f="$1"
# 读取首行判断是否为 LFS pointer(无需依赖 file 命令)
head -n1 "$f" 2>/dev/null | grep -q '^version https://git-lfs.github.com/spec/v1$'
}
log_info "检查组件二进制是否已从 LFS 拉取..."
while IFS= read -r component; do
component_path=$(grep "^$component:" "$TEMP_DIR/component_paths.txt" | cut -d':' -f2-)
bin_dir="$component_path/bin"
[[ -d "$bin_dir" ]] || continue
while IFS= read -r f; do
# 只检查常见可执行/包后缀;无后缀的也检查
case "$f" in
*.sh) continue;;
*) :;;
esac
if is_lfs_pointer "$f"; then
log_error "检测到 Git LFS 指针文件: $f"
log_error "请在仓库根目录执行: git lfs fetch --all && git lfs checkout"
log_error "或确保 CI 在打包前已还原 LFS 大文件。"
rm -rf "$TEMP_DIR"
exit 1
fi
done < <(find "$bin_dir" -maxdepth 1 -type f 2>/dev/null | sort)
done < "$COMPONENTS_FILE"
log_success "LFS 校验通过:未发现指针文件"
# 打包各个组件 # 打包各个组件
log_info "开始打包组件..." log_info "开始打包组件..."
@@ -234,7 +264,19 @@ while IFS= read -r component; do
# 进入组件目录 # 进入组件目录
cd "$component_path" cd "$component_path"
# 组件内二次防御:若包脚本缺失 LFS 校验,这里再次阻断
if [[ -d bin ]]; then
for f in bin/*; do
[[ -f "$f" ]] || continue
if head -n1 "$f" 2>/dev/null | grep -q '^version https://git-lfs.github.com/spec/v1$'; then
log_error "组件 $component 含 LFS 指针文件: $f"
log_error "请执行: git lfs fetch --all && git lfs checkout"
cd "$CURRENT_DIR"; rm -rf "$TEMP_DIR"; exit 1
fi
done
fi
# 检查组件是否有 package.sh # 检查组件是否有 package.sh
if [[ ! -f "package.sh" ]]; then if [[ ! -f "package.sh" ]]; then
log_error "$component 缺少 package.sh 文件" log_error "$component 缺少 package.sh 文件"

View File

@@ -48,6 +48,31 @@ BACKUPS_DIR="$INSTALL_DIR/backups" # 备份目录
CURRENT_LINK="$INSTALL_DIR/current" # 当前版本软链接 CURRENT_LINK="$INSTALL_DIR/current" # 当前版本软链接
LATEST_VERSION_FILE="$INSTALL_DIR/LATEST_VERSION" # 当前版本记录文件 LATEST_VERSION_FILE="$INSTALL_DIR/LATEST_VERSION" # 当前版本记录文件
# 预检查:Agent 元数据与 hostname 约束
require_agent_metadata() {
local hn
hn="$(hostname)"
local ok=false
# 三元环境变量
if [[ -n "${AGENT_ENV:-}" && -n "${AGENT_USER:-}" && -n "${AGENT_INSTANCE:-}" ]]; then
ok=true
fi
# host 形如 env-user-instance-xxx
if [[ "$hn" =~ ^[^-]+-[^-]+-[^-]+-.*$ ]]; then
ok=true
fi
if [[ "$ok" == false ]]; then
log_error "检测到 hostname 与 Agent 元数据不完整:"
log_error " 当前 hostname: $hn"
log_error " AGENT_ENV='${AGENT_ENV:-}' AGENT_USER='${AGENT_USER:-}' AGENT_INSTANCE='${AGENT_INSTANCE:-}'"
echo
log_info "请满足以下其一后重试:"
log_info " 方式A:设置 hostname 为 env-user-instance-任意,例如 dev-alice-node001-pod-0"
log_info " 方式B:导出环境变量:export AGENT_ENV=dev AGENT_USER=alice AGENT_INSTANCE=node001"
exit 1
fi
}
# 检查必需的FTP参数 # 检查必需的FTP参数
check_ftp_params() { check_ftp_params() {
local missing_params=() local missing_params=()
@@ -873,6 +898,47 @@ rollback_version() {
fi fi
} }
# 自检实现:等待 node.json 就绪且健康,并验证 last_report 持续更新
selfcheck_post_install() {
local hn="$(hostname)"
local node_file="/private/argus/agent/${AGENT_HOSTNAME:-$hn}/node.json"
local deadline=$(( $(date +%s) + 300 ))
local t1="" t2=""
while :; do
if [[ -f "$node_file" ]]; then
if command -v jq >/dev/null 2>&1; then
local ok_health lr
ok_health=$(jq -er '(.health["metric-argus-agent"].status=="healthy") and (.health["metric-node-exporter"].status=="healthy") and (.health["metric-fluent-bit"].status=="healthy") and (.health["metric-dcgm-exporter"].status=="healthy")' "$node_file" 2>/dev/null || echo false)
lr=$(jq -r '.last_report // ""' "$node_file" 2>/dev/null)
if [[ "$ok_health" == true && -n "$lr" ]]; then
if [[ -z "$t1" ]]; then
t1="$lr"
# agent 默认 60s 上报,等待 70s 再校验一次
sleep 70
continue
fi
t2="$lr"
if [[ "$t2" != "$t1" ]]; then
return 0
fi
# 若未变化,再等待一会儿直到超时
sleep 10
fi
else
# 无 jq 时的宽松校验
if grep -q '"status"\s*:\s*"healthy"' "$node_file"; then
return 0
fi
fi
fi
if (( $(date +%s) >= deadline )); then
log_error "自检超时:未在 5 分钟内确认 last_report 持续更新 或 健康状态不满足(路径:$node_file)"
return 1
fi
sleep 5
done
}
# 主函数 # 主函数
main() { main() {
echo "==========================================" echo "=========================================="
@@ -912,17 +978,26 @@ main() {
# return 0 # return 0
# fi # fi
check_ftp_params check_ftp_params
check_system check_system
require_agent_metadata
if [[ "$ACTION" == "uninstall" ]]; then if [[ "$ACTION" == "uninstall" ]]; then
uninstall_argus_metric uninstall_argus_metric
else else
install_argus_metric install_argus_metric
fi fi
# 安装后自检:最多等待 5 分钟,确认 node.json 存在且健康
echo echo
log_info "操作完成!" log_info "开始安装后自检(最多等待 5 分钟)..."
selfcheck_post_install || {
log_error "安装后自检未通过,请查看 /var/log/argus-agent.log 以及 /opt/argus-metric/versions/*/.install.log"
exit 1
}
echo
log_success "全部自检通过,安装完成!"
} }
# 脚本入口 # 脚本入口

View File

@@ -67,7 +67,8 @@ RUN chmod +x /usr/local/bin/start-ftp-supervised.sh
COPY vsftpd.conf /etc/vsftpd/vsftpd.conf COPY vsftpd.conf /etc/vsftpd/vsftpd.conf
COPY dns-monitor.sh /usr/local/bin/dns-monitor.sh COPY dns-monitor.sh /usr/local/bin/dns-monitor.sh
RUN chmod +x /usr/local/bin/dns-monitor.sh COPY dns-publish.sh /usr/local/bin/dns-publish.sh
RUN chmod +x /usr/local/bin/dns-monitor.sh /usr/local/bin/dns-publish.sh
USER root USER root

View File

@@ -66,6 +66,17 @@ ${FTP_BASE_PATH}/
/private/argus/etc/ /private/argus/etc/
└── ${DOMAIN} # 容器IP记录文件 └── ${DOMAIN} # 容器IP记录文件
## DNS 同步到 FTP share(运行期)
- 运行期最新的 DNS 列表由 bind/master 写入挂载点 `/private/argus/etc/dns.conf`。
- FTP 容器内置 `dns-publish`(Supervised):每 10s 比较并将该文件原子同步为 `${FTP_BASE_PATH}/share/dns.conf`,供客户端下载安装脚本直接读取。
- 同步特性:
- 原子更新:写入 `${DST}.tmp` 后 `mv -f` 覆盖,避免读到半写文件。
- 权限:0644;属主 `${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}`。
- 可观测:日志 `/var/log/supervisor/dns-publish.log`。
> 注:构建/发布阶段可能也会将静态 `config/dns.conf` 拷贝到 share;当 FTP 容器运行后,dns-publish 会用运行期最新文件覆盖该静态文件。
``` ```
## vsftpd 配置说明 ## vsftpd 配置说明
@@ -156,4 +167,4 @@ curl -fsS 'ftp://ftpuser:ZGClab1234!@177.177.70.200/setup.sh' -o setup.sh
# root用户直接执行,非root用户需要使用sudo # root用户直接执行,非root用户需要使用sudo
chmod +x setup.sh chmod +x setup.sh
bash setup.sh --server {$域名} --user ftpuser --password 'ZGClab1234!' bash setup.sh --server {$域名} --user ftpuser --password 'ZGClab1234!'
``` ```

View File

@@ -0,0 +1,40 @@
#!/bin/bash
set -uo pipefail
# Publish latest /private/argus/etc/dns.conf to ${FTP_BASE_PATH}/share/dns.conf
SRC="/private/argus/etc/dns.conf"
FTP_BASE_PATH="${FTP_BASE_PATH:-/private/argus/ftp}"
DST_DIR="${FTP_BASE_PATH}/share"
DST="${DST_DIR}/dns.conf"
UID_VAL="${ARGUS_BUILD_UID:-2133}"
GID_VAL="${ARGUS_BUILD_GID:-2015}"
INTERVAL="${DNS_PUBLISH_INTERVAL:-10}"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') [DNS-Publish] $*"; }
mkdir -p "$DST_DIR" 2>/dev/null || true
log "service start: SRC=$SRC DST=$DST interval=${INTERVAL}s"
while true; do
if [[ -f "$SRC" ]]; then
# Only sync when content differs
if ! cmp -s "$SRC" "$DST" 2>/dev/null; then
tmp="${DST}.tmp"
if cp "$SRC" "$tmp" 2>/dev/null; then
mv -f "$tmp" "$DST"
chown "$UID_VAL":"$GID_VAL" "$DST" 2>/dev/null || true
chmod 0644 "$DST" 2>/dev/null || true
ts_src=$(date -r "$SRC" '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null || echo "?")
log "synced dns.conf (src mtime=$ts_src) -> $DST"
else
log "ERROR: copy failed $SRC -> $tmp"
fi
fi
else
log "waiting for source $SRC"
fi
sleep "$INTERVAL"
done

View File

@@ -28,6 +28,18 @@ stopwaitsecs=10
killasgroup=true killasgroup=true
stopasgroup=true stopasgroup=true
[program:dns-publish]
command=/usr/local/bin/dns-publish.sh
user=root
stdout_logfile=/var/log/supervisor/dns-publish.log
stderr_logfile=/var/log/supervisor/dns-publish_error.log
autorestart=true
startretries=3
startsecs=5
stopwaitsecs=10
killasgroup=true
stopasgroup=true
[unix_http_server] [unix_http_server]
file=/var/run/supervisor.sock file=/var/run/supervisor.sock
chmod=0700 chmod=0700

7
src/sys/tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
private/
private-nodea/
private-nodeb/
tmp/
.env

View File

@@ -1,13 +1,17 @@
# ARGUS 系统级端到端测试(Sys E2E) # ARGUS 系统级端到端测试(Sys E2E)
本目录包含将 log 与 agent 两线验证合并后的系统级端到端测试。依赖 bind/master/es/kibana + 两个“日志节点”(每个节点容器内同时运行 Fluent Bit 与 argus-agent)。 本目录包含将 log、metric 与 agent 三线验证合并后的系统级端到端测试。依赖 bind/master/es/kibana/metric(ftp+prometheus+grafana+alertmanager)/web-proxy/web-frontend + 两个“计算节点”(每个节点容器内同时运行 Fluent Bit 与 argus-agent)。
--- ---
## 一、如何运行 ## 一、如何运行
- 前置条件 - 前置条件
- 已构建镜像:`argus-elasticsearch:latest`、`argus-kibana:latest`、`argus-bind9:latest`、`argus-master:latest` - 已构建镜像:
- 基座:`argus-elasticsearch:latest`、`argus-kibana:latest`、`argus-bind9:latest`、`argus-master:latest`
- 节点:`argus-sys-node:latest`
- 指标:`argus-metric-ftp:latest`、`argus-metric-prometheus:latest`、`argus-metric-grafana:latest`、`argus-alertmanager:latest`
- 前端与代理:`argus-web-frontend:latest`、`argus-web-proxy:latest`
- 可用根目录命令构建:`./build/build_images.sh [--intranet]` - 可用根目录命令构建:`./build/build_images.sh [--intranet]`
- 主机具备 Docker 与 Docker Compose。 - 主机具备 Docker 与 Docker Compose。
@@ -33,11 +37,12 @@
- 一键执行 - 一键执行
- `cd src/sys/tests` - `cd src/sys/tests`
- `./scripts/00_e2e_test.sh`(CPU-only)或 `./scripts/00_e2e_test.sh --enable-gpu`(启用 GPU 流程) - `./scripts/00_e2e_test.sh`(CPU-only)或 `./scripts/00_e2e_test.sh --enable-gpu`(启用 GPU 流程)
- 可选:`--no-clean` 跳过清理,便于失败后现场排查
- 分步执行(推荐用于排查) - 分步执行(推荐用于排查)
- `./scripts/01_bootstrap.sh` 生成目录/拷贝 `update-dns.sh`/构建 agent 二进制/写 `.env` - `./scripts/01_bootstrap.sh` 生成目录/拷贝 `update-dns.sh`/构建 agent 二进制/写 `.env`
- `./scripts/02_up.sh` 启动 Compose 栈(工程名 `argus-sys`) - `./scripts/02_up.sh` 启动 Compose 栈(工程名 `argus-sys`)
- `./scripts/03_wait_ready.sh` 等待 ES/Kibana/Master/Fluent‑Bit/Bind 就绪(Kibana 必须返回 200 且 overall.level=available) - `./scripts/03_wait_ready.sh` 等待 ES/Kibana/Master/Fluent‑Bit/Bind/Prometheus/Grafana/Alertmanager/Web‑Proxy 就绪(Kibana 必须 200 且 overall.level=available;Web‑Proxy 8084/8085 要有 CORS 头)
- `./scripts/04_verify_dns_routing.sh` 校验 bind 解析与节点内域名解析 - `./scripts/04_verify_dns_routing.sh` 校验 bind 解析与节点内域名解析
- `./scripts/05_agent_register.sh` 获取两个节点的 `node_id` 与初始 IP,检查本地 `node.json` - `./scripts/05_agent_register.sh` 获取两个节点的 `node_id` 与初始 IP,检查本地 `node.json`
- `./scripts/06_write_health_and_assert.sh` 写健康文件并断言 `nodes.json` 仅包含 2 个在线节点 - `./scripts/06_write_health_and_assert.sh` 写健康文件并断言 `nodes.json` 仅包含 2 个在线节点
@@ -58,7 +63,7 @@
## 二、测试部署架构(docker-compose) ## 二、测试部署架构(docker-compose)
- 网络 - 网络
- 自定义 bridge:`argus-sys-net`,子网 `172.31.0.0/16` - 自定义 bridge:`sysnet`(Compose 工程名为 `argus-sys` 时实际为 `argus-sys_sysnet`),子网 `172.31.0.0/16`
- 固定地址:bind=`172.31.0.2`,master=`172.31.0.10` - 固定地址:bind=`172.31.0.2`,master=`172.31.0.10`
- 服务与端口(宿主机映射端口由 `01_bootstrap.sh` 自动分配并写入 `.env`) - 服务与端口(宿主机映射端口由 `01_bootstrap.sh` 自动分配并写入 `.env`)
@@ -66,9 +71,15 @@
- `bind`(`argus-bind9:latest`):监听 53/tcp+udp;负责同步 `*.argus.com` 记录 - `bind`(`argus-bind9:latest`):监听 53/tcp+udp;负责同步 `*.argus.com` 记录
- `master`(`argus-master:latest`):对外 `${MASTER_PORT}→3000`;API `http://localhost:${MASTER_PORT}` - `master`(`argus-master:latest`):对外 `${MASTER_PORT}→3000`;API `http://localhost:${MASTER_PORT}`
- `es`(`argus-elasticsearch:latest`):`${ES_HTTP_PORT}→9200`;单节点,无安全 - `es`(`argus-elasticsearch:latest`):`${ES_HTTP_PORT}→9200`;单节点,无安全
- `kibana`(`argus-kibana:latest`):`${KIBANA_PORT}→5601`;通过 `ELASTICSEARCH_HOSTS=http://es:9200` 访问 ES - `kibana`(`argus-kibana:latest`):`${KIBANA_PORT}→5601`
- `node-a`(`ubuntu:22.04`):同时运行 Fluent Bit + argus-agent,`hostname=dev-yyrshare-nbnyx10-cp2f-pod-0`,`${NODE_A_PORT}→2020` - `node-a`(`argus-sys-node:latest`):同时运行 Fluent Bit + argus-agent,`hostname=dev-yyrshare-nbnyx10-cp2f-pod-0`,`${NODE_A_PORT}→2020`
- `node-b`(`ubuntu:22.04`):同时运行 Fluent Bit + argus-agent,`hostname=dev-yyrshare-uuuu10-ep2f-pod-0`,`${NODE_B_PORT}→2020` - `node-b`(`argus-sys-node:latest`):同时运行 Fluent Bit + argus-agent,`hostname=dev-yyrshare-uuuu10-ep2f-pod-0`,`${NODE_B_PORT}→2020`
- `ftp`(`argus-metric-ftp:latest`):`${FTP_PORT}→21`/`${FTP_DATA_PORT}→20`/`${FTP_PASSIVE_HOST_RANGE}` 被动端口
- `prometheus`(`argus-metric-prometheus:latest`):`${PROMETHEUS_PORT}→9090`
- `grafana`(`argus-metric-grafana:latest`):`${GRAFANA_PORT}→3000`
- `alertmanager`(`argus-alertmanager:latest`):`${ALERTMANAGER_PORT}→9093`
- `web-frontend`(`argus-web-frontend:latest`):内部访问页面,使用 `web-proxy` 暴露的对外端口渲染超链
- `web-proxy`(`argus-web-proxy:latest`):多端口转发 8080..8085(首页、Grafana、Prometheus、Kibana、Alertmanager、Master API)
- 卷与目录 - 卷与目录
- 核心服务(bind/master/es/kibana)共享宿主 `./private` 挂载到容器 `/private` - 核心服务(bind/master/es/kibana)共享宿主 `./private` 挂载到容器 `/private`
@@ -83,7 +94,7 @@
- 节点入口 - 节点入口
- `scripts/node_entrypoint.sh`: - `scripts/node_entrypoint.sh`:
- 复制 `/assets/fluent-bit/*` 到容器 `/private`,后台启动 Fluent Bit(监听 2020) - 离线优先:将 `/assets/fluent-bit/packages` 与 `etc` 拷贝到 `/private`,执行 `/private/start-fluent-bit.sh` 安装/拉起 Fluent Bit(监听 2020)
- 以运行用户(映射 UID/GID)前台启动 `argus-agent` - 以运行用户(映射 UID/GID)前台启动 `argus-agent`
- 节点环境变量:`MASTER_ENDPOINT=http://master.argus.com:3000`、`REPORT_INTERVAL_SECONDS=2`、`ES_HOST=es`、`ES_PORT=9200`、`CLUSTER=local`、`RACK=dev` - 节点环境变量:`MASTER_ENDPOINT=http://master.argus.com:3000`、`REPORT_INTERVAL_SECONDS=2`、`ES_HOST=es`、`ES_PORT=9200`、`CLUSTER=local`、`RACK=dev`
@@ -106,6 +117,10 @@
- Master `/readyz` 成功 - Master `/readyz` 成功
- Fluent Bit 指标接口 `:2020/:2021` 可访问 - Fluent Bit 指标接口 `:2020/:2021` 可访问
- bind `named-checkconf` 通过 - bind `named-checkconf` 通过
- Prometheus `/-/ready` 可用
- Grafana `GET /api/health` 返回 200 且 `database=ok`
- Alertmanager `GET /api/v2/status` 成功
- Web‑Proxy:8080 首页 200;8083 首页 200/302;8084/8085 对来自 8080 的请求需返回 `Access-Control-Allow-Origin`(CORS)
- `04_verify_dns_routing.sh` - `04_verify_dns_routing.sh`
- 目的:验证从 bind → 节点容器的解析链路 - 目的:验证从 bind → 节点容器的解析链路
@@ -142,6 +157,28 @@
--- ---
## 注意事项(2025‑10‑29 更新)
- 宿主 inotify 限制导致 03 卡住(Fluent Bit in_tail EMFILE)
- 现象:`03_wait_ready.sh` 一直等待 `:2020/:2021 /api/v2/metrics`;节点日志出现 `tail_fs_inotify.c errno=24 Too many open files`,Fluent Bit 启动失败。
- 根因:宿主 `fs.inotify.max_user_instances` 上限过低(常见默认 128),被其他进程占满;并非容器内 `ulimit -n` 过低。
- 处理:在宿主执行(临时):
- `sudo sysctl -w fs.inotify.max_user_instances=1024 fs.inotify.max_user_watches=1048576`
- 建议永久:写入 `/etc/sysctl.d/99-argus-inotify.conf` 后 `sudo sysctl --system`
- 提示:节点入口里对 sysctl 的写操作不影响宿主;需在宿主调整。
- Metric 安装制品包含 Git LFS 指针导致 node‑exporter 启动失败
- 现象:第 11 步在线安装后,日志显示 `Node Exporter 服务启动失败`;容器内 `/usr/local/bin/node-exporter` 头部是文本:`version https://git-lfs.github.com/spec/v1`。
- 根因:发布到 FTP 的安装包在打包前未执行 `git lfs fetch/checkout`,将指针文件打入制品。
- 处理:在仓库根目录执行 `git lfs fetch --all && git lfs checkout` 后,重跑 `src/metric/tests/scripts/02_publish_artifact.sh` 再重试 `11_metric_node_install.sh`。
- 防呆:已在 `all-in-one-full/scripts/package_artifact.sh` 与组件 `plugins/*/package.sh` 增加 LFS 指针校验,发现即失败并提示修复。
建议:
- 运行前检查宿主 inotify 值(≥1024/≥1048576)与宿主端口占用(8080..8085、9200/5601/9090/9093/2020/2021/32300 等)。
- 如需排查失败,使用 `--no-clean` 保留现场,配合 `docker logs`、`curl` 与 `tmp/*.json` 进行定位。
---
如需更严格的断言(例如 Kibana 载入具体插件、ES 文档字段校验),可在 `07_*.sh` 中追加查询与校验逻辑。 如需更严格的断言(例如 Kibana 载入具体插件、ES 文档字段校验),可在 `07_*.sh` 中追加查询与校验逻辑。
--- ---

View File

@@ -24,13 +24,18 @@ else
fi fi
# ========== 读取 DNS ========== # ========== 读取 DNS ==========
if [ -f "$DNS_CONF_PRIVATE" ]; then RESOLVERS=""
echo "从 $DNS_CONF_PRIVATE 读取 DNS 服务器..." # 优先等待 /private/argus/etc/dns.conf 生成并读取其中的 IP
RESOLVERS=$(awk '/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {print $1}' "$DNS_CONF_PRIVATE" | tr '\n' ' ') for i in $(seq 1 10); do
fi if [ -f "$DNS_CONF_PRIVATE" ]; then
RESOLVERS=$(awk '/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/{print $1}' "$DNS_CONF_PRIVATE" | tr '\n' ' ')
fi
[ -n "$RESOLVERS" ] && break
sleep 1
done
# 如果 /private 文件不存在则 fallback # 若仍为空则回退到系统 resolv.conf
if [ -z "${RESOLVERS:-}" ]; then if [ -z "$RESOLVERS" ]; then
echo "未在 $DNS_CONF_PRIVATE 中找到有效 DNS,使用系统 /etc/resolv.conf" echo "未在 $DNS_CONF_PRIVATE 中找到有效 DNS,使用系统 /etc/resolv.conf"
RESOLVERS=$(awk '/^nameserver/ {print $2}' "$DNS_CONF_SYSTEM" | tr '\n' ' ') RESOLVERS=$(awk '/^nameserver/ {print $2}' "$DNS_CONF_SYSTEM" | tr '\n' ' ')
fi fi
@@ -47,8 +52,9 @@ echo "检测到 DNS 服务器列表: $RESOLVERS"
if [ -f "$TEMPLATE" ]; then if [ -f "$TEMPLATE" ]; then
echo "从模板生成 nginx.conf ..." echo "从模板生成 nginx.conf ..."
# 合并 Docker 内置 DNS 以保障解析 Compose 服务名 # 合并 Docker 内置 DNS 以保障解析 Compose 服务名
# 将 127.0.0.11 放在末尾,优先使用 /private/argus/etc/dns.conf 指向的 bind
if ! echo " $RESOLVERS " | grep -q " 127.0.0.11 "; then if ! echo " $RESOLVERS " | grep -q " 127.0.0.11 "; then
RESOLVERS="127.0.0.11 ${RESOLVERS}" RESOLVERS="${RESOLVERS} 127.0.0.11"
fi fi
sed "s|__RESOLVERS__|$RESOLVERS|" "$TEMPLATE" > "$TARGET" sed "s|__RESOLVERS__|$RESOLVERS|" "$TEMPLATE" > "$TARGET"
else else