Compare commits
11 Commits
dev_1.0.0_
...
252461c642
| Author | SHA1 | Date | |
|---|---|---|---|
| 252461c642 | |||
| 629aa88550 | |||
| c0c90a526d | |||
| af7cfbe74b | |||
| 3e38770473 | |||
| d130924b1a | |||
| 59a38513a4 | |||
|
|
d1b89c0cf6 | ||
| 1a768bc837 | |||
| 31ccb0b1b8 | |||
|
|
8fbe107ac9 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
src/metric/client-plugins/all-in-one-full/plugins/*/bin/* filter=lfs diff=lfs merge=lfs -text
|
||||||
@@ -10,19 +10,29 @@ Usage: $0 [OPTIONS]
|
|||||||
Options:
|
Options:
|
||||||
--intranet Use intranet mirror for log/bind builds
|
--intranet Use intranet mirror for log/bind builds
|
||||||
--master-offline Build master offline image (requires src/master/offline_wheels.tar.gz)
|
--master-offline Build master offline image (requires src/master/offline_wheels.tar.gz)
|
||||||
|
--metric Build metric module images (ftp, prometheus, grafana, test nodes)
|
||||||
|
--no-cache Build all images without using Docker layer cache
|
||||||
|
--only LIST Comma-separated targets to build: core,master,metric,web,alert,sys,all
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$0 # Build with default sources
|
$0 # Build with default sources
|
||||||
$0 --intranet # Build with intranet mirror
|
$0 --intranet # Build with intranet mirror
|
||||||
$0 --master-offline # Additionally build argus-master:offline
|
$0 --master-offline # Additionally build argus-master:offline
|
||||||
$0 --intranet --master-offline
|
$0 --metric # Additionally build metric module images
|
||||||
|
$0 --intranet --master-offline --metric
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
use_intranet=false
|
use_intranet=false
|
||||||
|
build_core=true
|
||||||
build_master=true
|
build_master=true
|
||||||
build_master_offline=false
|
build_master_offline=false
|
||||||
|
build_metric=true
|
||||||
|
build_web=true
|
||||||
|
build_alert=true
|
||||||
|
build_sys=true
|
||||||
|
no_cache=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -39,6 +49,35 @@ while [[ $# -gt 0 ]]; do
|
|||||||
build_master_offline=true
|
build_master_offline=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--metric)
|
||||||
|
build_metric=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-cache)
|
||||||
|
no_cache=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--only)
|
||||||
|
if [[ -z ${2:-} ]]; then
|
||||||
|
echo "--only requires a target list" >&2; exit 1
|
||||||
|
fi
|
||||||
|
sel="$2"; shift 2
|
||||||
|
# reset all, then enable selected
|
||||||
|
build_core=false; build_master=false; build_metric=false; build_web=false; build_alert=false; build_sys=false
|
||||||
|
IFS=',' read -ra parts <<< "$sel"
|
||||||
|
for p in "${parts[@]}"; do
|
||||||
|
case "$p" in
|
||||||
|
core) build_core=true ;;
|
||||||
|
master) build_master=true ;;
|
||||||
|
metric) build_metric=true ;;
|
||||||
|
web) build_web=true ;;
|
||||||
|
alert) build_alert=true ;;
|
||||||
|
sys) build_sys=true ;;
|
||||||
|
all) build_core=true; build_master=true; build_metric=true; build_web=true; build_alert=true; build_sys=true ;;
|
||||||
|
*) echo "Unknown --only target: $p" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
show_help
|
show_help
|
||||||
exit 0
|
exit 0
|
||||||
@@ -65,6 +104,10 @@ cd "$root"
|
|||||||
load_build_user
|
load_build_user
|
||||||
build_args+=("--build-arg" "ARGUS_BUILD_UID=${ARGUS_BUILD_UID}" "--build-arg" "ARGUS_BUILD_GID=${ARGUS_BUILD_GID}")
|
build_args+=("--build-arg" "ARGUS_BUILD_UID=${ARGUS_BUILD_UID}" "--build-arg" "ARGUS_BUILD_GID=${ARGUS_BUILD_GID}")
|
||||||
|
|
||||||
|
if [[ "$no_cache" == true ]]; then
|
||||||
|
build_args+=("--no-cache")
|
||||||
|
fi
|
||||||
|
|
||||||
master_root="$root/src/master"
|
master_root="$root/src/master"
|
||||||
master_offline_tar="$master_root/offline_wheels.tar.gz"
|
master_offline_tar="$master_root/offline_wheels.tar.gz"
|
||||||
master_offline_dir="$master_root/offline_wheels"
|
master_offline_dir="$master_root/offline_wheels"
|
||||||
@@ -105,14 +148,22 @@ build_image() {
|
|||||||
local image_name=$1
|
local image_name=$1
|
||||||
local dockerfile_path=$2
|
local dockerfile_path=$2
|
||||||
local tag=$3
|
local tag=$3
|
||||||
|
local context="."
|
||||||
shift 3
|
shift 3
|
||||||
|
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
context=$1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
local extra_args=("$@")
|
local extra_args=("$@")
|
||||||
|
|
||||||
echo "🔄 Building $image_name image..."
|
echo "🔄 Building $image_name image..."
|
||||||
echo " Dockerfile: $dockerfile_path"
|
echo " Dockerfile: $dockerfile_path"
|
||||||
echo " Tag: $tag"
|
echo " Tag: $tag"
|
||||||
|
echo " Context: $context"
|
||||||
|
|
||||||
if docker build "${build_args[@]}" "${extra_args[@]}" -f "$dockerfile_path" -t "$tag" .; then
|
if docker build "${build_args[@]}" "${extra_args[@]}" -f "$dockerfile_path" -t "$tag" "$context"; then
|
||||||
echo "✅ $image_name image built successfully"
|
echo "✅ $image_name image built successfully"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
@@ -121,9 +172,38 @@ build_image() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pull_base_image() {
|
||||||
|
local image_ref=$1
|
||||||
|
local attempts=${2:-3}
|
||||||
|
local delay=${3:-5}
|
||||||
|
|
||||||
|
# If the image already exists locally, skip pulling.
|
||||||
|
if docker image inspect "$image_ref" >/dev/null 2>&1; then
|
||||||
|
echo " Local image present; skip pull: $image_ref"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for ((i=1; i<=attempts; i++)); do
|
||||||
|
echo " Pulling base image ($i/$attempts): $image_ref"
|
||||||
|
if docker pull "$image_ref" >/dev/null; then
|
||||||
|
echo " Base image ready: $image_ref"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo " Pull failed: $image_ref"
|
||||||
|
if (( i < attempts )); then
|
||||||
|
echo " Retrying in ${delay}s..."
|
||||||
|
sleep "$delay"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "❌ Unable to pull base image after ${attempts} attempts: $image_ref"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
images_built=()
|
images_built=()
|
||||||
build_failed=false
|
build_failed=false
|
||||||
|
|
||||||
|
if [[ "$build_core" == true ]]; then
|
||||||
if build_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest"; then
|
if build_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest"; then
|
||||||
images_built+=("argus-elasticsearch:latest")
|
images_built+=("argus-elasticsearch:latest")
|
||||||
else
|
else
|
||||||
@@ -145,6 +225,7 @@ if build_image "BIND9" "src/bind/build/Dockerfile" "argus-bind9:latest"; then
|
|||||||
else
|
else
|
||||||
build_failed=true
|
build_failed=true
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -159,6 +240,9 @@ if [[ "$build_master" == true ]]; then
|
|||||||
if [[ "$build_master_offline" == true ]]; then
|
if [[ "$build_master_offline" == true ]]; then
|
||||||
master_args+=("--offline")
|
master_args+=("--offline")
|
||||||
fi
|
fi
|
||||||
|
if [[ "$no_cache" == true ]]; then
|
||||||
|
master_args+=("--no-cache")
|
||||||
|
fi
|
||||||
if ./scripts/build_images.sh "${master_args[@]}"; then
|
if ./scripts/build_images.sh "${master_args[@]}"; then
|
||||||
if [[ "$build_master_offline" == true ]]; then
|
if [[ "$build_master_offline" == true ]]; then
|
||||||
images_built+=("argus-master:offline")
|
images_built+=("argus-master:offline")
|
||||||
@@ -171,6 +255,127 @@ if [[ "$build_master" == true ]]; then
|
|||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$build_metric" == true ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Building Metric module images..."
|
||||||
|
|
||||||
|
metric_base_images=(
|
||||||
|
"ubuntu:22.04"
|
||||||
|
"ubuntu/prometheus:3-24.04_stable"
|
||||||
|
"grafana/grafana:11.1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
for base_image in "${metric_base_images[@]}"; do
|
||||||
|
if ! pull_base_image "$base_image"; then
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
metric_builds=(
|
||||||
|
"Metric FTP|src/metric/ftp/build/Dockerfile|argus-metric-ftp:latest|src/metric/ftp/build"
|
||||||
|
"Metric Prometheus|src/metric/prometheus/build/Dockerfile|argus-metric-prometheus:latest|src/metric/prometheus/build"
|
||||||
|
"Metric Grafana|src/metric/grafana/build/Dockerfile|argus-metric-grafana:latest|src/metric/grafana/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
for build_spec in "${metric_builds[@]}"; do
|
||||||
|
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||||
|
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||||
|
images_built+=("$image_tag")
|
||||||
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =======================================
|
||||||
|
# Sys (system tests) node images
|
||||||
|
# =======================================
|
||||||
|
|
||||||
|
if [[ "$build_sys" == true ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Building Sys node images..."
|
||||||
|
|
||||||
|
sys_base_images=(
|
||||||
|
"ubuntu:22.04"
|
||||||
|
"nvidia/cuda:12.2.2-runtime-ubuntu22.04"
|
||||||
|
)
|
||||||
|
|
||||||
|
for base_image in "${sys_base_images[@]}"; do
|
||||||
|
if ! pull_base_image "$base_image"; then
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
sys_builds=(
|
||||||
|
"Sys Node|src/sys/build/node/Dockerfile|argus-sys-node:latest|."
|
||||||
|
"Sys Metric Test Node|src/sys/build/test-node/Dockerfile|argus-sys-metric-test-node:latest|."
|
||||||
|
"Sys Metric Test GPU Node|src/sys/build/test-gpu-node/Dockerfile|argus-sys-metric-test-gpu-node:latest|."
|
||||||
|
)
|
||||||
|
|
||||||
|
for build_spec in "${sys_builds[@]}"; do
|
||||||
|
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||||
|
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||||
|
images_built+=("$image_tag")
|
||||||
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =======================================
|
||||||
|
# Web & Alert module images
|
||||||
|
# =======================================
|
||||||
|
|
||||||
|
if [[ "$build_web" == true || "$build_alert" == true ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Building Web and Alert module images..."
|
||||||
|
|
||||||
|
# Pre-pull commonly used base images for stability
|
||||||
|
web_alert_base_images=(
|
||||||
|
"node:20"
|
||||||
|
"ubuntu:24.04"
|
||||||
|
)
|
||||||
|
|
||||||
|
for base_image in "${web_alert_base_images[@]}"; do
|
||||||
|
if ! pull_base_image "$base_image"; then
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$build_web" == true ]]; then
|
||||||
|
web_builds=(
|
||||||
|
"Web Frontend|src/web/build_tools/frontend/Dockerfile|argus-web-frontend:latest|."
|
||||||
|
"Web Proxy|src/web/build_tools/proxy/Dockerfile|argus-web-proxy:latest|."
|
||||||
|
)
|
||||||
|
for build_spec in "${web_builds[@]}"; do
|
||||||
|
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||||
|
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||||
|
images_built+=("$image_tag")
|
||||||
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$build_alert" == true ]]; then
|
||||||
|
alert_builds=(
|
||||||
|
"Alertmanager|src/alert/alertmanager/build/Dockerfile|argus-alertmanager:latest|."
|
||||||
|
)
|
||||||
|
for build_spec in "${alert_builds[@]}"; do
|
||||||
|
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||||
|
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||||
|
images_built+=("$image_tag")
|
||||||
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
echo "📦 Build Summary"
|
echo "📦 Build Summary"
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
@@ -197,7 +402,6 @@ if [[ "$build_master_offline" == true ]]; then
|
|||||||
echo ""
|
echo ""
|
||||||
echo "🧳 Master offline wheels 已解压到 $master_offline_dir"
|
echo "🧳 Master offline wheels 已解压到 $master_offline_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🚀 Next steps:"
|
echo "🚀 Next steps:"
|
||||||
echo " ./build/save_images.sh --compress # 导出镜像"
|
echo " ./build/save_images.sh --compress # 导出镜像"
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ declare -A images=(
|
|||||||
["argus-kibana:latest"]="argus-kibana-latest.tar"
|
["argus-kibana:latest"]="argus-kibana-latest.tar"
|
||||||
["argus-bind9:latest"]="argus-bind9-latest.tar"
|
["argus-bind9:latest"]="argus-bind9-latest.tar"
|
||||||
["argus-master:offline"]="argus-master-offline.tar"
|
["argus-master:offline"]="argus-master-offline.tar"
|
||||||
|
["argus-metric-ftp:latest"]="argus-metric-ftp-latest.tar"
|
||||||
|
["argus-metric-prometheus:latest"]="argus-metric-prometheus-latest.tar"
|
||||||
|
["argus-metric-grafana:latest"]="argus-metric-grafana-latest.tar"
|
||||||
|
["argus-web-frontend:latest"]="argus-web-frontend-latest.tar"
|
||||||
|
["argus-web-proxy:latest"]="argus-web-proxy-latest.tar"
|
||||||
|
["argus-alertmanager:latest"]="argus-alertmanager-latest.tar"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 函数:检查镜像是否存在
|
# 函数:检查镜像是否存在
|
||||||
|
|||||||
1
deployment/.gitignore
vendored
Normal file
1
deployment/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
artifact/
|
||||||
16
deployment/build/README.md
Normal file
16
deployment/build/README.md
Normal 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).
|
||||||
87
deployment/build/build_client_package.sh
Executable file
87
deployment/build/build_client_package.sh
Executable 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
|
||||||
110
deployment/build/build_server_package.sh
Executable file
110
deployment/build/build_server_package.sh
Executable 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
33
deployment/build/common.sh
Executable 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"
|
||||||
|
}
|
||||||
|
|
||||||
29
deployment/build/templates/.env.example
Normal file
29
deployment/build/templates/.env.example
Normal 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
|
||||||
37
deployment/build/templates/client/INSTALL_CLIENT_zh.md
Normal file
37
deployment/build/templates/client/INSTALL_CLIENT_zh.md
Normal 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`
|
||||||
54
deployment/build/templates/client/publish.sh
Normal file
54
deployment/build/templates/client/publish.sh
Normal 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"
|
||||||
|
|
||||||
41
deployment/build/templates/docker-compose.filter.awk
Normal file
41
deployment/build/templates/docker-compose.filter.awk
Normal 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;
|
||||||
|
}
|
||||||
48
deployment/build/templates/docs/INSTALL_SERVER.md
Normal file
48
deployment/build/templates/docs/INSTALL_SERVER.md
Normal 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
|
||||||
|
|
||||||
28
deployment/build/templates/docs/INSTALL_SERVER_zh.md
Normal file
28
deployment/build/templates/docs/INSTALL_SERVER_zh.md
Normal 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`
|
||||||
|
|
||||||
21
deployment/build/templates/docs/TROUBLESHOOTING.md
Normal file
21
deployment/build/templates/docs/TROUBLESHOOTING.md
Normal 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
|
||||||
|
|
||||||
15
deployment/build/templates/docs/TROUBLESHOOTING_zh.md
Normal file
15
deployment/build/templates/docs/TROUBLESHOOTING_zh.md
Normal 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`
|
||||||
|
|
||||||
117
deployment/build/templates/scripts/server-diagnose.sh
Executable file
117
deployment/build/templates/scripts/server-diagnose.sh
Executable 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
|
||||||
194
deployment/build/templates/scripts/server-install.sh
Executable file
194
deployment/build/templates/scripts/server-install.sh
Executable 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 "$@"
|
||||||
73
deployment/build/templates/scripts/server-prepare-dirs.sh
Executable file
73
deployment/build/templates/scripts/server-prepare-dirs.sh
Executable 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"
|
||||||
90
deployment/build/templates/scripts/server-selfcheck.sh
Executable file
90
deployment/build/templates/scripts/server-selfcheck.sh
Executable 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
|
||||||
28
deployment/build/templates/scripts/server-status.sh
Executable file
28
deployment/build/templates/scripts/server-status.sh
Executable 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)"
|
||||||
|
|
||||||
16
deployment/build/templates/scripts/server-uninstall.sh
Executable file
16
deployment/build/templates/scripts/server-uninstall.sh
Executable 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
BIN
doc/metric_lists.xlsx
Normal file
Binary file not shown.
@@ -34,6 +34,18 @@ Agent 不再依赖配置文件;所有参数均由环境变量与主机名推
|
|||||||
| `MASTER_ENDPOINT` | 是 | N/A | Master 基础地址,可写 `http://host:3000` 或 `host:3000`(自动补全 `http://`)。 |
|
| `MASTER_ENDPOINT` | 是 | N/A | Master 基础地址,可写 `http://host:3000` 或 `host:3000`(自动补全 `http://`)。 |
|
||||||
| `REPORT_INTERVAL_SECONDS` | 否 | `60` | 状态上报间隔(秒)。必须为正整数。 |
|
| `REPORT_INTERVAL_SECONDS` | 否 | `60` | 状态上报间隔(秒)。必须为正整数。 |
|
||||||
| `AGENT_HOSTNAME` | 否 | `$(hostname)` | 覆盖容器内主机名,便于测试或特殊命名需求。 |
|
| `AGENT_HOSTNAME` | 否 | `$(hostname)` | 覆盖容器内主机名,便于测试或特殊命名需求。 |
|
||||||
|
| `AGENT_ENV` | 否 | 来源于主机名 | 运行环境标识(如 `dev`、`prod`)。与 `AGENT_USER`、`AGENT_INSTANCE` 必须同时设置。 |
|
||||||
|
| `AGENT_USER` | 否 | 来源于主机名 | 归属用户或团队标识。与 `AGENT_ENV`、`AGENT_INSTANCE` 必须同时设置。 |
|
||||||
|
| `AGENT_INSTANCE` | 否 | 来源于主机名 | 实例编号或别名。与 `AGENT_ENV`、`AGENT_USER` 必须同时设置。 |
|
||||||
|
|
||||||
|
主机名与元数据的解析优先级:
|
||||||
|
|
||||||
|
1. 若设置 `AGENT_ENV` / `AGENT_USER` / `AGENT_INSTANCE` 且全部存在,则直接使用这些值。
|
||||||
|
2. 否则检查历史 `node.json`(注册成功后由 Master 返回的信息),若包含 `env` / `user` / `instance` 则沿用。
|
||||||
|
3. 若以上均不可用,则按历史约定从主机名解析 `env-user-instance` 前缀。
|
||||||
|
4. 如果仍无法得到完整结果,Agent 启动会失败并提示需要提供上述环境变量。
|
||||||
|
|
||||||
|
> 提示:在首次部署时需确保环境变量或主机名能够提供完整信息。完成注册后,Agent 会把 Master 返回的元数据写入 `node.json`,后续重启无需再次提供环境变量就能保持一致性。
|
||||||
|
|
||||||
派生路径:
|
派生路径:
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ _HOSTNAME_PATTERN = re.compile(r"^([^-]+)-([^-]+)-([^-]+)-.*$")
|
|||||||
def collect_metadata(config: AgentConfig) -> Dict[str, Any]:
|
def collect_metadata(config: AgentConfig) -> Dict[str, Any]:
|
||||||
"""汇总节点注册需要的静态信息。"""
|
"""汇总节点注册需要的静态信息。"""
|
||||||
hostname = config.hostname
|
hostname = config.hostname
|
||||||
env, user, instance = _parse_hostname(hostname)
|
|
||||||
meta = {
|
meta = {
|
||||||
"hostname": hostname,
|
"hostname": hostname,
|
||||||
"ip": _detect_ip_address(),
|
"ip": _detect_ip_address(),
|
||||||
"env": env,
|
"env": config.environment,
|
||||||
"user": user,
|
"user": config.user,
|
||||||
"instance": instance,
|
"instance": config.instance,
|
||||||
"cpu_number": _detect_cpu_count(),
|
"cpu_number": _detect_cpu_count(),
|
||||||
"memory_in_bytes": _detect_memory_bytes(),
|
"memory_in_bytes": _detect_memory_bytes(),
|
||||||
"gpu_number": _detect_gpu_count(),
|
"gpu_number": _detect_gpu_count(),
|
||||||
|
|||||||
@@ -6,14 +6,21 @@ from dataclasses import dataclass
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
from .state import load_node_state
|
||||||
from .version import VERSION
|
from .version import VERSION
|
||||||
|
from .log import get_logger
|
||||||
|
|
||||||
DEFAULT_REPORT_INTERVAL_SECONDS: Final[int] = 60
|
DEFAULT_REPORT_INTERVAL_SECONDS: Final[int] = 60
|
||||||
|
|
||||||
|
LOGGER = get_logger("argus.agent.config")
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class AgentConfig:
|
class AgentConfig:
|
||||||
hostname: str
|
hostname: str
|
||||||
|
environment: str
|
||||||
|
user: str
|
||||||
|
instance: str
|
||||||
node_file: str
|
node_file: str
|
||||||
version: str
|
version: str
|
||||||
master_endpoint: str
|
master_endpoint: str
|
||||||
@@ -47,11 +54,68 @@ def _resolve_hostname() -> str:
|
|||||||
return os.environ.get("AGENT_HOSTNAME") or socket.gethostname()
|
return os.environ.get("AGENT_HOSTNAME") or socket.gethostname()
|
||||||
|
|
||||||
|
|
||||||
|
def _load_metadata_from_state(node_file: str) -> tuple[str, str, str] | None:
|
||||||
|
state = load_node_state(node_file)
|
||||||
|
if not state:
|
||||||
|
return None
|
||||||
|
|
||||||
|
meta = state.get("meta_data") or {}
|
||||||
|
env = meta.get("env") or state.get("env")
|
||||||
|
user = meta.get("user") or state.get("user")
|
||||||
|
instance = meta.get("instance") or state.get("instance")
|
||||||
|
|
||||||
|
if env and user and instance:
|
||||||
|
LOGGER.debug("Metadata resolved from node state", extra={"node_file": node_file})
|
||||||
|
return env, user, instance
|
||||||
|
|
||||||
|
LOGGER.warning(
|
||||||
|
"node.json missing metadata fields; ignoring",
|
||||||
|
extra={"node_file": node_file, "meta_data": meta},
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_metadata_fields(hostname: str, node_file: str) -> tuple[str, str, str]:
|
||||||
|
env = os.environ.get("AGENT_ENV")
|
||||||
|
user = os.environ.get("AGENT_USER")
|
||||||
|
instance = os.environ.get("AGENT_INSTANCE")
|
||||||
|
|
||||||
|
if env and user and instance:
|
||||||
|
return env, user, instance
|
||||||
|
|
||||||
|
if any([env, user, instance]):
|
||||||
|
LOGGER.warning(
|
||||||
|
"Incomplete metadata environment variables; falling back to persisted metadata",
|
||||||
|
extra={
|
||||||
|
"has_env": bool(env),
|
||||||
|
"has_user": bool(user),
|
||||||
|
"has_instance": bool(instance),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
state_metadata = _load_metadata_from_state(node_file)
|
||||||
|
if state_metadata is not None:
|
||||||
|
return state_metadata
|
||||||
|
|
||||||
|
from .collector import _parse_hostname # Local import to avoid circular dependency
|
||||||
|
|
||||||
|
env, user, instance = _parse_hostname(hostname)
|
||||||
|
|
||||||
|
if not all([env, user, instance]):
|
||||||
|
raise ValueError(
|
||||||
|
"Failed to determine metadata fields; set AGENT_ENV/USER/INSTANCE or use supported hostname pattern"
|
||||||
|
)
|
||||||
|
|
||||||
|
return env, user, instance
|
||||||
|
|
||||||
|
|
||||||
def load_config() -> AgentConfig:
|
def load_config() -> AgentConfig:
|
||||||
"""从环境变量推导配置,移除了外部配置文件依赖。"""
|
"""从环境变量推导配置,移除了外部配置文件依赖。"""
|
||||||
|
|
||||||
hostname = _resolve_hostname()
|
hostname = _resolve_hostname()
|
||||||
node_file = f"/private/argus/agent/{hostname}/node.json"
|
node_file = f"/private/argus/agent/{hostname}/node.json"
|
||||||
|
environment, user, instance = _resolve_metadata_fields(hostname, node_file)
|
||||||
|
|
||||||
health_dir = f"/private/argus/agent/{hostname}/health/"
|
health_dir = f"/private/argus/agent/{hostname}/health/"
|
||||||
|
|
||||||
master_endpoint_env = os.environ.get("MASTER_ENDPOINT")
|
master_endpoint_env = os.environ.get("MASTER_ENDPOINT")
|
||||||
@@ -66,6 +130,9 @@ def load_config() -> AgentConfig:
|
|||||||
|
|
||||||
return AgentConfig(
|
return AgentConfig(
|
||||||
hostname=hostname,
|
hostname=hostname,
|
||||||
|
environment=environment,
|
||||||
|
user=user,
|
||||||
|
instance=instance,
|
||||||
node_file=node_file,
|
node_file=node_file,
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
master_endpoint=master_endpoint,
|
master_endpoint=master_endpoint,
|
||||||
|
|||||||
BIN
src/agent/dist/argus-agent
vendored
BIN
src/agent/dist/argus-agent
vendored
Binary file not shown.
@@ -12,6 +12,8 @@ VENV_DIR="$BUILD_ROOT/venv"
|
|||||||
|
|
||||||
AGENT_BUILD_IMAGE="${AGENT_BUILD_IMAGE:-python:3.11-slim-bullseye}"
|
AGENT_BUILD_IMAGE="${AGENT_BUILD_IMAGE:-python:3.11-slim-bullseye}"
|
||||||
AGENT_BUILD_USE_DOCKER="${AGENT_BUILD_USE_DOCKER:-1}"
|
AGENT_BUILD_USE_DOCKER="${AGENT_BUILD_USE_DOCKER:-1}"
|
||||||
|
# 默认在容器内忽略代理以避免公司内网代理在 Docker 网络不可达导致 pip 失败(可用 0 关闭)
|
||||||
|
AGENT_BUILD_IGNORE_PROXY="${AGENT_BUILD_IGNORE_PROXY:-1}"
|
||||||
USED_DOCKER=0
|
USED_DOCKER=0
|
||||||
|
|
||||||
run_host_build() {
|
run_host_build() {
|
||||||
@@ -71,6 +73,7 @@ run_docker_build() {
|
|||||||
pass_env_if_set http_proxy
|
pass_env_if_set http_proxy
|
||||||
pass_env_if_set https_proxy
|
pass_env_if_set https_proxy
|
||||||
pass_env_if_set no_proxy
|
pass_env_if_set no_proxy
|
||||||
|
pass_env_if_set AGENT_BUILD_IGNORE_PROXY
|
||||||
|
|
||||||
build_script=$(cat <<'INNER'
|
build_script=$(cat <<'INNER'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -82,6 +85,10 @@ rm -rf build dist
|
|||||||
mkdir -p build/pyinstaller dist
|
mkdir -p build/pyinstaller dist
|
||||||
python3 -m venv --copies build/venv
|
python3 -m venv --copies build/venv
|
||||||
source build/venv/bin/activate
|
source build/venv/bin/activate
|
||||||
|
# 若指定忽略代理,则清空常见代理与 pip 镜像环境变量,避免容器内代理不可达
|
||||||
|
if [ "${AGENT_BUILD_IGNORE_PROXY:-1}" = "1" ]; then
|
||||||
|
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY PIP_INDEX_URL PIP_EXTRA_INDEX_URL PIP_TRUSTED_HOST
|
||||||
|
fi
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install .
|
pip install .
|
||||||
pip install pyinstaller==6.6.0
|
pip install pyinstaller==6.6.0
|
||||||
|
|||||||
0
src/agent/tests/__init__.py
Normal file
0
src/agent/tests/__init__.py
Normal file
@@ -60,6 +60,36 @@ services:
|
|||||||
ipv4_address: 172.28.0.20
|
ipv4_address: 172.28.0.20
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
agent_env:
|
||||||
|
image: ubuntu:22.04
|
||||||
|
container_name: argus-agent-env-e2e
|
||||||
|
hostname: host_abc
|
||||||
|
depends_on:
|
||||||
|
- master
|
||||||
|
- bind
|
||||||
|
environment:
|
||||||
|
- MASTER_ENDPOINT=http://master.argus.com:3000
|
||||||
|
- REPORT_INTERVAL_SECONDS=2
|
||||||
|
- AGENT_ENV=prod
|
||||||
|
- AGENT_USER=ml
|
||||||
|
- AGENT_INSTANCE=node-3
|
||||||
|
- AGENT_HOSTNAME=host_abc
|
||||||
|
- "ARGUS_BUILD_UID=${ARGUS_BUILD_UID:-2133}"
|
||||||
|
- "ARGUS_BUILD_GID=${ARGUS_BUILD_GID:-2015}"
|
||||||
|
volumes:
|
||||||
|
- ./private/argus/agent/host_abc:/private/argus/agent/host_abc
|
||||||
|
- ./private/argus/agent/host_abc/health:/private/argus/agent/host_abc/health
|
||||||
|
- ./private/argus/etc:/private/argus/etc
|
||||||
|
- ../dist/argus-agent:/usr/local/bin/argus-agent:ro
|
||||||
|
- ./scripts/agent_entrypoint.sh:/usr/local/bin/agent-entrypoint.sh:ro
|
||||||
|
- ../scripts/agent_deployment_verify.sh:/usr/local/bin/agent_deployment_verify.sh:ro
|
||||||
|
entrypoint:
|
||||||
|
- /usr/local/bin/agent-entrypoint.sh
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: 172.28.0.21
|
||||||
|
restart: always
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ SCRIPTS=(
|
|||||||
"02_up.sh"
|
"02_up.sh"
|
||||||
"03_wait_and_assert_registration.sh"
|
"03_wait_and_assert_registration.sh"
|
||||||
"04_write_health_files.sh"
|
"04_write_health_files.sh"
|
||||||
"08_verify_agent.sh"
|
"05_verify_agent.sh"
|
||||||
"05_assert_status_on_master.sh"
|
"06_assert_status_on_master.sh"
|
||||||
"06_restart_agent_and_reregister.sh"
|
"07_restart_agent_and_reregister.sh"
|
||||||
"07_down.sh"
|
"08_down.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
for script in "${SCRIPTS[@]}"; do
|
for script in "${SCRIPTS[@]}"; do
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ compose() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
docker container rm -f argus-agent-e2e argus-master-agent-e2e argus-bind-agent-e2e >/dev/null 2>&1 || true
|
docker container rm -f argus-agent-e2e argus-agent-env-e2e argus-master-agent-e2e argus-bind-agent-e2e >/dev/null 2>&1 || true
|
||||||
|
|
||||||
docker network rm tests_default >/dev/null 2>&1 || true
|
docker network rm tests_default >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||||||
TMP_ROOT="$TEST_ROOT/tmp"
|
TMP_ROOT="$TEST_ROOT/tmp"
|
||||||
API_BASE="http://localhost:32300/api/v1/master"
|
API_BASE="http://localhost:32300/api/v1/master"
|
||||||
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
||||||
|
ENV_AGENT_HOSTNAME="host_abc"
|
||||||
NODE_FILE="$TEST_ROOT/private/argus/agent/$AGENT_HOSTNAME/node.json"
|
NODE_FILE="$TEST_ROOT/private/argus/agent/$AGENT_HOSTNAME/node.json"
|
||||||
|
ENV_NODE_FILE="$TEST_ROOT/private/argus/agent/$ENV_AGENT_HOSTNAME/node.json"
|
||||||
|
|
||||||
mkdir -p "$TMP_ROOT"
|
mkdir -p "$TMP_ROOT"
|
||||||
|
|
||||||
node_id=""
|
primary_node_id=""
|
||||||
|
env_node_id=""
|
||||||
for _ in {1..30}; do
|
for _ in {1..30}; do
|
||||||
sleep 2
|
sleep 2
|
||||||
response=$(curl -sS "$API_BASE/nodes" || true)
|
response=$(curl -sS "$API_BASE/nodes" || true)
|
||||||
@@ -19,24 +22,49 @@ for _ in {1..30}; do
|
|||||||
fi
|
fi
|
||||||
list_file="$TMP_ROOT/nodes_list.json"
|
list_file="$TMP_ROOT/nodes_list.json"
|
||||||
echo "$response" > "$list_file"
|
echo "$response" > "$list_file"
|
||||||
node_id=$(python3 - "$list_file" <<'PY'
|
readarray -t node_ids < <(python3 - "$list_file" "$AGENT_HOSTNAME" "$ENV_AGENT_HOSTNAME" <<'PY'
|
||||||
import json, sys
|
import json, sys
|
||||||
|
|
||||||
with open(sys.argv[1]) as handle:
|
with open(sys.argv[1]) as handle:
|
||||||
nodes = json.load(handle)
|
nodes = json.load(handle)
|
||||||
print(nodes[0]["id"] if nodes else "")
|
|
||||||
|
target_primary = sys.argv[2]
|
||||||
|
target_env = sys.argv[3]
|
||||||
|
|
||||||
|
primary_id = ""
|
||||||
|
env_id = ""
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
if node.get("name") == target_primary:
|
||||||
|
primary_id = node.get("id", "")
|
||||||
|
if node.get("name") == target_env:
|
||||||
|
env_id = node.get("id", "")
|
||||||
|
|
||||||
|
print(primary_id)
|
||||||
|
print(env_id)
|
||||||
PY
|
PY
|
||||||
)
|
)
|
||||||
if [[ -n "$node_id" ]]; then
|
|
||||||
|
primary_node_id="${node_ids[0]}"
|
||||||
|
env_node_id="${node_ids[1]}"
|
||||||
|
|
||||||
|
if [[ -n "$primary_node_id" && -n "$env_node_id" ]]; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$node_id" ]]; then
|
if [[ -z "$primary_node_id" ]]; then
|
||||||
echo "[ERROR] Agent did not register within timeout" >&2
|
echo "[ERROR] Primary agent did not register within timeout" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$node_id" > "$TMP_ROOT/node_id"
|
if [[ -z "$env_node_id" ]]; then
|
||||||
|
echo "[ERROR] Env-variable agent did not register within timeout" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$primary_node_id" > "$TMP_ROOT/node_id"
|
||||||
|
echo "$env_node_id" > "$TMP_ROOT/node_id_host_abc"
|
||||||
|
|
||||||
if [[ ! -f "$NODE_FILE" ]]; then
|
if [[ ! -f "$NODE_FILE" ]]; then
|
||||||
echo "[ERROR] node.json not created at $NODE_FILE" >&2
|
echo "[ERROR] node.json not created at $NODE_FILE" >&2
|
||||||
@@ -50,8 +78,20 @@ with open(sys.argv[1]) as handle:
|
|||||||
assert "id" in node and node["id"], "node.json missing id"
|
assert "id" in node and node["id"], "node.json missing id"
|
||||||
PY
|
PY
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_NODE_FILE" ]]; then
|
||||||
|
echo "[ERROR] node.json not created at $ENV_NODE_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - "$ENV_NODE_FILE" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as handle:
|
||||||
|
node = json.load(handle)
|
||||||
|
assert "id" in node and node["id"], "env agent node.json missing id"
|
||||||
|
PY
|
||||||
|
|
||||||
detail_file="$TMP_ROOT/initial_detail.json"
|
detail_file="$TMP_ROOT/initial_detail.json"
|
||||||
curl -sS "$API_BASE/nodes/$node_id" -o "$detail_file"
|
curl -sS "$API_BASE/nodes/$primary_node_id" -o "$detail_file"
|
||||||
python3 - "$detail_file" "$TMP_ROOT/initial_ip" <<'PY'
|
python3 - "$detail_file" "$TMP_ROOT/initial_ip" <<'PY'
|
||||||
import json, sys, pathlib
|
import json, sys, pathlib
|
||||||
with open(sys.argv[1]) as handle:
|
with open(sys.argv[1]) as handle:
|
||||||
@@ -62,4 +102,5 @@ if not ip:
|
|||||||
pathlib.Path(sys.argv[2]).write_text(ip)
|
pathlib.Path(sys.argv[2]).write_text(ip)
|
||||||
PY
|
PY
|
||||||
|
|
||||||
echo "[INFO] Agent registered with node id $node_id"
|
echo "[INFO] Agent registered with node id $primary_node_id"
|
||||||
|
echo "[INFO] Env-variable agent registered with node id $env_node_id"
|
||||||
|
|||||||
60
src/agent/tests/scripts/05_verify_agent.sh
Executable file
60
src/agent/tests/scripts/05_verify_agent.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$TEST_ROOT/.." && pwd)"
|
||||||
|
VERIFY_SCRIPT="$REPO_ROOT/scripts/agent_deployment_verify.sh"
|
||||||
|
ENV_NODE_ID_FILE="$TEST_ROOT/tmp/node_id_host_abc"
|
||||||
|
PRIMARY_CONTAINER="argus-agent-e2e"
|
||||||
|
ENV_CONTAINER="argus-agent-env-e2e"
|
||||||
|
PRIMARY_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
||||||
|
ENV_HOSTNAME="host_abc"
|
||||||
|
|
||||||
|
if ! docker ps --format '{{.Names}}' | grep -q "^${PRIMARY_CONTAINER}$"; then
|
||||||
|
echo "[WARN] agent container not running; skip verification"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if docker exec -i "$PRIMARY_CONTAINER" bash -lc 'command -v curl >/dev/null && command -v jq >/dev/null'; then
|
||||||
|
echo "[INFO] curl/jq already installed in agent container"
|
||||||
|
else
|
||||||
|
echo "[INFO] Installing curl/jq in agent container"
|
||||||
|
docker exec -i "$PRIMARY_CONTAINER" bash -lc 'apt-get update >/dev/null 2>&1 && apt-get install -y curl jq >/dev/null 2>&1' || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$VERIFY_SCRIPT" ]]; then
|
||||||
|
echo "[ERROR] Verification script missing at $VERIFY_SCRIPT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_verifier() {
|
||||||
|
local container="$1" hostname="$2"
|
||||||
|
|
||||||
|
if ! docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
|
||||||
|
echo "[WARN] container $container not running; skip"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker exec -i "$container" bash -lc 'command -v /usr/local/bin/agent_deployment_verify.sh >/dev/null'; then
|
||||||
|
echo "[ERROR] /usr/local/bin/agent_deployment_verify.sh missing in $container" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Running verification for $hostname in $container"
|
||||||
|
docker exec -i "$container" env VERIFY_HOSTNAME="$hostname" /usr/local/bin/agent_deployment_verify.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
run_verifier "$PRIMARY_CONTAINER" "$PRIMARY_HOSTNAME"
|
||||||
|
|
||||||
|
if docker ps --format '{{.Names}}' | grep -q "^${ENV_CONTAINER}$"; then
|
||||||
|
if docker exec -i "$ENV_CONTAINER" bash -lc 'command -v curl >/dev/null && command -v jq >/dev/null'; then
|
||||||
|
echo "[INFO] curl/jq already installed in env agent container"
|
||||||
|
else
|
||||||
|
echo "[INFO] Installing curl/jq in env agent container"
|
||||||
|
docker exec -i "$ENV_CONTAINER" bash -lc 'apt-get update >/dev/null 2>&1 && apt-get install -y curl jq >/dev/null 2>&1' || true
|
||||||
|
fi
|
||||||
|
run_verifier "$ENV_CONTAINER" "$ENV_HOSTNAME"
|
||||||
|
else
|
||||||
|
echo "[WARN] env-driven agent container not running; skip secondary verification"
|
||||||
|
fi
|
||||||
@@ -6,6 +6,8 @@ TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||||||
TMP_ROOT="$TEST_ROOT/tmp"
|
TMP_ROOT="$TEST_ROOT/tmp"
|
||||||
API_BASE="http://localhost:32300/api/v1/master"
|
API_BASE="http://localhost:32300/api/v1/master"
|
||||||
NODE_ID="$(cat "$TMP_ROOT/node_id")"
|
NODE_ID="$(cat "$TMP_ROOT/node_id")"
|
||||||
|
ENV_NODE_ID="$(cat "$TMP_ROOT/node_id_host_abc")"
|
||||||
|
ENV_HOSTNAME="host_abc"
|
||||||
NODES_JSON="$TEST_ROOT/private/argus/metric/prometheus/nodes.json"
|
NODES_JSON="$TEST_ROOT/private/argus/metric/prometheus/nodes.json"
|
||||||
|
|
||||||
success=false
|
success=false
|
||||||
@@ -41,13 +43,36 @@ if [[ ! -f "$NODES_JSON" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 - "$NODES_JSON" <<'PY'
|
python3 - "$NODES_JSON" "$NODE_ID" "$ENV_NODE_ID" <<'PY'
|
||||||
import json, sys
|
import json, sys
|
||||||
with open(sys.argv[1]) as handle:
|
with open(sys.argv[1]) as handle:
|
||||||
nodes = json.load(handle)
|
nodes = json.load(handle)
|
||||||
assert len(nodes) == 1, nodes
|
|
||||||
entry = nodes[0]
|
expected_primary = sys.argv[2]
|
||||||
assert entry["node_id"], entry
|
expected_env = sys.argv[3]
|
||||||
|
|
||||||
|
ids = {entry.get("node_id") for entry in nodes}
|
||||||
|
assert expected_primary in ids, nodes
|
||||||
|
assert expected_env in ids, nodes
|
||||||
|
assert len(nodes) >= 2, nodes
|
||||||
PY
|
PY
|
||||||
|
|
||||||
echo "[INFO] Master reflects agent health and nodes.json entries"
|
echo "[INFO] Master reflects agent health and nodes.json entries"
|
||||||
|
|
||||||
|
env_detail_file="$TMP_ROOT/env_agent_detail.json"
|
||||||
|
curl -sS "$API_BASE/nodes/$ENV_NODE_ID" -o "$env_detail_file"
|
||||||
|
python3 - "$env_detail_file" "$ENV_HOSTNAME" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as handle:
|
||||||
|
node = json.load(handle)
|
||||||
|
|
||||||
|
expected_name = sys.argv[2]
|
||||||
|
|
||||||
|
assert node.get("name") == expected_name, node
|
||||||
|
meta = node.get("meta_data", {})
|
||||||
|
assert meta.get("env") == "prod", meta
|
||||||
|
assert meta.get("user") == "ml", meta
|
||||||
|
assert meta.get("instance") == "node-3", meta
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "[INFO] Env-variable agent reports expected metadata"
|
||||||
@@ -6,10 +6,20 @@ TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||||||
TMP_ROOT="$TEST_ROOT/tmp"
|
TMP_ROOT="$TEST_ROOT/tmp"
|
||||||
API_BASE="http://localhost:32300/api/v1/master"
|
API_BASE="http://localhost:32300/api/v1/master"
|
||||||
NODE_ID="$(cat "$TMP_ROOT/node_id")"
|
NODE_ID="$(cat "$TMP_ROOT/node_id")"
|
||||||
|
ENV_NODE_ID_FILE="$TMP_ROOT/node_id_host_abc"
|
||||||
|
if [[ ! -f "$ENV_NODE_ID_FILE" ]]; then
|
||||||
|
echo "[ERROR] Env agent node id file missing at $ENV_NODE_ID_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENV_NODE_ID="$(cat "$ENV_NODE_ID_FILE")"
|
||||||
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
||||||
|
ENV_AGENT_HOSTNAME="host_abc"
|
||||||
NETWORK_NAME="tests_default"
|
NETWORK_NAME="tests_default"
|
||||||
NEW_AGENT_IP="172.28.0.200"
|
NEW_AGENT_IP="172.28.0.200"
|
||||||
|
NEW_ENV_AGENT_IP="172.28.0.210"
|
||||||
ENTRYPOINT_SCRIPT="$SCRIPT_DIR/agent_entrypoint.sh"
|
ENTRYPOINT_SCRIPT="$SCRIPT_DIR/agent_entrypoint.sh"
|
||||||
|
VERIFY_SCRIPT="$TEST_ROOT/../scripts/agent_deployment_verify.sh"
|
||||||
ENV_FILE="$TEST_ROOT/.env"
|
ENV_FILE="$TEST_ROOT/.env"
|
||||||
|
|
||||||
# 中文提示:重启场景也需要同样的入口脚本,确保 DNS 注册逻辑一致
|
# 中文提示:重启场景也需要同样的入口脚本,确保 DNS 注册逻辑一致
|
||||||
@@ -18,6 +28,11 @@ if [[ ! -f "$ENTRYPOINT_SCRIPT" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$VERIFY_SCRIPT" ]]; then
|
||||||
|
echo "[ERROR] agent verification script missing at $VERIFY_SCRIPT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$TMP_ROOT/agent_binary_path" ]]; then
|
if [[ ! -f "$TMP_ROOT/agent_binary_path" ]]; then
|
||||||
echo "[ERROR] Agent binary path missing; rerun bootstrap" >&2
|
echo "[ERROR] Agent binary path missing; rerun bootstrap" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -74,15 +89,37 @@ if [[ "$prev_ip" != "$initial_ip" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
env_before_file="$TMP_ROOT/env_before_restart.json"
|
||||||
|
curl -sS "$API_BASE/nodes/$ENV_NODE_ID" -o "$env_before_file"
|
||||||
|
env_prev_last_updated=$(python3 - "$env_before_file" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as handle:
|
||||||
|
node = json.load(handle)
|
||||||
|
print(node.get("last_updated", ""))
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
env_prev_ip=$(python3 - "$env_before_file" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as handle:
|
||||||
|
node = json.load(handle)
|
||||||
|
print(node["meta_data"].get("ip", ""))
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
pushd "$TEST_ROOT" >/dev/null
|
pushd "$TEST_ROOT" >/dev/null
|
||||||
compose rm -sf agent
|
compose rm -sf agent
|
||||||
|
compose rm -sf agent_env
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
docker container rm -f argus-agent-e2e >/dev/null 2>&1 || true
|
docker container rm -f argus-agent-e2e >/dev/null 2>&1 || true
|
||||||
|
docker container rm -f argus-agent-env-e2e >/dev/null 2>&1 || true
|
||||||
|
|
||||||
AGENT_DIR="$TEST_ROOT/private/argus/agent/$AGENT_HOSTNAME"
|
AGENT_DIR="$TEST_ROOT/private/argus/agent/$AGENT_HOSTNAME"
|
||||||
HEALTH_DIR="$TEST_ROOT/private/argus/agent/$AGENT_HOSTNAME/health"
|
HEALTH_DIR="$TEST_ROOT/private/argus/agent/$AGENT_HOSTNAME/health"
|
||||||
|
|
||||||
|
ENV_AGENT_DIR="$TEST_ROOT/private/argus/agent/$ENV_AGENT_HOSTNAME"
|
||||||
|
ENV_HEALTH_DIR="$TEST_ROOT/private/argus/agent/$ENV_AGENT_HOSTNAME/health"
|
||||||
|
|
||||||
# 先以 sleep 方式启动容器,确保我们掌握注册时的网络状态
|
# 先以 sleep 方式启动容器,确保我们掌握注册时的网络状态
|
||||||
if ! docker run -d \
|
if ! docker run -d \
|
||||||
--name argus-agent-e2e \
|
--name argus-agent-e2e \
|
||||||
@@ -94,6 +131,7 @@ if ! docker run -d \
|
|||||||
-v "$TEST_ROOT/private/argus/etc:/private/argus/etc" \
|
-v "$TEST_ROOT/private/argus/etc:/private/argus/etc" \
|
||||||
-v "$AGENT_BINARY:/usr/local/bin/argus-agent:ro" \
|
-v "$AGENT_BINARY:/usr/local/bin/argus-agent:ro" \
|
||||||
-v "$ENTRYPOINT_SCRIPT:/usr/local/bin/agent-entrypoint.sh:ro" \
|
-v "$ENTRYPOINT_SCRIPT:/usr/local/bin/agent-entrypoint.sh:ro" \
|
||||||
|
-v "$VERIFY_SCRIPT:/usr/local/bin/agent_deployment_verify.sh:ro" \
|
||||||
-e MASTER_ENDPOINT=http://master.argus.com:3000 \
|
-e MASTER_ENDPOINT=http://master.argus.com:3000 \
|
||||||
-e REPORT_INTERVAL_SECONDS=2 \
|
-e REPORT_INTERVAL_SECONDS=2 \
|
||||||
-e ARGUS_BUILD_UID="$AGENT_UID" \
|
-e ARGUS_BUILD_UID="$AGENT_UID" \
|
||||||
@@ -141,3 +179,76 @@ if [[ "$success" != true ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[INFO] Agent restart produced successful re-registration with IP change"
|
echo "[INFO] Agent restart produced successful re-registration with IP change"
|
||||||
|
|
||||||
|
# ---- Restart env-driven agent without metadata environment variables ----
|
||||||
|
|
||||||
|
if [[ ! -d "$ENV_AGENT_DIR" ]]; then
|
||||||
|
echo "[ERROR] Env agent data dir missing at $ENV_AGENT_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "$ENV_HEALTH_DIR" ]]; then
|
||||||
|
mkdir -p "$ENV_HEALTH_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker run -d \
|
||||||
|
--name argus-agent-env-e2e \
|
||||||
|
--hostname "$ENV_AGENT_HOSTNAME" \
|
||||||
|
--network "$NETWORK_NAME" \
|
||||||
|
--ip "$NEW_ENV_AGENT_IP" \
|
||||||
|
-v "$ENV_AGENT_DIR:/private/argus/agent/$ENV_AGENT_HOSTNAME" \
|
||||||
|
-v "$ENV_HEALTH_DIR:/private/argus/agent/$ENV_AGENT_HOSTNAME/health" \
|
||||||
|
-v "$TEST_ROOT/private/argus/etc:/private/argus/etc" \
|
||||||
|
-v "$AGENT_BINARY:/usr/local/bin/argus-agent:ro" \
|
||||||
|
-v "$ENTRYPOINT_SCRIPT:/usr/local/bin/agent-entrypoint.sh:ro" \
|
||||||
|
-v "$VERIFY_SCRIPT:/usr/local/bin/agent_deployment_verify.sh:ro" \
|
||||||
|
-e MASTER_ENDPOINT=http://master.argus.com:3000 \
|
||||||
|
-e REPORT_INTERVAL_SECONDS=2 \
|
||||||
|
-e ARGUS_BUILD_UID="$AGENT_UID" \
|
||||||
|
-e ARGUS_BUILD_GID="$AGENT_GID" \
|
||||||
|
--entrypoint /usr/local/bin/agent-entrypoint.sh \
|
||||||
|
ubuntu:22.04 >/dev/null; then
|
||||||
|
echo "[ERROR] Failed to start env-driven agent container without metadata env" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
env_success=false
|
||||||
|
env_detail_file="$TMP_ROOT/env_post_restart.json"
|
||||||
|
for _ in {1..20}; do
|
||||||
|
sleep 3
|
||||||
|
if ! curl -sS "$API_BASE/nodes/$ENV_NODE_ID" -o "$env_detail_file"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if python3 - "$env_detail_file" "$env_prev_last_updated" "$ENV_NODE_ID" "$env_prev_ip" "$NEW_ENV_AGENT_IP" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as handle:
|
||||||
|
node = json.load(handle)
|
||||||
|
prev_last_updated = sys.argv[2]
|
||||||
|
expected_id = sys.argv[3]
|
||||||
|
old_ip = sys.argv[4]
|
||||||
|
expected_ip = sys.argv[5]
|
||||||
|
last_updated = node.get("last_updated")
|
||||||
|
current_ip = node["meta_data"].get("ip")
|
||||||
|
meta = node.get("meta_data", {})
|
||||||
|
assert node["id"] == expected_id
|
||||||
|
if current_ip != expected_ip:
|
||||||
|
raise SystemExit(1)
|
||||||
|
if current_ip == old_ip:
|
||||||
|
raise SystemExit(1)
|
||||||
|
if not last_updated or last_updated == prev_last_updated:
|
||||||
|
raise SystemExit(1)
|
||||||
|
if meta.get("env") != "prod" or meta.get("user") != "ml" or meta.get("instance") != "node-3":
|
||||||
|
raise SystemExit(1)
|
||||||
|
PY
|
||||||
|
then
|
||||||
|
env_success=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$env_success" != true ]]; then
|
||||||
|
echo "[ERROR] Env-driven agent did not reuse persisted metadata after restart" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Env-driven agent restart succeeded with persisted metadata"
|
||||||
@@ -13,7 +13,7 @@ compose() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
docker container rm -f argus-agent-e2e >/dev/null 2>&1 || true
|
docker container rm -f argus-agent-e2e argus-agent-env-e2e >/dev/null 2>&1 || true
|
||||||
|
|
||||||
pushd "$TEST_ROOT" >/dev/null
|
pushd "$TEST_ROOT" >/dev/null
|
||||||
compose down --remove-orphans
|
compose down --remove-orphans
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
VERIFY_SCRIPT="$(cd "$TEST_ROOT/.." && pwd)/scripts/agent_deployment_verify.sh"
|
|
||||||
|
|
||||||
if ! docker ps --format '{{.Names}}' | grep -q '^argus-agent-e2e$'; then
|
|
||||||
echo "[WARN] agent container not running; skip verification"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if docker exec -i argus-agent-e2e bash -lc 'command -v curl >/dev/null && command -v jq >/dev/null'; then
|
|
||||||
echo "[INFO] curl/jq already installed in agent container"
|
|
||||||
else
|
|
||||||
echo "[INFO] Installing curl/jq in agent container"
|
|
||||||
docker exec -i argus-agent-e2e bash -lc 'apt-get update >/dev/null 2>&1 && apt-get install -y curl jq >/dev/null 2>&1' || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if docker exec -i argus-agent-e2e bash -lc 'command -v /usr/local/bin/agent_deployment_verify.sh >/dev/null'; then
|
|
||||||
docker exec -i argus-agent-e2e /usr/local/bin/agent_deployment_verify.sh
|
|
||||||
elif [[ -x "$VERIFY_SCRIPT" ]]; then
|
|
||||||
docker exec -i argus-agent-e2e "$VERIFY_SCRIPT"
|
|
||||||
else
|
|
||||||
echo "[WARN] agent_deployment_verify.sh not found"
|
|
||||||
fi
|
|
||||||
151
src/agent/tests/test_config_metadata.py
Normal file
151
src/agent/tests/test_config_metadata.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from app.config import AgentConfig, load_config
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def temp_env(**overrides: str | None):
|
||||||
|
originals: dict[str, str | None] = {}
|
||||||
|
try:
|
||||||
|
for key, value in overrides.items():
|
||||||
|
originals[key] = os.environ.get(key)
|
||||||
|
if value is None:
|
||||||
|
os.environ.pop(key, None)
|
||||||
|
else:
|
||||||
|
os.environ[key] = value
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
for key, original in originals.items():
|
||||||
|
if original is None:
|
||||||
|
os.environ.pop(key, None)
|
||||||
|
else:
|
||||||
|
os.environ[key] = original
|
||||||
|
|
||||||
|
|
||||||
|
class LoadConfigMetadataTests(unittest.TestCase):
|
||||||
|
@patch("app.config.Path.mkdir")
|
||||||
|
def test_metadata_from_environment_variables(self, mock_mkdir):
|
||||||
|
with temp_env(
|
||||||
|
MASTER_ENDPOINT="http://master.local",
|
||||||
|
AGENT_HOSTNAME="dev-user-one-pod",
|
||||||
|
AGENT_ENV="prod",
|
||||||
|
AGENT_USER="ops",
|
||||||
|
AGENT_INSTANCE="node-1",
|
||||||
|
):
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
self.assertEqual(config.environment, "prod")
|
||||||
|
self.assertEqual(config.user, "ops")
|
||||||
|
self.assertEqual(config.instance, "node-1")
|
||||||
|
mock_mkdir.assert_called()
|
||||||
|
|
||||||
|
@patch("app.config.Path.mkdir")
|
||||||
|
def test_metadata_falls_back_to_hostname(self, mock_mkdir):
|
||||||
|
with temp_env(
|
||||||
|
MASTER_ENDPOINT="http://master.local",
|
||||||
|
AGENT_HOSTNAME="qa-team-abc-pod-2",
|
||||||
|
AGENT_ENV=None,
|
||||||
|
AGENT_USER=None,
|
||||||
|
AGENT_INSTANCE=None,
|
||||||
|
):
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
self.assertEqual(config.environment, "qa")
|
||||||
|
self.assertEqual(config.user, "team")
|
||||||
|
self.assertEqual(config.instance, "abc")
|
||||||
|
mock_mkdir.assert_called()
|
||||||
|
|
||||||
|
@patch("app.config._load_metadata_from_state", return_value=("prod", "ops", "node-1"))
|
||||||
|
@patch("app.config.Path.mkdir")
|
||||||
|
def test_metadata_from_node_state(self, mock_mkdir, mock_state):
|
||||||
|
with temp_env(
|
||||||
|
MASTER_ENDPOINT="http://master.local",
|
||||||
|
AGENT_HOSTNAME="host_abc",
|
||||||
|
AGENT_ENV=None,
|
||||||
|
AGENT_USER=None,
|
||||||
|
AGENT_INSTANCE=None,
|
||||||
|
):
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
self.assertEqual(config.environment, "prod")
|
||||||
|
self.assertEqual(config.user, "ops")
|
||||||
|
self.assertEqual(config.instance, "node-1")
|
||||||
|
mock_state.assert_called_once()
|
||||||
|
mock_mkdir.assert_called()
|
||||||
|
|
||||||
|
@patch("app.config.Path.mkdir")
|
||||||
|
def test_partial_environment_variables_fallback(self, mock_mkdir):
|
||||||
|
with temp_env(
|
||||||
|
MASTER_ENDPOINT="http://master.local",
|
||||||
|
AGENT_HOSTNAME="stage-ml-001-node",
|
||||||
|
AGENT_ENV="prod",
|
||||||
|
AGENT_USER=None,
|
||||||
|
AGENT_INSTANCE=None,
|
||||||
|
):
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
self.assertEqual(config.environment, "stage")
|
||||||
|
self.assertEqual(config.user, "ml")
|
||||||
|
self.assertEqual(config.instance, "001")
|
||||||
|
mock_mkdir.assert_called()
|
||||||
|
|
||||||
|
@patch("app.config.Path.mkdir")
|
||||||
|
def test_invalid_hostname_raises_error(self, mock_mkdir):
|
||||||
|
with temp_env(
|
||||||
|
MASTER_ENDPOINT="http://master.local",
|
||||||
|
AGENT_HOSTNAME="invalidhostname",
|
||||||
|
AGENT_ENV=None,
|
||||||
|
AGENT_USER=None,
|
||||||
|
AGENT_INSTANCE=None,
|
||||||
|
):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
load_config()
|
||||||
|
|
||||||
|
mock_mkdir.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
class CollectMetadataTests(unittest.TestCase):
|
||||||
|
@patch("app.collector._detect_ip_address", return_value="127.0.0.1")
|
||||||
|
@patch("app.collector._detect_gpu_count", return_value=0)
|
||||||
|
@patch("app.collector._detect_memory_bytes", return_value=1024)
|
||||||
|
@patch("app.collector._detect_cpu_count", return_value=8)
|
||||||
|
def test_collect_metadata_uses_config_fields(
|
||||||
|
self,
|
||||||
|
mock_cpu,
|
||||||
|
mock_memory,
|
||||||
|
mock_gpu,
|
||||||
|
mock_ip,
|
||||||
|
):
|
||||||
|
config = AgentConfig(
|
||||||
|
hostname="dev-user-001-pod",
|
||||||
|
environment="prod",
|
||||||
|
user="ops",
|
||||||
|
instance="node-1",
|
||||||
|
node_file="/tmp/node.json",
|
||||||
|
version="1.0.0",
|
||||||
|
master_endpoint="http://master.local",
|
||||||
|
report_interval_seconds=60,
|
||||||
|
health_dir="/tmp/health",
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.collector import collect_metadata
|
||||||
|
|
||||||
|
metadata = collect_metadata(config)
|
||||||
|
|
||||||
|
self.assertEqual(metadata["env"], "prod")
|
||||||
|
self.assertEqual(metadata["user"], "ops")
|
||||||
|
self.assertEqual(metadata["instance"], "node-1")
|
||||||
|
self.assertEqual(metadata["hostname"], "dev-user-001-pod")
|
||||||
|
self.assertEqual(metadata["ip"], "127.0.0.1")
|
||||||
|
self.assertEqual(metadata["cpu_number"], 8)
|
||||||
|
self.assertEqual(metadata["memory_in_bytes"], 1024)
|
||||||
|
self.assertEqual(metadata["gpu_number"], 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Alertmanager
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
1. 首先设置构建和部署的环境变量, 在项目根目录下执行:
|
||||||
|
```bash
|
||||||
|
cp src/alert/tests/.env.example src/alert/tests/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
然后找到复制出来的.env文件,修改环境变量。
|
||||||
|
|
||||||
|
2. 使用脚本构建,在项目根目录下执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash src/alert/alertmanager/build/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
构建成功后,会在项目根目录下生成argus-alertmanager-latest.tar
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
提供docker-compose部署。在src/alert/tests目录下
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 动态配置
|
||||||
|
配置文件放在`/private/argus/alert/alertmanager/alertmanager.yml`下,修改alertmanager.yml后,调用`http://alertmanager.alert.argus.com:9093/-/reload`接口(POST)可以重新加载配置.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:9093/-/reload
|
||||||
|
```
|
||||||
|
|||||||
96
src/alert/alertmanager/build/Dockerfile
Normal file
96
src/alert/alertmanager/build/Dockerfile
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# 基于 Ubuntu 24.04
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
# 切换到 root 用户
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# 安装必要依赖
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y wget supervisor net-tools inetutils-ping vim ca-certificates passwd && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 设置 Alertmanager 版本(与本地离线包保持一致)
|
||||||
|
ARG ALERTMANAGER_VERSION=0.28.1
|
||||||
|
|
||||||
|
# 使用仓库内预置的离线包构建(无需联网)
|
||||||
|
COPY src/alert/alertmanager/build/alertmanager-${ALERTMANAGER_VERSION}.linux-amd64.tar.gz /tmp/
|
||||||
|
RUN tar xvf /tmp/alertmanager-${ALERTMANAGER_VERSION}.linux-amd64.tar.gz -C /tmp && \
|
||||||
|
mv /tmp/alertmanager-${ALERTMANAGER_VERSION}.linux-amd64 /usr/local/alertmanager && \
|
||||||
|
rm -f /tmp/alertmanager-${ALERTMANAGER_VERSION}.linux-amd64.tar.gz
|
||||||
|
|
||||||
|
ENV ALERTMANAGER_BASE_PATH=/private/argus/alert/alertmanager
|
||||||
|
|
||||||
|
ARG ARGUS_BUILD_UID=2133
|
||||||
|
ARG ARGUS_BUILD_GID=2015
|
||||||
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID}
|
||||||
|
ENV ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/share/alertmanager && \
|
||||||
|
mkdir -p ${ALERTMANAGER_BASE_PATH} && \
|
||||||
|
mkdir -p /private/argus/etc && \
|
||||||
|
rm -rf /alertmanager && \
|
||||||
|
ln -s ${ALERTMANAGER_BASE_PATH} /alertmanager
|
||||||
|
|
||||||
|
# 创建 alertmanager 用户(可自定义 UID/GID)
|
||||||
|
# 创建 alertmanager 用户组
|
||||||
|
RUN set -eux; \
|
||||||
|
# 确保目标 GID 存在;若已被占用,直接使用该 GID(组名不限)\
|
||||||
|
if ! getent group "${ARGUS_BUILD_GID}" >/dev/null; then \
|
||||||
|
groupadd -g "${ARGUS_BUILD_GID}" alertmanager || true; \
|
||||||
|
fi; \
|
||||||
|
# 确保存在 alertmanager 用户;若 UID 已被占用,跳过并继续使用现有 UID 的用户
|
||||||
|
if ! id alertmanager >/dev/null 2>&1; then \
|
||||||
|
if getent passwd "${ARGUS_BUILD_UID}" >/dev/null; then \
|
||||||
|
# UID 已占用,则创建同名用户但不指定 UID(避免冲突),仅保证 user 存在
|
||||||
|
useradd -M -s /usr/sbin/nologin -g "${ARGUS_BUILD_GID}" alertmanager || true; \
|
||||||
|
else \
|
||||||
|
useradd -M -s /usr/sbin/nologin -u "${ARGUS_BUILD_UID}" -g "${ARGUS_BUILD_GID}" alertmanager || true; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
usermod -g "${ARGUS_BUILD_GID}" alertmanager || true; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUN chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" /usr/share/alertmanager /alertmanager ${ALERTMANAGER_BASE_PATH} /private/argus/etc /usr/local/bin || true
|
||||||
|
|
||||||
|
# 配置内网 apt 源 (如果指定了内网选项)
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "Configuring intranet apt sources..." && \
|
||||||
|
cp /etc/apt/sources.list /etc/apt/sources.list.bak && \
|
||||||
|
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
||||||
|
echo 'Acquire::https::Verify-Peer "false";' > /etc/apt/apt.conf.d/99disable-ssl-check && \
|
||||||
|
echo 'Acquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# 配置部署时使用的 apt 源
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "deb [trusted=yes] https://10.92.132.52/mirrors/ubuntu2204/ jammy main" > /etc/apt/sources.list; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建 supervisor 日志目录
|
||||||
|
RUN mkdir -p /var/log/supervisor
|
||||||
|
|
||||||
|
# 复制 supervisor 配置文件
|
||||||
|
COPY src/alert/alertmanager/build/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
# 复制启动脚本
|
||||||
|
COPY src/alert/alertmanager/build/start-am-supervised.sh /usr/local/bin/start-am-supervised.sh
|
||||||
|
RUN chmod +x /usr/local/bin/start-am-supervised.sh
|
||||||
|
|
||||||
|
# 复制 Alertmanager 配置文件
|
||||||
|
COPY src/alert/alertmanager/build/alertmanager.yml /etc/alertmanager/alertmanager.yml
|
||||||
|
RUN chmod +x /etc/alertmanager/alertmanager.yml
|
||||||
|
# COPY src/alert/alertmanager/build/alertmanager.yml ${ALERTMANAGER_BASE_PATH}/alertmanager.yml
|
||||||
|
|
||||||
|
# 复制 DNS 监控脚本
|
||||||
|
COPY src/alert/alertmanager/build/dns-monitor.sh /usr/local/bin/dns-monitor.sh
|
||||||
|
RUN chmod +x /usr/local/bin/dns-monitor.sh
|
||||||
|
|
||||||
|
# 保持 root 用户,由 supervisor 控制 user 切换
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# 暴露端口(Alertmanager 默认端口 9093)
|
||||||
|
EXPOSE 9093
|
||||||
|
|
||||||
|
# 使用 supervisor 作为入口点
|
||||||
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
Binary file not shown.
19
src/alert/alertmanager/build/alertmanager.yml
Normal file
19
src/alert/alertmanager/build/alertmanager.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
global:
|
||||||
|
resolve_timeout: 5m
|
||||||
|
|
||||||
|
route:
|
||||||
|
group_by: ['alertname', 'instance'] # 分组:相同 alertname + instance 的告警合并
|
||||||
|
group_wait: 30s # 第一个告警后,等 30s 看是否有同组告警一起发
|
||||||
|
group_interval: 5m # 同组告警变化后,至少 5 分钟再发一次
|
||||||
|
repeat_interval: 3h # 相同告警,3 小时重复提醒一次
|
||||||
|
receiver: 'null'
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
- name: 'null'
|
||||||
|
|
||||||
|
inhibit_rules:
|
||||||
|
- source_match:
|
||||||
|
severity: 'critical' # critical 告警存在时
|
||||||
|
target_match:
|
||||||
|
severity: 'warning' # 抑制相同 instance 的 warning 告警
|
||||||
|
equal: ['instance']
|
||||||
13
src/alert/alertmanager/build/build.sh
Normal file
13
src/alert/alertmanager/build/build.sh
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
docker pull ubuntu:24.04
|
||||||
|
|
||||||
|
source src/alert/tests/.env
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--build-arg ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
--build-arg ARGUS_BUILD_GID=${ARGUS_BUILD_GID} \
|
||||||
|
-f src/alert/alertmanager/build/Dockerfile \
|
||||||
|
-t argus-alertmanager:latest .
|
||||||
|
|
||||||
|
docker save -o argus-alertmanager-latest.tar argus-alertmanager:latest
|
||||||
68
src/alert/alertmanager/build/dns-monitor.sh
Normal file
68
src/alert/alertmanager/build/dns-monitor.sh
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# DNS监控脚本 - 每10秒检查dns.conf是否有变化
|
||||||
|
# 如果有变化则执行update-dns.sh脚本
|
||||||
|
|
||||||
|
DNS_CONF="/private/argus/etc/dns.conf"
|
||||||
|
DNS_BACKUP="/tmp/dns.conf.backup"
|
||||||
|
UPDATE_SCRIPT="/private/argus/etc/update-dns.sh"
|
||||||
|
LOG_FILE="/var/log/supervisor/dns-monitor.log"
|
||||||
|
|
||||||
|
# 确保日志文件存在
|
||||||
|
touch "$LOG_FILE"
|
||||||
|
|
||||||
|
log_message() {
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') [DNS-Monitor] $1" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message "DNS监控脚本启动"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if [ -f "$DNS_CONF" ]; then
|
||||||
|
if [ -f "$DNS_BACKUP" ]; then
|
||||||
|
# 比较文件内容
|
||||||
|
if ! cmp -s "$DNS_CONF" "$DNS_BACKUP"; then
|
||||||
|
log_message "检测到DNS配置变化"
|
||||||
|
|
||||||
|
# 更新备份文件
|
||||||
|
cp "$DNS_CONF" "$DNS_BACKUP"
|
||||||
|
|
||||||
|
# 执行更新脚本
|
||||||
|
if [ -x "$UPDATE_SCRIPT" ]; then
|
||||||
|
log_message "执行DNS更新脚本: $UPDATE_SCRIPT"
|
||||||
|
"$UPDATE_SCRIPT" >> "$LOG_FILE" 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_message "DNS更新脚本执行成功"
|
||||||
|
else
|
||||||
|
log_message "DNS更新脚本执行失败"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: 更新脚本不存在或不可执行: $UPDATE_SCRIPT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
|
||||||
|
# 第一次检测到配置文件,执行更新脚本
|
||||||
|
if [ -x "$UPDATE_SCRIPT" ]; then
|
||||||
|
log_message "执行DNS更新脚本: $UPDATE_SCRIPT"
|
||||||
|
"$UPDATE_SCRIPT" >> "$LOG_FILE" 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_message "DNS更新脚本执行成功"
|
||||||
|
|
||||||
|
# 第一次运行,创建备份并执行更新
|
||||||
|
cp "$DNS_CONF" "$DNS_BACKUP"
|
||||||
|
log_message "创建DNS配置备份文件"
|
||||||
|
|
||||||
|
else
|
||||||
|
log_message "DNS更新脚本执行失败"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: 更新脚本不存在或不可执行: $UPDATE_SCRIPT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: DNS配置文件不存在: $DNS_CONF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
22
src/alert/alertmanager/build/fetch-dist.sh
Normal file
22
src/alert/alertmanager/build/fetch-dist.sh
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 下载 Alertmanager 离线安装包到本目录,用于 Docker 构建时 COPY
|
||||||
|
# 用法:
|
||||||
|
# ./fetch-dist.sh [version]
|
||||||
|
# 示例:
|
||||||
|
# ./fetch-dist.sh 0.28.1
|
||||||
|
|
||||||
|
VER="${1:-0.28.1}"
|
||||||
|
OUT="alertmanager-${VER}.linux-amd64.tar.gz"
|
||||||
|
URL="https://github.com/prometheus/alertmanager/releases/download/v${VER}/${OUT}"
|
||||||
|
|
||||||
|
if [[ -f "$OUT" ]]; then
|
||||||
|
echo "[INFO] $OUT already exists, skip download"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Downloading $URL"
|
||||||
|
curl -fL --retry 3 --connect-timeout 10 -o "$OUT" "$URL"
|
||||||
|
echo "[OK] Saved to $(pwd)/$OUT"
|
||||||
|
|
||||||
25
src/alert/alertmanager/build/start-am-supervised.sh
Normal file
25
src/alert/alertmanager/build/start-am-supervised.sh
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "[INFO] Starting Alertmanager under supervisor..."
|
||||||
|
|
||||||
|
ALERTMANAGER_BASE_PATH=${ALERTMANAGER_BASE_PATH:-/private/argus/alert/alertmanager}
|
||||||
|
|
||||||
|
echo "[INFO] Alertmanager base path: ${ALERTMANAGER_BASE_PATH}"
|
||||||
|
|
||||||
|
# 使用容器内的 /etc/alertmanager/alertmanager.yml 作为配置文件,避免写入挂载卷导致的权限问题
|
||||||
|
echo "[INFO] Using /etc/alertmanager/alertmanager.yml as configuration"
|
||||||
|
|
||||||
|
|
||||||
|
# 记录容器 IP 地址
|
||||||
|
DOMAIN=alertmanager.alert.argus.com
|
||||||
|
IP=$(ifconfig | grep -A 1 eth0 | grep inet | awk '{print $2}')
|
||||||
|
echo "current IP: ${IP}"
|
||||||
|
echo "${IP}" > /private/argus/etc/${DOMAIN}
|
||||||
|
chmod 755 /private/argus/etc/${DOMAIN}
|
||||||
|
|
||||||
|
|
||||||
|
echo "[INFO] Starting Alertmanager process..."
|
||||||
|
|
||||||
|
# 启动 Alertmanager 主进程
|
||||||
|
exec /usr/local/alertmanager/alertmanager --config.file=/etc/alertmanager/alertmanager.yml --storage.path=/alertmanager --cluster.listen-address=""
|
||||||
39
src/alert/alertmanager/build/supervisord.conf
Normal file
39
src/alert/alertmanager/build/supervisord.conf
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
user=root
|
||||||
|
|
||||||
|
[program:alertmanager]
|
||||||
|
command=/usr/local/bin/start-am-supervised.sh
|
||||||
|
user=ubuntu
|
||||||
|
stdout_logfile=/var/log/supervisor/alertmanager.log
|
||||||
|
stderr_logfile=/var/log/supervisor/alertmanager_error.log
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
startsecs=10
|
||||||
|
stopwaitsecs=20
|
||||||
|
killasgroup=true
|
||||||
|
stopasgroup=true
|
||||||
|
|
||||||
|
[program:dns-monitor]
|
||||||
|
command=/usr/local/bin/dns-monitor.sh
|
||||||
|
user=root
|
||||||
|
stdout_logfile=/var/log/supervisor/dns-monitor.log
|
||||||
|
stderr_logfile=/var/log/supervisor/dns-monitor_error.log
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
startsecs=5
|
||||||
|
stopwaitsecs=10
|
||||||
|
killasgroup=true
|
||||||
|
stopasgroup=true
|
||||||
|
|
||||||
|
[unix_http_server]
|
||||||
|
file=/var/run/supervisor.sock
|
||||||
|
chmod=0700
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
serverurl=unix:///var/run/supervisor.sock
|
||||||
|
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||||
60
src/alert/alertmanager/config/rule_files/README.md
Normal file
60
src/alert/alertmanager/config/rule_files/README.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 告警配置
|
||||||
|
|
||||||
|
> 参考:[自定义Prometheus告警规则](https://yunlzheng.gitbook.io/prometheus-book/parti-prometheus-ji-chu/alert/prometheus-alert-rule)
|
||||||
|
|
||||||
|
在Prometheus中配置告警的有两个步骤:
|
||||||
|
|
||||||
|
1. 写告警规则文件(rules文件)
|
||||||
|
2. 在promethues.yml里加载规则,并配置Alertmanager
|
||||||
|
|
||||||
|
## 1. 编写告警规则文件
|
||||||
|
告警规则如下:
|
||||||
|
```yml
|
||||||
|
groups:
|
||||||
|
- name: example-rules
|
||||||
|
interval: 30s # 每30秒评估一次
|
||||||
|
rules:
|
||||||
|
- alert: InstanceDown
|
||||||
|
expr: up == 0
|
||||||
|
for: 1m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "实例 {{ $labels.instance }} 已宕机"
|
||||||
|
description: "{{ $labels.instance }} 在 {{ $labels.job }} 中无响应超过 1 分钟。"
|
||||||
|
|
||||||
|
- alert: HighCpuUsage
|
||||||
|
expr: 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "CPU 使用率过高"
|
||||||
|
description: "实例 {{ $labels.instance }} CPU 使用率超过 80% 持续 5 分钟。"
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `alert`:告警规则的名称。
|
||||||
|
- `expr`:基于PromQL表达式告警触发条件,用于计算是否有时间序列满足该条件。
|
||||||
|
- `for`:评估等待时间,可选参数。用于表示只有当触发条件持续一段时间后才发送告警。在等待期间新产生告警的状态为pending。
|
||||||
|
- `labels`:自定义标签,允许用户指定要附加到告警上的一组附加标签,可以在Alertmanager中做路由和分组。
|
||||||
|
- `annotations`:用于指定一组附加信息,比如用于描述告警详细信息的文字等,annotations的内容在告警产生时会一同作为参数发送到Alertmanager。可以提供告警摘要和详细信息。
|
||||||
|
|
||||||
|
## 2. promothues.yml里引用
|
||||||
|
在prometheus.yml中加上`rule_files`和`alerting`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
global:
|
||||||
|
[ evaluation_interval: <duration> | default = 1m ]
|
||||||
|
|
||||||
|
rule_files:
|
||||||
|
[ - <filepath_glob> ... ]
|
||||||
|
|
||||||
|
alerting:
|
||||||
|
alertmanagers:
|
||||||
|
- static_configs:
|
||||||
|
- targets:
|
||||||
|
- "alertmanager.alert.argus.com:9093" # Alertmanager 地址
|
||||||
|
|
||||||
|
```
|
||||||
37
src/alert/alertmanager/config/rule_files/example_rules.yml
Normal file
37
src/alert/alertmanager/config/rule_files/example_rules.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
groups:
|
||||||
|
- name: example-rules
|
||||||
|
interval: 30s # 每30秒评估一次
|
||||||
|
rules:
|
||||||
|
- alert: InstanceDown
|
||||||
|
expr: up == 0
|
||||||
|
for: 1m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "实例 {{ $labels.instance }} 已宕机"
|
||||||
|
description: "{{ $labels.instance }} 在 {{ $labels.job }} 中无响应超过 1 分钟。"
|
||||||
|
|
||||||
|
- alert: HighCpuUsage
|
||||||
|
expr: 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "CPU 使用率过高"
|
||||||
|
description: "实例 {{ $labels.instance }} CPU 使用率超过 80% 持续 5 分钟。"
|
||||||
|
- alert: HighMemoryUsage
|
||||||
|
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 80
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "内存使用率过高"
|
||||||
|
description: "实例 {{ $labels.instance }} 内存使用率超过 80% 持续 5 分钟。"
|
||||||
|
- alert: DiskSpaceLow
|
||||||
|
expr: (node_filesystem_size_bytes{fstype!~"tmpfs|overlay"} - node_filesystem_free_bytes{fstype!~"tmpfs|overlay"}) / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"} * 100 > 90
|
||||||
|
for: 10m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "磁盘空间不足"
|
||||||
|
description: "实例 {{ $labels.instance }} 磁盘空间不足超过 90% 持续 10 分钟。"
|
||||||
5
src/alert/tests/.env
Normal file
5
src/alert/tests/.env
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
DATA_ROOT=/home/argus/tmp/private/argus
|
||||||
|
ARGUS_BUILD_UID=1048
|
||||||
|
ARGUS_BUILD_GID=1048
|
||||||
|
|
||||||
|
USE_INTRANET=false
|
||||||
5
src/alert/tests/.env.example
Normal file
5
src/alert/tests/.env.example
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
DATA_ROOT=/home/argus/tmp/private/argus
|
||||||
|
ARGUS_UID=1048
|
||||||
|
ARGUS_GID=1048
|
||||||
|
|
||||||
|
USE_INTRANET=false
|
||||||
19
src/alert/tests/data/alertmanager/alertmanager.yml
Normal file
19
src/alert/tests/data/alertmanager/alertmanager.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
global:
|
||||||
|
resolve_timeout: 5m
|
||||||
|
|
||||||
|
route:
|
||||||
|
group_by: ['alertname', 'instance'] # 分组:相同 alertname + instance 的告警合并
|
||||||
|
group_wait: 30s # 第一个告警后,等 30s 看是否有同组告警一起发
|
||||||
|
group_interval: 5m # 同组告警变化后,至少 5 分钟再发一次
|
||||||
|
repeat_interval: 3h # 相同告警,3 小时重复提醒一次
|
||||||
|
receiver: 'null'
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
- name: 'null'
|
||||||
|
|
||||||
|
inhibit_rules:
|
||||||
|
- source_match:
|
||||||
|
severity: 'critical' # critical 告警存在时
|
||||||
|
target_match:
|
||||||
|
severity: 'warning' # 抑制相同 instance 的 warning 告警
|
||||||
|
equal: ['instance']
|
||||||
0
src/alert/tests/data/alertmanager/nflog
Normal file
0
src/alert/tests/data/alertmanager/nflog
Normal file
0
src/alert/tests/data/alertmanager/silences
Normal file
0
src/alert/tests/data/alertmanager/silences
Normal file
1
src/alert/tests/data/etc/alertmanager.alert.argus.com
Normal file
1
src/alert/tests/data/etc/alertmanager.alert.argus.com
Normal file
@@ -0,0 +1 @@
|
|||||||
|
172.18.0.2
|
||||||
37
src/alert/tests/docker-compose.yml
Normal file
37
src/alert/tests/docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
services:
|
||||||
|
alertmanager:
|
||||||
|
build:
|
||||||
|
context: ../../../
|
||||||
|
dockerfile: src/alert/alertmanager/build/Dockerfile
|
||||||
|
args:
|
||||||
|
ARGUS_BUILD_UID: ${ARGUS_BUILD_UID:-2133}
|
||||||
|
ARGUS_BUILD_GID: ${ARGUS_BUILD_GID:-2015}
|
||||||
|
USE_INTRANET: ${USE_INTRANET:-false}
|
||||||
|
image: argus-alertmanager:latest
|
||||||
|
container_name: argus-alertmanager
|
||||||
|
environment:
|
||||||
|
- ALERTMANAGER_BASE_PATH=/private/argus/alert/alertmanager
|
||||||
|
- ARGUS_BUILD_UID=${ARGUS_BUILD_UID:-2133}
|
||||||
|
- ARGUS_BUILD_GID=${ARGUS_BUILD_GID:-2015}
|
||||||
|
ports:
|
||||||
|
- "${ARGUS_PORT:-9093}:9093"
|
||||||
|
volumes:
|
||||||
|
- ${DATA_ROOT:-./data}/alert/alertmanager:/private/argus/alert/alertmanager
|
||||||
|
- ${DATA_ROOT:-./data}/etc:/private/argus/etc
|
||||||
|
networks:
|
||||||
|
- argus-debug-net
|
||||||
|
restart: unless-stopped
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
argus-debug-net:
|
||||||
|
driver: bridge
|
||||||
|
name: argus-debug-net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
alertmanager_data:
|
||||||
|
driver: local
|
||||||
19
src/alert/tests/scripts/01_bootstrap.sh
Normal file
19
src/alert/tests/scripts/01_bootstrap.sh
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)"
|
||||||
|
project_root="$(cd "$root/../../.." && pwd)"
|
||||||
|
|
||||||
|
source "$project_root/scripts/common/build_user.sh"
|
||||||
|
load_build_user
|
||||||
|
|
||||||
|
# 创建新的private目录结构 (基于argus目录结构)
|
||||||
|
echo "[INFO] Creating private directory structure for supervisor-based containers..."
|
||||||
|
mkdir -p "$root/private/argus/alert/alertmanager"
|
||||||
|
mkdir -p "$root/private/argus/etc/"
|
||||||
|
|
||||||
|
# 设置数据目录权限
|
||||||
|
echo "[INFO] Setting permissions for data directories..."
|
||||||
|
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/alert/alertmanager" 2>/dev/null || true
|
||||||
|
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/etc" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "[INFO] Supervisor-based containers will manage their own scripts and configurations"
|
||||||
10
src/alert/tests/scripts/02_up.sh
Normal file
10
src/alert/tests/scripts/02_up.sh
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
compose_cmd="docker compose"
|
||||||
|
if ! $compose_cmd version >/dev/null 2>&1; then
|
||||||
|
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
||||||
|
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
||||||
|
fi
|
||||||
|
$compose_cmd -p alert-mvp up -d --remove-orphans
|
||||||
|
echo "[OK] 服务已启动:Alertmanager http://localhost:9093"
|
||||||
106
src/alert/tests/scripts/03_alertmanager_add_alert.sh
Normal file
106
src/alert/tests/scripts/03_alertmanager_add_alert.sh
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Alertmanager 测试脚本
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
ALERTMANAGER_URL="http://localhost:9093"
|
||||||
|
TEST_ALERT_NAME_CRITICAL="NodeDown"
|
||||||
|
TEST_ALERT_NAME_WARNING="HighCPU"
|
||||||
|
TMP_LOG="/tmp/test-alertmanager.log"
|
||||||
|
|
||||||
|
# 等待参数
|
||||||
|
am_wait_attempts=30
|
||||||
|
am_wait_interval=2
|
||||||
|
|
||||||
|
GREEN="\033[1;32m"
|
||||||
|
RED="\033[1;31m"
|
||||||
|
YELLOW="\033[1;33m"
|
||||||
|
RESET="\033[0m"
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# 函数定义
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
wait_for_alertmanager() {
|
||||||
|
local attempt=1
|
||||||
|
echo "[INFO] 等待 Alertmanager 启动中..."
|
||||||
|
while (( attempt <= am_wait_attempts )); do
|
||||||
|
if curl -fsS "${ALERTMANAGER_URL}/api/v2/status" >/dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}[OK] Alertmanager 已就绪 (attempt=${attempt}/${am_wait_attempts})${RESET}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "[..] Alertmanager 尚未就绪 (${attempt}/${am_wait_attempts})"
|
||||||
|
sleep "${am_wait_interval}"
|
||||||
|
(( attempt++ ))
|
||||||
|
done
|
||||||
|
echo -e "${RED}[ERROR] Alertmanager 在 ${am_wait_attempts} 次尝试后仍未就绪${RESET}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${YELLOW}==== $1 ====${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# 主流程
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
log_step "测试 Alertmanager 开始"
|
||||||
|
echo "[INFO] Alertmanager 地址: $ALERTMANAGER_URL"
|
||||||
|
|
||||||
|
# Step 1: 等待 Alertmanager 启动
|
||||||
|
wait_for_alertmanager
|
||||||
|
|
||||||
|
# Step 2: 触发一个critical测试告警
|
||||||
|
echo "[INFO] 发送critical测试告警..."
|
||||||
|
curl -fsS -X POST "${ALERTMANAGER_URL}/api/v2/alerts" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '[
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"alertname": "'"${TEST_ALERT_NAME_CRITICAL}"'",
|
||||||
|
"instance": "node-1",
|
||||||
|
"severity": "critical"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "节点 node-1 宕机"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]' \
|
||||||
|
-o "$TMP_LOG"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}[OK] 已成功发送critical测试告警${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}[ERROR] critical告警发送失败!${RESET}"
|
||||||
|
cat "$TMP_LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 3: 触发一个warning测试告警
|
||||||
|
echo "[INFO] 发送warning测试告警..."
|
||||||
|
curl -fsS -X POST "${ALERTMANAGER_URL}/api/v2/alerts" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '[
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"alertname": "'"${TEST_ALERT_NAME_WARNING}"'",
|
||||||
|
"instance": "node-1",
|
||||||
|
"severity": "warning"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "节点 node-1 CPU 使用率过高"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]' \
|
||||||
|
-o "$TMP_LOG"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}[OK] 已成功发送warning测试告警${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}[ERROR] warning告警发送失败!${RESET}"
|
||||||
|
cat "$TMP_LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
71
src/alert/tests/scripts/04_query_alerts.sh
Normal file
71
src/alert/tests/scripts/04_query_alerts.sh
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# Alertmanager 测试脚本(含启动等待)
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
ALERTMANAGER_URL="http://localhost:9093"
|
||||||
|
TEST_ALERT_NAME_CRITICAL="NodeDown"
|
||||||
|
TEST_ALERT_NAME_WARNING="HighCPU"
|
||||||
|
TMP_LOG="/tmp/test-alertmanager.log"
|
||||||
|
|
||||||
|
# 等待参数
|
||||||
|
am_wait_attempts=30
|
||||||
|
am_wait_interval=2
|
||||||
|
|
||||||
|
GREEN="\033[1;32m"
|
||||||
|
RED="\033[1;31m"
|
||||||
|
YELLOW="\033[1;33m"
|
||||||
|
RESET="\033[0m"
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# 函数定义
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
wait_for_alertmanager() {
|
||||||
|
local attempt=1
|
||||||
|
echo "[INFO] 等待 Alertmanager 启动中..."
|
||||||
|
while (( attempt <= am_wait_attempts )); do
|
||||||
|
if curl -fsS "${ALERTMANAGER_URL}/api/v2/status" >/dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}[OK] Alertmanager 已就绪 (attempt=${attempt}/${am_wait_attempts})${RESET}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "[..] Alertmanager 尚未就绪 (${attempt}/${am_wait_attempts})"
|
||||||
|
sleep "${am_wait_interval}"
|
||||||
|
(( attempt++ ))
|
||||||
|
done
|
||||||
|
echo -e "${RED}[ERROR] Alertmanager 在 ${am_wait_attempts} 次尝试后仍未就绪${RESET}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${YELLOW}==== $1 ====${RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# 主流程
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
log_step "查询 Alertmanager 当前告警列表开始"
|
||||||
|
echo "[INFO] Alertmanager 地址: $ALERTMANAGER_URL"
|
||||||
|
|
||||||
|
# Step 1: 等待 Alertmanager 启动
|
||||||
|
wait_for_alertmanager
|
||||||
|
|
||||||
|
# Step 2: 查询当前告警列表
|
||||||
|
echo "[INFO] 查询当前告警..."
|
||||||
|
sleep 1
|
||||||
|
curl -fsS "${ALERTMANAGER_URL}/api/v2/alerts" | jq '.' || {
|
||||||
|
echo -e "${RED}[WARN] 无法解析返回 JSON,请检查 jq 是否安装${RESET}"
|
||||||
|
curl -s "${ALERTMANAGER_URL}/api/v2/alerts"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 3: 检查告警是否包含 NodeDown
|
||||||
|
if curl -fsS "${ALERTMANAGER_URL}/api/v2/alerts" | grep -q "${TEST_ALERT_NAME_CRITICAL}"; then
|
||||||
|
echo -e "${GREEN}✅ 测试通过:Alertmanager 已成功接收告警 ${TEST_ALERT_NAME_CRITICAL}${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ 测试失败:未检测到告警 ${TEST_ALERT_NAME_CRITICAL}${RESET}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_step "测试结束"
|
||||||
21
src/alert/tests/scripts/05_down.sh
Normal file
21
src/alert/tests/scripts/05_down.sh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
compose_cmd="docker compose"
|
||||||
|
if ! $compose_cmd version >/dev/null 2>&1; then
|
||||||
|
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
||||||
|
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
||||||
|
fi
|
||||||
|
$compose_cmd -p alert-mvp down
|
||||||
|
echo "[OK] 已停止所有容器"
|
||||||
|
|
||||||
|
# 清理private目录内容
|
||||||
|
echo "[INFO] 清理private目录内容..."
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
if [ -d "private" ]; then
|
||||||
|
# 删除private目录及其所有内容
|
||||||
|
rm -rf private
|
||||||
|
echo "[OK] 已清理private目录"
|
||||||
|
else
|
||||||
|
echo "[INFO] private目录不存在,无需清理"
|
||||||
|
fi
|
||||||
105
src/alert/tests/scripts/e2e_test.sh
Normal file
105
src/alert/tests/scripts/e2e_test.sh
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "======================================="
|
||||||
|
echo "ARGUS Alert System End-to-End Test"
|
||||||
|
echo "======================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 记录测试开始时间
|
||||||
|
test_start_time=$(date +%s)
|
||||||
|
|
||||||
|
# 函数:等待服务就绪
|
||||||
|
wait_for_services() {
|
||||||
|
echo "[INFO] Waiting for all services to be ready..."
|
||||||
|
local max_attempts=${SERVICE_WAIT_ATTEMPTS:-120}
|
||||||
|
local attempt=1
|
||||||
|
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
if curl -fs http://localhost:9093/api/v2/status >/dev/null 2>&1; then
|
||||||
|
echo "[OK] All services are ready!"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo " Waiting for services... ($attempt/$max_attempts)"
|
||||||
|
sleep 5
|
||||||
|
((attempt++))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[ERROR] Services not ready after $max_attempts attempts"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 函数:显示测试步骤
|
||||||
|
show_step() {
|
||||||
|
echo ""
|
||||||
|
echo "🔄 Step $1: $2"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 函数:验证步骤结果
|
||||||
|
verify_step() {
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ $1 - SUCCESS"
|
||||||
|
else
|
||||||
|
echo "❌ $1 - FAILED"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 开始端到端测试
|
||||||
|
show_step "1" "Bootstrap - Initialize environment"
|
||||||
|
./scripts/01_bootstrap.sh
|
||||||
|
verify_step "Bootstrap"
|
||||||
|
|
||||||
|
show_step "2" "Startup - Start all services"
|
||||||
|
./scripts/02_up.sh
|
||||||
|
verify_step "Service startup"
|
||||||
|
|
||||||
|
# 等待服务完全就绪
|
||||||
|
wait_for_services || exit 1
|
||||||
|
|
||||||
|
# 发送告警数据
|
||||||
|
show_step "3" "Add alerts - Send test alerts to Alertmanager"
|
||||||
|
./scripts/03_alertmanager_add_alert.sh
|
||||||
|
verify_step "Send test alerts"
|
||||||
|
|
||||||
|
# 查询告警数据
|
||||||
|
show_step "4" "Verify data - Query Alertmanager"
|
||||||
|
./scripts/04_query_alerts.sh
|
||||||
|
verify_step "Data verification"
|
||||||
|
|
||||||
|
|
||||||
|
# 检查服务健康状态
|
||||||
|
show_step "Health" "Check service health"
|
||||||
|
echo "[INFO] Checking service health..."
|
||||||
|
|
||||||
|
# 检查 Alertmanager 状态
|
||||||
|
if curl -fs "http://localhost:9093/api/v2/status" >/dev/null 2>&1; then
|
||||||
|
am_status="available"
|
||||||
|
echo "✅ Alertmanager status: $am_status"
|
||||||
|
else
|
||||||
|
am_status="unavailable"
|
||||||
|
echo "⚠️ Alertmanager status: $am_status"
|
||||||
|
fi
|
||||||
|
verify_step "Service health check"
|
||||||
|
|
||||||
|
# 清理环境
|
||||||
|
show_step "5" "Cleanup - Stop all services"
|
||||||
|
./scripts/05_down.sh
|
||||||
|
verify_step "Service cleanup"
|
||||||
|
|
||||||
|
# 计算总测试时间
|
||||||
|
test_end_time=$(date +%s)
|
||||||
|
total_time=$((test_end_time - test_start_time))
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "======================================="
|
||||||
|
echo "🎉 END-TO-END TEST COMPLETED SUCCESSFULLY!"
|
||||||
|
echo "======================================="
|
||||||
|
echo "📊 Test Summary:"
|
||||||
|
echo " • Total time: ${total_time}s"
|
||||||
|
echo " • Alertmanager status: $am_status"
|
||||||
|
echo " • All services started and stopped successfully"
|
||||||
|
echo ""
|
||||||
|
echo "✅ The ARGUS Alert system is working correctly!"
|
||||||
|
echo ""
|
||||||
@@ -26,6 +26,7 @@ RUN apt-get update && \
|
|||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
bind9 \
|
bind9 \
|
||||||
bind9utils \
|
bind9utils \
|
||||||
|
dnsutils \
|
||||||
bind9-doc \
|
bind9-doc \
|
||||||
supervisor \
|
supervisor \
|
||||||
net-tools \
|
net-tools \
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ log_message() {
|
|||||||
|
|
||||||
log_message "DNS监控脚本启动"
|
log_message "DNS监控脚本启动"
|
||||||
|
|
||||||
|
log_message "删除DNS备份文件(如果存在)"
|
||||||
|
rm -f $DNS_BACKUP
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
if [ -f "$DNS_CONF" ]; then
|
if [ -f "$DNS_CONF" ]; then
|
||||||
if [ -f "$DNS_BACKUP" ]; then
|
if [ -f "$DNS_BACKUP" ]; then
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,47 +1,96 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "[INFO] Starting Fluent Bit setup in Ubuntu container..."
|
echo "[INFO] Starting Fluent Bit setup in Ubuntu container (offline-first)..."
|
||||||
|
|
||||||
# 安装必要的工具
|
|
||||||
echo "[INFO] Installing required packages..."
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get update -qq
|
|
||||||
apt-get install -y -qq curl
|
|
||||||
|
|
||||||
# 解压bundle到/tmp
|
# Stage bundle to /tmp (read-only mount under /private)
|
||||||
echo "[INFO] Extracting fluent-bit bundle..."
|
echo "[INFO] Staging fluent-bit bundle..."
|
||||||
cp -r /private/etc /tmp
|
rm -rf /tmp/flb && mkdir -p /tmp/flb
|
||||||
cp -r /private/packages /tmp
|
cp -r /private/etc /tmp/flb/
|
||||||
cd /tmp
|
mkdir -p /tmp/flb/packages
|
||||||
|
cp -r /private/packages/* /tmp/flb/packages/ 2>/dev/null || true
|
||||||
|
|
||||||
# 安装 Fluent Bit 从 deb 包
|
# Helper: check and install a local deb if not already satisfied
|
||||||
echo "[INFO] Installing Fluent Bit from deb package..."
|
ensure_lib() {
|
||||||
dpkg -i /tmp/packages/fluent-bit_3.1.9_amd64.deb || true
|
local soname="$1"; shift
|
||||||
apt-get install -f -y -qq # 解决依赖问题
|
local pattern="$1"; shift
|
||||||
|
if ldconfig -p 2>/dev/null | grep -q "$soname"; then
|
||||||
|
echo "[OK] $soname already present"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local deb="$(ls /tmp/flb/packages/$pattern 2>/dev/null | head -n1 || true)"
|
||||||
|
if [[ -n "$deb" ]]; then
|
||||||
|
echo "[INFO] Installing local dependency: $(basename "$deb")"
|
||||||
|
dpkg -i "$deb" >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
echo "[WARN] Local deb for $soname not found (pattern=$pattern)"
|
||||||
|
fi
|
||||||
|
if ! ldconfig -p 2>/dev/null | grep -q "$soname"; then
|
||||||
|
echo "[WARN] $soname still missing after local install; attempting apt fallback"
|
||||||
|
apt-get update -qq || true
|
||||||
|
case "$soname" in
|
||||||
|
libpq.so.5) apt-get install -y -qq libpq5 || true ;;
|
||||||
|
libyaml-0.so.2) apt-get install -y -qq libyaml-0-2 || true ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
ldconfig 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Offline-first: satisfy runtime deps from local debs, fallback to apt only if necessary
|
||||||
|
ensure_lib "libpq.so.5" "libpq5_*_amd64.deb"
|
||||||
|
ensure_lib "libyaml-0.so.2" "libyaml-0-2_*_amd64.deb"
|
||||||
|
ensure_lib "libsasl2.so.2" "libsasl2-2_*_amd64.deb"
|
||||||
|
ensure_lib "libldap-2.5.so.0" "libldap-2.5-0_*_amd64.deb"
|
||||||
|
|
||||||
|
# Install fluent-bit main package from local bundle
|
||||||
|
FLB_DEB="$(ls /tmp/flb/packages/fluent-bit_*_amd64.deb 2>/dev/null | head -n1 || true)"
|
||||||
|
if [[ -z "$FLB_DEB" ]]; then
|
||||||
|
echo "[ERROR] fluent-bit deb not found under /private/packages" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[INFO] Installing Fluent Bit: $(basename "$FLB_DEB")"
|
||||||
|
dpkg -i "$FLB_DEB" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# If dpkg reported unresolved dependencies, try apt -f only as last resort
|
||||||
|
if ! command -v /opt/fluent-bit/bin/fluent-bit >/dev/null 2>&1; then
|
||||||
|
echo "[WARN] fluent-bit binary missing after dpkg; attempting apt --fix-broken"
|
||||||
|
apt-get install -f -y -qq || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure runtime library dependencies are satisfied (libsasl2, libldap are required via libpq/curl)
|
||||||
|
MISSING=$(ldd /opt/fluent-bit/bin/fluent-bit 2>/dev/null | awk '/not found/{print $1}' | xargs -r echo || true)
|
||||||
|
if [[ -n "$MISSING" ]]; then
|
||||||
|
echo "[WARN] missing shared libs: $MISSING"
|
||||||
|
apt-get update -qq || true
|
||||||
|
apt-get install -y -qq libsasl2-2 libldap-2.5-0 || true
|
||||||
|
apt-get install -f -y -qq || true
|
||||||
|
fi
|
||||||
|
|
||||||
# 验证 Fluent Bit 可以运行
|
|
||||||
echo "[INFO] Fluent Bit version:"
|
echo "[INFO] Fluent Bit version:"
|
||||||
/opt/fluent-bit/bin/fluent-bit --version
|
/opt/fluent-bit/bin/fluent-bit --version || { echo "[ERROR] fluent-bit not installed or libraries missing" >&2; exit 1; }
|
||||||
|
|
||||||
# 创建配置目录
|
# Place configuration
|
||||||
mkdir -p /etc/fluent-bit
|
mkdir -p /etc/fluent-bit
|
||||||
cp -r /tmp/etc/* /etc/fluent-bit/
|
cp -r /tmp/flb/etc/* /etc/fluent-bit/
|
||||||
|
|
||||||
# 创建日志和缓冲区目录
|
# Create logs/buffers dirs
|
||||||
mkdir -p /logs/train /logs/infer /buffers
|
mkdir -p /logs/train /logs/infer /buffers
|
||||||
chmod 755 /logs/train /logs/infer /buffers
|
chmod 755 /logs/train /logs/infer /buffers
|
||||||
|
|
||||||
# 等待 Elasticsearch 就绪
|
# Wait for Elasticsearch via bash /dev/tcp to avoid curl dependency
|
||||||
echo "[INFO] Waiting for Elasticsearch to be ready..."
|
echo "[INFO] Waiting for Elasticsearch to be ready (tcp ${ES_HOST}:${ES_PORT})..."
|
||||||
while ! curl -fs http://${ES_HOST}:${ES_PORT}/_cluster/health >/dev/null 2>&1; do
|
for i in $(seq 1 120); do
|
||||||
echo " Waiting for ES at ${ES_HOST}:${ES_PORT}..."
|
if exec 3<>/dev/tcp/${ES_HOST}/${ES_PORT}; then
|
||||||
sleep 5
|
exec 3<&- 3>&-
|
||||||
done
|
|
||||||
echo "[INFO] Elasticsearch is ready"
|
echo "[INFO] Elasticsearch is ready"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
[[ $i -eq 120 ]] && { echo "[ERROR] ES not reachable" >&2; exit 1; }
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
# 启动 Fluent Bit
|
|
||||||
echo "[INFO] Starting Fluent Bit with configuration from /etc/fluent-bit/"
|
echo "[INFO] Starting Fluent Bit with configuration from /etc/fluent-bit/"
|
||||||
echo "[INFO] Command: /opt/fluent-bit/bin/fluent-bit --config=/etc/fluent-bit/fluent-bit.conf"
|
echo "[INFO] Command: /opt/fluent-bit/bin/fluent-bit --config=/etc/fluent-bit/fluent-bit.conf"
|
||||||
exec /opt/fluent-bit/bin/fluent-bit \
|
exec /opt/fluent-bit/bin/fluent-bit --config=/etc/fluent-bit/fluent-bit.conf
|
||||||
--config=/etc/fluent-bit/fluent-bit.conf
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ES_HOST="${ELASTICSEARCH_HOSTS:-http://es:9200}"
|
ES_HOST="${ELASTICSEARCH_HOSTS:-http://es:9200}"
|
||||||
KB_HOST="http://localhost:5601"
|
KB_HOST="${KB_HOST:-http://127.0.0.1:5601}"
|
||||||
|
|
||||||
echo "[INFO] Starting Kibana post-start configuration..."
|
echo "[INFO] Starting Kibana post-start configuration..."
|
||||||
|
|
||||||
@@ -83,50 +83,37 @@ fix_replicas_idempotent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 幂等创建数据视图
|
# 幂等创建数据视图
|
||||||
|
create_or_ensure_data_view() {
|
||||||
|
local name="$1"
|
||||||
|
local title="$2"
|
||||||
|
|
||||||
|
local list_response
|
||||||
|
list_response=$(curl -fsS "$KB_HOST/api/data_views" -H 'kbn-xsrf: true' 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$list_response" ]; then
|
||||||
|
echo "[WARN] Failed to list data views, skipping creation check for $title"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$list_response" | grep -Fq "\"title\":\"$title\""; then
|
||||||
|
echo "[INFO] Data view $title already exists, skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Creating data view for $title indices (allowNoIndex)"
|
||||||
|
|
||||||
|
curl -fsS -X POST "$KB_HOST/api/data_views/data_view?allowNoIndex=true" \
|
||||||
|
-H 'kbn-xsrf: true' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "{\"data_view\":{\"name\":\"$name\",\"title\":\"$title\",\"timeFieldName\":\"@timestamp\",\"allowNoIndex\":true}}" \
|
||||||
|
>/dev/null && echo "[OK] Created $name data view" || echo "[WARN] Failed to create $name data view"
|
||||||
|
}
|
||||||
|
|
||||||
create_data_views_idempotent() {
|
create_data_views_idempotent() {
|
||||||
echo "[INFO] Checking and creating data views..."
|
echo "[INFO] Checking and creating data views..."
|
||||||
|
|
||||||
# 检查是否存在匹配的索引
|
create_or_ensure_data_view "train" "train-*"
|
||||||
local train_indices=$(curl -s "$ES_HOST/_cat/indices/train-*?h=index" 2>/dev/null | wc -l || echo "0")
|
create_or_ensure_data_view "infer" "infer-*"
|
||||||
local infer_indices=$(curl -s "$ES_HOST/_cat/indices/infer-*?h=index" 2>/dev/null | wc -l || echo "0")
|
|
||||||
|
|
||||||
# 创建 train 数据视图
|
|
||||||
if [ "$train_indices" -gt 0 ]; then
|
|
||||||
# 检查数据视图是否已存在
|
|
||||||
local train_exists=$(curl -s "$KB_HOST/api/data_views" -H 'kbn-xsrf: true' 2>/dev/null | grep '"title":"train-\*"' | wc -l )
|
|
||||||
|
|
||||||
if [ "$train_exists" -eq 0 ]; then
|
|
||||||
echo "[INFO] Creating data view for train-* indices"
|
|
||||||
curl -fsS -X POST "$KB_HOST/api/data_views/data_view" \
|
|
||||||
-H 'kbn-xsrf: true' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"data_view":{"name":"train","title":"train-*","timeFieldName":"@timestamp"}}' \
|
|
||||||
>/dev/null && echo "[OK] Created train data view" || echo "[WARN] Failed to create train data view"
|
|
||||||
else
|
|
||||||
echo "[INFO] Train data view already exists, skipping"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "[INFO] No train-* indices found, skipping train data view creation"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 创建 infer 数据视图
|
|
||||||
if [ "$infer_indices" -gt 0 ]; then
|
|
||||||
# 检查数据视图是否已存在
|
|
||||||
local infer_exists=$(curl -s "$KB_HOST/api/data_views" -H 'kbn-xsrf: true' 2>/dev/null | grep '"title":"infer-\*"' | wc -l )
|
|
||||||
|
|
||||||
if [ "$infer_exists" -eq 0 ]; then
|
|
||||||
echo "[INFO] Creating data view for infer-* indices"
|
|
||||||
curl -fsS -X POST "$KB_HOST/api/data_views/data_view" \
|
|
||||||
-H 'kbn-xsrf: true' \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"data_view":{"name":"infer","title":"infer-*","timeFieldName":"@timestamp"}}' \
|
|
||||||
>/dev/null && echo "[OK] Created infer data view" || echo "[WARN] Failed to create infer data view"
|
|
||||||
else
|
|
||||||
echo "[INFO] Infer data view already exists, skipping"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "[INFO] No infer-* indices found, skipping infer data view creation"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 主逻辑
|
# 主逻辑
|
||||||
|
|||||||
@@ -32,3 +32,42 @@ fi
|
|||||||
|
|
||||||
echo "[OK] 初始化完成: private/argus/log/{elasticsearch,kibana}"
|
echo "[OK] 初始化完成: private/argus/log/{elasticsearch,kibana}"
|
||||||
echo "[INFO] Fluent-bit files should be in fluent-bit/ directory"
|
echo "[INFO] Fluent-bit files should be in fluent-bit/ directory"
|
||||||
|
|
||||||
|
# 准备 Fluent Bit 离线依赖(从 metric all-in-one-full 复制 deb 到 ../fluent-bit/build/packages)
|
||||||
|
FLB_BUILD_PACKAGES_DIR="$root/../fluent-bit/build/packages"
|
||||||
|
mkdir -p "$FLB_BUILD_PACKAGES_DIR"
|
||||||
|
for deb in \
|
||||||
|
"$project_root/src/metric/client-plugins/all-in-one-full/plugins/fluent-bit/bin/libyaml-0-2_"*_amd64.deb \
|
||||||
|
"$project_root/src/metric/client-plugins/all-in-one-full/plugins/fluent-bit/bin/libpq5_"*_amd64.deb ; do
|
||||||
|
if ls $deb >/dev/null 2>&1; then
|
||||||
|
for f in $deb; do
|
||||||
|
base="$(basename "$f")"
|
||||||
|
if [[ ! -f "$FLB_BUILD_PACKAGES_DIR/$base" ]]; then
|
||||||
|
cp "$f" "$FLB_BUILD_PACKAGES_DIR/"
|
||||||
|
echo " [+] copied $base"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 额外:从 all-in-one-full 的 ubuntu22/curl.tar.gz 解包必要依赖(libsasl2/ldap),便于离线安装
|
||||||
|
CURLOPT_TAR="$project_root/src/metric/client-plugins/all-in-one-full/deps/ubuntu22/curl.tar.gz"
|
||||||
|
if [[ -f "$CURLOPT_TAR" ]]; then
|
||||||
|
tmpdir=$(mktemp -d)
|
||||||
|
if tar -xzf "$CURLOPT_TAR" -C "$tmpdir" 2>/dev/null; then
|
||||||
|
for p in \
|
||||||
|
libsasl2-2_*_amd64.deb \
|
||||||
|
libsasl2-modules-db_*_amd64.deb \
|
||||||
|
libldap-2.5-0_*_amd64.deb \
|
||||||
|
libidn2-0_*_amd64.deb \
|
||||||
|
libbrotli1_*_amd64.deb \
|
||||||
|
libssl3_*_amd64.deb ; do
|
||||||
|
src=$(ls "$tmpdir"/curl/$p 2>/dev/null | head -n1 || true)
|
||||||
|
if [[ -n "$src" ]]; then
|
||||||
|
base="$(basename "$src")"
|
||||||
|
[[ -f "$FLB_BUILD_PACKAGES_DIR/$base" ]] || cp "$src" "$FLB_BUILD_PACKAGES_DIR/" && echo " [+] staged $base"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -115,20 +115,32 @@ show_step "Health" "Check service health"
|
|||||||
echo "[INFO] Checking service health..."
|
echo "[INFO] Checking service health..."
|
||||||
|
|
||||||
# 检查 Elasticsearch 健康状态
|
# 检查 Elasticsearch 健康状态
|
||||||
|
health_check_ok=1
|
||||||
es_health=$(curl -s "http://localhost:9200/_cluster/health" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
|
es_health=$(curl -s "http://localhost:9200/_cluster/health" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
|
||||||
if [ "$es_health" = "green" ] || [ "$es_health" = "yellow" ]; then
|
if [ "$es_health" = "green" ] || [ "$es_health" = "yellow" ]; then
|
||||||
echo "✅ Elasticsearch health: $es_health"
|
echo "✅ Elasticsearch health: $es_health"
|
||||||
else
|
else
|
||||||
echo "❌ Elasticsearch health: $es_health"
|
echo "❌ Elasticsearch health: $es_health"
|
||||||
|
health_check_ok=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查 Kibana 状态
|
# 检查 Kibana 状态
|
||||||
if curl -fs "http://localhost:5601/api/status" >/dev/null 2>&1; then
|
if curl -fs "http://localhost:5601/api/status" >/dev/null 2>&1; then
|
||||||
kb_status="available"
|
kb_status="available"
|
||||||
echo "✅ Kibana status: $kb_status"
|
echo "✅ Kibana status: $kb_status"
|
||||||
|
|
||||||
|
data_views_json=$(curl -fs "http://localhost:5601/api/data_views" -H 'kbn-xsrf: true' 2>/dev/null || true)
|
||||||
|
if echo "$data_views_json" | grep -F '"title":"train-*"' >/dev/null 2>&1 && \
|
||||||
|
echo "$data_views_json" | grep -F '"title":"infer-*"' >/dev/null 2>&1; then
|
||||||
|
echo "✅ Kibana data views: train-* and infer-* present"
|
||||||
|
else
|
||||||
|
echo "❌ Kibana data views missing: train-* or infer-*"
|
||||||
|
health_check_ok=0
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
kb_status="unavailable"
|
kb_status="unavailable"
|
||||||
echo "⚠️ Kibana status: $kb_status"
|
echo "⚠️ Kibana status: $kb_status"
|
||||||
|
health_check_ok=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查 Fluent-Bit 指标
|
# 检查 Fluent-Bit 指标
|
||||||
@@ -139,6 +151,13 @@ if [ "$fb_host01_uptime" -gt 0 ] && [ "$fb_host02_uptime" -gt 0 ]; then
|
|||||||
echo "✅ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
echo "✅ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
||||||
else
|
else
|
||||||
echo "⚠️ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
echo "⚠️ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
||||||
|
health_check_ok=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$health_check_ok" -eq 1 ]; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
verify_step "Service health check"
|
verify_step "Service health check"
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ set -euo pipefail
|
|||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat >&2 <<'USAGE'
|
cat >&2 <<'USAGE'
|
||||||
Usage: $0 [--intranet] [--offline] [--tag <image_tag>]
|
Usage: $0 [--intranet] [--offline] [--tag <image_tag>] [--no-cache]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--intranet 使用指定的 PyPI 镜像源(默认清华镜像)。
|
--intranet 使用指定的 PyPI 镜像源(默认清华镜像)。
|
||||||
--offline 完全离线构建,依赖 offline_wheels/ 目录中的离线依赖包。
|
--offline 完全离线构建,依赖 offline_wheels/ 目录中的离线依赖包。
|
||||||
--tag <image_tag> 自定义镜像标签,默认 argus-master:latest。
|
--tag <image_tag> 自定义镜像标签,默认 argus-master:latest。
|
||||||
|
--no-cache 不使用 Docker 构建缓存。
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ IMAGE_TAG="${IMAGE_TAG:-argus-master:latest}"
|
|||||||
DOCKERFILE="src/master/Dockerfile"
|
DOCKERFILE="src/master/Dockerfile"
|
||||||
BUILD_ARGS=()
|
BUILD_ARGS=()
|
||||||
OFFLINE_MODE=0
|
OFFLINE_MODE=0
|
||||||
|
NO_CACHE=0
|
||||||
|
|
||||||
source "$PROJECT_ROOT/scripts/common/build_user.sh"
|
source "$PROJECT_ROOT/scripts/common/build_user.sh"
|
||||||
load_build_user
|
load_build_user
|
||||||
@@ -45,6 +47,11 @@ while [[ "$#" -gt 0 ]]; do
|
|||||||
IMAGE_TAG="$2"
|
IMAGE_TAG="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--no-cache)
|
||||||
|
NO_CACHE=1
|
||||||
|
BUILD_ARGS+=("--no-cache")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
2
src/metric/.gitignore
vendored
2
src/metric/.gitignore
vendored
@@ -4,4 +4,4 @@
|
|||||||
/client-plugins/demo-all-in-one/publish/
|
/client-plugins/demo-all-in-one/publish/
|
||||||
/client-plugins/demo-all-in-one/checklist
|
/client-plugins/demo-all-in-one/checklist
|
||||||
/client-plugins/demo-all-in-one/VERSION
|
/client-plugins/demo-all-in-one/VERSION
|
||||||
/client-plugins/all-in-one-full/
|
/client-plugins/all-in-one-full/artifact/
|
||||||
|
|||||||
@@ -4,6 +4,15 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# PID 文件检测,防止重复执行
|
||||||
|
PIDFILE="/var/run/check_health.pid"
|
||||||
|
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
|
||||||
|
echo "健康检查脚本已在运行中,跳过本次执行" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo $$ > "$PIDFILE"
|
||||||
|
trap "rm -f $PIDFILE" EXIT
|
||||||
|
|
||||||
# 获取脚本所在目录
|
# 获取脚本所在目录
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
HEALTH_LOG_FILE="$SCRIPT_DIR/.health_log"
|
HEALTH_LOG_FILE="$SCRIPT_DIR/.health_log"
|
||||||
|
|||||||
@@ -200,22 +200,22 @@ parse_version_info() {
|
|||||||
VERSION=$(grep '"version"' "$VERSION_FILE_PATH" | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
VERSION=$(grep '"version"' "$VERSION_FILE_PATH" | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
||||||
BUILD_TIME=$(grep '"build_time"' "$VERSION_FILE_PATH" | sed 's/.*"build_time": *"\([^"]*\)".*/\1/')
|
BUILD_TIME=$(grep '"build_time"' "$VERSION_FILE_PATH" | sed 's/.*"build_time": *"\([^"]*\)".*/\1/')
|
||||||
|
|
||||||
# 解析 artifact_list
|
# 解析 artifact_list(跳过字段名本身)
|
||||||
grep -A 100 '"artifact_list"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
grep -A 100 '"artifact_list"' "$VERSION_FILE_PATH" | grep -v '"artifact_list"' | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
||||||
component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/')
|
component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/')
|
||||||
version=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
version=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
||||||
echo "$component:$version" >> "$TEMP_DIR/components.txt"
|
echo "$component:$version" >> "$TEMP_DIR/components.txt"
|
||||||
done
|
done
|
||||||
|
|
||||||
# 解析 checksums
|
# 解析 checksums(跳过字段名本身)
|
||||||
grep -A 100 '"checksums"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
grep -A 100 '"checksums"' "$VERSION_FILE_PATH" | grep -v '"checksums"' | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
||||||
component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/')
|
component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/')
|
||||||
checksum=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
checksum=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
||||||
echo "$component:$checksum" >> "$TEMP_DIR/checksums.txt"
|
echo "$component:$checksum" >> "$TEMP_DIR/checksums.txt"
|
||||||
done
|
done
|
||||||
|
|
||||||
# 解析 install_order
|
# 解析 install_order(跳过字段名本身,只取数组元素)
|
||||||
grep -A 100 '"install_order"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+"' | while read line; do
|
grep -A 100 '"install_order"' "$VERSION_FILE_PATH" | grep -v '"install_order"' | grep -E '^\s*"[^"]+"' | while read line; do
|
||||||
component=$(echo "$line" | sed 's/.*"\([^"]*\)".*/\1/')
|
component=$(echo "$line" | sed 's/.*"\([^"]*\)".*/\1/')
|
||||||
echo "$component" >> "$TEMP_DIR/install_order.txt"
|
echo "$component" >> "$TEMP_DIR/install_order.txt"
|
||||||
done
|
done
|
||||||
@@ -317,6 +317,30 @@ create_install_dirs() {
|
|||||||
log_success "安装目录创建完成: $INSTALL_DIR"
|
log_success "安装目录创建完成: $INSTALL_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 获取系统版本
|
||||||
|
get_system_version() {
|
||||||
|
if [[ ! -f /etc/os-release ]]; then
|
||||||
|
log_error "无法检测操作系统版本"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source /etc/os-release
|
||||||
|
|
||||||
|
# 提取主版本号
|
||||||
|
case "$VERSION_ID" in
|
||||||
|
"20.04")
|
||||||
|
echo "ubuntu20"
|
||||||
|
;;
|
||||||
|
"22.04")
|
||||||
|
echo "ubuntu22"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_warning "未识别的Ubuntu版本: $VERSION_ID,尝试使用ubuntu22"
|
||||||
|
echo "ubuntu22"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# 安装系统依赖包
|
# 安装系统依赖包
|
||||||
install_system_deps() {
|
install_system_deps() {
|
||||||
log_info "检查系统依赖包..."
|
log_info "检查系统依赖包..."
|
||||||
@@ -330,21 +354,43 @@ install_system_deps() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查是否有tar.gz文件
|
# 获取系统版本对应的依赖目录
|
||||||
|
local system_version=$(get_system_version)
|
||||||
|
local version_deps_dir="$deps_dir/$system_version"
|
||||||
|
|
||||||
|
log_info "检测到系统版本: $system_version"
|
||||||
|
|
||||||
|
# 检查版本特定的依赖目录是否存在
|
||||||
|
if [[ ! -d "$version_deps_dir" ]]; then
|
||||||
|
log_warning "未找到 $system_version 版本的依赖目录: $version_deps_dir"
|
||||||
|
# 回退到旧的逻辑,检查根deps目录
|
||||||
local deps_count=$(find "$deps_dir" -name "*.tar.gz" | wc -l)
|
local deps_count=$(find "$deps_dir" -name "*.tar.gz" | wc -l)
|
||||||
if [[ $deps_count -eq 0 ]]; then
|
if [[ $deps_count -eq 0 ]]; then
|
||||||
log_info "deps 目录中没有 tar.gz 文件,跳过系统依赖包安装"
|
log_info "deps 目录中没有 tar.gz 文件,跳过系统依赖包安装"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
version_deps_dir="$deps_dir"
|
||||||
|
else
|
||||||
|
# 检查版本目录中是否有tar.gz文件
|
||||||
|
local deps_count=$(find "$version_deps_dir" -name "*.tar.gz" | wc -l)
|
||||||
|
if [[ $deps_count -eq 0 ]]; then
|
||||||
|
log_info "$system_version 版本目录中没有 tar.gz 文件,跳过系统依赖包安装"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "找到 $deps_count 个系统依赖包,开始安装..."
|
log_info "找到 $system_version 版本的依赖包,开始安装..."
|
||||||
|
|
||||||
# 创建临时目录用于解压依赖包
|
# 创建临时目录用于解压依赖包
|
||||||
local deps_temp_dir="$TEMP_DIR/deps"
|
local deps_temp_dir="${TEMP_DIR:-/tmp}/deps"
|
||||||
mkdir -p "$deps_temp_dir"
|
mkdir -p "$deps_temp_dir"
|
||||||
|
|
||||||
|
# 定义要检查的核心依赖
|
||||||
|
local CORE_DEPS=(jq cron curl)
|
||||||
|
local FAILED_DEPS=()
|
||||||
|
|
||||||
# 处理每个tar.gz文件
|
# 处理每个tar.gz文件
|
||||||
find "$deps_dir" -name "*.tar.gz" | while read tar_file; do
|
find "$version_deps_dir" -name "*.tar.gz" | while read tar_file; do
|
||||||
local tar_basename=$(basename "$tar_file")
|
local tar_basename=$(basename "$tar_file")
|
||||||
local extract_name="${tar_basename%.tar.gz}"
|
local extract_name="${tar_basename%.tar.gz}"
|
||||||
|
|
||||||
@@ -362,40 +408,61 @@ install_system_deps() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 进入解压目录,查找deb包
|
# 进入解压目录,查找deb包
|
||||||
cd "$extract_dir"
|
cd "$extract_dir" || continue
|
||||||
local deb_count=$(find . -name "*.deb" | wc -l)
|
local deb_files=(*.deb)
|
||||||
|
if [[ ${#deb_files[@]} -gt 0 ]]; then
|
||||||
|
log_info " 找到 ${#deb_files[@]} 个 deb 包,开始安装..."
|
||||||
|
|
||||||
if [[ $deb_count -gt 0 ]]; then
|
for deb in "${deb_files[@]}"; do
|
||||||
log_info " 找到 $deb_count 个 deb 包,开始安装..."
|
local pkg_name
|
||||||
|
pkg_name=$(dpkg-deb -f "$deb" Package 2>/dev/null)
|
||||||
|
|
||||||
# 1. 先尝试安装所有deb包
|
# 如果已安装,则跳过
|
||||||
log_info " 第1步:批量安装deb包..."
|
if dpkg -s "$pkg_name" &>/dev/null; then
|
||||||
if dpkg -i *.deb 2>/dev/null; then
|
log_success " $pkg_name 已安装,跳过"
|
||||||
log_success " 所有deb包安装成功"
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 尝试安装
|
||||||
|
log_info " 安装 $pkg_name..."
|
||||||
|
if DEBIAN_FRONTEND=noninteractive dpkg -i "$deb" &>/dev/null; then
|
||||||
|
log_success " $pkg_name 安装成功"
|
||||||
else
|
else
|
||||||
log_warning " 部分deb包安装失败,可能存在依赖问题"
|
log_warning " $pkg_name 安装失败,尝试修复依赖..."
|
||||||
|
if DEBIAN_FRONTEND=noninteractive apt-get install -f -y &>/dev/null; then
|
||||||
# 2. 使用apt-get修复依赖
|
if dpkg -s "$pkg_name" &>/dev/null; then
|
||||||
log_info " 第2步:修复依赖关系..."
|
log_success " $pkg_name 修复安装成功"
|
||||||
if apt-get install -f -y; then
|
|
||||||
log_success " 依赖关系修复完成"
|
|
||||||
else
|
else
|
||||||
log_error " 依赖关系修复失败"
|
log_error " $pkg_name 仍未安装成功"
|
||||||
# 继续处理其他包,不退出
|
FAILED_DEPS+=("$pkg_name")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error " $pkg_name 自动修复失败"
|
||||||
|
FAILED_DEPS+=("$pkg_name")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
else
|
else
|
||||||
log_info " $tar_basename 中没有找到deb包,跳过"
|
log_info " $tar_basename 中没有找到deb包,跳过"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 返回到依赖临时目录
|
# 返回到依赖临时目录
|
||||||
cd "$deps_temp_dir"
|
cd "$deps_temp_dir" || continue
|
||||||
done
|
done
|
||||||
|
|
||||||
# 检查并启动 cron 服务
|
# 检查并启动 cron 服务
|
||||||
start_cron_service
|
start_cron_service
|
||||||
|
|
||||||
log_success "系统依赖包安装完成"
|
# 总结安装结果
|
||||||
|
if [[ ${#FAILED_DEPS[@]} -gt 0 ]]; then
|
||||||
|
log_error "以下系统依赖未能成功安装,安装终止,请手动安装后重试:"
|
||||||
|
for f in "${FAILED_DEPS[@]}"; do
|
||||||
|
echo " - $f"
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
log_success "系统依赖包安装完成,全部就绪"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# 启动 cron 服务
|
# 启动 cron 服务
|
||||||
@@ -637,6 +704,18 @@ EOF
|
|||||||
log_success "安装记录已创建: $install_record_file"
|
log_success "安装记录已创建: $install_record_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 检查cron任务是否已存在
|
||||||
|
check_cron_task_exists() {
|
||||||
|
local task_pattern="$1"
|
||||||
|
local temp_cron="$2"
|
||||||
|
|
||||||
|
if grep -q "$task_pattern" "$temp_cron"; then
|
||||||
|
return 0 # 任务已存在
|
||||||
|
else
|
||||||
|
return 1 # 任务不存在
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# 设置健康检查定时任务
|
# 设置健康检查定时任务
|
||||||
setup_health_check_cron() {
|
setup_health_check_cron() {
|
||||||
log_info "设置健康检查定时任务..."
|
log_info "设置健康检查定时任务..."
|
||||||
@@ -661,7 +740,7 @@ setup_health_check_cron() {
|
|||||||
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
||||||
|
|
||||||
# 检查并删除旧的健康检查任务
|
# 检查并删除旧的健康检查任务
|
||||||
if grep -q "check_health.sh" "$temp_cron"; then
|
if check_cron_task_exists "check_health.sh" "$temp_cron"; then
|
||||||
log_info "发现旧的健康检查定时任务,正在更新..."
|
log_info "发现旧的健康检查定时任务,正在更新..."
|
||||||
# 删除所有包含check_health.sh的行
|
# 删除所有包含check_health.sh的行
|
||||||
grep -v "check_health.sh" "$temp_cron" > "$temp_cron.new"
|
grep -v "check_health.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
@@ -716,7 +795,7 @@ setup_dns_sync_cron() {
|
|||||||
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
||||||
|
|
||||||
# 检查并删除旧的 DNS 同步任务
|
# 检查并删除旧的 DNS 同步任务
|
||||||
if grep -q "sync_dns.sh" "$temp_cron"; then
|
if check_cron_task_exists "sync_dns.sh" "$temp_cron"; then
|
||||||
log_info "发现旧的 DNS 同步定时任务,正在更新..."
|
log_info "发现旧的 DNS 同步定时任务,正在更新..."
|
||||||
# 删除所有包含sync_dns.sh的行
|
# 删除所有包含sync_dns.sh的行
|
||||||
grep -v "sync_dns.sh" "$temp_cron" > "$temp_cron.new"
|
grep -v "sync_dns.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
@@ -724,16 +803,15 @@ setup_dns_sync_cron() {
|
|||||||
log_info "旧的 DNS 同步定时任务已删除"
|
log_info "旧的 DNS 同步定时任务已删除"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 添加新的定时任务(每30秒执行一次)
|
# 添加新的定时任务(每1分钟执行一次)
|
||||||
# 直接使用版本目录中的 DNS 同步脚本
|
# 直接使用版本目录中的 DNS 同步脚本
|
||||||
echo "# Argus-Metrics DNS 同步定时任务" >> "$temp_cron"
|
echo "# Argus-Metrics DNS 同步定时任务" >> "$temp_cron"
|
||||||
echo "* * * * * $sync_dns_script >> $INSTALL_DIR/.dns_sync.log 2>&1" >> "$temp_cron"
|
echo "* * * * * $sync_dns_script >> $INSTALL_DIR/.dns_sync.log 2>&1" >> "$temp_cron"
|
||||||
echo "* * * * * sleep 30; $sync_dns_script >> $INSTALL_DIR/.dns_sync.log 2>&1" >> "$temp_cron"
|
|
||||||
|
|
||||||
# 安装新的crontab
|
# 安装新的crontab
|
||||||
if crontab "$temp_cron"; then
|
if crontab "$temp_cron"; then
|
||||||
log_success "DNS 同步定时任务设置成功"
|
log_success "DNS 同步定时任务设置成功"
|
||||||
log_info " 执行频率: 每30秒"
|
log_info " 执行频率: 每1分钟"
|
||||||
log_info " 日志文件: $INSTALL_DIR/.dns_sync.log"
|
log_info " 日志文件: $INSTALL_DIR/.dns_sync.log"
|
||||||
log_info " 查看定时任务: crontab -l"
|
log_info " 查看定时任务: crontab -l"
|
||||||
log_info " 删除定时任务: crontab -e"
|
log_info " 删除定时任务: crontab -e"
|
||||||
@@ -771,7 +849,7 @@ setup_version_check_cron() {
|
|||||||
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
||||||
|
|
||||||
# 检查是否已存在版本校验定时任务
|
# 检查是否已存在版本校验定时任务
|
||||||
if grep -q "check_version.sh" "$temp_cron"; then
|
if check_cron_task_exists "check_version.sh" "$temp_cron"; then
|
||||||
log_info "发现旧的版本校验定时任务,正在更新..."
|
log_info "发现旧的版本校验定时任务,正在更新..."
|
||||||
# 删除所有包含check_version.sh的行
|
# 删除所有包含check_version.sh的行
|
||||||
grep -v "check_version.sh" "$temp_cron" > "$temp_cron.new"
|
grep -v "check_version.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
@@ -824,7 +902,7 @@ setup_restart_cron() {
|
|||||||
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
||||||
|
|
||||||
# 检查是否已存在自动重启定时任务
|
# 检查是否已存在自动重启定时任务
|
||||||
if grep -q "restart_unhealthy.sh" "$temp_cron"; then
|
if check_cron_task_exists "restart_unhealthy.sh" "$temp_cron"; then
|
||||||
log_info "发现旧的自动重启定时任务,正在更新..."
|
log_info "发现旧的自动重启定时任务,正在更新..."
|
||||||
# 删除所有包含restart_unhealthy.sh的行
|
# 删除所有包含restart_unhealthy.sh的行
|
||||||
grep -v "restart_unhealthy.sh" "$temp_cron" > "$temp_cron.new"
|
grep -v "restart_unhealthy.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
@@ -885,9 +963,9 @@ main() {
|
|||||||
check_system
|
check_system
|
||||||
find_version_file
|
find_version_file
|
||||||
create_install_dirs
|
create_install_dirs
|
||||||
|
install_system_deps
|
||||||
parse_version_info
|
parse_version_info
|
||||||
verify_checksums
|
verify_checksums
|
||||||
install_system_deps
|
|
||||||
install_components
|
install_components
|
||||||
copy_config_files
|
copy_config_files
|
||||||
create_install_record
|
create_install_record
|
||||||
@@ -895,6 +973,20 @@ main() {
|
|||||||
setup_dns_sync_cron
|
setup_dns_sync_cron
|
||||||
setup_version_check_cron
|
setup_version_check_cron
|
||||||
setup_restart_cron
|
setup_restart_cron
|
||||||
|
|
||||||
|
# 注释掉立即执行健康检查,避免与cron任务重复执行
|
||||||
|
# log_info "立即执行一次健康检查..."
|
||||||
|
# local check_health_script="$INSTALL_DIR/check_health.sh"
|
||||||
|
# if [[ -f "$check_health_script" ]]; then
|
||||||
|
# if "$check_health_script" >> "$INSTALL_DIR/.health_check.log" 2>&1; then
|
||||||
|
# log_success "健康检查执行完成"
|
||||||
|
# else
|
||||||
|
# log_warning "健康检查执行失败,请检查日志: $INSTALL_DIR/.health_check.log"
|
||||||
|
# fi
|
||||||
|
# else
|
||||||
|
# log_warning "健康检查脚本不存在: $check_health_script"
|
||||||
|
# fi
|
||||||
|
|
||||||
show_install_info
|
show_install_info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,26 +29,68 @@ log_error() {
|
|||||||
show_help() {
|
show_help() {
|
||||||
echo "Argus-Metric Artifact 发布脚本"
|
echo "Argus-Metric Artifact 发布脚本"
|
||||||
echo
|
echo
|
||||||
echo "用法: $0 <版本号>"
|
echo "用法: $0 <版本号> [选项]"
|
||||||
echo
|
echo
|
||||||
echo "参数:"
|
echo "参数:"
|
||||||
echo " <版本号> 要发布的版本号,对应 artifact 目录中的版本"
|
echo " <版本号> 要发布的版本号,对应 artifact 目录中的版本"
|
||||||
echo
|
echo
|
||||||
|
echo "选项:"
|
||||||
|
echo " --output-dir <路径> 指定输出目录 (默认: /private/argus/ftp/share/)"
|
||||||
|
echo " --owner <uid:gid> 指定文件所有者 (默认: 2133:2015)"
|
||||||
|
echo " -h, --help 显示此帮助信息"
|
||||||
|
echo
|
||||||
echo "示例:"
|
echo "示例:"
|
||||||
echo " $0 1.20.0 # 发布 1.20.0 版本"
|
echo " $0 1.20.0 # 使用默认配置发布"
|
||||||
|
echo " $0 1.20.0 --output-dir /tmp/publish # 指定输出目录"
|
||||||
|
echo " $0 1.20.0 --owner 1000:1000 # 指定文件所有者"
|
||||||
|
echo " $0 1.20.0 --output-dir /srv/ftp --owner root:root # 同时指定两者"
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
# 检查参数
|
# 默认配置
|
||||||
if [[ $# -ne 1 ]]; then
|
DEFAULT_PUBLISH_DIR="/private/argus/ftp/share/"
|
||||||
|
DEFAULT_OWNER="2133:2015"
|
||||||
|
|
||||||
|
# 解析参数
|
||||||
|
VERSION=""
|
||||||
|
PUBLISH_DIR="$DEFAULT_PUBLISH_DIR"
|
||||||
|
OWNER="$DEFAULT_OWNER"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--output-dir)
|
||||||
|
PUBLISH_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--owner)
|
||||||
|
OWNER="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$VERSION" ]]; then
|
||||||
|
VERSION="$1"
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
log_error "未知参数: $1"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# 检查版本号是否提供
|
||||||
|
if [[ -z "$VERSION" ]]; then
|
||||||
log_error "请提供版本号参数"
|
log_error "请提供版本号参数"
|
||||||
show_help
|
show_help
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION="$1"
|
|
||||||
ARTIFACT_DIR="artifact/$VERSION"
|
ARTIFACT_DIR="artifact/$VERSION"
|
||||||
PUBLISH_DIR="/Users/sundapeng/Project/nlp/aiops/client-plugins/all-in-one/publish/"
|
|
||||||
|
|
||||||
# 检查版本目录是否存在
|
# 检查版本目录是否存在
|
||||||
if [[ ! -d "$ARTIFACT_DIR" ]]; then
|
if [[ ! -d "$ARTIFACT_DIR" ]]; then
|
||||||
@@ -57,11 +99,32 @@ if [[ ! -d "$ARTIFACT_DIR" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "开始发布版本: $VERSION"
|
log_info "开始发布版本: $VERSION"
|
||||||
|
log_info "输出目录: $PUBLISH_DIR"
|
||||||
|
log_info "文件所有者: $OWNER"
|
||||||
|
|
||||||
# 确保发布目录存在
|
# 确保发布目录存在
|
||||||
log_info "确保发布目录存在: $PUBLISH_DIR"
|
log_info "确保发布目录存在: $PUBLISH_DIR"
|
||||||
mkdir -p "$PUBLISH_DIR"
|
mkdir -p "$PUBLISH_DIR"
|
||||||
|
|
||||||
|
IFS=':' read -r OWNER_UID OWNER_GID <<< "$OWNER"
|
||||||
|
if [[ -z "$OWNER_UID" || -z "$OWNER_GID" ]]; then
|
||||||
|
log_error "--owner 格式不正确,应为 uid:gid"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CURRENT_UID=$(id -u)
|
||||||
|
CURRENT_GID=$(id -g)
|
||||||
|
if [[ "$OWNER_UID" != "$CURRENT_UID" || "$OWNER_GID" != "$CURRENT_GID" ]]; then
|
||||||
|
if [[ "$CURRENT_UID" -ne 0 ]]; then
|
||||||
|
log_error "当前用户 (${CURRENT_UID}:${CURRENT_GID}) 无法设置所有者为 ${OWNER_UID}:${OWNER_GID}"
|
||||||
|
log_error "请以目标用户运行脚本或预先调整目录权限"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
NEED_CHOWN=true
|
||||||
|
else
|
||||||
|
NEED_CHOWN=false
|
||||||
|
fi
|
||||||
|
|
||||||
# 创建临时目录用于打包
|
# 创建临时目录用于打包
|
||||||
TEMP_PACKAGE_DIR="/tmp/argus-metric-package-$$"
|
TEMP_PACKAGE_DIR="/tmp/argus-metric-package-$$"
|
||||||
mkdir -p "$TEMP_PACKAGE_DIR"
|
mkdir -p "$TEMP_PACKAGE_DIR"
|
||||||
@@ -167,17 +230,28 @@ cd "$TEMP_PACKAGE_DIR"
|
|||||||
tar -czf "$PUBLISH_DIR/$TAR_NAME" *
|
tar -czf "$PUBLISH_DIR/$TAR_NAME" *
|
||||||
cd - > /dev/null
|
cd - > /dev/null
|
||||||
|
|
||||||
|
if [[ "$NEED_CHOWN" == true ]]; then
|
||||||
|
log_info "设置文件所有者为: $OWNER"
|
||||||
|
chown "$OWNER" "$PUBLISH_DIR/$TAR_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
# 清理临时目录
|
# 清理临时目录
|
||||||
rm -rf "$TEMP_PACKAGE_DIR"
|
rm -rf "$TEMP_PACKAGE_DIR"
|
||||||
|
|
||||||
# 更新 LATEST_VERSION 文件
|
# 更新 LATEST_VERSION 文件
|
||||||
log_info "更新 LATEST_VERSION 文件..."
|
log_info "更新 LATEST_VERSION 文件..."
|
||||||
echo "$VERSION" > "$PUBLISH_DIR/LATEST_VERSION"
|
echo "$VERSION" > "$PUBLISH_DIR/LATEST_VERSION"
|
||||||
|
if [[ "$NEED_CHOWN" == true ]]; then
|
||||||
|
chown "$OWNER" "$PUBLISH_DIR/LATEST_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
# 复制 DNS 配置文件到发布目录根目录(直接从 config 目录复制)
|
# 复制 DNS 配置文件到发布目录根目录(直接从 config 目录复制)
|
||||||
if [[ -f "config/dns.conf" ]]; then
|
if [[ -f "config/dns.conf" ]]; then
|
||||||
log_info "复制 DNS 配置文件到发布目录根目录..."
|
log_info "复制 DNS 配置文件到发布目录根目录..."
|
||||||
cp "config/dns.conf" "$PUBLISH_DIR/"
|
cp "config/dns.conf" "$PUBLISH_DIR/"
|
||||||
|
if [[ "$NEED_CHOWN" == true ]]; then
|
||||||
|
chown "$OWNER" "$PUBLISH_DIR/dns.conf"
|
||||||
|
fi
|
||||||
log_success "DNS 配置文件复制完成: $PUBLISH_DIR/dns.conf"
|
log_success "DNS 配置文件复制完成: $PUBLISH_DIR/dns.conf"
|
||||||
else
|
else
|
||||||
log_warning "未找到 config/dns.conf 文件,跳过 DNS 配置文件复制"
|
log_warning "未找到 config/dns.conf 文件,跳过 DNS 配置文件复制"
|
||||||
@@ -187,6 +261,9 @@ fi
|
|||||||
if [[ -f "scripts/setup.sh" ]]; then
|
if [[ -f "scripts/setup.sh" ]]; then
|
||||||
log_info "复制 setup.sh 到发布目录..."
|
log_info "复制 setup.sh 到发布目录..."
|
||||||
cp "scripts/setup.sh" "$PUBLISH_DIR/"
|
cp "scripts/setup.sh" "$PUBLISH_DIR/"
|
||||||
|
if [[ "$NEED_CHOWN" == true ]]; then
|
||||||
|
chown "$OWNER" "$PUBLISH_DIR/setup.sh"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 显示发布结果
|
# 显示发布结果
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
# 此脚本会检查各组件的健康状态,并重启不健康的组件
|
# 此脚本会检查各组件的健康状态,并重启不健康的组件
|
||||||
|
|
||||||
|
# PID 文件检测,防止重复执行
|
||||||
|
PIDFILE="/var/run/restart_unhealthy.pid"
|
||||||
|
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
|
||||||
|
echo "自动重启脚本已在运行中,跳过本次执行" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo $$ > "$PIDFILE"
|
||||||
|
trap "rm -f $PIDFILE" EXIT
|
||||||
|
|
||||||
# 获取脚本所在目录
|
# 获取脚本所在目录
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
INSTALL_RECORD_FILE="$SCRIPT_DIR/.install_record"
|
INSTALL_RECORD_FILE="$SCRIPT_DIR/.install_record"
|
||||||
|
|||||||
@@ -1,244 +1,143 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# DNS 同步脚本
|
|
||||||
# 比较 FTP 根目录的 dns.conf 和本地的 dns.conf,如果有变化则同步到 /etc/resolv.conf
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# 颜色定义
|
# 颜色
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# 日志函数 - 输出到 stderr 避免影响函数返回值
|
# 日志函数
|
||||||
log_info() {
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2; }
|
||||||
echo -e "${BLUE}[INFO]${NC} $1" >&2
|
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; }
|
||||||
}
|
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2; }
|
||||||
|
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||||
|
|
||||||
log_success() {
|
|
||||||
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warning() {
|
|
||||||
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
# 获取脚本所在目录
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
LOCAL_DNS_CONF="/opt/argus-metric/dns.conf"
|
LOCAL_DNS_CONF="/opt/argus-metric/dns.conf"
|
||||||
REMOTE_DNS_CONF_URL=""
|
|
||||||
RESOLV_CONF="/etc/resolv.conf"
|
RESOLV_CONF="/etc/resolv.conf"
|
||||||
|
ALT_RESOLV_CONF="/run/resolv.conf"
|
||||||
LOG_FILE="/opt/argus-metric/.dns_sync.log"
|
LOG_FILE="/opt/argus-metric/.dns_sync.log"
|
||||||
|
REMOTE_DNS_CONF_URL=""
|
||||||
|
|
||||||
# 从环境变量或配置文件获取 FTP 服务器信息
|
# 获取 FTP 配置
|
||||||
get_ftp_config() {
|
get_ftp_config() {
|
||||||
# 优先从环境变量获取配置
|
|
||||||
log_info "获取 FTP 配置信息..."
|
log_info "获取 FTP 配置信息..."
|
||||||
|
|
||||||
# 如果环境变量中没有设置,则尝试从配置文件读取
|
|
||||||
if [[ -z "$FTP_SERVER" || -z "$FTP_USER" || -z "$FTP_PASSWORD" ]]; then
|
if [[ -z "$FTP_SERVER" || -z "$FTP_USER" || -z "$FTP_PASSWORD" ]]; then
|
||||||
local config_file="$SCRIPT_DIR/config.env"
|
[[ -f "$SCRIPT_DIR/config.env" ]] && source "$SCRIPT_DIR/config.env"
|
||||||
if [[ -f "$config_file" ]]; then
|
|
||||||
log_info "从配置文件读取 FTP 配置: $config_file"
|
|
||||||
source "$config_file"
|
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
log_info "使用环境变量中的 FTP 配置"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 设置默认值(如果环境变量和配置文件都没有设置)
|
|
||||||
FTP_SERVER="${FTP_SERVER:-localhost}"
|
FTP_SERVER="${FTP_SERVER:-localhost}"
|
||||||
FTP_USER="${FTP_USER:-ftpuser}"
|
FTP_USER="${FTP_USER:-ftpuser}"
|
||||||
FTP_PASSWORD="${FTP_PASSWORD:-ZGClab1234!}"
|
FTP_PASSWORD="${FTP_PASSWORD:-ZGClab1234!}"
|
||||||
|
|
||||||
# 构建远程 DNS 配置文件 URL
|
|
||||||
REMOTE_DNS_CONF_URL="ftp://${FTP_USER}:${FTP_PASSWORD}@${FTP_SERVER}/dns.conf"
|
REMOTE_DNS_CONF_URL="ftp://${FTP_USER}:${FTP_PASSWORD}@${FTP_SERVER}/dns.conf"
|
||||||
|
|
||||||
log_info "FTP 配置来源: ${FTP_CONFIG_SOURCE:-环境变量/配置文件}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 下载远程 DNS 配置文件
|
# 下载远程 dns.conf
|
||||||
download_remote_dns_conf() {
|
download_remote_dns_conf() {
|
||||||
local temp_file="/tmp/dns.conf.remote.$$"
|
local tmp="/tmp/dns.remote.$$"
|
||||||
|
|
||||||
log_info "从 FTP 服务器下载 DNS 配置文件..."
|
|
||||||
log_info "远程地址: $REMOTE_DNS_CONF_URL"
|
|
||||||
log_info "FTP 服务器: $FTP_SERVER"
|
|
||||||
log_info "FTP 用户: $FTP_USER"
|
|
||||||
|
|
||||||
# 先测试 FTP 连接
|
|
||||||
log_info "测试 FTP 连接..."
|
log_info "测试 FTP 连接..."
|
||||||
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/" >/dev/null 2>&1; then
|
if ! curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/" >/dev/null; then
|
||||||
log_success "FTP 服务器连接成功"
|
log_error "无法连接到 FTP 服务器: $FTP_SERVER"; return 1
|
||||||
else
|
|
||||||
log_error "无法连接到 FTP 服务器: $FTP_SERVER"
|
|
||||||
log_error "请检查:"
|
|
||||||
log_error " 1. FTP 服务器是否运行"
|
|
||||||
log_error " 2. 网络连接是否正常"
|
|
||||||
log_error " 3. 服务器地址是否正确"
|
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
|
if ! curl -u "${FTP_USER}:${FTP_PASSWORD}" -sf "ftp://${FTP_SERVER}/dns.conf" -o "$tmp" 2>/dev/null; then
|
||||||
# 测试 dns.conf 文件是否存在
|
log_error "下载 dns.conf 失败"; rm -f "$tmp"; return 1
|
||||||
log_info "检查远程 dns.conf 文件是否存在..."
|
|
||||||
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/dns.conf" >/dev/null 2>&1; then
|
|
||||||
log_success "远程 dns.conf 文件存在"
|
|
||||||
else
|
|
||||||
log_error "远程 dns.conf 文件不存在或无法访问"
|
|
||||||
log_error "请检查 FTP 服务器根目录下是否有 dns.conf 文件"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 尝试下载文件
|
|
||||||
log_info "开始下载 dns.conf 文件..."
|
|
||||||
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sf "ftp://${FTP_SERVER}/dns.conf" -o "$temp_file" 2>/dev/null; then
|
|
||||||
log_success "远程 DNS 配置文件下载成功"
|
|
||||||
echo "$temp_file"
|
|
||||||
else
|
|
||||||
log_error "下载 dns.conf 文件失败"
|
|
||||||
log_error "尝试手动测试命令:"
|
|
||||||
log_error " curl -u ${FTP_USER}:${FTP_PASSWORD} ftp://${FTP_SERVER}/dns.conf"
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
|
echo "$tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 比较两个文件是否相同
|
# 文件比较
|
||||||
compare_files() {
|
compare_files() { diff -q "$1" "$2" >/dev/null 2>&1; }
|
||||||
local file1="$1"
|
|
||||||
local file2="$2"
|
|
||||||
|
|
||||||
if [[ ! -f "$file1" || ! -f "$file2" ]]; then
|
# 从 dns.conf 提取有效 IP
|
||||||
return 1
|
get_dns_ips() {
|
||||||
fi
|
grep -Eo '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' "$1" | sort -u
|
||||||
|
|
||||||
# 使用 diff 比较文件内容
|
|
||||||
if diff -q "$file1" "$file2" >/dev/null 2>&1; then
|
|
||||||
return 0 # 文件相同
|
|
||||||
else
|
|
||||||
return 1 # 文件不同
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 将 DNS 配置追加到 /etc/resolv.conf
|
# 安全更新 resolv.conf(保留符号链接)
|
||||||
update_resolv_conf() {
|
update_resolv_conf() {
|
||||||
local dns_conf_file="$1"
|
local dns_conf="$1"
|
||||||
|
local dns_ips
|
||||||
|
mapfile -t dns_ips < <(get_dns_ips "$dns_conf")
|
||||||
|
[[ ${#dns_ips[@]} -eq 0 ]] && { log_warning "未检测到有效 DNS"; return; }
|
||||||
|
|
||||||
log_info "更新 /etc/resolv.conf 文件..."
|
local target_file="$RESOLV_CONF"
|
||||||
|
if [[ ! -w "$RESOLV_CONF" ]]; then
|
||||||
# 备份原始文件
|
log_warning "/etc/resolv.conf 不可写,使用兜底路径 $ALT_RESOLV_CONF"
|
||||||
if [[ -f "$RESOLV_CONF" ]]; then
|
target_file="$ALT_RESOLV_CONF"
|
||||||
cp "$RESOLV_CONF" "${RESOLV_CONF}.backup.$(date +%Y%m%d_%H%M%S)"
|
|
||||||
log_info "已备份原始 resolv.conf 文件"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 读取 DNS 配置文件并追加到 resolv.conf
|
local temp="/tmp/resolv.new.$$"
|
||||||
while IFS= read -r line; do
|
cp "$target_file" "${target_file}.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||||||
# 跳过空行和注释行
|
log_info "更新 DNS 配置文件: $target_file"
|
||||||
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
|
|
||||||
|
|
||||||
# 验证是否为有效的 IP 地址
|
# 写入新的 nameserver 行
|
||||||
if [[ "$line" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
for ip in "${dns_ips[@]}"; do
|
||||||
# 检查是否已存在相同的 nameserver 行
|
echo "nameserver $ip"
|
||||||
if ! grep -q "nameserver $line" "$RESOLV_CONF" 2>/dev/null; then
|
done >"$temp"
|
||||||
echo "nameserver $line" >> "$RESOLV_CONF"
|
|
||||||
log_info "添加 DNS 服务器: $line"
|
# 追加原内容(去掉重复 nameserver)
|
||||||
|
grep -v '^nameserver' "$target_file" >>"$temp" 2>/dev/null || true
|
||||||
|
awk '!a[$0]++' "$temp" >"${temp}.uniq"
|
||||||
|
|
||||||
|
# ⚙️ 使用 cat 原地覆盖,避免 mv 引发 “设备忙”
|
||||||
|
if cat "${temp}.uniq" >"$target_file" 2>/dev/null; then
|
||||||
|
chmod 644 "$target_file"
|
||||||
|
log_success "DNS 更新完成: ${dns_ips[*]}"
|
||||||
else
|
else
|
||||||
log_info "DNS 服务器已存在,跳过: $line"
|
log_error "无法写入 $target_file,可能被系统锁定"
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
log_warning "跳过无效的 DNS 地址: $line"
|
|
||||||
fi
|
|
||||||
done < "$dns_conf_file"
|
|
||||||
|
|
||||||
# 设置文件权限
|
rm -f "$temp" "${temp}.uniq"
|
||||||
chmod 644 "$RESOLV_CONF"
|
|
||||||
|
|
||||||
log_success "/etc/resolv.conf 文件更新完成"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 记录同步日志
|
# 检查 resolv.conf 是否包含 dns.conf 内容
|
||||||
log_sync() {
|
ensure_dns_in_resolv() {
|
||||||
local message="$1"
|
local dns_conf="$1"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local dns_ips
|
||||||
echo "[$timestamp] $message" >> "$LOG_FILE"
|
mapfile -t dns_ips < <(get_dns_ips "$dns_conf")
|
||||||
|
[[ ${#dns_ips[@]} -eq 0 ]] && return
|
||||||
|
|
||||||
|
for ip in "${dns_ips[@]}"; do
|
||||||
|
if ! grep -q "nameserver $ip" "$RESOLV_CONF" 2>/dev/null; then
|
||||||
|
log_warning "检测到 /etc/resolv.conf 缺少 $ip,执行兜底修复"
|
||||||
|
update_resolv_conf "$dns_conf"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
log_info "/etc/resolv.conf 已包含所有 DNS"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 主函数
|
log_sync() { echo "[$(date '+%F %T')] $1" >>"$LOG_FILE"; }
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
log_info "开始 DNS 同步检查..."
|
log_info "开始 DNS 同步检查..."
|
||||||
log_sync "DNS 同步检查开始"
|
mkdir -p /opt/argus-metric
|
||||||
|
|
||||||
# 确保系统目录存在
|
|
||||||
mkdir -p "/opt/argus-metric"
|
|
||||||
|
|
||||||
# 获取 FTP 配置
|
|
||||||
get_ftp_config
|
get_ftp_config
|
||||||
|
local remote_file
|
||||||
# 检查本地 DNS 配置文件是否存在
|
if ! remote_file=$(download_remote_dns_conf); then
|
||||||
if [[ ! -f "$LOCAL_DNS_CONF" ]]; then
|
log_error "下载失败"; log_sync "同步失败"; exit 1
|
||||||
log_warning "本地 DNS 配置文件不存在: $LOCAL_DNS_CONF"
|
|
||||||
log_warning "将下载远程配置文件并更新系统 DNS 设置"
|
|
||||||
|
|
||||||
# 下载远程配置文件
|
|
||||||
if remote_file=$(download_remote_dns_conf); then
|
|
||||||
# 复制到本地
|
|
||||||
cp "$remote_file" "$LOCAL_DNS_CONF"
|
|
||||||
log_success "远程 DNS 配置文件已保存到本地"
|
|
||||||
|
|
||||||
# 更新 resolv.conf
|
|
||||||
update_resolv_conf "$LOCAL_DNS_CONF"
|
|
||||||
log_sync "首次同步完成,DNS 配置已更新"
|
|
||||||
|
|
||||||
# 清理临时文件
|
|
||||||
rm -f "$remote_file"
|
|
||||||
else
|
|
||||||
log_error "无法下载远程 DNS 配置文件,同步失败"
|
|
||||||
log_sync "同步失败:无法下载远程配置文件"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
log_info "本地 DNS 配置文件存在: $LOCAL_DNS_CONF"
|
|
||||||
|
|
||||||
# 下载远程配置文件进行比较
|
if [[ ! -f "$LOCAL_DNS_CONF" ]]; then
|
||||||
if remote_file=$(download_remote_dns_conf); then
|
log_info "本地 dns.conf 不存在,初始化..."
|
||||||
# 比较文件
|
cp "$remote_file" "$LOCAL_DNS_CONF"
|
||||||
if compare_files "$LOCAL_DNS_CONF" "$remote_file"; then
|
update_resolv_conf "$LOCAL_DNS_CONF"
|
||||||
log_info "DNS 配置文件无变化,无需更新"
|
log_sync "首次同步完成"
|
||||||
log_sync "DNS 配置文件无变化"
|
else
|
||||||
else
|
if compare_files "$LOCAL_DNS_CONF" "$remote_file"; then
|
||||||
log_info "检测到 DNS 配置文件有变化,开始同步..."
|
log_info "dns.conf 无变化"
|
||||||
log_sync "检测到 DNS 配置文件变化,开始同步"
|
ensure_dns_in_resolv "$LOCAL_DNS_CONF"
|
||||||
|
log_sync "dns.conf 无变化,执行兜底检查"
|
||||||
# 更新本地配置文件
|
else
|
||||||
|
log_info "检测到 DNS 配置更新"
|
||||||
cp "$remote_file" "$LOCAL_DNS_CONF"
|
cp "$remote_file" "$LOCAL_DNS_CONF"
|
||||||
log_success "本地 DNS 配置文件已更新"
|
|
||||||
|
|
||||||
# 更新 resolv.conf
|
|
||||||
update_resolv_conf "$LOCAL_DNS_CONF"
|
update_resolv_conf "$LOCAL_DNS_CONF"
|
||||||
log_sync "DNS 配置同步完成"
|
log_sync "DNS 配置同步完成"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# 清理临时文件
|
|
||||||
rm -f "$remote_file"
|
rm -f "$remote_file"
|
||||||
else
|
log_success "DNS 同步流程完成"
|
||||||
log_error "无法下载远程 DNS 配置文件,跳过本次同步"
|
|
||||||
log_sync "同步失败:无法下载远程配置文件"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "DNS 同步检查完成"
|
|
||||||
log_sync "DNS 同步检查完成"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 脚本入口
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
main "$@"
|
main "$@"
|
||||||
fi
|
fi
|
||||||
|
|||||||
59
src/metric/client-plugins/all-in-one-full/README.md
Normal file
59
src/metric/client-plugins/all-in-one-full/README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# 客户侧组件安装包构建、发布流程
|
||||||
|
|
||||||
|
## 第一步:配置版本和组件
|
||||||
|
|
||||||
|
首先搞定配置文件:
|
||||||
|
|
||||||
|
1. 把 `.checklist.example` 重命名成 `checklist`
|
||||||
|
2. 把 `.VERSION.example` 重命名成 `VERSION`
|
||||||
|
|
||||||
|
### checklist 文件格式
|
||||||
|
```
|
||||||
|
# 组件名称 目录路径 版本号 [依赖组件] [安装顺序]
|
||||||
|
dcgm-exporter-installer /path/to/dcgm-exporter-installer 1.1.0
|
||||||
|
node-exporter-installer /path/to/node-exporter-installer 1.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### VERSION 文件
|
||||||
|
设置需要发布的版本号,比如 `1.29.0`
|
||||||
|
|
||||||
|
> 建议用 `version-manager.sh` 来管理版本
|
||||||
|
|
||||||
|
## 第二步:构建安装包
|
||||||
|
|
||||||
|
直接跑脚本:
|
||||||
|
```bash
|
||||||
|
./package_artifact.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
构建完的东西会放在 `artifact/` 目录下,按版本分文件夹。
|
||||||
|
|
||||||
|
如果版本已经存在了,想要覆盖重新构建:
|
||||||
|
```bash
|
||||||
|
./package_artifact.sh --force
|
||||||
|
```
|
||||||
|
|
||||||
|
构建完可以手工测试安装包。
|
||||||
|
|
||||||
|
## 第三步:发布安装包
|
||||||
|
|
||||||
|
用这个脚本发布:
|
||||||
|
```bash
|
||||||
|
./publish_artifact.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
发布后的内容在 `publish/` 目录里,包含:
|
||||||
|
- 压缩版本的安装包
|
||||||
|
- 一键安装的bash脚本
|
||||||
|
|
||||||
|
## 第四步:部署到FTP服务器
|
||||||
|
|
||||||
|
把发布的内容上传到FTP服务器,客户端就可以通过一键命令安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL http://your-ftp-server/install.sh | sh -
|
||||||
|
|
||||||
|
curl -fsSL "ftp://ftpuser:{PASSWD}!@10.211.55.4/share/setup.sh" | sudo bash -s -- --server 10.211.55.4 --user ftpuser --password {PASSWD}
|
||||||
|
```
|
||||||
|
|
||||||
|
这样客户就能直接从FTP服务器下载并安装组件了。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1.29.0
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# 组件名称 目录路径 版本号 [依赖组件] [安装顺序]
|
||||||
|
dcgm-exporter-installer /Users/sundapeng/Project/nlp/aiops/client-plugins/dcgm-exporter-installer 1.1.0
|
||||||
|
node-exporter-installer /Users/sundapeng/Project/nlp/aiops/client-plugins/node-exporter-installer 1.1.0
|
||||||
1
src/metric/client-plugins/all-in-one-full/config/VERSION
Normal file
1
src/metric/client-plugins/all-in-one-full/config/VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1.40.0
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# 组件名称 目录路径 版本号 [依赖组件] [安装顺序]
|
||||||
|
argus-agent plugins/argus-agent 1.0.0
|
||||||
|
node-exporter plugins/node-exporter 1.0.0
|
||||||
|
dcgm-exporter plugins/dcgm-exporter 1.0.0
|
||||||
|
fluent-bit plugins/fluent-bit 1.0.0
|
||||||
14
src/metric/client-plugins/all-in-one-full/config/config.env
Normal file
14
src/metric/client-plugins/all-in-one-full/config/config.env
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Elasticsearch
|
||||||
|
ES_HOST=es.log.argus.com
|
||||||
|
ES_PORT=9200
|
||||||
|
|
||||||
|
# Argus-Agent
|
||||||
|
# 连接master服务
|
||||||
|
MASTER_ENDPOINT=master.argus.com:3000
|
||||||
|
# 上报状态间隔描述
|
||||||
|
REPORT_INTERVAL_SECONDS=5
|
||||||
|
|
||||||
|
# FTP
|
||||||
|
FTP_SERVER=172.31.0.40
|
||||||
|
FTP_USER=ftpuser
|
||||||
|
FTP_PASSWORD=ZGClab1234!
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Argus Metric 配置文件示例
|
||||||
|
# 复制此文件为 config.env 并根据需要修改配置
|
||||||
|
|
||||||
|
# 连接master服务
|
||||||
|
MASTER_ENDPOINT=master.argus.com:3000
|
||||||
|
|
||||||
|
# 上报状态间隔描述(秒)
|
||||||
|
REPORT_INTERVAL_SECONDS=60
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
172.31.0.2
|
||||||
Binary file not shown.
BIN
src/metric/client-plugins/all-in-one-full/deps/jq-curl.tar.gz
Normal file
BIN
src/metric/client-plugins/all-in-one-full/deps/jq-curl.tar.gz
Normal file
Binary file not shown.
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu20/cron.tar.gz
Executable file
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu20/cron.tar.gz
Executable file
Binary file not shown.
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu20/curl.tar.gz
Executable file
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu20/curl.tar.gz
Executable file
Binary file not shown.
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu20/jq.tar.gz
Executable file
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu20/jq.tar.gz
Executable file
Binary file not shown.
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu22/cron.tar.gz
Executable file
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu22/cron.tar.gz
Executable file
Binary file not shown.
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu22/curl.tar.gz
Executable file
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu22/curl.tar.gz
Executable file
Binary file not shown.
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu22/jq.tar.gz
Executable file
BIN
src/metric/client-plugins/all-in-one-full/deps/ubuntu22/jq.tar.gz
Executable file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user