Compare commits
20 Commits
955d1a17b2
...
dev_1.0.0_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c7cd1d09f | ||
|
|
63fe5c61f5 | ||
|
|
8764a02604 | ||
|
|
b4556d86c2 | ||
|
|
f51dbb400a | ||
|
|
5bd0209ca6 | ||
|
|
231b27bb42 | ||
|
|
8a50657b43 | ||
|
|
afbe700520 | ||
|
|
35ed0f72f2 | ||
|
|
752d2991ec | ||
|
|
471f3e13f9 | ||
| 59a38513a4 | |||
|
|
d1b89c0cf6 | ||
| 1a768bc837 | |||
| 31ccb0b1b8 | |||
|
|
8fbe107ac9 | ||
| c098f1d3ce | |||
| 1e5e91b193 | |||
| 8a38d3d0b2 |
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
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.idea/
|
||||||
@@ -5,3 +5,10 @@
|
|||||||
项目文档:【腾讯文档】GPU集群运维系统
|
项目文档:【腾讯文档】GPU集群运维系统
|
||||||
https://docs.qq.com/doc/DQUxDdmhIZ1dpeERk
|
https://docs.qq.com/doc/DQUxDdmhIZ1dpeERk
|
||||||
|
|
||||||
|
## 构建账号配置
|
||||||
|
|
||||||
|
镜像构建和运行账号的 UID/GID 可通过 `configs/build_user.conf` 配置,详细说明见 `doc/build-user-config.md`。
|
||||||
|
|
||||||
|
## 本地端口占用提示
|
||||||
|
|
||||||
|
如需运行 BIND 模块端到端测试且宿主机 53 端口已占用,可通过环境变量 `HOST_DNS_PORT`(默认 1053)指定对外映射端口,例如 `HOST_DNS_PORT=12053 ./scripts/00_e2e_test.sh`。
|
||||||
|
|||||||
@@ -10,18 +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_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
|
||||||
@@ -29,10 +40,44 @@ while [[ $# -gt 0 ]]; do
|
|||||||
use_intranet=true
|
use_intranet=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--master)
|
||||||
|
build_master=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--master-offline)
|
--master-offline)
|
||||||
|
build_master=true
|
||||||
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
|
||||||
@@ -46,8 +91,23 @@ while [[ $# -gt 0 ]]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
. "$root/scripts/common/build_user.sh"
|
||||||
|
|
||||||
|
declare -a build_args=()
|
||||||
|
|
||||||
|
if [[ "$use_intranet" == true ]]; then
|
||||||
|
build_args+=("--build-arg" "USE_INTRANET=true")
|
||||||
|
fi
|
||||||
|
|
||||||
cd "$root"
|
cd "$root"
|
||||||
|
|
||||||
|
load_build_user
|
||||||
|
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"
|
||||||
@@ -75,12 +135,12 @@ echo "======================================="
|
|||||||
|
|
||||||
if [[ "$use_intranet" == true ]]; then
|
if [[ "$use_intranet" == true ]]; then
|
||||||
echo "🌐 Mode: Intranet (Using internal mirror: 10.68.64.1)"
|
echo "🌐 Mode: Intranet (Using internal mirror: 10.68.64.1)"
|
||||||
build_args="--build-arg USE_INTRANET=true"
|
|
||||||
else
|
else
|
||||||
echo "🌐 Mode: Public (Using default package sources)"
|
echo "🌐 Mode: Public (Using default package sources)"
|
||||||
build_args=""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "👤 Build user UID:GID -> ${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}"
|
||||||
|
|
||||||
echo "📁 Build context: $root"
|
echo "📁 Build context: $root"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -88,13 +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 extra_args=$4
|
local context="."
|
||||||
|
shift 3
|
||||||
|
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
context=$1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
@@ -103,44 +172,210 @@ 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_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest" ""; then
|
if [[ "$build_core" == true ]]; then
|
||||||
images_built+=("argus-elasticsearch:latest")
|
if build_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest"; then
|
||||||
else
|
images_built+=("argus-elasticsearch:latest")
|
||||||
build_failed=true
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if build_image "Kibana" "src/log/kibana/build/Dockerfile" "argus-kibana:latest"; then
|
||||||
|
images_built+=("argus-kibana:latest")
|
||||||
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if build_image "BIND9" "src/bind/build/Dockerfile" "argus-bind9:latest"; then
|
||||||
|
images_built+=("argus-bind9:latest")
|
||||||
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if build_image "Kibana" "src/log/kibana/build/Dockerfile" "argus-kibana:latest" ""; then
|
if [[ "$build_master" == true ]]; then
|
||||||
images_built+=("argus-kibana:latest")
|
echo ""
|
||||||
else
|
echo "🔄 Building Master image..."
|
||||||
build_failed=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if build_image "BIND9" "src/bind/build/Dockerfile" "argus-bind9:latest" ""; then
|
|
||||||
images_built+=("argus-bind9:latest")
|
|
||||||
else
|
|
||||||
build_failed=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [[ "$build_master_offline" == true ]]; then
|
|
||||||
echo "🏗️ Building master offline image"
|
|
||||||
pushd "$master_root" >/dev/null
|
pushd "$master_root" >/dev/null
|
||||||
if ./scripts/build_images.sh --offline --tag argus-master:offline; then
|
master_args=("--tag" "argus-master:latest")
|
||||||
images_built+=("argus-master:offline")
|
if [[ "$use_intranet" == true ]]; then
|
||||||
|
master_args+=("--intranet")
|
||||||
|
fi
|
||||||
|
if [[ "$build_master_offline" == true ]]; then
|
||||||
|
master_args+=("--offline")
|
||||||
|
fi
|
||||||
|
if [[ "$no_cache" == true ]]; then
|
||||||
|
master_args+=("--no-cache")
|
||||||
|
fi
|
||||||
|
if ./scripts/build_images.sh "${master_args[@]}"; then
|
||||||
|
if [[ "$build_master_offline" == true ]]; then
|
||||||
|
images_built+=("argus-master:offline")
|
||||||
|
else
|
||||||
|
images_built+=("argus-master:latest")
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
build_failed=true
|
build_failed=true
|
||||||
fi
|
fi
|
||||||
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 "======================================="
|
||||||
@@ -167,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 函数:检查镜像是否存在
|
# 函数:检查镜像是否存在
|
||||||
|
|||||||
2
configs/.gitignore
vendored
Normal file
2
configs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Local overrides for build user/group settings
|
||||||
|
build_user.local.conf
|
||||||
6
configs/build_user.conf
Normal file
6
configs/build_user.conf
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Default build-time UID/GID for Argus images
|
||||||
|
# Override by creating configs/build_user.local.conf with the same format.
|
||||||
|
# Syntax: KEY=VALUE, supports UID/GID only. Whitespace and lines starting with # are ignored.
|
||||||
|
|
||||||
|
UID=2133
|
||||||
|
GID=2015
|
||||||
38
doc/build-user-config.md
Normal file
38
doc/build-user-config.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Argus 镜像构建 UID/GID 配置说明
|
||||||
|
|
||||||
|
通过统一配置文件可以为 Kibana、Elasticsearch、Bind、Master 等容器指定运行账号,解决跨机器部署时 UID/GID 不一致导致的权限问题。
|
||||||
|
|
||||||
|
## 配置入口
|
||||||
|
|
||||||
|
- 默认配置存放在 `configs/build_user.conf`,内容示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UID=2133
|
||||||
|
GID=2015
|
||||||
|
```
|
||||||
|
|
||||||
|
- 如果需要本地覆盖,可在 `configs/` 下新建 `build_user.local.conf`,字段与默认文件一致。该文件已列入 `.gitignore`,不会被意外提交。
|
||||||
|
- 亦可在执行脚本前通过环境变量 `ARGUS_BUILD_UID` / `ARGUS_BUILD_GID` 强制指定值,优先级最高。
|
||||||
|
|
||||||
|
## 作用范围
|
||||||
|
|
||||||
|
- `build/build_images.sh` 在构建 log/bind/master 镜像时读取配置,并传递 `--build-arg ARGUS_BUILD_UID/GID`;控制台会输出当前使用的 UID/GID。
|
||||||
|
- `src/master/scripts/build_images.sh` 同步使用配置,确保单独构建 master 镜像时行为一致。
|
||||||
|
- 各镜像 Dockerfile 会根据传入的 UID/GID 调整容器内账号(如 `elasticsearch`、`kibana`、`bind`、`argus`),并以环境变量形式暴露运行时可见值。
|
||||||
|
- Master 启动脚本会在执行 DNS 逻辑后,降权到配置的账号运行 `gunicorn`,确保写入 `/private/argus/**` 的文件具备正确属主。
|
||||||
|
- Log 模块测试脚本 `01_bootstrap.sh` 会根据配置修正挂载目录属主,方便端到端测试在任意用户下运行。
|
||||||
|
|
||||||
|
## 使用建议
|
||||||
|
|
||||||
|
1. 初次克隆仓库后无需修改,默认 UID/GID 保持向后兼容。
|
||||||
|
2. 如果在目标环境中使用新的账号(例如 `uid=4001,gid=4001`):
|
||||||
|
- 编辑 `configs/build_user.local.conf` 填入新值;
|
||||||
|
- 使用新账号登录,并确保其加入宿主机的 `docker` 组;
|
||||||
|
- 重新执行 `build/build_images.sh` 或相关模块的构建脚本。
|
||||||
|
3. 切换配置后建议重新运行目标模块的端到端脚本(如 `src/log/tests/scripts/01_bootstrap.sh`、`src/master/tests/scripts/00_e2e_test.sh`、`src/agent/tests/scripts/00_e2e_test.sh`),验证 `/private/argus` 下文件属主是否为期望账号。
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
- **镜像构建报错 `groupmod: GID already in use`**:说明所选 GID 已存在于基础镜像,建议换用未占用的值,或在自定义基础镜像中先移除冲突。
|
||||||
|
- **容器内运行时报写权限不足**:检查宿主机挂载目录是否已经由目标 UID/GID 创建;必要时重新执行模块的 `01_bootstrap.sh` 之类的准备脚本。
|
||||||
|
- **仍看到旧 UID/GID**:确认脚本执行时未继承旧缓存,可运行 `ARGUS_BUILD_UID=... ARGUS_BUILD_GID=... ./build/build_images.sh` 强制覆盖。
|
||||||
115
scripts/common/build_user.sh
Normal file
115
scripts/common/build_user.sh
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Shared helper to load Argus build user/group configuration.
|
||||||
|
# Usage:
|
||||||
|
# source "${PROJECT_ROOT}/scripts/common/build_user.sh"
|
||||||
|
# load_build_user
|
||||||
|
# echo "$ARGUS_BUILD_UID:$ARGUS_BUILD_GID"
|
||||||
|
|
||||||
|
ARGUS_BUILD_UID_DEFAULT=2133
|
||||||
|
ARGUS_BUILD_GID_DEFAULT=2015
|
||||||
|
|
||||||
|
shopt -s extglob
|
||||||
|
|
||||||
|
_ARGUS_BUILD_USER_LOADED="${_ARGUS_BUILD_USER_LOADED:-0}"
|
||||||
|
|
||||||
|
_argus_build_user_script_dir() {
|
||||||
|
local dir
|
||||||
|
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
echo "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
argus_project_root() {
|
||||||
|
local script_dir
|
||||||
|
script_dir="$(_argus_build_user_script_dir)"
|
||||||
|
(cd "$script_dir/../.." >/dev/null && pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
_argus_trim() {
|
||||||
|
local value="$1"
|
||||||
|
value="${value##+([[:space:]])}"
|
||||||
|
value="${value%%+([[:space:]])}"
|
||||||
|
printf '%s' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
_argus_is_number() {
|
||||||
|
[[ "$1" =~ ^[0-9]+$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
load_build_user() {
|
||||||
|
if [[ "$_ARGUS_BUILD_USER_LOADED" == "1" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local project_root config_files config uid gid
|
||||||
|
project_root="$(argus_project_root)"
|
||||||
|
config_files=(
|
||||||
|
"$project_root/configs/build_user.local.conf"
|
||||||
|
"$project_root/configs/build_user.conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
uid="$ARGUS_BUILD_UID_DEFAULT"
|
||||||
|
gid="$ARGUS_BUILD_GID_DEFAULT"
|
||||||
|
|
||||||
|
for config in "${config_files[@]}"; do
|
||||||
|
if [[ -f "$config" ]]; then
|
||||||
|
while IFS= read -r raw_line || [[ -n "$raw_line" ]]; do
|
||||||
|
local line key value
|
||||||
|
line="${raw_line%%#*}"
|
||||||
|
line="$(_argus_trim "${line}")"
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
if [[ "$line" != *=* ]]; then
|
||||||
|
echo "[ARGUS build_user] Ignoring malformed line in $config: $raw_line" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
key="${line%%=*}"
|
||||||
|
value="${line#*=}"
|
||||||
|
key="$(_argus_trim "$key")"
|
||||||
|
value="$(_argus_trim "$value")"
|
||||||
|
case "$key" in
|
||||||
|
UID)
|
||||||
|
uid="$value"
|
||||||
|
;;
|
||||||
|
GID)
|
||||||
|
gid="$value"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ARGUS build_user] Unknown key '$key' in $config" >&2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "$config"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "${ARGUS_BUILD_UID:-}" ]]; then
|
||||||
|
uid="$ARGUS_BUILD_UID"
|
||||||
|
fi
|
||||||
|
if [[ -n "${ARGUS_BUILD_GID:-}" ]]; then
|
||||||
|
gid="$ARGUS_BUILD_GID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _argus_is_number "$uid"; then
|
||||||
|
echo "[ARGUS build_user] Invalid UID '$uid'" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! _argus_is_number "$gid"; then
|
||||||
|
echo "[ARGUS build_user] Invalid GID '$gid'" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export ARGUS_BUILD_UID="$uid"
|
||||||
|
export ARGUS_BUILD_GID="$gid"
|
||||||
|
_ARGUS_BUILD_USER_LOADED=1
|
||||||
|
}
|
||||||
|
|
||||||
|
argus_build_user_args() {
|
||||||
|
load_build_user
|
||||||
|
printf '%s' "--build-arg ARGUS_BUILD_UID=${ARGUS_BUILD_UID} --build-arg ARGUS_BUILD_GID=${ARGUS_BUILD_GID}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_build_user() {
|
||||||
|
load_build_user
|
||||||
|
echo "ARGUS build user: UID=${ARGUS_BUILD_UID} GID=${ARGUS_BUILD_GID}"
|
||||||
|
}
|
||||||
2
src/agent/.gitignore
vendored
2
src/agent/.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
build/
|
build/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
|
.env
|
||||||
|
|||||||
@@ -9,7 +9,21 @@ cd src/agent
|
|||||||
./scripts/build_binary.sh # 生成 dist/argus-agent
|
./scripts/build_binary.sh # 生成 dist/argus-agent
|
||||||
```
|
```
|
||||||
|
|
||||||
脚本会在本地创建虚拟环境、通过 `pyproject.toml` 安装依赖,并调用 PyInstaller 产出单文件可执行程序。构建过程中生成的 `build/`、`dist/` 会被 `.gitignore` 忽略。
|
脚本默认会在 Docker 容器 (`python:3.11-slim-bullseye`) 内执行 PyInstaller,确保产物运行时兼容 glibc 2.31+(覆盖 2.35 环境)。构建流程注意事项:
|
||||||
|
|
||||||
|
- 每次构建前会清理 `build/`、`dist/` 并在容器内重新创建虚拟环境。
|
||||||
|
- 需要使用内网 Python 镜像时,可通过 `PIP_INDEX_URL`、`PIP_EXTRA_INDEX_URL`、`PIP_TRUSTED_HOST` 等环境变量传入,脚本会自动透传给容器。
|
||||||
|
- 如果宿主机无法运行 Docker,可设置 `AGENT_BUILD_USE_DOCKER=0` 回退到本地构建;此时代码必须在 glibc ≤ 2.35 的机器上执行。
|
||||||
|
|
||||||
|
构建结束后脚本会在 `build/compat_check/` 下解包关键动态库并输出最高 `GLIBC_x.y` 版本,便于快速核对兼容性。如果结果中缺少 `libssl.so.3` / `libcrypto.so.3`,表示系统会在目标宿主机上使用本地 OpenSSL 库,无需额外处理。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
strings build/compat_check/libpython*.so.1.0 | grep -Eo 'GLIBC_[0-9]+\.[0-9]+' | sort -Vu | tail -n1
|
||||||
|
```
|
||||||
|
|
||||||
|
如遇构建失败,常见原因是 Docker 不可用(请改用 `AGENT_BUILD_USE_DOCKER=0`)或无法访问 Python 包镜像(先设置上述镜像环境变量后重试)。
|
||||||
|
|
||||||
## 运行时配置
|
## 运行时配置
|
||||||
|
|
||||||
@@ -20,11 +34,23 @@ 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`,后续重启无需再次提供环境变量就能保持一致性。
|
||||||
|
|
||||||
派生路径:
|
派生路径:
|
||||||
|
|
||||||
- 节点信息:`/private/argus/agent/<hostname>/node.json`
|
- 节点信息:`/private/argus/agent/<hostname>/node.json`
|
||||||
- 子模块健康目录:`/private/argus/agent/health/<hostname>/`
|
- 子模块健康目录:`/private/argus/agent/<hostname>/health/`
|
||||||
|
|
||||||
健康目录中的文件需遵循 `<模块前缀>-*.json` 命名(例如 `log-fluentbit.json`、`metric-node-exporter.json`),文件内容会原样并入上报的 `health` 字段。
|
健康目录中的文件需遵循 `<模块前缀>-*.json` 命名(例如 `log-fluentbit.json`、`metric-node-exporter.json`),文件内容会原样并入上报的 `health` 字段。
|
||||||
|
|
||||||
@@ -50,4 +76,3 @@ cd src/agent/tests
|
|||||||
4. 清理 `tests/private/` 与临时容器网络。
|
4. 清理 `tests/private/` 与临时容器网络。
|
||||||
|
|
||||||
如需在真实环境部署,只需将 `dist/argus-agent` 连同健康目录挂载到目标主机,并按上表设置环境变量即可。
|
如需在真实环境部署,只需将 `dist/argus-agent` 连同健康目录挂载到目标主机,并按上表设置环境变量即可。
|
||||||
|
|
||||||
|
|||||||
@@ -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,12 +54,69 @@ 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"
|
||||||
health_dir = f"/private/argus/agent/health/{hostname}/"
|
environment, user, instance = _resolve_metadata_fields(hostname, node_file)
|
||||||
|
|
||||||
|
health_dir = f"/private/argus/agent/{hostname}/health/"
|
||||||
|
|
||||||
master_endpoint_env = os.environ.get("MASTER_ENDPOINT")
|
master_endpoint_env = os.environ.get("MASTER_ENDPOINT")
|
||||||
if master_endpoint_env is None:
|
if master_endpoint_env is None:
|
||||||
@@ -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.
690
src/agent/scripts/agent_deployment_verify.sh
Executable file
690
src/agent/scripts/agent_deployment_verify.sh
Executable file
@@ -0,0 +1,690 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
LOG_PREFIX="[AGENT-VERIFY]"
|
||||||
|
MASTER_ENDPOINT_DEFAULT=""
|
||||||
|
AGENT_DATA_ROOT_DEFAULT="/private/argus/agent"
|
||||||
|
AGENT_ETC_ROOT_DEFAULT="/private/argus/etc"
|
||||||
|
REPORT_INTERVAL_DEFAULT="2"
|
||||||
|
|
||||||
|
ALLOW_CONFIG_TOUCH="false"
|
||||||
|
KEEP_TEST_HEALTH="false"
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo "${LOG_PREFIX} INFO $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo "${LOG_PREFIX} WARN $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo "${LOG_PREFIX} ERROR $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: agent_deployment_verify.sh [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--allow-config-touch Enable optional config PUT dry-run check.
|
||||||
|
--keep-test-health Keep the temporary verify health file after checks.
|
||||||
|
-h, --help Show this help message.
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
MASTER_ENDPOINT (required) Master API base endpoint, e.g. http://master:3000
|
||||||
|
AGENT_DATA_ROOT (default: /private/argus/agent)
|
||||||
|
AGENT_ETC_ROOT (default: /private/argus/etc)
|
||||||
|
VERIFY_HOSTNAME (default: output of hostname)
|
||||||
|
REPORT_INTERVAL_SECONDS (default: 2) Agent report interval in seconds
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--allow-config-touch)
|
||||||
|
ALLOW_CONFIG_TOUCH="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--keep-test-health)
|
||||||
|
KEEP_TEST_HEALTH="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown option: $1"
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
MASTER_ENDPOINT="${MASTER_ENDPOINT:-$MASTER_ENDPOINT_DEFAULT}"
|
||||||
|
AGENT_DATA_ROOT="${AGENT_DATA_ROOT:-$AGENT_DATA_ROOT_DEFAULT}"
|
||||||
|
AGENT_ETC_ROOT="${AGENT_ETC_ROOT:-$AGENT_ETC_ROOT_DEFAULT}"
|
||||||
|
VERIFY_HOSTNAME="${VERIFY_HOSTNAME:-$(hostname)}"
|
||||||
|
REPORT_INTERVAL_SECONDS="${REPORT_INTERVAL_SECONDS:-$REPORT_INTERVAL_DEFAULT}"
|
||||||
|
|
||||||
|
if [[ -z "$MASTER_ENDPOINT" ]]; then
|
||||||
|
log_error "MASTER_ENDPOINT is required"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$REPORT_INTERVAL_SECONDS" =~ ^[0-9]+$ ]] || [[ "$REPORT_INTERVAL_SECONDS" -le 0 ]]; then
|
||||||
|
log_warn "Invalid REPORT_INTERVAL_SECONDS='$REPORT_INTERVAL_SECONDS', fallback to $REPORT_INTERVAL_DEFAULT"
|
||||||
|
REPORT_INTERVAL_SECONDS="$REPORT_INTERVAL_DEFAULT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
normalize_endpoint() {
|
||||||
|
local endpoint="$1"
|
||||||
|
if [[ "$endpoint" != http://* && "$endpoint" != https://* ]]; then
|
||||||
|
endpoint="http://$endpoint"
|
||||||
|
fi
|
||||||
|
endpoint="${endpoint%/}"
|
||||||
|
echo "$endpoint"
|
||||||
|
}
|
||||||
|
|
||||||
|
MASTER_BASE="$(normalize_endpoint "$MASTER_ENDPOINT")"
|
||||||
|
|
||||||
|
NODE_DIR="$AGENT_DATA_ROOT/$VERIFY_HOSTNAME"
|
||||||
|
NODE_JSON="$NODE_DIR/node.json"
|
||||||
|
HEALTH_DIR="$NODE_DIR/health"
|
||||||
|
DNS_CONF="$AGENT_ETC_ROOT/dns.conf"
|
||||||
|
UPDATE_SCRIPT="$AGENT_ETC_ROOT/update-dns.sh"
|
||||||
|
|
||||||
|
declare -a RESULTS_PASS=()
|
||||||
|
declare -a RESULTS_WARN=()
|
||||||
|
declare -a RESULTS_FAIL=()
|
||||||
|
|
||||||
|
add_result() {
|
||||||
|
local level="$1" message="$2"
|
||||||
|
case "$level" in
|
||||||
|
PASS)
|
||||||
|
RESULTS_PASS+=("$message")
|
||||||
|
log_info "$message"
|
||||||
|
;;
|
||||||
|
WARN)
|
||||||
|
RESULTS_WARN+=("$message")
|
||||||
|
log_warn "$message"
|
||||||
|
;;
|
||||||
|
FAIL)
|
||||||
|
RESULTS_FAIL+=("$message")
|
||||||
|
log_error "$message"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
HAS_JQ="0"
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
HAS_JQ="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
log_error "curl command not found; please install curl (e.g. apt-get install -y curl)"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$HAS_JQ" == "0" ]] && ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
log_error "Neither jq nor python3 is available for JSON processing"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
CURL_OPTS=(--fail --show-error --silent --max-time 10)
|
||||||
|
|
||||||
|
curl_json() {
|
||||||
|
local url="$1"
|
||||||
|
if ! curl "${CURL_OPTS[@]}" "$url"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
json_query() {
|
||||||
|
local json="$1" jq_expr="$2" py_expr="$3"
|
||||||
|
if [[ "$HAS_JQ" == "1" ]]; then
|
||||||
|
if ! output=$(printf '%s' "$json" | jq -e -r "$jq_expr" 2>/dev/null); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf '%s' "$output"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - "$py_expr" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
expr = sys.argv[1]
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
value = eval(expr, {}, {"data": data})
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
if value is None:
|
||||||
|
sys.exit(1)
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
print(json.dumps(value))
|
||||||
|
else:
|
||||||
|
print(value)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
json_length() {
|
||||||
|
local json="$1" jq_expr="$2" py_expr="$3"
|
||||||
|
if [[ "$HAS_JQ" == "1" ]]; then
|
||||||
|
if ! output=$(printf '%s' "$json" | jq -e "$jq_expr" 2>/dev/null); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf '%s' "$output"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - "$py_expr" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
expr = sys.argv[1]
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
value = eval(expr, {}, {"data": data})
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
print(len(value))
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
json_has_key() {
|
||||||
|
local json="$1" jq_expr="$2" py_expr="$3"
|
||||||
|
if [[ "$HAS_JQ" == "1" ]]; then
|
||||||
|
if printf '%s' "$json" | jq -e "$jq_expr" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - "$py_expr" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
expr = sys.argv[1]
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
value = eval(expr, {}, {"data": data})
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
if value:
|
||||||
|
sys.exit(0)
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
iso_to_epoch() {
|
||||||
|
local value="$1"
|
||||||
|
if command -v date >/dev/null 2>&1; then
|
||||||
|
date -d "$value" +%s 2>/dev/null && return 0
|
||||||
|
fi
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$value" <<'PY'
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
value = sys.argv[1]
|
||||||
|
if value is None or value == "":
|
||||||
|
sys.exit(1)
|
||||||
|
if value.endswith('Z'):
|
||||||
|
value = value[:-1] + '+00:00'
|
||||||
|
try:
|
||||||
|
dt = datetime.fromisoformat(value)
|
||||||
|
except ValueError:
|
||||||
|
sys.exit(1)
|
||||||
|
print(int(dt.timestamp()))
|
||||||
|
PY
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_json_file() {
|
||||||
|
local path="$1"
|
||||||
|
if [[ "$HAS_JQ" == "1" ]]; then
|
||||||
|
jq empty "$path" >/dev/null 2>&1 && return 0
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$path" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
path = sys.argv[1]
|
||||||
|
with open(path, 'r', encoding='utf-8') as handle:
|
||||||
|
json.load(handle)
|
||||||
|
PY
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_directory() {
|
||||||
|
local dir="$1"
|
||||||
|
if [[ ! -d "$dir" ]]; then
|
||||||
|
log_warn "Creating missing directory $dir"
|
||||||
|
mkdir -p "$dir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_HEALTH_FILE=""
|
||||||
|
TEST_HEALTH_BACKUP=""
|
||||||
|
TEST_HEALTH_EXISTED="false"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "$TEST_HEALTH_FILE" ]]; then
|
||||||
|
if [[ "$TEST_HEALTH_EXISTED" == "true" ]]; then
|
||||||
|
printf '%s' "$TEST_HEALTH_BACKUP" > "$TEST_HEALTH_FILE"
|
||||||
|
elif [[ "$KEEP_TEST_HEALTH" == "true" ]]; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
rm -f "$TEST_HEALTH_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
log_info "Starting agent deployment verification for hostname '$VERIFY_HOSTNAME'"
|
||||||
|
|
||||||
|
# 4.2 Master health checks
|
||||||
|
health_resp=""
|
||||||
|
if ! health_resp=$(curl "${CURL_OPTS[@]}" -w '\n%{http_code} %{time_total}' "$MASTER_BASE/healthz" 2>/tmp/agent_verify_healthz.err); then
|
||||||
|
error_detail=$(cat /tmp/agent_verify_healthz.err || true)
|
||||||
|
add_result FAIL "GET /healthz failed: $error_detail"
|
||||||
|
else
|
||||||
|
http_meta=$(tail -n1 <<<"$health_resp")
|
||||||
|
payload=$(head -n -1 <<<"$health_resp" || true)
|
||||||
|
status_code=${http_meta%% *}
|
||||||
|
elapsed=${http_meta##* }
|
||||||
|
add_result PASS "GET /healthz status=$status_code elapsed=${elapsed}s payload=$payload"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_healthz.err
|
||||||
|
|
||||||
|
if ! readyz_resp=$(curl "${CURL_OPTS[@]}" -w '\n%{http_code} %{time_total}' "$MASTER_BASE/readyz" 2>/tmp/agent_verify_readyz.err); then
|
||||||
|
error_detail=$(cat /tmp/agent_verify_readyz.err || true)
|
||||||
|
add_result FAIL "GET /readyz failed: $error_detail"
|
||||||
|
readyz_payload=""
|
||||||
|
else
|
||||||
|
readyz_meta=$(tail -n1 <<<"$readyz_resp")
|
||||||
|
readyz_payload=$(head -n -1 <<<"$readyz_resp" || true)
|
||||||
|
readyz_status=${readyz_meta%% *}
|
||||||
|
readyz_elapsed=${readyz_meta##* }
|
||||||
|
add_result PASS "GET /readyz status=$readyz_status elapsed=${readyz_elapsed}s"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_readyz.err
|
||||||
|
|
||||||
|
# 4.3 Nodes list and detail
|
||||||
|
if ! nodes_json=$(curl_json "$MASTER_BASE/api/v1/master/nodes" 2>/tmp/agent_verify_nodes.err); then
|
||||||
|
error_detail=$(cat /tmp/agent_verify_nodes.err || true)
|
||||||
|
add_result FAIL "GET /api/v1/master/nodes failed: $error_detail"
|
||||||
|
nodes_json=""
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_nodes.err
|
||||||
|
|
||||||
|
NODE_ENTRY=""
|
||||||
|
NODE_ID=""
|
||||||
|
NODE_IP=""
|
||||||
|
if [[ -n "$nodes_json" ]]; then
|
||||||
|
if [[ "$HAS_JQ" == "1" ]]; then
|
||||||
|
NODE_ENTRY=$(printf '%s' "$nodes_json" | jq -e --arg name "$VERIFY_HOSTNAME" '.[] | select(.name == $name)') || NODE_ENTRY=""
|
||||||
|
else
|
||||||
|
NODE_ENTRY=$(python3 - "$VERIFY_HOSTNAME" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
hostname = sys.argv[1]
|
||||||
|
nodes = json.load(sys.stdin)
|
||||||
|
for node in nodes:
|
||||||
|
if node.get("name") == hostname:
|
||||||
|
import json as _json
|
||||||
|
print(_json.dumps(node))
|
||||||
|
sys.exit(0)
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
|
) || NODE_ENTRY=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$NODE_ENTRY" ]]; then
|
||||||
|
add_result FAIL "Current node '$VERIFY_HOSTNAME' not found in master nodes list"
|
||||||
|
else
|
||||||
|
if NODE_ID=$(json_query "$NODE_ENTRY" '.id' 'data["id"]'); then
|
||||||
|
add_result PASS "Discovered node id '$NODE_ID' for hostname '$VERIFY_HOSTNAME'"
|
||||||
|
else
|
||||||
|
add_result FAIL "Failed to extract node id from master response"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NODE_ENTRY" ]] && NODE_DETAIL=$(curl_json "$MASTER_BASE/api/v1/master/nodes/$NODE_ID" 2>/tmp/agent_verify_node_detail.err); then
|
||||||
|
NODE_DETAIL_JSON="$NODE_DETAIL"
|
||||||
|
add_result PASS "Fetched node detail for $NODE_ID"
|
||||||
|
if NODE_IP=$(json_query "$NODE_DETAIL_JSON" '.meta_data.ip // .meta_data.host_ip // empty' 'data.get("meta_data", {}).get("ip") or data.get("meta_data", {}).get("host_ip") or ""'); then
|
||||||
|
if [[ -n "$NODE_IP" ]]; then
|
||||||
|
add_result PASS "Registered node IP=$NODE_IP"
|
||||||
|
else
|
||||||
|
add_result INFO "Node detail does not expose IP fields"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
error_detail=$(cat /tmp/agent_verify_node_detail.err 2>/dev/null || true)
|
||||||
|
add_result FAIL "Failed to fetch node detail for $NODE_ID: $error_detail"
|
||||||
|
NODE_DETAIL_JSON=""
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_node_detail.err
|
||||||
|
|
||||||
|
if stats_json=$(curl_json "$MASTER_BASE/api/v1/master/nodes/statistics" 2>/tmp/agent_verify_stats.err); then
|
||||||
|
if total_nodes=$(json_query "$stats_json" '.total // .total_nodes' 'data.get("total") or data.get("total_nodes")'); then
|
||||||
|
if [[ "$total_nodes" =~ ^[0-9]+$ ]] && [[ "$total_nodes" -ge 1 ]]; then
|
||||||
|
add_result PASS "Statistics total=$total_nodes"
|
||||||
|
else
|
||||||
|
add_result WARN "Statistics total field not numeric: $total_nodes"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result WARN "Unable to read total field from statistics"
|
||||||
|
fi
|
||||||
|
|
||||||
|
active_nodes=""
|
||||||
|
if [[ "$HAS_JQ" == "1" ]]; then
|
||||||
|
active_nodes=$(printf '%s' "$stats_json" | jq -e 'if .status_statistics then (.status_statistics[] | select(.status == "online") | .count) else empty end' 2>/dev/null | head -n1 || true)
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
active_nodes=$(printf '%s' "$stats_json" | python3 -c 'import json,sys; data=json.load(sys.stdin); print(next((row.get("count") for row in data.get("status_statistics", []) if row.get("status")=="online"), ""))' 2>/dev/null)
|
||||||
|
fi
|
||||||
|
if [[ -n "$active_nodes" ]]; then
|
||||||
|
add_result PASS "Online nodes reported by master: $active_nodes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$HAS_JQ" == "1" ]]; then
|
||||||
|
node_count=$(printf '%s' "$nodes_json" | jq 'length')
|
||||||
|
else
|
||||||
|
node_count=$(json_length "$nodes_json" 'length' 'len(data)')
|
||||||
|
fi
|
||||||
|
if [[ "$total_nodes" =~ ^[0-9]+$ ]] && [[ "$node_count" =~ ^[0-9]+$ ]] && [[ "$total_nodes" -lt "$node_count" ]]; then
|
||||||
|
add_result WARN "Statistics total=$total_nodes less than nodes list count=$node_count"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
error_detail=$(cat /tmp/agent_verify_stats.err 2>/dev/null || true)
|
||||||
|
add_result FAIL "Failed to fetch node statistics: $error_detail"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_stats.err
|
||||||
|
else
|
||||||
|
NODE_DETAIL_JSON=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4.4 Agent persistence checks
|
||||||
|
if [[ -f "$NODE_JSON" ]]; then
|
||||||
|
node_file_content="$(cat "$NODE_JSON")"
|
||||||
|
if node_id_local=$(json_query "$node_file_content" '.id' 'data["id"]'); then
|
||||||
|
if [[ "$NODE_ID" != "" && "$node_id_local" == "$NODE_ID" ]]; then
|
||||||
|
add_result PASS "node.json id matches master ($NODE_ID)"
|
||||||
|
else
|
||||||
|
add_result FAIL "node.json id '$node_id_local' differs from master id '$NODE_ID'"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result FAIL "Unable to extract id from node.json"
|
||||||
|
fi
|
||||||
|
if node_name_local=$(json_query "$node_file_content" '.name' 'data["name"]'); then
|
||||||
|
if [[ "$node_name_local" == "$VERIFY_HOSTNAME" ]]; then
|
||||||
|
add_result PASS "node.json name matches $VERIFY_HOSTNAME"
|
||||||
|
else
|
||||||
|
add_result FAIL "node.json name '$node_name_local' differs from hostname '$VERIFY_HOSTNAME'"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result FAIL "Unable to extract name from node.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if register_time=$(json_query "$node_file_content" '.register_time' 'data.get("register_time")'); then
|
||||||
|
if iso_to_epoch "$register_time" >/dev/null 2>&1; then
|
||||||
|
add_result PASS "node.json register_time valid ISO timestamp"
|
||||||
|
else
|
||||||
|
add_result WARN "node.json register_time invalid: $register_time"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result WARN "node.json missing register_time"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if last_updated=$(json_query "$node_file_content" '.last_updated' 'data.get("last_updated")'); then
|
||||||
|
if iso_to_epoch "$last_updated" >/dev/null 2>&1; then
|
||||||
|
add_result PASS "node.json last_updated valid ISO timestamp"
|
||||||
|
else
|
||||||
|
add_result WARN "node.json last_updated invalid: $last_updated"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result WARN "node.json missing last_updated"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result FAIL "node.json not found at $NODE_JSON"
|
||||||
|
node_file_content=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_directory "$HEALTH_DIR"
|
||||||
|
|
||||||
|
if [[ -d "$HEALTH_DIR" ]]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
health_files=("$HEALTH_DIR"/*.json)
|
||||||
|
shopt -u nullglob
|
||||||
|
if [[ ${#health_files[@]} -eq 0 ]]; then
|
||||||
|
add_result WARN "Health directory $HEALTH_DIR is empty"
|
||||||
|
else
|
||||||
|
for hf in "${health_files[@]}"; do
|
||||||
|
base=$(basename "$hf")
|
||||||
|
if [[ "$base" != *-* ]]; then
|
||||||
|
add_result WARN "Health file $base does not follow <module>-*.json"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! validate_json_file "$hf" >/dev/null 2>&1; then
|
||||||
|
add_result WARN "Health file $base is not valid JSON"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result WARN "Health directory $HEALTH_DIR missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if getent hosts master.argus.com >/dev/null 2>&1; then
|
||||||
|
resolved_ips=$(getent hosts master.argus.com | awk '{print $1}' | xargs)
|
||||||
|
add_result PASS "master.argus.com resolves to $resolved_ips"
|
||||||
|
else
|
||||||
|
add_result FAIL "Failed to resolve master.argus.com"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4.5 Master-Node status consistency
|
||||||
|
sleep_interval=$((REPORT_INTERVAL_SECONDS + 2))
|
||||||
|
|
||||||
|
if [[ -n "$NODE_DETAIL_JSON" ]]; then
|
||||||
|
detail_pre="$NODE_DETAIL_JSON"
|
||||||
|
else
|
||||||
|
detail_pre=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$detail_pre" && -n "$NODE_ID" ]]; then
|
||||||
|
if detail_pre=$(curl_json "$MASTER_BASE/api/v1/master/nodes/$NODE_ID" 2>/tmp/agent_verify_detail_pre.err); then
|
||||||
|
add_result PASS "Fetched node detail pre-check"
|
||||||
|
else
|
||||||
|
error_detail=$(cat /tmp/agent_verify_detail_pre.err 2>/dev/null || true)
|
||||||
|
add_result FAIL "Unable to fetch node detail for status check: $error_detail"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_detail_pre.err
|
||||||
|
fi
|
||||||
|
|
||||||
|
server_ts_pre=""
|
||||||
|
agent_ts_pre=""
|
||||||
|
server_ts_post=""
|
||||||
|
agent_ts_post=""
|
||||||
|
|
||||||
|
if [[ -n "$detail_pre" ]]; then
|
||||||
|
server_ts_pre=$(json_query "$detail_pre" '.last_report' 'data.get("last_report")' || echo "")
|
||||||
|
agent_ts_pre=$(json_query "$detail_pre" '.agent_last_report' 'data.get("agent_last_report")' || echo "")
|
||||||
|
log_info "Captured initial last_report timestamps server='$server_ts_pre' agent='$agent_ts_pre'"
|
||||||
|
|
||||||
|
sleep "$sleep_interval"
|
||||||
|
|
||||||
|
if detail_post=$(curl_json "$MASTER_BASE/api/v1/master/nodes/$NODE_ID" 2>/tmp/agent_verify_detail_post.err); then
|
||||||
|
server_ts_post=$(json_query "$detail_post" '.last_report' 'data.get("last_report")' || echo "")
|
||||||
|
agent_ts_post=$(json_query "$detail_post" '.agent_last_report' 'data.get("agent_last_report")' || echo "")
|
||||||
|
if [[ "$server_ts_post" != "$server_ts_pre" ]]; then
|
||||||
|
add_result PASS "last_report.server_timestamp advanced (pre=$server_ts_pre post=$server_ts_post)"
|
||||||
|
else
|
||||||
|
add_result FAIL "last_report.server_timestamp did not change after ${sleep_interval}s"
|
||||||
|
fi
|
||||||
|
if [[ "$agent_ts_post" != "$agent_ts_pre" ]]; then
|
||||||
|
add_result PASS "last_report.agent_timestamp advanced"
|
||||||
|
else
|
||||||
|
add_result FAIL "last_report.agent_timestamp did not change"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$node_file_content" ]]; then
|
||||||
|
if node_last_updated=$(json_query "$node_file_content" '.last_updated' 'data.get("last_updated")'); then
|
||||||
|
if epoch_post=$(iso_to_epoch "$server_ts_post" 2>/dev/null); then
|
||||||
|
if node_epoch=$(iso_to_epoch "$node_last_updated" 2>/dev/null); then
|
||||||
|
diff=$((epoch_post - node_epoch))
|
||||||
|
[[ $diff -lt 0 ]] && diff=$((-diff))
|
||||||
|
tolerance=$((REPORT_INTERVAL_SECONDS * 2))
|
||||||
|
if [[ $diff -le $tolerance ]]; then
|
||||||
|
add_result PASS "last_report.server_timestamp and node.json last_updated within tolerance ($diff s)"
|
||||||
|
else
|
||||||
|
add_result WARN "Timestamp gap between master ($server_ts_post) and node.json ($node_last_updated) is ${diff}s"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_DETAIL_JSON="$detail_post"
|
||||||
|
else
|
||||||
|
error_detail=$(cat /tmp/agent_verify_detail_post.err 2>/dev/null || true)
|
||||||
|
add_result FAIL "Failed to fetch node detail post-check: $error_detail"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_detail_post.err
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4.6 Health simulation
|
||||||
|
TEST_HEALTH_FILE="$HEALTH_DIR/verify-master.json"
|
||||||
|
ensure_directory "$HEALTH_DIR"
|
||||||
|
|
||||||
|
if [[ -f "$TEST_HEALTH_FILE" ]]; then
|
||||||
|
TEST_HEALTH_EXISTED="true"
|
||||||
|
TEST_HEALTH_BACKUP="$(cat "$TEST_HEALTH_FILE")"
|
||||||
|
else
|
||||||
|
TEST_HEALTH_EXISTED="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_health_file() {
|
||||||
|
local message="$1"
|
||||||
|
cat > "$TEST_HEALTH_FILE" <<HEALTHJSON
|
||||||
|
{"status":"ok","message":"$message"}
|
||||||
|
HEALTHJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_health_in_master() {
|
||||||
|
local expected_message="$1"
|
||||||
|
local detail_json="$2"
|
||||||
|
local message
|
||||||
|
if message=$(json_query "$detail_json" '.health["verify-master"].message' 'data.get("health", {}).get("verify-master", {}).get("message")'); then
|
||||||
|
if [[ "$message" == "$expected_message" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_health_from_master() {
|
||||||
|
local detail_json="$1"
|
||||||
|
if json_has_key "$detail_json" '(.health | has("verify-master"))' '"verify-master" in data.get("health", {})'; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
health_message_one="verify $(date +%s)"
|
||||||
|
create_health_file "$health_message_one"
|
||||||
|
add_result PASS "Created test health file $TEST_HEALTH_FILE"
|
||||||
|
|
||||||
|
sleep "$sleep_interval"
|
||||||
|
if detail_health_one=$(curl_json "$MASTER_BASE/api/v1/master/nodes/$NODE_ID" 2>/tmp/agent_verify_health1.err); then
|
||||||
|
if validate_health_in_master "$health_message_one" "$detail_health_one"; then
|
||||||
|
add_result PASS "Master reflects verify-master health message"
|
||||||
|
else
|
||||||
|
add_result FAIL "Master health payload does not match test message"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
error_detail=$(cat /tmp/agent_verify_health1.err 2>/dev/null || true)
|
||||||
|
add_result FAIL "Failed to fetch node detail during health validation: $error_detail"
|
||||||
|
detail_health_one=""
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_health1.err
|
||||||
|
|
||||||
|
health_message_two="verify $(date +%s)-update"
|
||||||
|
create_health_file "$health_message_two"
|
||||||
|
sleep "$sleep_interval"
|
||||||
|
if detail_health_two=$(curl_json "$MASTER_BASE/api/v1/master/nodes/$NODE_ID" 2>/tmp/agent_verify_health2.err); then
|
||||||
|
if validate_health_in_master "$health_message_two" "$detail_health_two"; then
|
||||||
|
add_result PASS "Master health updated to new message"
|
||||||
|
else
|
||||||
|
add_result FAIL "Master health message did not update"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
error_detail=$(cat /tmp/agent_verify_health2.err 2>/dev/null || true)
|
||||||
|
add_result FAIL "Failed to fetch node detail after health update: $error_detail"
|
||||||
|
detail_health_two=""
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_health2.err
|
||||||
|
|
||||||
|
rm -f "$TEST_HEALTH_FILE"
|
||||||
|
sleep "$sleep_interval"
|
||||||
|
if detail_health_three=$(curl_json "$MASTER_BASE/api/v1/master/nodes/$NODE_ID" 2>/tmp/agent_verify_health3.err); then
|
||||||
|
if remove_health_from_master "$detail_health_three"; then
|
||||||
|
add_result PASS "Master health no longer lists verify-master after removal"
|
||||||
|
else
|
||||||
|
add_result FAIL "Master health still contains verify-master after file deletion"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
error_detail=$(cat /tmp/agent_verify_health3.err 2>/dev/null || true)
|
||||||
|
add_result FAIL "Failed to fetch node detail after health removal: $error_detail"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_health3.err
|
||||||
|
|
||||||
|
if [[ "$TEST_HEALTH_EXISTED" == "true" ]]; then
|
||||||
|
printf '%s' "$TEST_HEALTH_BACKUP" > "$TEST_HEALTH_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Optional config touch
|
||||||
|
if [[ "$ALLOW_CONFIG_TOUCH" == "true" ]]; then
|
||||||
|
if [[ -n "$NODE_ID" ]]; then
|
||||||
|
payload='{"label": {"verify": "true"}}'
|
||||||
|
if curl "${CURL_OPTS[@]}" -X PUT -H 'Content-Type: application/json' -d "$payload" "$MASTER_BASE/api/v1/master/nodes/$NODE_ID/config" >/tmp/agent_verify_config.log 2>&1; then
|
||||||
|
add_result PASS "Config PUT dry-run succeeded"
|
||||||
|
else
|
||||||
|
add_result WARN "Config PUT dry-run failed: $(cat /tmp/agent_verify_config.log)"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/agent_verify_config.log
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_result WARN "Config PUT dry-run skipped (enable with --allow-config-touch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Result summary
|
||||||
|
echo
|
||||||
|
echo "==== Verification Summary ===="
|
||||||
|
for entry in "${RESULTS_PASS[@]}"; do
|
||||||
|
printf 'PASS: %s\n' "$entry"
|
||||||
|
done
|
||||||
|
for entry in "${RESULTS_WARN[@]}"; do
|
||||||
|
printf 'WARN: %s\n' "$entry"
|
||||||
|
done
|
||||||
|
for entry in "${RESULTS_FAIL[@]}"; do
|
||||||
|
printf 'FAIL: %s\n' "$entry"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#RESULTS_FAIL[@]} -gt 0 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -6,37 +6,271 @@ MODULE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||||||
BUILD_ROOT="$MODULE_ROOT/build"
|
BUILD_ROOT="$MODULE_ROOT/build"
|
||||||
DIST_DIR="$MODULE_ROOT/dist"
|
DIST_DIR="$MODULE_ROOT/dist"
|
||||||
PYINSTALLER_BUILD="$BUILD_ROOT/pyinstaller"
|
PYINSTALLER_BUILD="$BUILD_ROOT/pyinstaller"
|
||||||
|
PYINSTALLER_SPEC="$PYINSTALLER_BUILD/spec"
|
||||||
|
PYINSTALLER_WORK="$PYINSTALLER_BUILD/work"
|
||||||
VENV_DIR="$BUILD_ROOT/venv"
|
VENV_DIR="$BUILD_ROOT/venv"
|
||||||
|
|
||||||
mkdir -p "$PYINSTALLER_BUILD"
|
AGENT_BUILD_IMAGE="${AGENT_BUILD_IMAGE:-python:3.11-slim-bullseye}"
|
||||||
mkdir -p "$DIST_DIR"
|
AGENT_BUILD_USE_DOCKER="${AGENT_BUILD_USE_DOCKER:-1}"
|
||||||
|
# 默认在容器内忽略代理以避免公司内网代理在 Docker 网络不可达导致 pip 失败(可用 0 关闭)
|
||||||
|
AGENT_BUILD_IGNORE_PROXY="${AGENT_BUILD_IGNORE_PROXY:-1}"
|
||||||
|
USED_DOCKER=0
|
||||||
|
|
||||||
if [[ ! -d "$VENV_DIR" ]]; then
|
run_host_build() {
|
||||||
python3 -m venv "$VENV_DIR"
|
echo "[INFO] Using host Python environment for build" >&2
|
||||||
|
rm -rf "$BUILD_ROOT" "$DIST_DIR"
|
||||||
|
mkdir -p "$PYINSTALLER_BUILD" "$DIST_DIR"
|
||||||
|
python3 -m venv --copies "$VENV_DIR"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$VENV_DIR/bin/activate"
|
||||||
|
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install .
|
||||||
|
pip install "pyinstaller==6.6.0"
|
||||||
|
|
||||||
|
pyinstaller \
|
||||||
|
--clean \
|
||||||
|
--onefile \
|
||||||
|
--name argus-agent \
|
||||||
|
--distpath "$DIST_DIR" \
|
||||||
|
--workpath "$PYINSTALLER_WORK" \
|
||||||
|
--specpath "$PYINSTALLER_SPEC" \
|
||||||
|
--add-data "$MODULE_ROOT/pyproject.toml:." \
|
||||||
|
"$MODULE_ROOT/entry.py"
|
||||||
|
|
||||||
|
chmod +x "$DIST_DIR/argus-agent"
|
||||||
|
deactivate
|
||||||
|
}
|
||||||
|
|
||||||
|
run_docker_build() {
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] docker 命令不存在,无法在容器内构建。请安装 Docker 或设置 AGENT_BUILD_USE_DOCKER=0" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
USED_DOCKER=1
|
||||||
|
echo "[INFO] Building agent binary inside $AGENT_BUILD_IMAGE" >&2
|
||||||
|
|
||||||
|
local host_uid host_gid
|
||||||
|
host_uid="$(id -u)"
|
||||||
|
host_gid="$(id -g)"
|
||||||
|
docker_env=("--rm" "-v" "$MODULE_ROOT:/workspace" "-w" "/workspace" "--env" "TARGET_UID=${host_uid}" "--env" "TARGET_GID=${host_gid}")
|
||||||
|
|
||||||
|
pass_env_if_set() {
|
||||||
|
local var="$1"
|
||||||
|
local value="${!var:-}"
|
||||||
|
if [[ -n "$value" ]]; then
|
||||||
|
docker_env+=("--env" "$var=$value")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pass_env_if_set PIP_INDEX_URL
|
||||||
|
pass_env_if_set PIP_EXTRA_INDEX_URL
|
||||||
|
pass_env_if_set PIP_TRUSTED_HOST
|
||||||
|
pass_env_if_set HTTP_PROXY
|
||||||
|
pass_env_if_set HTTPS_PROXY
|
||||||
|
pass_env_if_set NO_PROXY
|
||||||
|
pass_env_if_set http_proxy
|
||||||
|
pass_env_if_set https_proxy
|
||||||
|
pass_env_if_set no_proxy
|
||||||
|
pass_env_if_set AGENT_BUILD_IGNORE_PROXY
|
||||||
|
|
||||||
|
build_script=$(cat <<'INNER'
|
||||||
|
set -euo pipefail
|
||||||
|
cd /workspace
|
||||||
|
apt-get update >/dev/null
|
||||||
|
apt-get install -y --no-install-recommends binutils >/dev/null
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
rm -rf build dist
|
||||||
|
mkdir -p build/pyinstaller dist
|
||||||
|
python3 -m venv --copies build/venv
|
||||||
|
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
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
source "$VENV_DIR/bin/activate"
|
|
||||||
|
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install "$MODULE_ROOT"
|
pip install .
|
||||||
pip install "pyinstaller==6.6.0"
|
pip install pyinstaller==6.6.0
|
||||||
|
|
||||||
rm -rf "$PYINSTALLER_BUILD"/*
|
|
||||||
rm -f "$DIST_DIR/argus-agent"
|
|
||||||
|
|
||||||
pyinstaller \
|
pyinstaller \
|
||||||
--clean \
|
--clean \
|
||||||
--onefile \
|
--onefile \
|
||||||
--name argus-agent \
|
--name argus-agent \
|
||||||
--distpath "$DIST_DIR" \
|
--distpath dist \
|
||||||
--workpath "$PYINSTALLER_BUILD/work" \
|
--workpath build/pyinstaller/work \
|
||||||
--specpath "$PYINSTALLER_BUILD/spec" \
|
--specpath build/pyinstaller/spec \
|
||||||
--add-data "$MODULE_ROOT/pyproject.toml:." \
|
--add-data /workspace/pyproject.toml:. \
|
||||||
"$MODULE_ROOT/entry.py"
|
entry.py
|
||||||
|
chmod +x dist/argus-agent
|
||||||
|
|
||||||
chmod +x "$DIST_DIR/argus-agent"
|
TARGET_UID="${TARGET_UID:-0}"
|
||||||
|
TARGET_GID="${TARGET_GID:-0}"
|
||||||
|
chown -R "$TARGET_UID:$TARGET_GID" dist build 2>/dev/null || true
|
||||||
|
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
from PyInstaller.archive.readers import CArchiveReader
|
||||||
|
import sys
|
||||||
|
|
||||||
|
archive = Path('dist/argus-agent')
|
||||||
|
out_dir = Path('build/compat_check')
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
major, minor = sys.version_info[:2]
|
||||||
|
libpython = f'libpython{major}.{minor}.so.1.0'
|
||||||
|
expected_libs = [
|
||||||
|
libpython,
|
||||||
|
'libssl.so.3',
|
||||||
|
'libcrypto.so.3',
|
||||||
|
]
|
||||||
|
reader = CArchiveReader(str(archive))
|
||||||
|
extracted = []
|
||||||
|
missing = []
|
||||||
|
for name in expected_libs:
|
||||||
|
try:
|
||||||
|
data = reader.extract(name)
|
||||||
|
except KeyError:
|
||||||
|
missing.append(name)
|
||||||
|
continue
|
||||||
|
(out_dir / name).write_bytes(data)
|
||||||
|
extracted.append(name)
|
||||||
|
(out_dir / 'manifest').write_text('\n'.join(extracted))
|
||||||
|
if extracted:
|
||||||
|
print('[INFO] Extracted libraries: ' + ', '.join(extracted))
|
||||||
|
if missing:
|
||||||
|
print('[WARN] Missing expected libraries in bundle: ' + ', '.join(missing))
|
||||||
|
PY
|
||||||
|
|
||||||
|
compat_check() {
|
||||||
|
local lib_path="$1"
|
||||||
|
if [[ ! -f "$lib_path" ]]; then
|
||||||
|
echo "[WARN] Missing $lib_path for GLIBC check"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local max_glibc
|
||||||
|
max_glibc=$(strings -a "$lib_path" | grep -Eo 'GLIBC_[0-9]+\.[0-9]+' | sort -Vu | tail -n 1 || true)
|
||||||
|
if [[ -n "$max_glibc" ]]; then
|
||||||
|
echo "[INFO] $lib_path references up to $max_glibc"
|
||||||
|
else
|
||||||
|
echo "[INFO] $lib_path does not expose GLIBC version strings"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
compat_libs=()
|
||||||
|
if [[ -f build/compat_check/manifest ]]; then
|
||||||
|
mapfile -t compat_libs < build/compat_check/manifest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#compat_libs[@]} -eq 0 ]]; then
|
||||||
|
echo "[WARN] No libraries captured for GLIBC inspection"
|
||||||
|
else
|
||||||
|
for lib in "${compat_libs[@]}"; do
|
||||||
|
compat_check "build/compat_check/$lib"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
deactivate
|
deactivate
|
||||||
|
INNER
|
||||||
|
)
|
||||||
|
|
||||||
|
if ! docker run "${docker_env[@]}" "$AGENT_BUILD_IMAGE" bash -lc "$build_script"; then
|
||||||
|
echo "[ERROR] Docker 构建失败,请检查 Docker 权限或设置 AGENT_BUILD_USE_DOCKER=0 在兼容主机上构建" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$AGENT_BUILD_USE_DOCKER" == "1" ]]; then
|
||||||
|
run_docker_build
|
||||||
|
else
|
||||||
|
run_host_build
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$DIST_DIR/argus-agent" ]]; then
|
||||||
|
echo "[ERROR] Agent binary was not produced" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$USED_DOCKER" != "1" ]]; then
|
||||||
|
if [[ ! -x "$VENV_DIR/bin/python" ]]; then
|
||||||
|
echo "[WARN] PyInstaller virtualenv missing at $VENV_DIR; skipping compatibility check" >&2
|
||||||
|
else
|
||||||
|
COMPAT_DIR="$BUILD_ROOT/compat_check"
|
||||||
|
rm -rf "$COMPAT_DIR"
|
||||||
|
mkdir -p "$COMPAT_DIR"
|
||||||
|
|
||||||
|
EXTRACT_SCRIPT=$(cat <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
from PyInstaller.archive.readers import CArchiveReader
|
||||||
|
import sys
|
||||||
|
|
||||||
|
archive = Path('dist/argus-agent')
|
||||||
|
out_dir = Path('build/compat_check')
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
major, minor = sys.version_info[:2]
|
||||||
|
libpython = f'libpython{major}.{minor}.so.1.0'
|
||||||
|
expected_libs = [
|
||||||
|
libpython,
|
||||||
|
'libssl.so.3',
|
||||||
|
'libcrypto.so.3',
|
||||||
|
]
|
||||||
|
reader = CArchiveReader(str(archive))
|
||||||
|
extracted = []
|
||||||
|
missing = []
|
||||||
|
for name in expected_libs:
|
||||||
|
try:
|
||||||
|
data = reader.extract(name)
|
||||||
|
except KeyError:
|
||||||
|
missing.append(name)
|
||||||
|
continue
|
||||||
|
(out_dir / name).write_bytes(data)
|
||||||
|
extracted.append(name)
|
||||||
|
(out_dir / 'manifest').write_text('\n'.join(extracted))
|
||||||
|
if extracted:
|
||||||
|
print('[INFO] Extracted libraries: ' + ', '.join(extracted))
|
||||||
|
if missing:
|
||||||
|
print('[WARN] Missing expected libraries in bundle: ' + ', '.join(missing))
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
|
"$VENV_DIR/bin/python" - <<PY
|
||||||
|
$EXTRACT_SCRIPT
|
||||||
|
PY
|
||||||
|
|
||||||
|
compat_libs=()
|
||||||
|
if [[ -f "$COMPAT_DIR/manifest" ]]; then
|
||||||
|
mapfile -t compat_libs < "$COMPAT_DIR/manifest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_glibc_version() {
|
||||||
|
local lib_path="$1"
|
||||||
|
if [[ ! -f "$lib_path" ]]; then
|
||||||
|
echo "[WARN] Skipping GLIBC check; file not found: $lib_path" >&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v strings >/dev/null 2>&1; then
|
||||||
|
local max_glibc
|
||||||
|
max_glibc=$(strings -a "$lib_path" | grep -Eo 'GLIBC_[0-9]+\.[0-9]+' | sort -Vu | tail -n 1 || true)
|
||||||
|
if [[ -n "$max_glibc" ]]; then
|
||||||
|
echo "[INFO] $lib_path references up to $max_glibc"
|
||||||
|
else
|
||||||
|
echo "[INFO] $lib_path does not expose GLIBC version strings"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[WARN] strings command unavailable; cannot inspect $lib_path" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ${#compat_libs[@]} -eq 0 ]]; then
|
||||||
|
echo "[WARN] No libraries captured for GLIBC inspection" >&2
|
||||||
|
else
|
||||||
|
for lib in "${compat_libs[@]}"; do
|
||||||
|
check_glibc_version "$COMPAT_DIR/$lib"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[INFO] Compatibility check executed inside container"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[INFO] Agent binary generated at $DIST_DIR/argus-agent"
|
echo "[INFO] Agent binary generated at $DIST_DIR/argus-agent"
|
||||||
|
|||||||
0
src/agent/tests/__init__.py
Normal file
0
src/agent/tests/__init__.py
Normal file
@@ -1,16 +1,19 @@
|
|||||||
services:
|
services:
|
||||||
bind:
|
bind:
|
||||||
image: ${BIND_IMAGE_TAG:-argus-bind9:e2e}
|
image: ${BIND_IMAGE_TAG:-argus-bind9:latest}
|
||||||
container_name: argus-bind-agent-e2e
|
container_name: argus-bind-agent-e2e
|
||||||
volumes:
|
volumes:
|
||||||
- ./private:/private
|
- ./private:/private
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
ipv4_address: 172.28.0.2
|
ipv4_address: 172.28.0.2
|
||||||
|
environment:
|
||||||
|
- "ARGUS_BUILD_UID=${ARGUS_BUILD_UID:-2133}"
|
||||||
|
- "ARGUS_BUILD_GID=${ARGUS_BUILD_GID:-2015}"
|
||||||
|
restart: always
|
||||||
|
|
||||||
master:
|
master:
|
||||||
image: argus-master:dev
|
image: argus-master:latest
|
||||||
container_name: argus-master-agent-e2e
|
container_name: argus-master-agent-e2e
|
||||||
depends_on:
|
depends_on:
|
||||||
- bind
|
- bind
|
||||||
@@ -18,6 +21,8 @@ services:
|
|||||||
- OFFLINE_THRESHOLD_SECONDS=6
|
- OFFLINE_THRESHOLD_SECONDS=6
|
||||||
- ONLINE_THRESHOLD_SECONDS=2
|
- ONLINE_THRESHOLD_SECONDS=2
|
||||||
- SCHEDULER_INTERVAL_SECONDS=1
|
- SCHEDULER_INTERVAL_SECONDS=1
|
||||||
|
- "ARGUS_BUILD_UID=${ARGUS_BUILD_UID:-2133}"
|
||||||
|
- "ARGUS_BUILD_GID=${ARGUS_BUILD_GID:-2015}"
|
||||||
ports:
|
ports:
|
||||||
- "32300:3000"
|
- "32300:3000"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -27,9 +32,10 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
ipv4_address: 172.28.0.10
|
ipv4_address: 172.28.0.10
|
||||||
|
restart: always
|
||||||
|
|
||||||
agent:
|
agent:
|
||||||
image: ubuntu:24.04
|
image: ubuntu:22.04
|
||||||
container_name: argus-agent-e2e
|
container_name: argus-agent-e2e
|
||||||
hostname: dev-e2euser-e2einst-pod-0
|
hostname: dev-e2euser-e2einst-pod-0
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -38,17 +44,51 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- MASTER_ENDPOINT=http://master.argus.com:3000
|
- MASTER_ENDPOINT=http://master.argus.com:3000
|
||||||
- REPORT_INTERVAL_SECONDS=2
|
- REPORT_INTERVAL_SECONDS=2
|
||||||
|
- "ARGUS_BUILD_UID=${ARGUS_BUILD_UID:-2133}"
|
||||||
|
- "ARGUS_BUILD_GID=${ARGUS_BUILD_GID:-2015}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./private/argus/agent/dev-e2euser-e2einst-pod-0:/private/argus/agent/dev-e2euser-e2einst-pod-0
|
- ./private/argus/agent/dev-e2euser-e2einst-pod-0:/private/argus/agent/dev-e2euser-e2einst-pod-0
|
||||||
- ./private/argus/agent/health/dev-e2euser-e2einst-pod-0:/private/argus/agent/health/dev-e2euser-e2einst-pod-0
|
- ./private/argus/agent/dev-e2euser-e2einst-pod-0/health:/private/argus/agent/dev-e2euser-e2einst-pod-0/health
|
||||||
- ./private/argus/etc:/private/argus/etc
|
- ./private/argus/etc:/private/argus/etc
|
||||||
- ../dist/argus-agent:/usr/local/bin/argus-agent:ro
|
- ../dist/argus-agent:/usr/local/bin/argus-agent:ro
|
||||||
- ./scripts/agent_entrypoint.sh:/usr/local/bin/agent-entrypoint.sh: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:
|
entrypoint:
|
||||||
- /usr/local/bin/agent-entrypoint.sh
|
- /usr/local/bin/agent-entrypoint.sh
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
ipv4_address: 172.28.0.20
|
ipv4_address: 172.28.0.20
|
||||||
|
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:
|
||||||
|
|||||||
@@ -7,9 +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"
|
||||||
"05_assert_status_on_master.sh"
|
"05_verify_agent.sh"
|
||||||
"06_restart_agent_and_reregister.sh"
|
"06_assert_status_on_master.sh"
|
||||||
"07_down.sh"
|
"07_restart_agent_and_reregister.sh"
|
||||||
|
"08_down.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
for script in "${SCRIPTS[@]}"; do
|
for script in "${SCRIPTS[@]}"; do
|
||||||
|
|||||||
@@ -11,13 +11,21 @@ TMP_ROOT="$TEST_ROOT/tmp"
|
|||||||
|
|
||||||
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
||||||
AGENT_CONFIG_DIR="$PRIVATE_ROOT/argus/agent/$AGENT_HOSTNAME"
|
AGENT_CONFIG_DIR="$PRIVATE_ROOT/argus/agent/$AGENT_HOSTNAME"
|
||||||
AGENT_HEALTH_DIR="$PRIVATE_ROOT/argus/agent/health/$AGENT_HOSTNAME"
|
AGENT_HEALTH_DIR="$PRIVATE_ROOT/argus/agent/$AGENT_HOSTNAME/health"
|
||||||
MASTER_PRIVATE_DIR="$PRIVATE_ROOT/argus/master"
|
MASTER_PRIVATE_DIR="$PRIVATE_ROOT/argus/master"
|
||||||
METRIC_PRIVATE_DIR="$PRIVATE_ROOT/argus/metric/prometheus"
|
METRIC_PRIVATE_DIR="$PRIVATE_ROOT/argus/metric/prometheus"
|
||||||
DNS_DIR="$PRIVATE_ROOT/argus/etc"
|
DNS_DIR="$PRIVATE_ROOT/argus/etc"
|
||||||
BIND_IMAGE_TAG="${BIND_IMAGE_TAG:-argus-bind9:e2e}"
|
BIND_IMAGE_TAG="${BIND_IMAGE_TAG:-argus-bind9:latest}"
|
||||||
BIND_ROOT="$(cd "$MASTER_ROOT/../bind" && pwd)"
|
BIND_ROOT="$(cd "$MASTER_ROOT/../bind" && pwd)"
|
||||||
|
|
||||||
|
ensure_image() {
|
||||||
|
local image="$1"
|
||||||
|
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
||||||
|
echo "[ERROR] Docker image '$image' 未找到,请先运行统一构建脚本 (例如 ./build/build_images.sh) 生成所需镜像" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
mkdir -p "$AGENT_CONFIG_DIR"
|
mkdir -p "$AGENT_CONFIG_DIR"
|
||||||
mkdir -p "$AGENT_HEALTH_DIR"
|
mkdir -p "$AGENT_HEALTH_DIR"
|
||||||
mkdir -p "$MASTER_PRIVATE_DIR"
|
mkdir -p "$MASTER_PRIVATE_DIR"
|
||||||
@@ -35,9 +43,8 @@ else
|
|||||||
echo "[WARN] bind update script missing at $BIND_ROOT/build/update-dns.sh"
|
echo "[WARN] bind update script missing at $BIND_ROOT/build/update-dns.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pushd "$MASTER_ROOT" >/dev/null
|
ensure_image "argus-master:latest"
|
||||||
./scripts/build_images.sh --tag argus-master:dev
|
ensure_image "$BIND_IMAGE_TAG"
|
||||||
popd >/dev/null
|
|
||||||
|
|
||||||
AGENT_BINARY="$AGENT_ROOT/dist/argus-agent"
|
AGENT_BINARY="$AGENT_ROOT/dist/argus-agent"
|
||||||
|
|
||||||
@@ -50,11 +57,6 @@ if [[ ! -x "$AGENT_BINARY" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 中文提示:构建测试专用 bind9 镜像,确保解析服务可用
|
|
||||||
pushd "$REPO_ROOT" >/dev/null
|
|
||||||
docker build -f src/bind/build/Dockerfile -t "$BIND_IMAGE_TAG" .
|
|
||||||
popd >/dev/null
|
|
||||||
|
|
||||||
echo "$AGENT_BINARY" > "$TMP_ROOT/agent_binary_path"
|
echo "$AGENT_BINARY" > "$TMP_ROOT/agent_binary_path"
|
||||||
echo "$BIND_IMAGE_TAG" > "$TMP_ROOT/bind_image_tag"
|
echo "$BIND_IMAGE_TAG" > "$TMP_ROOT/bind_image_tag"
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,19 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$TEST_ROOT/../../.." && pwd)"
|
||||||
|
|
||||||
TMP_ROOT="$TEST_ROOT/tmp"
|
TMP_ROOT="$TEST_ROOT/tmp"
|
||||||
|
ENV_FILE="$TEST_ROOT/.env"
|
||||||
|
|
||||||
|
source "$REPO_ROOT/scripts/common/build_user.sh"
|
||||||
|
load_build_user
|
||||||
|
export ARGUS_BUILD_UID ARGUS_BUILD_GID
|
||||||
|
|
||||||
|
cat > "$ENV_FILE" <<EOF
|
||||||
|
ARGUS_BUILD_UID=$ARGUS_BUILD_UID
|
||||||
|
ARGUS_BUILD_GID=$ARGUS_BUILD_GID
|
||||||
|
EOF
|
||||||
|
|
||||||
if [[ ! -f "$TMP_ROOT/agent_binary_path" ]]; then
|
if [[ ! -f "$TMP_ROOT/agent_binary_path" ]]; then
|
||||||
echo "[ERROR] Agent binary path missing; run 01_bootstrap.sh first" >&2
|
echo "[ERROR] Agent binary path missing; run 01_bootstrap.sh first" >&2
|
||||||
@@ -17,7 +28,7 @@ if [[ ! -x "$AGENT_BINARY" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
BIND_IMAGE_TAG_VALUE="argus-bind9:e2e"
|
BIND_IMAGE_TAG_VALUE="argus-bind9:latest"
|
||||||
if [[ -f "$TMP_ROOT/bind_image_tag" ]]; then
|
if [[ -f "$TMP_ROOT/bind_image_tag" ]]; then
|
||||||
BIND_IMAGE_TAG_VALUE="$(cat "$TMP_ROOT/bind_image_tag")"
|
BIND_IMAGE_TAG_VALUE="$(cat "$TMP_ROOT/bind_image_tag")"
|
||||||
fi
|
fi
|
||||||
@@ -30,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"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
HEALTH_DIR="$TEST_ROOT/private/argus/agent/health/dev-e2euser-e2einst-pod-0"
|
HEALTH_DIR="$TEST_ROOT/private/argus/agent/dev-e2euser-e2einst-pod-0/health"
|
||||||
|
|
||||||
cat > "$HEALTH_DIR/log-fluentbit.json" <<JSON
|
cat > "$HEALTH_DIR/log-fluentbit.json" <<JSON
|
||||||
{
|
{
|
||||||
|
|||||||
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"
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
TMP_ROOT="$TEST_ROOT/tmp"
|
|
||||||
API_BASE="http://localhost:32300/api/v1/master"
|
|
||||||
NODE_ID="$(cat "$TMP_ROOT/node_id")"
|
|
||||||
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
|
||||||
NETWORK_NAME="tests_default"
|
|
||||||
NEW_AGENT_IP="172.28.0.200"
|
|
||||||
ENTRYPOINT_SCRIPT="$SCRIPT_DIR/agent_entrypoint.sh"
|
|
||||||
|
|
||||||
# 中文提示:重启场景也需要同样的入口脚本,确保 DNS 注册逻辑一致
|
|
||||||
if [[ ! -f "$ENTRYPOINT_SCRIPT" ]]; then
|
|
||||||
echo "[ERROR] agent entrypoint script missing at $ENTRYPOINT_SCRIPT" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -f "$TMP_ROOT/agent_binary_path" ]]; then
|
|
||||||
echo "[ERROR] Agent binary path missing; rerun bootstrap" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
AGENT_BINARY="$(cat "$TMP_ROOT/agent_binary_path")"
|
|
||||||
if [[ ! -x "$AGENT_BINARY" ]]; then
|
|
||||||
echo "[ERROR] Agent binary not executable: $AGENT_BINARY" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
compose() {
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
|
||||||
docker compose "$@"
|
|
||||||
else
|
|
||||||
docker-compose "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
before_file="$TMP_ROOT/before_restart.json"
|
|
||||||
curl -sS "$API_BASE/nodes/$NODE_ID" -o "$before_file"
|
|
||||||
prev_last_updated=$(python3 - "$before_file" <<'PY'
|
|
||||||
import json, sys
|
|
||||||
with open(sys.argv[1]) as handle:
|
|
||||||
node = json.load(handle)
|
|
||||||
print(node.get("last_updated", ""))
|
|
||||||
PY
|
|
||||||
)
|
|
||||||
prev_ip=$(python3 - "$before_file" <<'PY'
|
|
||||||
import json, sys
|
|
||||||
with open(sys.argv[1]) as handle:
|
|
||||||
node = json.load(handle)
|
|
||||||
print(node["meta_data"].get("ip", ""))
|
|
||||||
PY
|
|
||||||
)
|
|
||||||
initial_ip=$(cat "$TMP_ROOT/initial_ip")
|
|
||||||
if [[ "$prev_ip" != "$initial_ip" ]]; then
|
|
||||||
echo "[ERROR] Expected initial IP $initial_ip, got $prev_ip" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
pushd "$TEST_ROOT" >/dev/null
|
|
||||||
compose rm -sf agent
|
|
||||||
popd >/dev/null
|
|
||||||
|
|
||||||
docker container rm -f argus-agent-e2e >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
AGENT_DIR="$TEST_ROOT/private/argus/agent/$AGENT_HOSTNAME"
|
|
||||||
HEALTH_DIR="$TEST_ROOT/private/argus/agent/health/$AGENT_HOSTNAME"
|
|
||||||
|
|
||||||
# 先以 sleep 方式启动容器,确保我们掌握注册时的网络状态
|
|
||||||
if ! docker run -d \
|
|
||||||
--name argus-agent-e2e \
|
|
||||||
--hostname "$AGENT_HOSTNAME" \
|
|
||||||
--network "$NETWORK_NAME" \
|
|
||||||
--ip "$NEW_AGENT_IP" \
|
|
||||||
-v "$AGENT_DIR:/private/argus/agent/$AGENT_HOSTNAME" \
|
|
||||||
-v "$HEALTH_DIR:/private/argus/agent/health/$AGENT_HOSTNAME" \
|
|
||||||
-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" \
|
|
||||||
-e MASTER_ENDPOINT=http://master.argus.com:3000 \
|
|
||||||
-e REPORT_INTERVAL_SECONDS=2 \
|
|
||||||
--entrypoint /usr/local/bin/agent-entrypoint.sh \
|
|
||||||
ubuntu:24.04 >/dev/null; then
|
|
||||||
echo "[ERROR] Failed to start agent container with custom IP" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
success=false
|
|
||||||
detail_file="$TMP_ROOT/post_restart.json"
|
|
||||||
for _ in {1..20}; do
|
|
||||||
sleep 3
|
|
||||||
if ! curl -sS "$API_BASE/nodes/$NODE_ID" -o "$detail_file"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if python3 - "$detail_file" "$prev_last_updated" "$NODE_ID" "$prev_ip" "$NEW_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")
|
|
||||||
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)
|
|
||||||
PY
|
|
||||||
then
|
|
||||||
success=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$success" != true ]]; then
|
|
||||||
echo "[ERROR] Agent did not report expected new IP $NEW_AGENT_IP after restart" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[INFO] Agent restart produced successful re-registration with IP change"
|
|
||||||
254
src/agent/tests/scripts/07_restart_agent_and_reregister.sh
Executable file
254
src/agent/tests/scripts/07_restart_agent_and_reregister.sh
Executable file
@@ -0,0 +1,254 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
TMP_ROOT="$TEST_ROOT/tmp"
|
||||||
|
API_BASE="http://localhost:32300/api/v1/master"
|
||||||
|
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"
|
||||||
|
ENV_AGENT_HOSTNAME="host_abc"
|
||||||
|
NETWORK_NAME="tests_default"
|
||||||
|
NEW_AGENT_IP="172.28.0.200"
|
||||||
|
NEW_ENV_AGENT_IP="172.28.0.210"
|
||||||
|
ENTRYPOINT_SCRIPT="$SCRIPT_DIR/agent_entrypoint.sh"
|
||||||
|
VERIFY_SCRIPT="$TEST_ROOT/../scripts/agent_deployment_verify.sh"
|
||||||
|
ENV_FILE="$TEST_ROOT/.env"
|
||||||
|
|
||||||
|
# 中文提示:重启场景也需要同样的入口脚本,确保 DNS 注册逻辑一致
|
||||||
|
if [[ ! -f "$ENTRYPOINT_SCRIPT" ]]; then
|
||||||
|
echo "[ERROR] agent entrypoint script missing at $ENTRYPOINT_SCRIPT" >&2
|
||||||
|
exit 1
|
||||||
|
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
|
||||||
|
echo "[ERROR] Agent binary path missing; rerun bootstrap" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
AGENT_BINARY="$(cat "$TMP_ROOT/agent_binary_path")"
|
||||||
|
if [[ ! -x "$AGENT_BINARY" ]]; then
|
||||||
|
echo "[ERROR] Agent binary not executable: $AGENT_BINARY" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
else
|
||||||
|
REPO_ROOT="$(cd "$TEST_ROOT/../../.." && pwd)"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$REPO_ROOT/scripts/common/build_user.sh"
|
||||||
|
load_build_user
|
||||||
|
fi
|
||||||
|
|
||||||
|
AGENT_UID="${ARGUS_BUILD_UID:-2133}"
|
||||||
|
AGENT_GID="${ARGUS_BUILD_GID:-2015}"
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
docker compose "$@"
|
||||||
|
else
|
||||||
|
docker-compose "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
before_file="$TMP_ROOT/before_restart.json"
|
||||||
|
curl -sS "$API_BASE/nodes/$NODE_ID" -o "$before_file"
|
||||||
|
prev_last_updated=$(python3 - "$before_file" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as handle:
|
||||||
|
node = json.load(handle)
|
||||||
|
print(node.get("last_updated", ""))
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
prev_ip=$(python3 - "$before_file" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
with open(sys.argv[1]) as handle:
|
||||||
|
node = json.load(handle)
|
||||||
|
print(node["meta_data"].get("ip", ""))
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
initial_ip=$(cat "$TMP_ROOT/initial_ip")
|
||||||
|
if [[ "$prev_ip" != "$initial_ip" ]]; then
|
||||||
|
echo "[ERROR] Expected initial IP $initial_ip, got $prev_ip" >&2
|
||||||
|
exit 1
|
||||||
|
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
|
||||||
|
compose rm -sf agent
|
||||||
|
compose rm -sf agent_env
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
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"
|
||||||
|
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 方式启动容器,确保我们掌握注册时的网络状态
|
||||||
|
if ! docker run -d \
|
||||||
|
--name argus-agent-e2e \
|
||||||
|
--hostname "$AGENT_HOSTNAME" \
|
||||||
|
--network "$NETWORK_NAME" \
|
||||||
|
--ip "$NEW_AGENT_IP" \
|
||||||
|
-v "$AGENT_DIR:/private/argus/agent/$AGENT_HOSTNAME" \
|
||||||
|
-v "$HEALTH_DIR:/private/argus/agent/$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 agent container with custom IP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
success=false
|
||||||
|
detail_file="$TMP_ROOT/post_restart.json"
|
||||||
|
for _ in {1..20}; do
|
||||||
|
sleep 3
|
||||||
|
if ! curl -sS "$API_BASE/nodes/$NODE_ID" -o "$detail_file"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if python3 - "$detail_file" "$prev_last_updated" "$NODE_ID" "$prev_ip" "$NEW_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")
|
||||||
|
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)
|
||||||
|
PY
|
||||||
|
then
|
||||||
|
success=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$success" != true ]]; then
|
||||||
|
echo "[ERROR] Agent did not report expected new IP $NEW_AGENT_IP after restart" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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"
|
||||||
@@ -3,6 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
ENV_FILE="$TEST_ROOT/.env"
|
||||||
|
|
||||||
compose() {
|
compose() {
|
||||||
if docker compose version >/dev/null 2>&1; then
|
if docker compose version >/dev/null 2>&1; then
|
||||||
@@ -12,13 +13,24 @@ 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
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
rm -rf "$TEST_ROOT/private"
|
if [[ -d "$TEST_ROOT/private" ]]; then
|
||||||
|
docker run --rm \
|
||||||
|
-v "$TEST_ROOT/private:/target" \
|
||||||
|
ubuntu:24.04 \
|
||||||
|
chown -R "$(id -u):$(id -g)" /target >/dev/null 2>&1 || true
|
||||||
|
rm -rf "$TEST_ROOT/private"
|
||||||
|
fi
|
||||||
|
|
||||||
rm -rf "$TEST_ROOT/tmp"
|
rm -rf "$TEST_ROOT/tmp"
|
||||||
|
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
rm -f "$ENV_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[INFO] Agent E2E environment cleaned up"
|
echo "[INFO] Agent E2E environment cleaned up"
|
||||||
@@ -5,11 +5,36 @@ LOG_PREFIX="[AGENT-ENTRYPOINT]"
|
|||||||
DNS_SCRIPT="/private/argus/etc/update-dns.sh"
|
DNS_SCRIPT="/private/argus/etc/update-dns.sh"
|
||||||
DNS_CONF="/private/argus/etc/dns.conf"
|
DNS_CONF="/private/argus/etc/dns.conf"
|
||||||
TARGET_DOMAIN="master.argus.com"
|
TARGET_DOMAIN="master.argus.com"
|
||||||
|
AGENT_UID="${ARGUS_BUILD_UID:-2133}"
|
||||||
|
AGENT_GID="${ARGUS_BUILD_GID:-2015}"
|
||||||
|
AGENT_HOSTNAME="${HOSTNAME:-unknown}"
|
||||||
|
AGENT_DATA_DIR="/private/argus/agent/${AGENT_HOSTNAME}"
|
||||||
|
AGENT_HEALTH_DIR="${AGENT_DATA_DIR}/health"
|
||||||
|
RUNTIME_GROUP="argusagent"
|
||||||
|
RUNTIME_USER="argusagent"
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
echo "${LOG_PREFIX} $*"
|
echo "${LOG_PREFIX} $*"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mkdir -p "$AGENT_DATA_DIR" "$AGENT_HEALTH_DIR"
|
||||||
|
chown -R "$AGENT_UID:$AGENT_GID" "$AGENT_DATA_DIR" "$AGENT_HEALTH_DIR" 2>/dev/null || true
|
||||||
|
chown -R "$AGENT_UID:$AGENT_GID" "/private/argus/etc" 2>/dev/null || true
|
||||||
|
|
||||||
|
if ! getent group "$AGENT_GID" >/dev/null 2>&1; then
|
||||||
|
groupadd -g "$AGENT_GID" "$RUNTIME_GROUP"
|
||||||
|
else
|
||||||
|
RUNTIME_GROUP="$(getent group "$AGENT_GID" | cut -d: -f1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! getent passwd "$AGENT_UID" >/dev/null 2>&1; then
|
||||||
|
useradd -u "$AGENT_UID" -g "$AGENT_GID" -M -s /bin/bash "$RUNTIME_USER"
|
||||||
|
else
|
||||||
|
RUNTIME_USER="$(getent passwd "$AGENT_UID" | cut -d: -f1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "运行用户: $RUNTIME_USER ($AGENT_UID:$AGENT_GID)"
|
||||||
|
|
||||||
# 中文提示:等待 bind 下发的 update-dns.sh 脚本
|
# 中文提示:等待 bind 下发的 update-dns.sh 脚本
|
||||||
for _ in {1..30}; do
|
for _ in {1..30}; do
|
||||||
if [[ -x "$DNS_SCRIPT" ]]; then
|
if [[ -x "$DNS_SCRIPT" ]]; then
|
||||||
@@ -21,9 +46,14 @@ done
|
|||||||
|
|
||||||
if [[ -x "$DNS_SCRIPT" ]]; then
|
if [[ -x "$DNS_SCRIPT" ]]; then
|
||||||
log "执行 update-dns.sh 更新容器 DNS"
|
log "执行 update-dns.sh 更新容器 DNS"
|
||||||
if ! "$DNS_SCRIPT"; then
|
while true; do
|
||||||
log "update-dns.sh 执行失败,继续尝试默认 DNS"
|
if "$DNS_SCRIPT"; then
|
||||||
fi
|
log "update-dns.sh 执行成功"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
log "update-dns.sh 执行失败,3 秒后重试"
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
else
|
else
|
||||||
log "未获取到 update-dns.sh,使用镜像默认 DNS"
|
log "未获取到 update-dns.sh,使用镜像默认 DNS"
|
||||||
fi
|
fi
|
||||||
@@ -46,4 +76,4 @@ for _ in {1..30}; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
log "启动 argus-agent"
|
log "启动 argus-agent"
|
||||||
exec /usr/local/bin/argus-agent
|
exec su -s /bin/bash -c /usr/local/bin/argus-agent "$RUNTIME_USER"
|
||||||
|
|||||||
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_BUILD_UID=1048
|
||||||
|
ARGUS_BUILD_GID=1048
|
||||||
|
|
||||||
|
USE_INTRANET=false
|
||||||
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
|
||||||
113
src/alert/tests/scripts/verify_alertmanager.sh
Normal file
113
src/alert/tests/scripts/verify_alertmanager.sh
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# verify_alertmanager.sh
|
||||||
|
# 用于部署后验证 Prometheus 与 Alertmanager 通信链路是否正常
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# 基础配置
|
||||||
|
#=============================
|
||||||
|
PROM_URL="${PROM_URL:-http://prom.metric.argus.com:9090}"
|
||||||
|
ALERT_URL="${ALERT_URL:-http://alertmanager.alert.argus.com:9093}"
|
||||||
|
# TODO: 根据实际部署环境调整规则目录
|
||||||
|
DATA_ROOT="${DATA_ROOT:-/private/argus}"
|
||||||
|
RULE_DIR = "$DATA_ROOT/metric/prometheus/rules"
|
||||||
|
TMP_RULE="/tmp/test_rule.yml"
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# 辅助函数
|
||||||
|
#=============================
|
||||||
|
GREEN="\033[32m"; RED="\033[31m"; YELLOW="\033[33m"; RESET="\033[0m"
|
||||||
|
|
||||||
|
log_info() { echo -e "${YELLOW}[INFO]${RESET} $1"; }
|
||||||
|
log_success() { echo -e "${GREEN}[OK]${RESET} $1"; }
|
||||||
|
log_error() { echo -e "${RED}[ERROR]${RESET} $1"; }
|
||||||
|
|
||||||
|
fail_exit() { log_error "$1"; exit 1; }
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 1: 检查 Alertmanager 是否可访问
|
||||||
|
#=============================
|
||||||
|
log_info "检查 Alertmanager 状态..."
|
||||||
|
if curl -sSf "${ALERT_URL}/api/v2/status" >/dev/null 2>&1; then
|
||||||
|
log_success "Alertmanager 服务正常 (${ALERT_URL})"
|
||||||
|
else
|
||||||
|
fail_exit "无法访问 Alertmanager,请检查端口映射与容器状态。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 2: 手动发送测试告警
|
||||||
|
#=============================
|
||||||
|
log_info "发送手动测试告警..."
|
||||||
|
curl -s -XPOST "${ALERT_URL}/api/v2/alerts" -H "Content-Type: application/json" -d '[
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"alertname": "ManualTestAlert",
|
||||||
|
"severity": "info"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "This is a test alert from deploy verification"
|
||||||
|
},
|
||||||
|
"startsAt": "'$(date -Iseconds)'"
|
||||||
|
}
|
||||||
|
]' >/dev/null && log_success "测试告警已成功发送到 Alertmanager"
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 3: 检查 Prometheus 配置中是否包含 Alertmanager
|
||||||
|
#=============================
|
||||||
|
log_info "检查 Prometheus 是否配置了 Alertmanager..."
|
||||||
|
if curl -s "${PROM_URL}/api/v1/status/config" | grep -q "alertmanagers"; then
|
||||||
|
log_success "Prometheus 已配置 Alertmanager 目标"
|
||||||
|
else
|
||||||
|
fail_exit "Prometheus 未配置 Alertmanager,请检查 prometheus.yml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 4: 创建并加载测试告警规则
|
||||||
|
#=============================
|
||||||
|
log_info "创建临时测试规则 ${TMP_RULE} ..."
|
||||||
|
cat <<EOF > "${TMP_RULE}"
|
||||||
|
groups:
|
||||||
|
- name: deploy-verify-group
|
||||||
|
rules:
|
||||||
|
- alert: DeployVerifyAlert
|
||||||
|
expr: vector(1)
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Deployment verification alert"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p "${RULE_DIR}"
|
||||||
|
cp "${TMP_RULE}" "${RULE_DIR}/test_rule.yml"
|
||||||
|
|
||||||
|
log_info "重载 Prometheus 以加载新规则..."
|
||||||
|
if curl -s -X POST "${PROM_URL}/-/reload" >/dev/null; then
|
||||||
|
log_success "Prometheus 已重载规则"
|
||||||
|
else
|
||||||
|
fail_exit "Prometheus reload 失败,请检查 API 可访问性。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 5: 等待并验证 Alertmanager 是否收到告警
|
||||||
|
#=============================
|
||||||
|
log_info "等待告警触发 (约5秒)..."
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if curl -s "${ALERT_URL}/api/v2/alerts" | grep -q "DeployVerifyAlert"; then
|
||||||
|
log_success "Prometheus → Alertmanager 告警链路验证成功"
|
||||||
|
else
|
||||||
|
fail_exit "未在 Alertmanager 中检测到 DeployVerifyAlert,请检查网络或配置。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 6: 清理测试规则
|
||||||
|
#=============================
|
||||||
|
log_info "清理临时测试规则..."
|
||||||
|
rm -f "${RULE_DIR}/test_rule.yml" "${TMP_RULE}"
|
||||||
|
|
||||||
|
curl -s -X POST "${PROM_URL}/-/reload" >/dev/null \
|
||||||
|
&& log_success "Prometheus 已清理验证规则" \
|
||||||
|
|| log_error "Prometheus reload 清理失败,请手动确认。"
|
||||||
|
|
||||||
|
log_success "部署验证全部通过!Prometheus ↔ Alertmanager 通信正常。"
|
||||||
@@ -6,6 +6,11 @@ ENV TZ=Asia/Shanghai
|
|||||||
|
|
||||||
# 设置构建参数
|
# 设置构建参数
|
||||||
ARG USE_INTRANET=false
|
ARG USE_INTRANET=false
|
||||||
|
ARG ARGUS_BUILD_UID=2133
|
||||||
|
ARG ARGUS_BUILD_GID=2015
|
||||||
|
|
||||||
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
# 配置内网 apt 源 (如果指定了内网选项)
|
# 配置内网 apt 源 (如果指定了内网选项)
|
||||||
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
@@ -21,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 \
|
||||||
@@ -29,6 +35,24 @@ RUN apt-get update && \
|
|||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 调整 bind 用户与用户组 ID 以匹配宿主机配置
|
||||||
|
RUN set -eux; \
|
||||||
|
current_gid="$(getent group bind | awk -F: '{print $3}')"; \
|
||||||
|
if [ -z "$current_gid" ]; then \
|
||||||
|
groupadd -g "${ARGUS_BUILD_GID}" bind; \
|
||||||
|
elif [ "$current_gid" != "${ARGUS_BUILD_GID}" ]; then \
|
||||||
|
groupmod -g "${ARGUS_BUILD_GID}" bind; \
|
||||||
|
fi; \
|
||||||
|
if id bind >/dev/null 2>&1; then \
|
||||||
|
current_uid="$(id -u bind)"; \
|
||||||
|
if [ "$current_uid" != "${ARGUS_BUILD_UID}" ]; then \
|
||||||
|
usermod -u "${ARGUS_BUILD_UID}" bind; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
useradd -m -u "${ARGUS_BUILD_UID}" -g "${ARGUS_BUILD_GID}" bind; \
|
||||||
|
fi; \
|
||||||
|
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" /var/cache/bind /var/lib/bind
|
||||||
|
|
||||||
# 配置部署时使用的apt源
|
# 配置部署时使用的apt源
|
||||||
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
echo "deb [trusted=yes] https://10.92.132.52/mirrors/ubuntu2204/ jammy main" > /etc/apt/sources.list; \
|
echo "deb [trusted=yes] https://10.92.132.52/mirrors/ubuntu2204/ jammy main" > /etc/apt/sources.list; \
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ SLEEP_SECONDS=10
|
|||||||
RELOAD_SCRIPT="/usr/local/bin/reload-bind9.sh" # 这里放你已有脚本的路径
|
RELOAD_SCRIPT="/usr/local/bin/reload-bind9.sh" # 这里放你已有脚本的路径
|
||||||
|
|
||||||
mkdir -p "$(dirname "$LOCKFILE")" "$BACKUP_DIR"
|
mkdir -p "$(dirname "$LOCKFILE")" "$BACKUP_DIR"
|
||||||
|
BACKUP_UID="${ARGUS_BUILD_UID:-2133}"
|
||||||
|
BACKUP_GID="${ARGUS_BUILD_GID:-2015}"
|
||||||
|
chown -R "$BACKUP_UID:$BACKUP_GID" "$BACKUP_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
is_ipv4() {
|
is_ipv4() {
|
||||||
local ip="$1"
|
local ip="$1"
|
||||||
@@ -33,6 +36,7 @@ upsert_record() {
|
|||||||
local changed=0
|
local changed=0
|
||||||
|
|
||||||
cp -a "$ZONE_DB" "$BACKUP_DIR/db.argus.com.$ts.bak"
|
cp -a "$ZONE_DB" "$BACKUP_DIR/db.argus.com.$ts.bak"
|
||||||
|
chown "$BACKUP_UID:$BACKUP_GID" "$BACKUP_DIR/db.argus.com.$ts.bak" 2>/dev/null || true
|
||||||
|
|
||||||
local cur_ip
|
local cur_ip
|
||||||
cur_ip="$(get_current_ip "$name" || true)"
|
cur_ip="$(get_current_ip "$name" || true)"
|
||||||
@@ -61,7 +65,10 @@ upsert_record() {
|
|||||||
echo "[SKIP] ${name} unchanged (${new_ip})"
|
echo "[SKIP] ${name} unchanged (${new_ip})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return $changed
|
if [[ $changed -eq 1 ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
@@ -70,7 +77,7 @@ while true; do
|
|||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
NEED_RELOAD=0
|
NEED_RELOAD=0
|
||||||
|
|
||||||
for f in "$WATCH_DIR"/*.argus.com; do
|
for f in "$WATCH_DIR"/*.argus.com; do
|
||||||
base="$(basename "$f")"
|
base="$(basename "$f")"
|
||||||
name="${base%.argus.com}"
|
name="${base%.argus.com}"
|
||||||
ip="$(grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' "$f" | tail -n1 || true)"
|
ip="$(grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' "$f" | tail -n1 || true)"
|
||||||
@@ -97,4 +104,3 @@ while true; do
|
|||||||
|
|
||||||
sleep "$SLEEP_SECONDS"
|
sleep "$SLEEP_SECONDS"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ chmod 777 /private 2>/dev/null || true
|
|||||||
# Create persistent directories for BIND9 configs and DNS sync
|
# Create persistent directories for BIND9 configs and DNS sync
|
||||||
mkdir -p /private/argus/bind
|
mkdir -p /private/argus/bind
|
||||||
mkdir -p /private/argus/etc
|
mkdir -p /private/argus/etc
|
||||||
|
chown bind:bind /private/argus 2>/dev/null || true
|
||||||
|
chown -R bind:bind /private/argus/bind /private/argus/etc
|
||||||
|
|
||||||
# Copy configuration files to persistent storage if they don't exist
|
# Copy configuration files to persistent storage if they don't exist
|
||||||
if [ ! -f /private/argus/bind/named.conf.local ]; then
|
if [ ! -f /private/argus/bind/named.conf.local ]; then
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ services:
|
|||||||
image: argus-bind9:latest
|
image: argus-bind9:latest
|
||||||
container_name: argus-bind9-test
|
container_name: argus-bind9-test
|
||||||
ports:
|
ports:
|
||||||
- "53:53/tcp"
|
- "${HOST_DNS_PORT:-1053}:53/tcp"
|
||||||
- "53:53/udp"
|
- "${HOST_DNS_PORT:-1053}:53/udp"
|
||||||
volumes:
|
volumes:
|
||||||
- ./private:/private
|
- ./private:/private
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HOST_DNS_PORT="${HOST_DNS_PORT:-1053}"
|
||||||
|
|
||||||
|
export HOST_DNS_PORT
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "BIND9 DNS Server End-to-End Test Suite"
|
echo "BIND9 DNS Server End-to-End Test Suite"
|
||||||
|
|||||||
@@ -7,13 +7,17 @@ set -e
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
HOST_DNS_PORT="${HOST_DNS_PORT:-1053}"
|
||||||
|
|
||||||
|
export HOST_DNS_PORT
|
||||||
|
|
||||||
cd "$TEST_DIR"
|
cd "$TEST_DIR"
|
||||||
|
|
||||||
echo "Starting BIND9 test container..."
|
echo "Starting BIND9 test container..."
|
||||||
|
|
||||||
# Ensure private directory exists with proper permissions
|
# Ensure private directory exists with proper permissions
|
||||||
mkdir -p private
|
mkdir -p private/argus/bind
|
||||||
|
mkdir -p private/argus/etc
|
||||||
chmod 777 private
|
chmod 777 private
|
||||||
|
|
||||||
# Start the container
|
# Start the container
|
||||||
@@ -35,4 +39,4 @@ fi
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "BIND9 test environment is ready!"
|
echo "BIND9 test environment is ready!"
|
||||||
echo "DNS server listening on localhost:53"
|
echo "DNS server listening on localhost:${HOST_DNS_PORT}"
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
HOST_DNS_PORT="${HOST_DNS_PORT:-1053}"
|
||||||
|
|
||||||
echo "Testing DNS resolution with dig..."
|
echo "Testing DNS resolution with dig..."
|
||||||
|
echo "Using DNS server localhost:${HOST_DNS_PORT}"
|
||||||
|
|
||||||
# Function to test DNS query
|
# Function to test DNS query
|
||||||
test_dns_query() {
|
test_dns_query() {
|
||||||
@@ -19,7 +22,7 @@ test_dns_query() {
|
|||||||
echo "Expected IP: $expected_ip"
|
echo "Expected IP: $expected_ip"
|
||||||
|
|
||||||
# Perform dig query
|
# Perform dig query
|
||||||
result=$(dig @localhost $hostname.argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
||||||
|
|
||||||
if [ "$result" = "QUERY_FAILED" ]; then
|
if [ "$result" = "QUERY_FAILED" ]; then
|
||||||
echo "✗ DNS query failed"
|
echo "✗ DNS query failed"
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
HOST_DNS_PORT="${HOST_DNS_PORT:-1053}"
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
echo "=== DNS Auto-Sync Functionality Test ==="
|
echo "=== DNS Auto-Sync Functionality Test ==="
|
||||||
|
echo "Using DNS server localhost:${HOST_DNS_PORT}"
|
||||||
|
|
||||||
# Check if container is running
|
# Check if container is running
|
||||||
if ! docker compose ps | grep -q "Up"; then
|
if ! docker compose ps | grep -q "Up"; then
|
||||||
@@ -36,7 +39,7 @@ test_dns_query() {
|
|||||||
# Wait a moment for DNS cache
|
# Wait a moment for DNS cache
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
result=$(dig @localhost $hostname.argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
||||||
|
|
||||||
if [ "$result" = "$expected_ip" ]; then
|
if [ "$result" = "$expected_ip" ]; then
|
||||||
echo "✓ $result"
|
echo "✓ $result"
|
||||||
@@ -90,7 +93,7 @@ echo ""
|
|||||||
echo "Step 2: Testing initial DNS configuration..."
|
echo "Step 2: Testing initial DNS configuration..."
|
||||||
|
|
||||||
# Get current IP for web.argus.com (may have been changed by previous tests)
|
# Get current IP for web.argus.com (may have been changed by previous tests)
|
||||||
current_web_ip=$(dig @localhost web.argus.com A +short 2>/dev/null || echo "UNKNOWN")
|
current_web_ip=$(dig @localhost -p "$HOST_DNS_PORT" web.argus.com A +short 2>/dev/null || echo "UNKNOWN")
|
||||||
echo "Current web.argus.com IP: $current_web_ip"
|
echo "Current web.argus.com IP: $current_web_ip"
|
||||||
|
|
||||||
# Test that DNS is working (regardless of specific IP)
|
# Test that DNS is working (regardless of specific IP)
|
||||||
@@ -185,7 +188,7 @@ docker compose exec bind9 bash -c 'echo "this is not an IP address" > /private/a
|
|||||||
wait_for_sync
|
wait_for_sync
|
||||||
|
|
||||||
# Verify invalid record was not added (should fail to resolve)
|
# Verify invalid record was not added (should fail to resolve)
|
||||||
result=$(dig @localhost invalid.argus.com A +short 2>/dev/null || echo "NO_RESULT")
|
result=$(dig @localhost -p "$HOST_DNS_PORT" invalid.argus.com A +short 2>/dev/null || echo "NO_RESULT")
|
||||||
if [ "$result" = "NO_RESULT" ] || [ -z "$result" ]; then
|
if [ "$result" = "NO_RESULT" ] || [ -z "$result" ]; then
|
||||||
echo "✓ Invalid IP correctly ignored"
|
echo "✓ Invalid IP correctly ignored"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
HOST_DNS_PORT="${HOST_DNS_PORT:-1053}"
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
echo "=== DNS Configuration Reload Test ==="
|
echo "=== DNS Configuration Reload Test ==="
|
||||||
|
echo "Using DNS server localhost:${HOST_DNS_PORT}"
|
||||||
|
|
||||||
# Check if container is running
|
# Check if container is running
|
||||||
if ! docker compose ps | grep -q "Up"; then
|
if ! docker compose ps | grep -q "Up"; then
|
||||||
@@ -32,7 +35,7 @@ test_dns_query() {
|
|||||||
echo "Testing: $description"
|
echo "Testing: $description"
|
||||||
echo "Query: $hostname.argus.com -> Expected: $expected_ip"
|
echo "Query: $hostname.argus.com -> Expected: $expected_ip"
|
||||||
|
|
||||||
result=$(dig @localhost $hostname.argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
||||||
|
|
||||||
if [ "$result" = "$expected_ip" ]; then
|
if [ "$result" = "$expected_ip" ]; then
|
||||||
echo "✓ $result"
|
echo "✓ $result"
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
HOST_DNS_PORT="${HOST_DNS_PORT:-1053}"
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
echo "=== Configuration Persistence Test ==="
|
echo "=== Configuration Persistence Test ==="
|
||||||
|
echo "Using DNS server localhost:${HOST_DNS_PORT}"
|
||||||
|
|
||||||
# Check if dig is available
|
# Check if dig is available
|
||||||
if ! command -v dig &> /dev/null; then
|
if ! command -v dig &> /dev/null; then
|
||||||
@@ -25,7 +28,7 @@ test_dns_query() {
|
|||||||
echo "Testing: $description"
|
echo "Testing: $description"
|
||||||
echo "Query: $hostname.argus.com -> Expected: $expected_ip"
|
echo "Query: $hostname.argus.com -> Expected: $expected_ip"
|
||||||
|
|
||||||
result=$(dig @localhost $hostname.argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
||||||
|
|
||||||
if [ "$result" = "$expected_ip" ]; then
|
if [ "$result" = "$expected_ip" ]; then
|
||||||
echo "✓ $result"
|
echo "✓ $result"
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ set -e
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
TEST_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
HOST_DNS_PORT="${HOST_DNS_PORT:-1053}"
|
||||||
|
|
||||||
|
export HOST_DNS_PORT
|
||||||
|
|
||||||
# Parse command line arguments
|
# Parse command line arguments
|
||||||
FULL_CLEANUP=true
|
FULL_CLEANUP=true
|
||||||
|
|||||||
@@ -3,10 +3,29 @@ FROM docker.elastic.co/elasticsearch/elasticsearch:8.13.4
|
|||||||
# 切换到 root 用户进行系统级安装
|
# 切换到 root 用户进行系统级安装
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
# 修改elasticsearch用户的UID和GID
|
ARG ARGUS_BUILD_UID=2133
|
||||||
RUN usermod -u 2133 elasticsearch && \
|
ARG ARGUS_BUILD_GID=2015
|
||||||
groupmod -g 2015 elasticsearch && \
|
|
||||||
chown -R elasticsearch:elasticsearch /usr/share/elasticsearch
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
|
# 调整 elasticsearch 用户与用户组 ID 以匹配宿主机配置
|
||||||
|
RUN set -eux; \
|
||||||
|
current_gid="$(getent group elasticsearch | awk -F: '{print $3}')"; \
|
||||||
|
if [ -z "$current_gid" ]; then \
|
||||||
|
groupadd -g "${ARGUS_BUILD_GID}" elasticsearch; \
|
||||||
|
elif [ "$current_gid" != "${ARGUS_BUILD_GID}" ]; then \
|
||||||
|
groupmod -g "${ARGUS_BUILD_GID}" elasticsearch; \
|
||||||
|
fi; \
|
||||||
|
if id elasticsearch >/dev/null 2>&1; then \
|
||||||
|
current_uid="$(id -u elasticsearch)"; \
|
||||||
|
if [ "$current_uid" != "${ARGUS_BUILD_UID}" ]; then \
|
||||||
|
usermod -u "${ARGUS_BUILD_UID}" elasticsearch; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
useradd -m -u "${ARGUS_BUILD_UID}" -g "${ARGUS_BUILD_GID}" elasticsearch; \
|
||||||
|
fi; \
|
||||||
|
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" /usr/share/elasticsearch
|
||||||
|
|
||||||
# 设置构建参数
|
# 设置构建参数
|
||||||
ARG USE_INTRANET=false
|
ARG USE_INTRANET=false
|
||||||
|
|||||||
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>&-
|
||||||
|
echo "[INFO] Elasticsearch is ready"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
[[ $i -eq 120 ]] && { echo "[ERROR] ES not reachable" >&2; exit 1; }
|
||||||
|
sleep 1
|
||||||
done
|
done
|
||||||
echo "[INFO] Elasticsearch is ready"
|
|
||||||
|
|
||||||
# 启动 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
|
|
||||||
|
|||||||
@@ -3,10 +3,29 @@ FROM docker.elastic.co/kibana/kibana:8.13.4
|
|||||||
# 切换到 root 用户进行系统级安装
|
# 切换到 root 用户进行系统级安装
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
# 修改kibana用户的UID和GID
|
ARG ARGUS_BUILD_UID=2133
|
||||||
RUN usermod -u 2133 kibana && \
|
ARG ARGUS_BUILD_GID=2015
|
||||||
groupmod -g 2015 kibana && \
|
|
||||||
chown -R kibana:kibana /usr/share/kibana
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
|
# 调整 kibana 用户与用户组 ID 以匹配宿主机配置
|
||||||
|
RUN set -eux; \
|
||||||
|
current_gid="$(getent group kibana | awk -F: '{print $3}')"; \
|
||||||
|
if [ -z "$current_gid" ]; then \
|
||||||
|
groupadd -g "${ARGUS_BUILD_GID}" kibana; \
|
||||||
|
elif [ "$current_gid" != "${ARGUS_BUILD_GID}" ]; then \
|
||||||
|
groupmod -g "${ARGUS_BUILD_GID}" kibana; \
|
||||||
|
fi; \
|
||||||
|
if id kibana >/dev/null 2>&1; then \
|
||||||
|
current_uid="$(id -u kibana)"; \
|
||||||
|
if [ "$current_uid" != "${ARGUS_BUILD_UID}" ]; then \
|
||||||
|
usermod -u "${ARGUS_BUILD_UID}" kibana; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
useradd -m -u "${ARGUS_BUILD_UID}" -g "${ARGUS_BUILD_GID}" kibana; \
|
||||||
|
fi; \
|
||||||
|
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" /usr/share/kibana
|
||||||
|
|
||||||
# 设置构建参数
|
# 设置构建参数
|
||||||
ARG USE_INTRANET=false
|
ARG USE_INTRANET=false
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 主逻辑
|
# 主逻辑
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 30
|
retries: 30
|
||||||
|
restart: always
|
||||||
|
|
||||||
kibana:
|
kibana:
|
||||||
build:
|
build:
|
||||||
@@ -73,13 +74,11 @@ services:
|
|||||||
interval: 15s
|
interval: 15s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 30
|
retries: 30
|
||||||
|
restart: always
|
||||||
|
|
||||||
bind9:
|
bind9:
|
||||||
image: argus-bind9:latest
|
image: argus-bind9:latest
|
||||||
ports:
|
|
||||||
- "53:53/tcp"
|
|
||||||
- "53:53/udp"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./private/argus:/private/argus/
|
- ./private/argus:/private/argus/
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)"
|
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目录结构)
|
# 创建新的private目录结构 (基于argus目录结构)
|
||||||
echo "[INFO] Creating private directory structure for supervisor-based containers..."
|
echo "[INFO] Creating private directory structure for supervisor-based containers..."
|
||||||
@@ -11,9 +15,9 @@ mkdir -p "$root/private/argus/etc/"
|
|||||||
|
|
||||||
# 设置数据目录权限(ES 和 Kibana 容器都使用 UID 1000)
|
# 设置数据目录权限(ES 和 Kibana 容器都使用 UID 1000)
|
||||||
echo "[INFO] Setting permissions for data directories..."
|
echo "[INFO] Setting permissions for data directories..."
|
||||||
sudo chown -R 2133:2015 "$root/private/argus/log/elasticsearch" 2>/dev/null || true
|
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/log/elasticsearch" 2>/dev/null || true
|
||||||
sudo chown -R 2133:2015 "$root/private/argus/log/kibana" 2>/dev/null || true
|
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/log/kibana" 2>/dev/null || true
|
||||||
sudo chown -R 2133:2015 "$root/private/argus/etc" 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"
|
echo "[INFO] Supervisor-based containers will manage their own scripts and configurations"
|
||||||
|
|
||||||
@@ -28,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
|
||||||
|
|||||||
@@ -4,8 +4,22 @@ set -euo pipefail
|
|||||||
# 获取fluent-bit-host01容器名称
|
# 获取fluent-bit-host01容器名称
|
||||||
container_name="logging-mvp-fluent-bit-host01-1"
|
container_name="logging-mvp-fluent-bit-host01-1"
|
||||||
|
|
||||||
# 检查容器是否存在并运行
|
wait_for_container() {
|
||||||
if ! docker ps | grep -q "$container_name"; then
|
local name="$1"
|
||||||
|
local attempts=30
|
||||||
|
local delay=5
|
||||||
|
local i
|
||||||
|
for ((i = 1; i <= attempts; i++)); do
|
||||||
|
if docker ps --format '{{.Names}}' | grep -Fx "$name" >/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "[INFO] 等待容器 $name 启动中... ($i/$attempts)"
|
||||||
|
sleep "$delay"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! wait_for_container "$container_name"; then
|
||||||
echo "[ERROR] Fluent Bit容器 $container_name 未运行"
|
echo "[ERROR] Fluent Bit容器 $container_name 未运行"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -4,8 +4,22 @@ set -euo pipefail
|
|||||||
# 获取fluent-bit-host02容器名称
|
# 获取fluent-bit-host02容器名称
|
||||||
container_name="logging-mvp-fluent-bit-host02-1"
|
container_name="logging-mvp-fluent-bit-host02-1"
|
||||||
|
|
||||||
# 检查容器是否存在并运行
|
wait_for_container() {
|
||||||
if ! docker ps | grep -q "$container_name"; then
|
local name="$1"
|
||||||
|
local attempts=30
|
||||||
|
local delay=5
|
||||||
|
local i
|
||||||
|
for ((i = 1; i <= attempts; i++)); do
|
||||||
|
if docker ps --format '{{.Names}}' | grep -Fx "$name" >/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "[INFO] 等待容器 $name 启动中... ($i/$attempts)"
|
||||||
|
sleep "$delay"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! wait_for_container "$container_name"; then
|
||||||
echo "[ERROR] Fluent Bit容器 $container_name 未运行"
|
echo "[ERROR] Fluent Bit容器 $container_name 未运行"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,7 +1,42 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ES endpoint and wait strategy
|
||||||
ES="${ES:-http://localhost:9200}"
|
ES="${ES:-http://localhost:9200}"
|
||||||
|
es_wait_attempts="${ES_WAIT_ATTEMPTS:-60}" # total attempts to wait for ES
|
||||||
|
es_wait_interval="${ES_WAIT_INTERVAL:-2}" # seconds between attempts
|
||||||
|
|
||||||
echo "[i] 查询 ES 端点:$ES"
|
echo "[i] 查询 ES 端点:$ES"
|
||||||
|
|
||||||
|
wait_for_es() {
|
||||||
|
local attempt=1
|
||||||
|
while (( attempt <= es_wait_attempts )); do
|
||||||
|
# 等待集群达到至少 yellow 状态;请求失败则重试
|
||||||
|
if curl -fsS "$ES/_cluster/health?wait_for_status=yellow&timeout=1s" >/dev/null 2>&1; then
|
||||||
|
echo "[ok] Elasticsearch 已就绪 (attempt=${attempt}/${es_wait_attempts})"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "[..] 等待 Elasticsearch 可用中 (${attempt}/${es_wait_attempts})"
|
||||||
|
sleep "${es_wait_interval}"
|
||||||
|
(( attempt++ ))
|
||||||
|
done
|
||||||
|
echo "[err] Elasticsearch 在 ${es_wait_attempts} 次尝试后仍不可用"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_count() {
|
||||||
|
# 对缺失索引返回 0,避免 404 触发失败
|
||||||
|
local pattern="$1"
|
||||||
|
local json
|
||||||
|
json=$(curl -fsS "$ES/${pattern}/_count?ignore_unavailable=true&allow_no_indices=true" 2>/dev/null || echo '{}')
|
||||||
|
echo "$json" | sed -E 's/.*"count":([0-9]+).*/\1/' | awk 'NF{print $0;exit} END{if(NR==0)print 0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_es
|
||||||
|
|
||||||
|
# 列出相关索引(可能为空,允许)
|
||||||
curl -fsS "$ES/_cat/indices?v" | egrep 'train-|infer-|logstash' || true
|
curl -fsS "$ES/_cat/indices?v" | egrep 'train-|infer-|logstash' || true
|
||||||
printf "train-* 计数:"; curl -fsS "$ES/train-*/_count" | sed -E 's/.*"count":([0-9]+).*/\1/'; echo
|
|
||||||
printf "infer-* 计数:"; curl -fsS "$ES/infer-*/_count" | sed -E 's/.*"count":([0-9]+).*/\1/'; echo
|
# 打印计数,缺失索引按 0 处理
|
||||||
|
printf "train-* 计数:"; safe_count "train-*"; echo
|
||||||
|
printf "infer-* 计数:"; safe_count "infer-*"; echo
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ get_log_count() {
|
|||||||
# 函数:等待服务就绪
|
# 函数:等待服务就绪
|
||||||
wait_for_services() {
|
wait_for_services() {
|
||||||
echo "[INFO] Waiting for all services to be ready..."
|
echo "[INFO] Waiting for all services to be ready..."
|
||||||
local max_attempts=60
|
local max_attempts=${SERVICE_WAIT_ATTEMPTS:-120}
|
||||||
local attempt=1
|
local attempt=1
|
||||||
|
|
||||||
while [ $attempt -le $max_attempts ]; do
|
while [ $attempt -le $max_attempts ]; do
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ SHELL ["/bin/bash", "-c"]
|
|||||||
ARG PIP_INDEX_URL=
|
ARG PIP_INDEX_URL=
|
||||||
ARG USE_OFFLINE=0
|
ARG USE_OFFLINE=0
|
||||||
ARG USE_INTRANET=false
|
ARG USE_INTRANET=false
|
||||||
|
ARG ARGUS_BUILD_UID=2133
|
||||||
|
ARG ARGUS_BUILD_GID=2015
|
||||||
|
|
||||||
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
ENV PIP_NO_CACHE_DIR=1 \
|
ENV PIP_NO_CACHE_DIR=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
@@ -36,11 +41,12 @@ RUN if [[ "$USE_INTRANET" == "true" ]]; then \
|
|||||||
if [[ -f /etc/apt/sources.list ]]; then cp /etc/apt/sources.list /etc/apt/sources.list.bak; fi && \
|
if [[ -f /etc/apt/sources.list ]]; then cp /etc/apt/sources.list /etc/apt/sources.list.bak; fi && \
|
||||||
mkdir -p /etc/apt && \
|
mkdir -p /etc/apt && \
|
||||||
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
||||||
|
rm -rf /etc/apt/sources.list.d && \
|
||||||
echo 'Acquire::https::Verify-Peer "false";' > /etc/apt/apt.conf.d/99disable-ssl-check && \
|
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; \
|
echo 'Acquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
||||||
fi && \
|
fi && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y supervisor net-tools inetutils-ping vim && \
|
apt-get install -y supervisor net-tools inetutils-ping && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@@ -51,6 +57,18 @@ RUN if [[ "$USE_INTRANET" == "true" ]]; then \
|
|||||||
|
|
||||||
RUN mkdir -p /var/log/supervisor
|
RUN mkdir -p /var/log/supervisor
|
||||||
|
|
||||||
|
RUN set -eux; \
|
||||||
|
if getent group argus >/dev/null; then \
|
||||||
|
groupmod -g "${ARGUS_BUILD_GID}" argus; \
|
||||||
|
else \
|
||||||
|
groupadd -g "${ARGUS_BUILD_GID}" argus; \
|
||||||
|
fi; \
|
||||||
|
if id argus >/dev/null 2>&1; then \
|
||||||
|
usermod -u "${ARGUS_BUILD_UID}" -g "${ARGUS_BUILD_GID}" argus; \
|
||||||
|
else \
|
||||||
|
useradd -m -u "${ARGUS_BUILD_UID}" -g "${ARGUS_BUILD_GID}" -s /bin/bash argus; \
|
||||||
|
fi
|
||||||
|
|
||||||
COPY ./src/master/build/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
COPY ./src/master/build/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
COPY ./src/master/build/start-master.sh /usr/local/bin/start-master.sh
|
COPY ./src/master/build/start-master.sh /usr/local/bin/start-master.sh
|
||||||
COPY ./src/master/build/dns-monitor.sh /usr/local/bin/dns-monitor.sh
|
COPY ./src/master/build/dns-monitor.sh /usr/local/bin/dns-monitor.sh
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Argus Master 是基于 Flask + SQLite 的节点管理服务,负责:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src/master
|
cd src/master
|
||||||
./scripts/build_images.sh # 生成 argus-master:dev 镜像
|
./scripts/build_images.sh # 生成 argus-master:latest 镜像
|
||||||
```
|
```
|
||||||
|
|
||||||
如需离线构建,先在有网环境运行准备脚本:
|
如需离线构建,先在有网环境运行准备脚本:
|
||||||
@@ -25,7 +25,7 @@ cd src/master
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src/master
|
cd src/master
|
||||||
./scripts/build_images.sh --offline --tag argus-master:dev
|
./scripts/build_images.sh --offline --tag argus-master:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
若内网缺少 `python:3.11-slim`,请提前在外网 `docker save` 后通过离线介质 `docker load`。
|
若内网缺少 `python:3.11-slim`,请提前在外网 `docker save` 后通过离线介质 `docker load`。
|
||||||
|
|||||||
@@ -5,8 +5,17 @@ set -euo pipefail
|
|||||||
DNS_DIR="/private/argus/etc"
|
DNS_DIR="/private/argus/etc"
|
||||||
DNS_SCRIPT="${DNS_DIR}/update-dns.sh"
|
DNS_SCRIPT="${DNS_DIR}/update-dns.sh"
|
||||||
MASTER_DOMAIN_FILE="${DNS_DIR}/master.argus.com"
|
MASTER_DOMAIN_FILE="${DNS_DIR}/master.argus.com"
|
||||||
|
RUNTIME_USER="${ARGUS_RUNTIME_USER:-argus}"
|
||||||
|
RUNTIME_UID="${ARGUS_BUILD_UID:-2133}"
|
||||||
|
RUNTIME_GID="${ARGUS_BUILD_GID:-2015}"
|
||||||
|
MASTER_DATA_DIR="/private/argus/master"
|
||||||
|
METRIC_DIR="/private/argus/metric/prometheus"
|
||||||
|
|
||||||
mkdir -p "$DNS_DIR"
|
mkdir -p "$DNS_DIR"
|
||||||
|
chown -R "$RUNTIME_UID:$RUNTIME_GID" "$DNS_DIR" 2>/dev/null || true
|
||||||
|
mkdir -p "$MASTER_DATA_DIR"
|
||||||
|
mkdir -p "$METRIC_DIR"
|
||||||
|
chown -R "$RUNTIME_UID:$RUNTIME_GID" "$MASTER_DATA_DIR" "$METRIC_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
if [[ -x "$DNS_SCRIPT" ]]; then
|
if [[ -x "$DNS_SCRIPT" ]]; then
|
||||||
echo "[INFO] Running update-dns.sh before master starts"
|
echo "[INFO] Running update-dns.sh before master starts"
|
||||||
@@ -21,6 +30,7 @@ MASTER_IP=$(ifconfig | grep -A 1 eth0 | grep inet | awk '{print $2}' || true)
|
|||||||
if [[ -n "${MASTER_IP}" ]]; then
|
if [[ -n "${MASTER_IP}" ]]; then
|
||||||
echo "current IP: ${MASTER_IP}"
|
echo "current IP: ${MASTER_IP}"
|
||||||
echo "${MASTER_IP}" > "$MASTER_DOMAIN_FILE"
|
echo "${MASTER_IP}" > "$MASTER_DOMAIN_FILE"
|
||||||
|
chown "$RUNTIME_UID:$RUNTIME_GID" "$MASTER_DOMAIN_FILE" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
echo "[WARN] Failed to detect master IP via ifconfig"
|
echo "[WARN] Failed to detect master IP via ifconfig"
|
||||||
fi
|
fi
|
||||||
@@ -35,4 +45,15 @@ else
|
|||||||
EXTRA_ARRAY=()
|
EXTRA_ARRAY=()
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec gunicorn --bind "$BIND_ADDR" --workers "$WORKERS" "${EXTRA_ARRAY[@]}" "app:create_app()"
|
command=(gunicorn --bind "$BIND_ADDR" --workers "$WORKERS")
|
||||||
|
if [[ ${#EXTRA_ARRAY[@]} -gt 0 ]]; then
|
||||||
|
command+=("${EXTRA_ARRAY[@]}")
|
||||||
|
fi
|
||||||
|
command+=("app:create_app()")
|
||||||
|
|
||||||
|
if command -v runuser >/dev/null 2>&1; then
|
||||||
|
exec runuser -u "$RUNTIME_USER" -- "${command[@]}"
|
||||||
|
else
|
||||||
|
printf -v _cmd_str '%q ' "${command[@]}"
|
||||||
|
exec su -s /bin/bash -m "$RUNTIME_USER" -c "exec ${_cmd_str}"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -3,22 +3,28 @@ 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:dev。
|
--tag <image_tag> 自定义镜像标签,默认 argus-master:latest。
|
||||||
|
--no-cache 不使用 Docker 构建缓存。
|
||||||
USAGE
|
USAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||||
MODULE_ROOT="$PROJECT_ROOT/src/master"
|
MODULE_ROOT="$PROJECT_ROOT/src/master"
|
||||||
IMAGE_TAG="${IMAGE_TAG:-argus-master:dev}"
|
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"
|
||||||
|
load_build_user
|
||||||
|
BUILD_ARGS+=("--build-arg" "ARGUS_BUILD_UID=${ARGUS_BUILD_UID}" "--build-arg" "ARGUS_BUILD_GID=${ARGUS_BUILD_GID}")
|
||||||
|
|
||||||
cd "$PROJECT_ROOT"
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
@@ -41,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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ usage() {
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
DEFAULT_OUTPUT="$PROJECT_ROOT/images/argus-master-dev.tar"
|
DEFAULT_OUTPUT="$PROJECT_ROOT/images/argus-master-dev.tar"
|
||||||
IMAGE_TAG="${IMAGE_TAG:-argus-master:dev}"
|
IMAGE_TAG="${IMAGE_TAG:-argus-master:latest}"
|
||||||
OUTPUT_PATH="$DEFAULT_OUTPUT"
|
OUTPUT_PATH="$DEFAULT_OUTPUT"
|
||||||
|
|
||||||
while [[ "$#" -gt 0 ]]; do
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
master:
|
master:
|
||||||
image: ${MASTER_IMAGE_TAG:-argus-master:dev}
|
image: ${MASTER_IMAGE_TAG:-argus-master:latest}
|
||||||
container_name: argus-master-e2e
|
container_name: argus-master-e2e
|
||||||
environment:
|
environment:
|
||||||
- OFFLINE_THRESHOLD_SECONDS=6
|
- OFFLINE_THRESHOLD_SECONDS=6
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ SCRIPTS=(
|
|||||||
|
|
||||||
for script in "${SCRIPTS[@]}"; do
|
for script in "${SCRIPTS[@]}"; do
|
||||||
echo "[TEST] Running $script"
|
echo "[TEST] Running $script"
|
||||||
MASTER_IMAGE_TAG="${MASTER_IMAGE_TAG:-argus-master:dev}" "$SCRIPT_DIR/$script"
|
MASTER_IMAGE_TAG="${MASTER_IMAGE_TAG:-argus-master:latest}" "$SCRIPT_DIR/$script"
|
||||||
echo "[TEST] $script completed"
|
echo "[TEST] $script completed"
|
||||||
echo
|
echo
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ fi
|
|||||||
|
|
||||||
pushd "$TEST_ROOT" >/dev/null
|
pushd "$TEST_ROOT" >/dev/null
|
||||||
compose down --remove-orphans || true
|
compose down --remove-orphans || true
|
||||||
MASTER_IMAGE_TAG="${MASTER_IMAGE_TAG:-argus-master:dev}" compose up -d
|
MASTER_IMAGE_TAG="${MASTER_IMAGE_TAG:-argus-master:latest}" compose up -d
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
echo "[INFO] Master container is up on http://localhost:31300"
|
echo "[INFO] Master container is up on http://localhost:31300"
|
||||||
|
|||||||
7
src/metric/.gitignore
vendored
Normal file
7
src/metric/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/prometheus/data/
|
||||||
|
/client-plugins/dcgm-exporter-installer/
|
||||||
|
/client-plugins/demo-all-in-one/artifact/
|
||||||
|
/client-plugins/demo-all-in-one/publish/
|
||||||
|
/client-plugins/demo-all-in-one/checklist
|
||||||
|
/client-plugins/demo-all-in-one/VERSION
|
||||||
|
/client-plugins/all-in-one-full/artifact/
|
||||||
65
src/metric/client-plugins/all-in-one-demo/README.md
Normal file
65
src/metric/client-plugins/all-in-one-demo/README.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# 客户侧组件安装包构建、发布流程
|
||||||
|
|
||||||
|
## 第一步:配置版本和组件
|
||||||
|
|
||||||
|
首先搞定配置文件:
|
||||||
|
|
||||||
|
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 'ftp://{$USER}:{$PASSWD}@{$your-ftp-server}/setup.sh' -o setup.sh
|
||||||
|
|
||||||
|
# root用户直接执行,非root用户需要使用sudo
|
||||||
|
chmod +x setup.sh
|
||||||
|
bash setup.sh --server {$your-ftp-server} --user {$USER} --password {$PASSWD}
|
||||||
|
|
||||||
|
示例:
|
||||||
|
curl -fsS 'ftp://ftpuser:ZGClab1234!@177.177.70.200/setup.sh' -o setup.sh
|
||||||
|
chmod +x setup.sh
|
||||||
|
bash setup.sh --server {$域名} --user ftpuser --password 'ZGClab1234!'
|
||||||
|
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Argus Metric 配置文件示例
|
||||||
|
# 复制此文件为 config.env 并根据需要修改配置
|
||||||
|
|
||||||
|
# 连接master服务
|
||||||
|
MASTER_ENDPOINT=master.argus.com:3000
|
||||||
|
|
||||||
|
# 上报状态间隔描述(秒)
|
||||||
|
REPORT_INTERVAL_SECONDS=60
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Elasticsearch
|
||||||
|
ES_HOST=es.log.argus.com
|
||||||
|
ES_PORT=9200
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
177.177.17.106
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Node Exporter 健康检查脚本
|
||||||
|
# 输出 JSON 格式结果
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 检查 Node Exporter 健康状态
|
||||||
|
check_health() {
|
||||||
|
local url="http://localhost:9100"
|
||||||
|
local metrics_url="$url/metrics"
|
||||||
|
local name="node-exporter"
|
||||||
|
local status="unhealth"
|
||||||
|
local reason=""
|
||||||
|
|
||||||
|
# 检查 curl 是否可用
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
reason="curl 命令不可用,无法进行健康检查"
|
||||||
|
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 测试根路径连接
|
||||||
|
local http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$http_code" == "200" ]]; then
|
||||||
|
# 测试 metrics 端点
|
||||||
|
local metrics_code=$(curl -s -o /dev/null -w "%{http_code}" "$metrics_url" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$metrics_code" == "200" ]]; then
|
||||||
|
status="health"
|
||||||
|
reason="success"
|
||||||
|
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
reason="Metrics 端点异常 (HTTP $metrics_code)"
|
||||||
|
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
reason="HTTP 服务异常 (HTTP $http_code),请检查 Node Exporter 是否正在运行在端口 9100"
|
||||||
|
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
check_health
|
||||||
|
}
|
||||||
|
|
||||||
|
# 脚本入口
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
343
src/metric/client-plugins/all-in-one-demo/plugins/node-exporter/install.sh
Executable file
343
src/metric/client-plugins/all-in-one-demo/plugins/node-exporter/install.sh
Executable file
@@ -0,0 +1,343 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 日志函数
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 更新安装记录
|
||||||
|
update_install_record() {
|
||||||
|
local pid="$1"
|
||||||
|
# 使用传入的安装目录参数,如果没有则使用默认值
|
||||||
|
local install_base_dir="${2:-/opt/argus-metric/current}"
|
||||||
|
local install_record="$install_base_dir/.install_record"
|
||||||
|
|
||||||
|
# 如果安装记录文件不存在,说明是首次安装,由主安装脚本统一创建
|
||||||
|
if [[ ! -f "$install_record" ]]; then
|
||||||
|
log_info "安装记录文件不存在,将由主安装脚本创建"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 如果文件存在,说明是重启场景,只更新 PID 字段
|
||||||
|
if command -v jq &> /dev/null; then
|
||||||
|
# 读取当前 PID
|
||||||
|
local current_pid=$(jq -r '.components."node-exporter".pid // ""' "$install_record" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -z "$current_pid" ]]; then
|
||||||
|
log_warning "无法读取当前 PID,跳过更新"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 使用 jq 只更新 pid 字段,保持字符串类型,保留其他字段
|
||||||
|
jq --arg new_pid "$pid" '.components."node-exporter".pid = $new_pid' "$install_record" > "$install_record.tmp" && mv "$install_record.tmp" "$install_record"
|
||||||
|
log_info "PID 已更新: $current_pid -> $pid"
|
||||||
|
else
|
||||||
|
log_warning "jq 命令不可用,无法更新安装记录文件"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示帮助信息
|
||||||
|
show_help() {
|
||||||
|
echo "Node Exporter 安装脚本"
|
||||||
|
echo
|
||||||
|
echo "用法: $0 [选项]"
|
||||||
|
echo
|
||||||
|
echo "选项:"
|
||||||
|
echo " --help 显示此帮助信息"
|
||||||
|
echo
|
||||||
|
echo "示例:"
|
||||||
|
echo " $0 # 安装 Node Exporter"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
INSTALL_DIR=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--help|-h)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# 如果参数不是以--开头,则认为是安装目录
|
||||||
|
if [[ ! "$arg" =~ ^-- ]]; then
|
||||||
|
INSTALL_DIR="$arg"
|
||||||
|
else
|
||||||
|
log_error "未知参数: $arg"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# 检查是否为 root 用户
|
||||||
|
check_root() {
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "此脚本需要 root 权限运行"
|
||||||
|
log_info "请使用: sudo $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查系统要求
|
||||||
|
check_system() {
|
||||||
|
log_info "检查系统要求..."
|
||||||
|
|
||||||
|
# 检查操作系统
|
||||||
|
if [[ ! -f /etc/os-release ]]; then
|
||||||
|
log_error "无法检测操作系统版本"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source /etc/os-release
|
||||||
|
log_info "检测到操作系统: $NAME $VERSION"
|
||||||
|
|
||||||
|
# 检查是否为 Linux 系统
|
||||||
|
if [[ "$ID" != "ubuntu" && "$ID" != "debian" && "$ID" != "centos" && "$ID" != "rhel" && "$ID" != "fedora" ]]; then
|
||||||
|
log_warning "此脚本主要针对常见 Linux 发行版,其他系统可能需要调整"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查系统架构
|
||||||
|
local arch=$(uname -m)
|
||||||
|
log_info "系统架构: $arch"
|
||||||
|
|
||||||
|
if [[ "$arch" != "x86_64" && "$arch" != "amd64" ]]; then
|
||||||
|
log_warning "当前架构为 $arch,node_exporter 主要支持 x86_64/amd64"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_existing_service() {
|
||||||
|
log_info "检查并停止可能运行的 Node Exporter 服务..."
|
||||||
|
|
||||||
|
# 当前脚本 PID,防止误杀
|
||||||
|
SELF_PID=$$
|
||||||
|
|
||||||
|
# 1. 停止 systemd 服务(如果存在)
|
||||||
|
if systemctl list-units --full -all | grep -q "node_exporter.service"; then
|
||||||
|
log_info "检测到 systemd 服务 node_exporter,正在停止..."
|
||||||
|
systemctl stop node_exporter || true
|
||||||
|
systemctl disable node_exporter || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. 清理可能存在的 PID 文件
|
||||||
|
for pid_file in /var/run/node-exporter.pid /var/run/node_exporter.pid /tmp/node_exporter.pid; do
|
||||||
|
if [[ -f "$pid_file" ]]; then
|
||||||
|
local pid=$(cat "$pid_file")
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
log_info "发现 Node Exporter (PID: $pid),正在停止..."
|
||||||
|
kill "$pid"
|
||||||
|
sleep 2
|
||||||
|
kill -0 "$pid" 2>/dev/null && kill -9 "$pid"
|
||||||
|
fi
|
||||||
|
rm -f "$pid_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. 用 pgrep 查找进程,排除当前脚本
|
||||||
|
local pids=$(pgrep -f "node_exporter|node-exporter|/usr/local/bin/node-exporter" | grep -vw "$SELF_PID" || true)
|
||||||
|
if [[ -n "$pids" ]]; then
|
||||||
|
log_info "发现 Node Exporter 进程 (PID: $pids),正在停止..."
|
||||||
|
for pid in $pids; do
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. 兜底:检查是否有进程占用 9100 端口
|
||||||
|
local listen_pids=$(lsof -ti:9100 2>/dev/null || true)
|
||||||
|
if [[ -n "$listen_pids" ]]; then
|
||||||
|
log_warning "发现占用 9100 端口的进程 (PID: $listen_pids),强制终止..."
|
||||||
|
for pid in $listen_pids; do
|
||||||
|
kill -9 "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. 最终验证
|
||||||
|
if netstat -tuln 2>/dev/null | grep -q ":9100 "; then
|
||||||
|
log_error "端口 9100 仍被占用,请手动检查"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
log_success "旧的 Node Exporter 已完全停止"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 安装 Node Exporter 二进制文件
|
||||||
|
install_node_exporter() {
|
||||||
|
log_info "安装 Node Exporter..."
|
||||||
|
|
||||||
|
local binary_file="bin/node_exporter"
|
||||||
|
local install_dir="/usr/local/bin"
|
||||||
|
|
||||||
|
if [[ ! -f "$binary_file" ]]; then
|
||||||
|
log_error "找不到 Node Exporter 二进制文件: $binary_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 停止可能运行的服务
|
||||||
|
stop_existing_service
|
||||||
|
|
||||||
|
# 复制二进制文件并重命名为统一格式
|
||||||
|
cp "$binary_file" "$install_dir/node-exporter"
|
||||||
|
chmod +x "$install_dir/node-exporter"
|
||||||
|
|
||||||
|
log_success "Node Exporter 二进制文件安装完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建用户和组
|
||||||
|
create_user() {
|
||||||
|
log_info "创建 node_exporter 用户..."
|
||||||
|
|
||||||
|
# 检查用户是否已存在
|
||||||
|
if id "node_exporter" &>/dev/null; then
|
||||||
|
log_info "用户 node_exporter 已存在"
|
||||||
|
else
|
||||||
|
useradd --no-create-home --shell /bin/false node_exporter
|
||||||
|
log_success "用户 node_exporter 创建完成"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 安装配置文件
|
||||||
|
install_config() {
|
||||||
|
log_info "安装配置文件..."
|
||||||
|
|
||||||
|
local config_dir="/etc/node_exporter"
|
||||||
|
|
||||||
|
# 创建配置目录
|
||||||
|
mkdir -p "$config_dir"
|
||||||
|
|
||||||
|
# 创建文本文件收集器目录
|
||||||
|
mkdir -p "/var/lib/node_exporter/textfile_collector"
|
||||||
|
chown node_exporter:node_exporter "/var/lib/node_exporter/textfile_collector"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 启动 Node Exporter 服务
|
||||||
|
start_node_exporter() {
|
||||||
|
log_info "启动 Node Exporter 服务..."
|
||||||
|
|
||||||
|
local binary_path="/usr/local/bin/node-exporter"
|
||||||
|
local log_file="/var/log/node-exporter.log"
|
||||||
|
local pid_file="/var/run/node-exporter.pid"
|
||||||
|
|
||||||
|
# 检查服务是否已经在运行
|
||||||
|
if [[ -f "$pid_file" ]]; then
|
||||||
|
local pid=$(cat "$pid_file")
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
log_info "Node Exporter 服务已在运行 (PID: $pid)"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_warning "发现过期的 PID 文件,正在清理..."
|
||||||
|
rm -f "$pid_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查端口是否被占用
|
||||||
|
if netstat -tuln 2>/dev/null | grep -q ":9100 "; then
|
||||||
|
log_warning "端口 9100 已被占用,请检查是否有其他服务在运行"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
log_info "正在启动 Node Exporter..."
|
||||||
|
nohup "$binary_path" --web.listen-address=:9100 > "$log_file" 2>&1 &
|
||||||
|
local pid=$!
|
||||||
|
|
||||||
|
# 保存 PID
|
||||||
|
echo "$pid" > "$pid_file"
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# 检查服务是否成功启动
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
log_success "Node Exporter 服务启动成功 (PID: $pid)"
|
||||||
|
log_info "日志文件: $log_file"
|
||||||
|
log_info "PID 文件: $pid_file"
|
||||||
|
|
||||||
|
# 更新安装记录
|
||||||
|
update_install_record "$pid" "$INSTALL_DIR"
|
||||||
|
else
|
||||||
|
log_error "Node Exporter 服务启动失败"
|
||||||
|
rm -f "$pid_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 显示安装信息
|
||||||
|
show_install_info() {
|
||||||
|
log_success "Node Exporter 安装完成!"
|
||||||
|
echo
|
||||||
|
echo "安装信息:"
|
||||||
|
echo " 二进制文件: /usr/local/bin/node-exporter"
|
||||||
|
echo " 运行用户: node_exporter"
|
||||||
|
echo " 配置目录: /etc/node_exporter/"
|
||||||
|
echo " 默认端口: 9100"
|
||||||
|
echo
|
||||||
|
echo "使用方法:"
|
||||||
|
echo " 手动启动: /usr/local/bin/node-exporter --web.listen-address=:9100"
|
||||||
|
echo " 后台启动: nohup /usr/local/bin/node-exporter --web.listen-address=:9100 &"
|
||||||
|
echo
|
||||||
|
echo "测试连接:"
|
||||||
|
echo " curl http://localhost:9100/metrics"
|
||||||
|
echo " curl http://localhost:9100"
|
||||||
|
echo
|
||||||
|
echo "Prometheus 配置示例:"
|
||||||
|
echo " - job_name: 'node_exporter'"
|
||||||
|
echo " static_configs:"
|
||||||
|
echo " - targets: ['localhost:9100']"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Node Exporter 安装脚本 v1.0"
|
||||||
|
echo "=========================================="
|
||||||
|
echo
|
||||||
|
|
||||||
|
check_root
|
||||||
|
check_system
|
||||||
|
|
||||||
|
log_info "开始安装 Node Exporter..."
|
||||||
|
|
||||||
|
install_node_exporter
|
||||||
|
create_user
|
||||||
|
install_config
|
||||||
|
start_node_exporter
|
||||||
|
|
||||||
|
show_install_info
|
||||||
|
}
|
||||||
|
|
||||||
|
# 脚本入口
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
87
src/metric/client-plugins/all-in-one-demo/plugins/node-exporter/package.sh
Executable file
87
src/metric/client-plugins/all-in-one-demo/plugins/node-exporter/package.sh
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取当前目录
|
||||||
|
CURRENT_DIR=$(pwd)
|
||||||
|
PACKAGE_NAME="node-exporter-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
PACKAGE_FILE="${PACKAGE_NAME}.tar.gz"
|
||||||
|
|
||||||
|
log_info "开始打包 Node Exporter 安装包..."
|
||||||
|
|
||||||
|
# 检查必要文件
|
||||||
|
log_info "检查必要文件..."
|
||||||
|
|
||||||
|
required_files=(
|
||||||
|
"install.sh"
|
||||||
|
"uninstall.sh"
|
||||||
|
"bin/node_exporter"
|
||||||
|
"check_health.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
missing_files=()
|
||||||
|
for file in "${required_files[@]}"; do
|
||||||
|
if [[ ! -f "$file" ]]; then
|
||||||
|
missing_files+=("$file")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#missing_files[@]} -gt 0 ]]; then
|
||||||
|
echo "缺少以下文件:"
|
||||||
|
for file in "${missing_files[@]}"; do
|
||||||
|
echo " - $file"
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "所有必要文件检查完成"
|
||||||
|
|
||||||
|
# 创建临时目录
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
log_info "创建临时目录: $TEMP_DIR"
|
||||||
|
|
||||||
|
# 复制文件到临时目录
|
||||||
|
cp -r . "$TEMP_DIR/$PACKAGE_NAME"
|
||||||
|
|
||||||
|
# 进入临时目录
|
||||||
|
cd "$TEMP_DIR"
|
||||||
|
|
||||||
|
# 创建压缩包
|
||||||
|
log_info "创建压缩包: $PACKAGE_FILE"
|
||||||
|
tar -czf "$PACKAGE_FILE" "$PACKAGE_NAME"
|
||||||
|
|
||||||
|
# 移动压缩包到原目录
|
||||||
|
mv "$PACKAGE_FILE" "$CURRENT_DIR/"
|
||||||
|
|
||||||
|
# 清理临时目录
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
|
||||||
|
# 返回原目录
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
|
|
||||||
|
# 显示结果
|
||||||
|
log_success "打包完成!"
|
||||||
|
echo
|
||||||
|
echo "安装包文件: $PACKAGE_FILE"
|
||||||
|
echo "文件大小: $(du -h "$PACKAGE_FILE" | cut -f1)"
|
||||||
|
echo
|
||||||
|
echo "使用方法:"
|
||||||
|
echo "1. 将 $PACKAGE_FILE 传输到目标服务器"
|
||||||
|
echo "2. 解压: tar -xzf $PACKAGE_FILE"
|
||||||
|
echo "3. 进入目录: cd $PACKAGE_NAME"
|
||||||
|
echo "4. 运行安装: sudo ./install.sh"
|
||||||
|
echo
|
||||||
|
echo "注意: 请确保所有必要文件都存在"
|
||||||
239
src/metric/client-plugins/all-in-one-demo/plugins/node-exporter/uninstall.sh
Executable file
239
src/metric/client-plugins/all-in-one-demo/plugins/node-exporter/uninstall.sh
Executable file
@@ -0,0 +1,239 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Node Exporter 卸载脚本
|
||||||
|
# 版本: 1.0
|
||||||
|
# 作者: AIOps Team
|
||||||
|
# 日期: $(date +%Y-%m-%d)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 日志函数
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查是否为 root 用户
|
||||||
|
check_root() {
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "此脚本需要 root 权限运行"
|
||||||
|
log_info "请使用: sudo $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 停止运行中的进程
|
||||||
|
stop_processes() {
|
||||||
|
log_info "停止 Node Exporter 进程..."
|
||||||
|
|
||||||
|
local pid_file="/var/run/node-exporter.pid"
|
||||||
|
local stopped=false
|
||||||
|
|
||||||
|
# 首先尝试通过 PID 文件停止服务
|
||||||
|
if [[ -f "$pid_file" ]]; then
|
||||||
|
local pid=$(cat "$pid_file")
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
log_info "通过 PID 文件停止服务 (PID: $pid)..."
|
||||||
|
kill "$pid"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# 检查进程是否已停止
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
log_warning "进程未响应,强制终止..."
|
||||||
|
kill -9 "$pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
log_success "Node Exporter 进程已停止"
|
||||||
|
stopped=true
|
||||||
|
else
|
||||||
|
log_warning "PID 文件存在但进程已不存在,清理 PID 文件"
|
||||||
|
rm -f "$pid_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 查找并杀死所有 node_exporter 和 node-exporter 进程
|
||||||
|
local pids=$(pgrep -f "node_exporter\|node-exporter" 2>/dev/null || true)
|
||||||
|
if [[ -n "$pids" ]]; then
|
||||||
|
log_info "发现 node_exporter 或 node-exporter 进程,正在停止..."
|
||||||
|
for pid in $pids; do
|
||||||
|
log_info "停止进程 PID: $pid"
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# 检查是否还有进程在运行,如果有则强制终止
|
||||||
|
local remaining_pids=$(pgrep -f "node_exporter\|node-exporter" 2>/dev/null || true)
|
||||||
|
if [[ -n "$remaining_pids" ]]; then
|
||||||
|
log_warning "进程未响应,强制终止..."
|
||||||
|
for pid in $remaining_pids; do
|
||||||
|
log_info "强制终止进程 PID: $pid"
|
||||||
|
kill -9 "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 最终检查
|
||||||
|
if pgrep -f "node_exporter\|node-exporter" > /dev/null; then
|
||||||
|
log_error "无法停止所有 node_exporter 进程"
|
||||||
|
else
|
||||||
|
log_success "所有 Node Exporter 进程已停止"
|
||||||
|
stopped=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "Node Exporter 进程未运行"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理 PID 文件
|
||||||
|
rm -f "$pid_file"
|
||||||
|
|
||||||
|
if [[ "$stopped" == "false" ]]; then
|
||||||
|
log_warning "未发现需要停止的 Node Exporter 进程"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 删除二进制文件
|
||||||
|
remove_binary() {
|
||||||
|
log_info "删除 Node Exporter 二进制文件..."
|
||||||
|
|
||||||
|
local binary_files=(
|
||||||
|
"/usr/local/bin/node-exporter"
|
||||||
|
"/usr/local/bin/node_exporter"
|
||||||
|
)
|
||||||
|
|
||||||
|
local deleted=false
|
||||||
|
for binary_file in "${binary_files[@]}"; do
|
||||||
|
if [[ -f "$binary_file" ]]; then
|
||||||
|
rm -f "$binary_file"
|
||||||
|
log_success "二进制文件已删除: $binary_file"
|
||||||
|
deleted=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$deleted" == "false" ]]; then
|
||||||
|
log_info "二进制文件不存在"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 删除配置文件
|
||||||
|
remove_config() {
|
||||||
|
log_info "删除配置文件..."
|
||||||
|
|
||||||
|
local config_dir="/etc/node_exporter"
|
||||||
|
|
||||||
|
if [[ -d "$config_dir" ]]; then
|
||||||
|
rm -rf "$config_dir"
|
||||||
|
log_success "配置目录已删除"
|
||||||
|
else
|
||||||
|
log_info "配置目录不存在"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 删除数据目录
|
||||||
|
remove_data_dir() {
|
||||||
|
log_info "删除数据目录..."
|
||||||
|
|
||||||
|
local data_dir="/var/lib/node_exporter"
|
||||||
|
|
||||||
|
if [[ -d "$data_dir" ]]; then
|
||||||
|
rm -rf "$data_dir"
|
||||||
|
log_success "数据目录已删除"
|
||||||
|
else
|
||||||
|
log_info "数据目录不存在"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查用户状态(可选)
|
||||||
|
check_user_status() {
|
||||||
|
log_info "检查 node_exporter 用户状态..."
|
||||||
|
|
||||||
|
if id "node_exporter" &>/dev/null; then
|
||||||
|
log_info "检测到 node_exporter 用户存在"
|
||||||
|
log_warning "node_exporter 是系统用户,可能被其他服务使用"
|
||||||
|
log_info "为了系统稳定性,将保留 node_exporter 用户"
|
||||||
|
log_info "如需手动删除,请运行: sudo userdel node_exporter"
|
||||||
|
else
|
||||||
|
log_info "node_exporter 用户不存在"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 清理日志文件
|
||||||
|
cleanup_logs() {
|
||||||
|
log_info "清理日志文件..."
|
||||||
|
|
||||||
|
# 清理 journal 日志
|
||||||
|
journalctl --vacuum-time=1s --quiet || true
|
||||||
|
|
||||||
|
# 删除安装脚本创建的日志文件
|
||||||
|
rm -f /var/log/node-exporter.log
|
||||||
|
|
||||||
|
log_success "日志文件已清理"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示卸载信息
|
||||||
|
show_uninstall_info() {
|
||||||
|
log_success "Node Exporter 卸载完成!"
|
||||||
|
echo
|
||||||
|
echo "已删除的内容:"
|
||||||
|
echo " - 二进制文件: /usr/local/bin/node-exporter"
|
||||||
|
echo " - 配置目录: /etc/node_exporter"
|
||||||
|
echo " - 数据目录: /var/lib/node_exporter"
|
||||||
|
echo " - 相关日志文件"
|
||||||
|
echo
|
||||||
|
echo "注意:"
|
||||||
|
echo " - node_exporter 用户已保留(系统用户,可能被其他服务使用)"
|
||||||
|
echo " - 如需完全清理,请手动检查并删除相关文件"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Node Exporter 卸载脚本 v1.0"
|
||||||
|
echo "=========================================="
|
||||||
|
echo
|
||||||
|
|
||||||
|
check_root
|
||||||
|
|
||||||
|
log_warning "此操作将完全卸载 Node Exporter"
|
||||||
|
read -p "确认继续?(y/N): " confirm
|
||||||
|
|
||||||
|
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||||
|
log_info "取消卸载操作"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "开始卸载 Node Exporter..."
|
||||||
|
|
||||||
|
stop_processes
|
||||||
|
remove_binary
|
||||||
|
remove_config
|
||||||
|
remove_data_dir
|
||||||
|
cleanup_logs
|
||||||
|
|
||||||
|
# 检查用户状态
|
||||||
|
check_user_status
|
||||||
|
|
||||||
|
show_uninstall_info
|
||||||
|
}
|
||||||
|
|
||||||
|
# 脚本入口
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
286
src/metric/client-plugins/all-in-one-demo/scripts/check_health.sh
Executable file
286
src/metric/client-plugins/all-in-one-demo/scripts/check_health.sh
Executable file
@@ -0,0 +1,286 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 整体健康检查脚本,调用各个组件的健康检查并将结果写入 .health_log 文件
|
||||||
|
|
||||||
|
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)"
|
||||||
|
HEALTH_LOG_FILE="$SCRIPT_DIR/.health_log"
|
||||||
|
INSTALL_RECORD_FILE="$SCRIPT_DIR/.install_record"
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 日志函数 - 输出到 stderr 避免影响 JSON 结果
|
||||||
|
log_info() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查单个组件健康状态
|
||||||
|
check_component() {
|
||||||
|
local component_name="$1"
|
||||||
|
local check_script_path="$2"
|
||||||
|
|
||||||
|
log_info "检查 $component_name 健康状态..."
|
||||||
|
|
||||||
|
if [[ ! -f "$check_script_path" ]]; then
|
||||||
|
log_error "健康检查脚本不存在: $check_script_path"
|
||||||
|
echo "{\"name\": \"$component_name\", \"status\": \"unhealth\", \"reason\": \"健康检查脚本不存在: $check_script_path\"}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -x "$check_script_path" ]]; then
|
||||||
|
log_error "健康检查脚本无执行权限: $check_script_path"
|
||||||
|
echo "{\"name\": \"$component_name\", \"status\": \"unhealth\", \"reason\": \"健康检查脚本无执行权限: $check_script_path\"}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 执行健康检查脚本,只捕获 stdout,stderr 输出到终端
|
||||||
|
local result
|
||||||
|
if result=$("$check_script_path" 2>/dev/null); then
|
||||||
|
log_success "$component_name 健康检查通过"
|
||||||
|
echo "$result"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_warning "$component_name 健康检查失败"
|
||||||
|
echo "$result"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成时间戳
|
||||||
|
get_timestamp() {
|
||||||
|
date '+%Y-%m-%d %H:%M:%S'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成UTC时间戳
|
||||||
|
get_utc_timestamp() {
|
||||||
|
date -u '+%Y-%m-%dT%H:%M:%SZ'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取主机名
|
||||||
|
get_hostname() {
|
||||||
|
echo "${HOSTNAME:-$(hostname)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建健康状态目录
|
||||||
|
create_health_dir() {
|
||||||
|
local hostname=$(get_hostname)
|
||||||
|
local health_dir="/private/argus/agent/$hostname/health"
|
||||||
|
|
||||||
|
if [[ ! -d "$health_dir" ]]; then
|
||||||
|
log_info "创建健康状态目录: $health_dir"
|
||||||
|
mkdir -p "$health_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$health_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 写入单个模块的健康状态JSON文件
|
||||||
|
write_component_health_json() {
|
||||||
|
local component_name="$1"
|
||||||
|
local status="$2"
|
||||||
|
local error_msg="$3"
|
||||||
|
local health_dir="$4"
|
||||||
|
|
||||||
|
# 生成模块名前缀-xxx.json格式的文件名
|
||||||
|
local module_prefix="metric"
|
||||||
|
local filename="${module_prefix}-${component_name}.json"
|
||||||
|
local filepath="$health_dir/$filename"
|
||||||
|
|
||||||
|
# 生成UTC时间戳
|
||||||
|
local timestamp=$(get_utc_timestamp)
|
||||||
|
|
||||||
|
# 构建JSON内容
|
||||||
|
local json_content=$(cat << EOF
|
||||||
|
{
|
||||||
|
"status": "$status",
|
||||||
|
"error": "$error_msg",
|
||||||
|
"timestamp": "$timestamp"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# 写入文件
|
||||||
|
echo "$json_content" > "$filepath"
|
||||||
|
log_info "已写入模块健康状态文件: $filepath"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 从安装记录文件中读取组件安装目录
|
||||||
|
read_install_record() {
|
||||||
|
local install_record_file="$1"
|
||||||
|
|
||||||
|
if [[ ! -f "$install_record_file" ]]; then
|
||||||
|
log_error "安装记录文件不存在: $install_record_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否有 jq 命令来解析 JSON
|
||||||
|
if command -v jq &> /dev/null; then
|
||||||
|
# 使用 jq 解析 JSON
|
||||||
|
local components_json
|
||||||
|
if components_json=$(jq -r '.components | to_entries[] | "\(.key):\(.value.install_dir)"' "$install_record_file" 2>/dev/null); then
|
||||||
|
echo "$components_json"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "无法解析安装记录文件 JSON 格式: $install_record_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# 如果没有 jq,尝试简单的文本解析
|
||||||
|
log_warning "jq 命令不可用,尝试简单文本解析"
|
||||||
|
|
||||||
|
# 查找所有 install_dir 行
|
||||||
|
local components=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ "$line" =~ \"install_dir\":[[:space:]]*\"([^\"]+)\" ]]; then
|
||||||
|
local install_dir="${BASH_REMATCH[1]}"
|
||||||
|
# 从路径中提取组件名称
|
||||||
|
local component_name=$(basename "$install_dir")
|
||||||
|
components+=("$component_name:$install_dir")
|
||||||
|
fi
|
||||||
|
done < "$install_record_file"
|
||||||
|
|
||||||
|
if [[ ${#components[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${components[@]}"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "无法从安装记录文件中提取组件信息"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
echo "==========================================" >&2
|
||||||
|
echo " 整体健康检查脚本" >&2
|
||||||
|
echo "==========================================" >&2
|
||||||
|
echo >&2
|
||||||
|
|
||||||
|
# 记录健康检查开始时间
|
||||||
|
local start_time=$(get_timestamp)
|
||||||
|
log_info "健康检查开始时间: $start_time"
|
||||||
|
|
||||||
|
# 创建健康状态目录
|
||||||
|
local health_dir
|
||||||
|
health_dir=$(create_health_dir)
|
||||||
|
|
||||||
|
# 从安装记录文件中读取组件信息
|
||||||
|
log_info "从安装记录文件读取组件信息: $INSTALL_RECORD_FILE"
|
||||||
|
local components_info
|
||||||
|
if ! components_info=$(read_install_record "$INSTALL_RECORD_FILE"); then
|
||||||
|
log_error "无法读取安装记录文件,健康检查终止"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 存储所有检查结果
|
||||||
|
local all_results=()
|
||||||
|
local overall_status="health"
|
||||||
|
|
||||||
|
# 逐个检查组件
|
||||||
|
while IFS= read -r component_info; do
|
||||||
|
if [[ -n "$component_info" ]]; then
|
||||||
|
IFS=':' read -r component_name install_dir <<< "$component_info"
|
||||||
|
local check_script_path="$install_dir/check_health.sh"
|
||||||
|
|
||||||
|
local result
|
||||||
|
local component_status="healthy"
|
||||||
|
local error_msg=""
|
||||||
|
|
||||||
|
if result=$(check_component "$component_name" "$check_script_path"); then
|
||||||
|
all_results+=("$result")
|
||||||
|
else
|
||||||
|
all_results+=("$result")
|
||||||
|
overall_status="unhealth"
|
||||||
|
component_status="unhealthy"
|
||||||
|
# 从结果中提取错误信息
|
||||||
|
if command -v jq &> /dev/null; then
|
||||||
|
error_msg=$(echo "$result" | jq -r '.reason // ""' 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
# 简单的文本解析提取错误信息
|
||||||
|
if [[ "$result" =~ \"reason\":[[:space:]]*\"([^\"]+)\" ]]; then
|
||||||
|
error_msg="${BASH_REMATCH[1]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 写入单个模块的健康状态JSON文件
|
||||||
|
write_component_health_json "$component_name" "$component_status" "$error_msg" "$health_dir"
|
||||||
|
fi
|
||||||
|
done <<< "$components_info"
|
||||||
|
|
||||||
|
# 记录健康检查结束时间
|
||||||
|
local end_time=$(get_timestamp)
|
||||||
|
log_info "健康检查结束时间: $end_time"
|
||||||
|
|
||||||
|
# 构建完整的健康检查结果 JSON
|
||||||
|
local health_check_result=$(cat << EOF
|
||||||
|
{
|
||||||
|
"start_time": "$start_time",
|
||||||
|
"end_time": "$end_time",
|
||||||
|
"overall_status": "$overall_status",
|
||||||
|
"components": [
|
||||||
|
$(printf '%s,\n' "${all_results[@]}" | sed '$s/,$//')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# 写入健康日志文件
|
||||||
|
log_info "将健康检查结果写入日志文件: $HEALTH_LOG_FILE"
|
||||||
|
echo "$health_check_result" >> "$HEALTH_LOG_FILE"
|
||||||
|
|
||||||
|
# 输出 JSON 结果到 stdout
|
||||||
|
echo "$health_check_result"
|
||||||
|
|
||||||
|
# 显示总结到 stderr
|
||||||
|
echo >&2
|
||||||
|
echo "==========================================" >&2
|
||||||
|
echo " 健康检查总结" >&2
|
||||||
|
echo "==========================================" >&2
|
||||||
|
echo "开始时间: $start_time" >&2
|
||||||
|
echo "结束时间: $end_time" >&2
|
||||||
|
echo "整体状态: $overall_status" >&2
|
||||||
|
echo "日志文件: $HEALTH_LOG_FILE" >&2
|
||||||
|
echo >&2
|
||||||
|
|
||||||
|
if [[ "$overall_status" == "health" ]]; then
|
||||||
|
log_success "所有组件健康检查通过!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
log_error "部分组件健康检查失败,请查看上述详细信息"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 脚本入口
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
240
src/metric/client-plugins/all-in-one-demo/scripts/check_version.sh
Executable file
240
src/metric/client-plugins/all-in-one-demo/scripts/check_version.sh
Executable file
@@ -0,0 +1,240 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 版本校验脚本
|
||||||
|
# 比较本地 LATEST_VERSION 与 FTP 的 VERSION 版本,如果不一致则更新对应版本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 日志函数 - 输出到 stderr 避免影响函数返回值
|
||||||
|
log_info() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取脚本所在目录
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# 动态获取当前版本目录
|
||||||
|
get_current_version_dir() {
|
||||||
|
# 查找 /opt/argus-metric/versions/ 下的最新版本目录
|
||||||
|
local versions_dir="/opt/argus-metric/versions"
|
||||||
|
if [[ -d "$versions_dir" ]]; then
|
||||||
|
# 按版本号排序,获取最新的版本目录
|
||||||
|
local latest_version_dir=$(ls -1 "$versions_dir" 2>/dev/null | sort -V | tail -1)
|
||||||
|
if [[ -n "$latest_version_dir" ]]; then
|
||||||
|
echo "$versions_dir/$latest_version_dir"
|
||||||
|
else
|
||||||
|
echo "/opt/argus-metric"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "/opt/argus-metric"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取当前版本目录
|
||||||
|
CURRENT_VERSION_DIR=$(get_current_version_dir)
|
||||||
|
# LATEST_VERSION 文件在根目录
|
||||||
|
LOCAL_VERSION_FILE="/opt/argus-metric/LATEST_VERSION"
|
||||||
|
REMOTE_VERSION_URL=""
|
||||||
|
LOG_FILE="$CURRENT_VERSION_DIR/.version_check.log"
|
||||||
|
|
||||||
|
# 从环境变量或配置文件获取 FTP 服务器信息
|
||||||
|
get_ftp_config() {
|
||||||
|
# 优先从环境变量获取配置
|
||||||
|
log_info "获取 FTP 配置信息..."
|
||||||
|
|
||||||
|
# 如果环境变量中没有设置,则尝试从配置文件读取
|
||||||
|
if [[ -z "$FTP_SERVER" || -z "$FTP_USER" || -z "$FTP_PASSWORD" ]]; then
|
||||||
|
local config_file="$SCRIPT_DIR/../config/config.env"
|
||||||
|
if [[ -f "$config_file" ]]; then
|
||||||
|
log_info "从配置文件读取 FTP 配置: $config_file"
|
||||||
|
source "$config_file"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "使用环境变量中的 FTP 配置"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置默认值(如果环境变量和配置文件都没有设置)
|
||||||
|
FTP_SERVER="${FTP_SERVER:-localhost}"
|
||||||
|
FTP_USER="${FTP_USER:-ftpuser}"
|
||||||
|
FTP_PASSWORD="${FTP_PASSWORD:-ZGClab1234!}"
|
||||||
|
|
||||||
|
# 构建远程版本文件 URL
|
||||||
|
REMOTE_VERSION_URL="ftp://${FTP_USER}:${FTP_PASSWORD}@${FTP_SERVER}/LATEST_VERSION"
|
||||||
|
|
||||||
|
log_info "FTP 配置来源: ${FTP_CONFIG_SOURCE:-环境变量/配置文件}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取远程版本号
|
||||||
|
get_remote_version() {
|
||||||
|
log_info "从 FTP 服务器获取远程版本号..."
|
||||||
|
log_info "远程地址: $REMOTE_VERSION_URL"
|
||||||
|
|
||||||
|
# 先测试 FTP 连接
|
||||||
|
log_info "测试 FTP 连接..."
|
||||||
|
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/" >/dev/null 2>&1; then
|
||||||
|
log_success "FTP 服务器连接成功"
|
||||||
|
else
|
||||||
|
log_error "无法连接到 FTP 服务器: $FTP_SERVER"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 测试 LATEST_VERSION 文件是否存在
|
||||||
|
log_info "检查远程 LATEST_VERSION 文件是否存在..."
|
||||||
|
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/LATEST_VERSION" >/dev/null 2>&1; then
|
||||||
|
log_success "远程 LATEST_VERSION 文件存在"
|
||||||
|
else
|
||||||
|
log_error "远程 LATEST_VERSION 文件不存在或无法访问"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 获取远程版本号
|
||||||
|
local remote_version
|
||||||
|
if remote_version=$(curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfL "ftp://${FTP_SERVER}/LATEST_VERSION" 2>/dev/null | tr -d '[:space:]'); then
|
||||||
|
if [[ -n "$remote_version" ]]; then
|
||||||
|
log_success "获取到远程版本号: $remote_version"
|
||||||
|
echo "$remote_version"
|
||||||
|
else
|
||||||
|
log_error "远程版本号为空"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "获取远程版本号失败"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取本地版本号
|
||||||
|
get_local_version() {
|
||||||
|
if [[ -f "$LOCAL_VERSION_FILE" ]]; then
|
||||||
|
local local_version=$(cat "$LOCAL_VERSION_FILE" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
if [[ -n "$local_version" ]]; then
|
||||||
|
log_info "本地版本号: $local_version"
|
||||||
|
echo "$local_version"
|
||||||
|
else
|
||||||
|
log_warning "本地版本文件为空"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warning "本地版本文件不存在: $LOCAL_VERSION_FILE"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 更新到新版本
|
||||||
|
update_to_version() {
|
||||||
|
local new_version="$1"
|
||||||
|
local temp_dir="/tmp/argus-update-$$"
|
||||||
|
local setup_script="$temp_dir/setup.sh"
|
||||||
|
|
||||||
|
log_info "开始更新到版本: $new_version"
|
||||||
|
|
||||||
|
# 创建临时目录
|
||||||
|
mkdir -p "$temp_dir"
|
||||||
|
|
||||||
|
# 下载最新的 setup.sh
|
||||||
|
log_info "从 FTP 服务器下载最新的安装脚本..."
|
||||||
|
local setup_url="ftp://${FTP_USER}:${FTP_PASSWORD}@${FTP_SERVER}/setup.sh"
|
||||||
|
|
||||||
|
if curl -fsS "$setup_url" -o "$setup_script"; then
|
||||||
|
log_success "安装脚本下载完成"
|
||||||
|
else
|
||||||
|
log_error "下载安装脚本失败: $setup_url"
|
||||||
|
rm -rf "$temp_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加执行权限
|
||||||
|
chmod +x "$setup_script"
|
||||||
|
|
||||||
|
# 执行安装脚本
|
||||||
|
log_info "执行安装脚本进行版本更新..."
|
||||||
|
if "$setup_script" --server "$FTP_SERVER" --user "$FTP_USER" --password "$FTP_PASSWORD" --version "$new_version"; then
|
||||||
|
log_success "版本更新完成: $new_version"
|
||||||
|
rm -rf "$temp_dir"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "版本更新失败: $new_version"
|
||||||
|
rm -rf "$temp_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 记录检查日志
|
||||||
|
log_check() {
|
||||||
|
local message="$1"
|
||||||
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
echo "[$timestamp] $message" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
log_info "开始版本校验检查..."
|
||||||
|
log_check "版本校验检查开始"
|
||||||
|
|
||||||
|
# 确保系统目录存在
|
||||||
|
mkdir -p "/opt/argus-metric"
|
||||||
|
mkdir -p "$CURRENT_VERSION_DIR"
|
||||||
|
|
||||||
|
log_info "当前版本目录: $CURRENT_VERSION_DIR"
|
||||||
|
|
||||||
|
# 获取 FTP 配置
|
||||||
|
get_ftp_config
|
||||||
|
|
||||||
|
# 获取本地版本号
|
||||||
|
local local_version
|
||||||
|
local_version=$(get_local_version)
|
||||||
|
|
||||||
|
# 获取远程版本号
|
||||||
|
local remote_version
|
||||||
|
if ! remote_version=$(get_remote_version); then
|
||||||
|
log_error "无法获取远程版本号,跳过本次检查"
|
||||||
|
log_check "版本校验失败:无法获取远程版本号"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 比较版本号
|
||||||
|
if [[ "$local_version" == "$remote_version" ]]; then
|
||||||
|
log_info "版本一致,无需更新 (本地: $local_version, 远程: $remote_version)"
|
||||||
|
log_check "版本校验完成:版本一致 ($local_version)"
|
||||||
|
else
|
||||||
|
log_info "检测到版本不一致 (本地: $local_version, 远程: $remote_version)"
|
||||||
|
log_check "检测到版本不一致:本地($local_version) -> 远程($remote_version)"
|
||||||
|
|
||||||
|
# 更新到新版本
|
||||||
|
if update_to_version "$remote_version"; then
|
||||||
|
log_success "版本更新成功: $local_version -> $remote_version"
|
||||||
|
log_check "版本更新成功:$local_version -> $remote_version"
|
||||||
|
else
|
||||||
|
log_error "版本更新失败"
|
||||||
|
log_check "版本更新失败:$local_version -> $remote_version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "版本校验检查完成"
|
||||||
|
log_check "版本校验检查完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 脚本入口
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
995
src/metric/client-plugins/all-in-one-demo/scripts/install_artifact.sh
Executable file
995
src/metric/client-plugins/all-in-one-demo/scripts/install_artifact.sh
Executable file
@@ -0,0 +1,995 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 配置变量
|
||||||
|
INSTALL_DIR="${1:-$(pwd)}" # 使用第一个参数作为安装目录,如果没有参数则使用当前目录
|
||||||
|
TEMP_DIR="/tmp/metrics-install-$$"
|
||||||
|
VERSION_FILE="version.json"
|
||||||
|
|
||||||
|
|
||||||
|
# 加载配置文件
|
||||||
|
load_config() {
|
||||||
|
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
local config_file="$script_dir/config.env"
|
||||||
|
|
||||||
|
if [[ -f "$config_file" ]]; then
|
||||||
|
log_info "加载配置文件: $config_file"
|
||||||
|
# 导出配置文件中的环境变量
|
||||||
|
set -a # 自动导出所有变量
|
||||||
|
source "$config_file"
|
||||||
|
set +a # 关闭自动导出
|
||||||
|
log_success "配置文件加载完成"
|
||||||
|
else
|
||||||
|
log_warning "配置文件不存在: $config_file,使用默认配置"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 复制配置文件到安装目录
|
||||||
|
copy_config_files() {
|
||||||
|
log_info "复制配置文件到安装目录..."
|
||||||
|
|
||||||
|
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
local source_config="$script_dir/../config/config.env"
|
||||||
|
local target_config="$INSTALL_DIR/config.env"
|
||||||
|
|
||||||
|
if [[ -f "$source_config" ]]; then
|
||||||
|
# 检查源文件和目标文件是否是同一个文件
|
||||||
|
if [[ "$source_config" == "$target_config" ]]; then
|
||||||
|
log_info "配置文件已在目标位置,跳过复制"
|
||||||
|
log_success "配置文件已存在: $target_config"
|
||||||
|
else
|
||||||
|
if cp "$source_config" "$target_config"; then
|
||||||
|
log_success "配置文件复制完成: $target_config"
|
||||||
|
else
|
||||||
|
log_error "配置文件复制失败"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warning "源配置文件不存在: $source_config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制版本校验脚本
|
||||||
|
log_info "复制版本校验脚本到安装目录..."
|
||||||
|
local target_check_version="$INSTALL_DIR/check_version.sh"
|
||||||
|
|
||||||
|
# 检查目标文件是否已存在(从 artifact 包中解压出来的)
|
||||||
|
if [[ -f "$target_check_version" ]]; then
|
||||||
|
log_info "版本校验脚本已存在,设置执行权限..."
|
||||||
|
chmod +x "$target_check_version"
|
||||||
|
log_success "版本校验脚本权限设置完成: $target_check_version"
|
||||||
|
else
|
||||||
|
log_warning "版本校验脚本不存在: $target_check_version"
|
||||||
|
log_info "请确保 check_version.sh 已包含在 artifact 包中"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_root() {
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log_error "此脚本需要 root 权限运行"
|
||||||
|
log_info "请使用: sudo $0 [安装目录]"
|
||||||
|
log_info "如果不指定安装目录,将使用当前目录: $(pwd)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查系统要求
|
||||||
|
check_system() {
|
||||||
|
log_info "检查系统要求..."
|
||||||
|
|
||||||
|
# 检查操作系统
|
||||||
|
if [[ ! -f /etc/os-release ]]; then
|
||||||
|
log_error "无法检测操作系统版本"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source /etc/os-release
|
||||||
|
log_info "检测到操作系统: $NAME $VERSION"
|
||||||
|
|
||||||
|
# 检查系统架构
|
||||||
|
arch=$(uname -m)
|
||||||
|
log_info "系统架构: $arch"
|
||||||
|
|
||||||
|
# 检查磁盘空间
|
||||||
|
available_space=$(df / | awk 'NR==2 {print $4}')
|
||||||
|
if [[ $available_space -lt 10485760 ]]; then # 10GB in KB
|
||||||
|
log_warning "可用磁盘空间不足 10GB,当前可用: $(($available_space / 1024 / 1024))GB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查内存
|
||||||
|
total_mem=$(free -m | awk 'NR==2{print $2}')
|
||||||
|
if [[ $total_mem -lt 4096 ]]; then # 4GB
|
||||||
|
log_warning "系统内存不足 4GB,当前: ${total_mem}MB"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 查找版本文件
|
||||||
|
find_version_file() {
|
||||||
|
log_info "查找版本信息文件..."
|
||||||
|
|
||||||
|
# 在当前目录查找
|
||||||
|
if [[ -f "$VERSION_FILE" ]]; then
|
||||||
|
VERSION_FILE_PATH="$(pwd)/$VERSION_FILE"
|
||||||
|
log_success "找到版本文件: $VERSION_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 在 artifact 目录查找
|
||||||
|
for version_dir in artifact/*/; do
|
||||||
|
if [[ -f "${version_dir}${VERSION_FILE}" ]]; then
|
||||||
|
VERSION_FILE_PATH="$(cd "$(dirname "${version_dir}${VERSION_FILE}")" && pwd)/$(basename "${version_dir}${VERSION_FILE}")"
|
||||||
|
log_success "找到版本文件: $VERSION_FILE_PATH"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log_error "未找到版本信息文件 $VERSION_FILE"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解析版本信息
|
||||||
|
parse_version_info() {
|
||||||
|
log_info "解析版本信息..."
|
||||||
|
|
||||||
|
if [[ ! -f "$VERSION_FILE_PATH" ]]; then
|
||||||
|
log_error "版本文件不存在: $VERSION_FILE_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 使用 jq 解析 JSON(如果可用)
|
||||||
|
if command -v jq &> /dev/null; then
|
||||||
|
# 验证JSON文件格式
|
||||||
|
if ! jq empty "$VERSION_FILE_PATH" 2>/dev/null; then
|
||||||
|
log_error "JSON文件格式错误,请检查 $VERSION_FILE_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$(jq -r '.version' "$VERSION_FILE_PATH")
|
||||||
|
BUILD_TIME=$(jq -r '.build_time' "$VERSION_FILE_PATH")
|
||||||
|
|
||||||
|
# 解析 artifact_list
|
||||||
|
if jq -e '.artifact_list' "$VERSION_FILE_PATH" > /dev/null 2>&1; then
|
||||||
|
jq -r '.artifact_list | to_entries[] | "\(.key):\(.value)"' "$VERSION_FILE_PATH" > "$TEMP_DIR/components.txt"
|
||||||
|
else
|
||||||
|
log_error "version.json 中缺少 artifact_list 字段"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 解析 checksums
|
||||||
|
if jq -e '.checksums' "$VERSION_FILE_PATH" > /dev/null 2>&1; then
|
||||||
|
jq -r '.checksums | to_entries[] | "\(.key):\(.value)"' "$VERSION_FILE_PATH" > "$TEMP_DIR/checksums.txt"
|
||||||
|
else
|
||||||
|
log_error "version.json 中缺少 checksums 字段"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 解析 install_order(现在包含完整的文件名)
|
||||||
|
if jq -e '.install_order' "$VERSION_FILE_PATH" > /dev/null 2>&1; then
|
||||||
|
jq -r '.install_order[]' "$VERSION_FILE_PATH" > "$TEMP_DIR/install_order.txt"
|
||||||
|
else
|
||||||
|
log_error "version.json 中缺少 install_order 字段"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
log_warning "jq 未安装,使用简单的 JSON 解析"
|
||||||
|
# 简单的 JSON 解析
|
||||||
|
VERSION=$(grep '"version"' "$VERSION_FILE_PATH" | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
||||||
|
BUILD_TIME=$(grep '"build_time"' "$VERSION_FILE_PATH" | sed 's/.*"build_time": *"\([^"]*\)".*/\1/')
|
||||||
|
|
||||||
|
# 解析 artifact_list(跳过字段名本身)
|
||||||
|
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/')
|
||||||
|
version=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
||||||
|
echo "$component:$version" >> "$TEMP_DIR/components.txt"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 解析 checksums(跳过字段名本身)
|
||||||
|
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/')
|
||||||
|
checksum=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
||||||
|
echo "$component:$checksum" >> "$TEMP_DIR/checksums.txt"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 解析 install_order(跳过字段名本身,只取数组元素)
|
||||||
|
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/')
|
||||||
|
echo "$component" >> "$TEMP_DIR/install_order.txt"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 验证解析结果
|
||||||
|
if [[ ! -f "$TEMP_DIR/components.txt" || ! -s "$TEMP_DIR/components.txt" ]]; then
|
||||||
|
log_error "无法解析 artifact_list,请检查 version.json 格式"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$TEMP_DIR/checksums.txt" || ! -s "$TEMP_DIR/checksums.txt" ]]; then
|
||||||
|
log_error "无法解析 checksums,请检查 version.json 格式"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$TEMP_DIR/install_order.txt" || ! -s "$TEMP_DIR/install_order.txt" ]]; then
|
||||||
|
log_error "无法解析 install_order,请检查 version.json 格式"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "版本信息解析完成"
|
||||||
|
log_info " 版本: $VERSION"
|
||||||
|
log_info " 构建时间: $BUILD_TIME"
|
||||||
|
|
||||||
|
component_count=0
|
||||||
|
if [[ -f "$TEMP_DIR/components.txt" ]]; then
|
||||||
|
component_count=$(wc -l < "$TEMP_DIR/components.txt")
|
||||||
|
log_info " 组件数量: $component_count"
|
||||||
|
log_info " 组件列表:"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
component=$(echo "$line" | cut -d':' -f1)
|
||||||
|
version=$(echo "$line" | cut -d':' -f2)
|
||||||
|
log_info " - $component v$version"
|
||||||
|
done < "$TEMP_DIR/components.txt"
|
||||||
|
else
|
||||||
|
log_error "components.txt 文件不存在"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 验证文件完整性
|
||||||
|
verify_checksums() {
|
||||||
|
log_info "验证文件完整性..."
|
||||||
|
|
||||||
|
artifact_dir=$(dirname "$VERSION_FILE_PATH")
|
||||||
|
log_info "Artifact 目录: $artifact_dir"
|
||||||
|
failed_verification=0
|
||||||
|
|
||||||
|
if [[ -f "$TEMP_DIR/checksums.txt" ]]; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
component=$(echo "$line" | cut -d':' -f1)
|
||||||
|
expected_checksum=$(echo "$line" | cut -d':' -f2-)
|
||||||
|
|
||||||
|
# 查找匹配的 tar 文件
|
||||||
|
actual_file=""
|
||||||
|
for file in "$artifact_dir/${component}-"*.tar.gz; do
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
actual_file="$file"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$actual_file" ]]; then
|
||||||
|
log_error "找不到组件文件: $component"
|
||||||
|
failed_verification=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 计算实际校验和
|
||||||
|
actual_checksum="sha256:$(sha256sum "$actual_file" | cut -d' ' -f1)"
|
||||||
|
|
||||||
|
if [[ "$actual_checksum" == "$expected_checksum" ]]; then
|
||||||
|
log_success " $component: 校验通过"
|
||||||
|
else
|
||||||
|
log_error " $component: 校验失败"
|
||||||
|
log_error " 期望: $expected_checksum"
|
||||||
|
log_error " 实际: $actual_checksum"
|
||||||
|
failed_verification=1
|
||||||
|
fi
|
||||||
|
done < "$TEMP_DIR/checksums.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $failed_verification -eq 1 ]]; then
|
||||||
|
log_error "文件完整性验证失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "所有文件校验通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建安装目录
|
||||||
|
create_install_dirs() {
|
||||||
|
log_info "创建安装目录..."
|
||||||
|
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
mkdir -p "$TEMP_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() {
|
||||||
|
log_info "检查系统依赖包..."
|
||||||
|
|
||||||
|
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
local deps_dir="$script_dir/deps"
|
||||||
|
|
||||||
|
# 检查deps目录是否存在
|
||||||
|
if [[ ! -d "$deps_dir" ]]; then
|
||||||
|
log_info "deps 目录不存在,跳过系统依赖包安装"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 获取系统版本对应的依赖目录
|
||||||
|
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)
|
||||||
|
if [[ $deps_count -eq 0 ]]; then
|
||||||
|
log_info "deps 目录中没有 tar.gz 文件,跳过系统依赖包安装"
|
||||||
|
return 0
|
||||||
|
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 "找到 $system_version 版本的依赖包,开始安装..."
|
||||||
|
|
||||||
|
# 创建临时目录用于解压依赖包
|
||||||
|
local deps_temp_dir="${TEMP_DIR:-/tmp}/deps"
|
||||||
|
mkdir -p "$deps_temp_dir"
|
||||||
|
|
||||||
|
# 定义要检查的核心依赖
|
||||||
|
local CORE_DEPS=(jq cron curl)
|
||||||
|
local FAILED_DEPS=()
|
||||||
|
|
||||||
|
# 处理每个tar.gz文件
|
||||||
|
find "$version_deps_dir" -name "*.tar.gz" | while read tar_file; do
|
||||||
|
local tar_basename=$(basename "$tar_file")
|
||||||
|
local extract_name="${tar_basename%.tar.gz}"
|
||||||
|
|
||||||
|
log_info "处理依赖包: $tar_basename"
|
||||||
|
|
||||||
|
# 解压到临时目录
|
||||||
|
local extract_dir="$deps_temp_dir/$extract_name"
|
||||||
|
mkdir -p "$extract_dir"
|
||||||
|
|
||||||
|
if tar -xzf "$tar_file" -C "$extract_dir" 2>/dev/null; then
|
||||||
|
log_success " $tar_basename 解压完成"
|
||||||
|
else
|
||||||
|
log_error " $tar_basename 解压失败"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 进入解压目录,查找deb包
|
||||||
|
cd "$extract_dir" || continue
|
||||||
|
local deb_files=(*.deb)
|
||||||
|
if [[ ${#deb_files[@]} -gt 0 ]]; then
|
||||||
|
log_info " 找到 ${#deb_files[@]} 个 deb 包,开始安装..."
|
||||||
|
|
||||||
|
for deb in "${deb_files[@]}"; do
|
||||||
|
local pkg_name
|
||||||
|
pkg_name=$(dpkg-deb -f "$deb" Package 2>/dev/null)
|
||||||
|
|
||||||
|
# 如果已安装,则跳过
|
||||||
|
if dpkg -s "$pkg_name" &>/dev/null; then
|
||||||
|
log_success " $pkg_name 已安装,跳过"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 尝试安装
|
||||||
|
log_info " 安装 $pkg_name..."
|
||||||
|
if DEBIAN_FRONTEND=noninteractive dpkg -i "$deb" &>/dev/null; then
|
||||||
|
log_success " $pkg_name 安装成功"
|
||||||
|
else
|
||||||
|
log_warning " $pkg_name 安装失败,尝试修复依赖..."
|
||||||
|
if DEBIAN_FRONTEND=noninteractive apt-get install -f -y &>/dev/null; then
|
||||||
|
if dpkg -s "$pkg_name" &>/dev/null; then
|
||||||
|
log_success " $pkg_name 修复安装成功"
|
||||||
|
else
|
||||||
|
log_error " $pkg_name 仍未安装成功"
|
||||||
|
FAILED_DEPS+=("$pkg_name")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error " $pkg_name 自动修复失败"
|
||||||
|
FAILED_DEPS+=("$pkg_name")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_info " $tar_basename 中没有找到deb包,跳过"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 返回到依赖临时目录
|
||||||
|
cd "$deps_temp_dir" || continue
|
||||||
|
done
|
||||||
|
|
||||||
|
# 检查并启动 cron 服务
|
||||||
|
start_cron_service
|
||||||
|
|
||||||
|
# 总结安装结果
|
||||||
|
if [[ ${#FAILED_DEPS[@]} -gt 0 ]]; then
|
||||||
|
log_error "以下系统依赖未能成功安装,安装终止,请手动安装后重试:"
|
||||||
|
for f in "${FAILED_DEPS[@]}"; do
|
||||||
|
echo " - $f"
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
log_success "系统依赖包安装完成,全部就绪"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 启动 cron 服务
|
||||||
|
start_cron_service() {
|
||||||
|
log_info "检查并启动 cron 服务..."
|
||||||
|
|
||||||
|
# 检查 cron 是否已经在运行
|
||||||
|
if pgrep -x "cron" > /dev/null; then
|
||||||
|
log_success "cron 服务已在运行"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 /usr/sbin/cron 是否存在
|
||||||
|
if [[ ! -f "/usr/sbin/cron" ]]; then
|
||||||
|
log_warning "cron 可执行文件不存在,跳过启动"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 启动 cron 服务
|
||||||
|
log_info "启动 cron 服务..."
|
||||||
|
if /usr/sbin/cron start 2>/dev/null || /usr/sbin/cron 2>/dev/null; then
|
||||||
|
log_success "cron 服务启动成功"
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if pgrep -x "cron" > /dev/null; then
|
||||||
|
log_success "cron 服务运行正常"
|
||||||
|
else
|
||||||
|
log_warning "cron 服务可能未正常启动"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "cron 服务启动失败"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 安装组件
|
||||||
|
install_components() {
|
||||||
|
log_info "开始安装组件..."
|
||||||
|
|
||||||
|
artifact_dir=$(dirname "$VERSION_FILE_PATH")
|
||||||
|
log_info "Artifact 目录: $artifact_dir"
|
||||||
|
install_count=0
|
||||||
|
total_count=0
|
||||||
|
|
||||||
|
if [[ -f "$TEMP_DIR/install_order.txt" ]]; then
|
||||||
|
total_count=$(wc -l < "$TEMP_DIR/install_order.txt")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$TEMP_DIR/install_order.txt" ]]; then
|
||||||
|
while IFS= read -r filename; do
|
||||||
|
install_count=$((install_count + 1))
|
||||||
|
|
||||||
|
# 从文件名中提取组件名(去掉时间戳后缀)
|
||||||
|
component=$(echo "$filename" | sed 's/-[0-9]\{8\}-[0-9]\{6\}\.tar\.gz$//')
|
||||||
|
|
||||||
|
log_info "[$install_count/$total_count] 安装 $component..."
|
||||||
|
log_info " 文件名: $filename"
|
||||||
|
|
||||||
|
# 直接使用完整的文件名
|
||||||
|
tar_file="$artifact_dir/$filename"
|
||||||
|
|
||||||
|
if [[ ! -f "$tar_file" ]]; then
|
||||||
|
log_error "找不到组件文件: $filename"
|
||||||
|
log_info " 期望路径: $tar_file"
|
||||||
|
log_info " 当前目录: $(pwd)"
|
||||||
|
log_info " 目录内容:"
|
||||||
|
ls -la "$artifact_dir" | while read line; do
|
||||||
|
log_info " $line"
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info " 找到文件: $tar_file"
|
||||||
|
|
||||||
|
# 解压到临时目录
|
||||||
|
component_temp_dir="$TEMP_DIR/$component"
|
||||||
|
mkdir -p "$component_temp_dir"
|
||||||
|
|
||||||
|
if tar -xzf "$tar_file" -C "$component_temp_dir" 2>/dev/null; then
|
||||||
|
log_success " $component 解压完成"
|
||||||
|
else
|
||||||
|
log_error " $component 解压失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 查找解压后的目录
|
||||||
|
extracted_dir=""
|
||||||
|
for dir in "$component_temp_dir"/*; do
|
||||||
|
if [[ -d "$dir" ]]; then
|
||||||
|
extracted_dir="$dir"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$extracted_dir" ]]; then
|
||||||
|
log_error " $component 解压后未找到目录"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 执行安装脚本
|
||||||
|
if [[ -f "$extracted_dir/install.sh" ]]; then
|
||||||
|
log_info " 执行 $component 安装脚本..."
|
||||||
|
if (cd "$extracted_dir" && ./install.sh "$INSTALL_DIR"); then
|
||||||
|
log_success " $component 安装完成"
|
||||||
|
else
|
||||||
|
log_error " $component 安装失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error " $component 缺少 install.sh 文件"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 将解压后的目录移动到安装目录,保留组件目录
|
||||||
|
component_install_dir="$INSTALL_DIR/$component"
|
||||||
|
# 简化安装逻辑:直接删除旧目录,不进行备份
|
||||||
|
if [[ -d "$component_install_dir" ]]; then
|
||||||
|
log_info " 组件目录已存在,删除旧版本: $component_install_dir"
|
||||||
|
rm -rf "$component_install_dir"
|
||||||
|
# log_info " 组件目录已存在,备份后更新: $component_install_dir"
|
||||||
|
# mv "$component_install_dir" "${component_install_dir}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
fi
|
||||||
|
mv "$extracted_dir" "$component_install_dir"
|
||||||
|
log_success " 组件目录已保存: $component_install_dir"
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -rf "$component_temp_dir"
|
||||||
|
done < "$TEMP_DIR/install_order.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "所有组件安装完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建安装记录
|
||||||
|
create_install_record() {
|
||||||
|
log_info "创建安装记录..."
|
||||||
|
|
||||||
|
# 等待一段时间确保所有进程都已启动
|
||||||
|
log_info "等待进程启动..."
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
local install_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
local install_record_file="$INSTALL_DIR/.install_record"
|
||||||
|
|
||||||
|
# 创建 JSON 格式的安装记录
|
||||||
|
cat > "$install_record_file" << EOF
|
||||||
|
{
|
||||||
|
"version": "$VERSION",
|
||||||
|
"build_time": "$BUILD_TIME",
|
||||||
|
"install_time": "$install_time",
|
||||||
|
"install_dir": "$INSTALL_DIR",
|
||||||
|
"install_pid": $$,
|
||||||
|
"components": {
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 添加组件信息
|
||||||
|
local first_component=true
|
||||||
|
if [[ -f "$TEMP_DIR/components.txt" ]]; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
component=$(echo "$line" | cut -d':' -f1)
|
||||||
|
version=$(echo "$line" | cut -d':' -f2)
|
||||||
|
|
||||||
|
# 获取组件的进程信息
|
||||||
|
local component_pid=""
|
||||||
|
|
||||||
|
# 根据组件名查找进程,使用多种方法确保能找到PID
|
||||||
|
case "$component" in
|
||||||
|
"node-exporter")
|
||||||
|
# 尝试多种方式查找node_exporter进程
|
||||||
|
component_pid=$(pgrep -f "node_exporter" | head -1)
|
||||||
|
if [[ -z "$component_pid" ]]; then
|
||||||
|
component_pid=$(pgrep -f "node-exporter" | head -1)
|
||||||
|
fi
|
||||||
|
if [[ -z "$component_pid" ]]; then
|
||||||
|
component_pid=$(ps aux | grep -v grep | grep "node_exporter" | awk '{print $2}' | head -1)
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"dcgm-exporter")
|
||||||
|
# 查找dcgm-exporter进程
|
||||||
|
component_pid=$(pgrep -f "dcgm-exporter" | head -1)
|
||||||
|
if [[ -z "$component_pid" ]]; then
|
||||||
|
component_pid=$(pgrep -f "dcgm_exporter" | head -1)
|
||||||
|
fi
|
||||||
|
if [[ -z "$component_pid" ]]; then
|
||||||
|
component_pid=$(ps aux | grep -v grep | grep "dcgm-exporter" | awk '{print $2}' | head -1)
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"fluent-bit")
|
||||||
|
# 查找fluent-bit进程
|
||||||
|
component_pid=$(pgrep -f "fluent-bit" | head -1)
|
||||||
|
if [[ -z "$component_pid" ]]; then
|
||||||
|
component_pid=$(pgrep -f "fluent_bit" | head -1)
|
||||||
|
fi
|
||||||
|
if [[ -z "$component_pid" ]]; then
|
||||||
|
component_pid=$(ps aux | grep -v grep | grep "fluent-bit" | awk '{print $2}' | head -1)
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"argus-agent")
|
||||||
|
# 查找argus-agent进程
|
||||||
|
component_pid=$(pgrep -f "argus-agent" | head -1)
|
||||||
|
if [[ -z "$component_pid" ]]; then
|
||||||
|
component_pid=$(ps aux | grep -v grep | grep "argus-agent" | awk '{print $2}' | head -1)
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 记录找到的PID信息
|
||||||
|
if [[ -n "$component_pid" ]]; then
|
||||||
|
log_info " 找到 $component 进程 PID: $component_pid"
|
||||||
|
else
|
||||||
|
log_warning " 未找到 $component 进程"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加逗号分隔符
|
||||||
|
if [[ "$first_component" == "true" ]]; then
|
||||||
|
first_component=false
|
||||||
|
else
|
||||||
|
echo "," >> "$install_record_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加组件信息
|
||||||
|
cat >> "$install_record_file" << EOF
|
||||||
|
"$component": {
|
||||||
|
"version": "$version",
|
||||||
|
"pid": "$component_pid",
|
||||||
|
"install_dir": "$INSTALL_DIR/$component"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
done < "$TEMP_DIR/components.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 结束 JSON
|
||||||
|
cat >> "$install_record_file" << EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
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() {
|
||||||
|
log_info "设置健康检查定时任务..."
|
||||||
|
|
||||||
|
# 直接使用当前安装目录,不依赖current软链接
|
||||||
|
# INSTALL_DIR 是 /opt/argus-metric/versions/1.34.0
|
||||||
|
local check_health_script="$INSTALL_DIR/check_health.sh"
|
||||||
|
|
||||||
|
# 检查健康检查脚本是否存在
|
||||||
|
if [[ ! -f "$check_health_script" ]]; then
|
||||||
|
log_error "健康检查脚本不存在: $check_health_script"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 确保脚本有执行权限
|
||||||
|
chmod +x "$check_health_script"
|
||||||
|
|
||||||
|
# 创建临时crontab文件
|
||||||
|
local temp_cron="/tmp/crontab_$$"
|
||||||
|
|
||||||
|
# 获取当前用户的crontab(如果存在)
|
||||||
|
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
||||||
|
|
||||||
|
# 检查并删除旧的健康检查任务
|
||||||
|
if check_cron_task_exists "check_health.sh" "$temp_cron"; then
|
||||||
|
log_info "发现旧的健康检查定时任务,正在更新..."
|
||||||
|
# 删除所有包含check_health.sh的行
|
||||||
|
grep -v "check_health.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
|
mv "$temp_cron.new" "$temp_cron"
|
||||||
|
log_info "旧的健康检查定时任务已删除"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加新的定时任务(每5分钟执行一次)
|
||||||
|
echo "# Argus-Metrics 健康检查定时任务" >> "$temp_cron"
|
||||||
|
echo "*/5 * * * * $check_health_script >> $INSTALL_DIR/.health_cron.log 2>&1" >> "$temp_cron"
|
||||||
|
|
||||||
|
# 安装新的crontab
|
||||||
|
if crontab "$temp_cron"; then
|
||||||
|
log_success "健康检查定时任务设置成功"
|
||||||
|
log_info " 执行频率: 每5分钟"
|
||||||
|
log_info " 日志文件: $INSTALL_DIR/.health_cron.log"
|
||||||
|
log_info " 查看定时任务: crontab -l"
|
||||||
|
log_info " 删除定时任务: crontab -e"
|
||||||
|
else
|
||||||
|
log_error "健康检查定时任务设置失败"
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
|
||||||
|
log_info "健康检查通过crontab自动执行"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 设置 DNS 同步定时任务
|
||||||
|
setup_dns_sync_cron() {
|
||||||
|
log_info "设置 DNS 同步定时任务..."
|
||||||
|
|
||||||
|
# 使用当前版本目录中的 DNS 同步脚本
|
||||||
|
local sync_dns_script="$INSTALL_DIR/sync_dns.sh"
|
||||||
|
|
||||||
|
# 检查 DNS 同步脚本是否存在
|
||||||
|
if [[ ! -f "$sync_dns_script" ]]; then
|
||||||
|
log_warning "DNS 同步脚本不存在: $sync_dns_script"
|
||||||
|
log_warning "跳过 DNS 同步定时任务设置"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 确保脚本有执行权限
|
||||||
|
chmod +x "$sync_dns_script"
|
||||||
|
|
||||||
|
# 创建临时crontab文件
|
||||||
|
local temp_cron="/tmp/crontab_$$"
|
||||||
|
|
||||||
|
# 获取当前用户的crontab(如果存在)
|
||||||
|
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
||||||
|
|
||||||
|
# 检查并删除旧的 DNS 同步任务
|
||||||
|
if check_cron_task_exists "sync_dns.sh" "$temp_cron"; then
|
||||||
|
log_info "发现旧的 DNS 同步定时任务,正在更新..."
|
||||||
|
# 删除所有包含sync_dns.sh的行
|
||||||
|
grep -v "sync_dns.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
|
mv "$temp_cron.new" "$temp_cron"
|
||||||
|
log_info "旧的 DNS 同步定时任务已删除"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加新的定时任务(每1分钟执行一次)
|
||||||
|
# 直接使用版本目录中的 DNS 同步脚本
|
||||||
|
echo "# Argus-Metrics DNS 同步定时任务" >> "$temp_cron"
|
||||||
|
echo "* * * * * $sync_dns_script >> $INSTALL_DIR/.dns_sync.log 2>&1" >> "$temp_cron"
|
||||||
|
|
||||||
|
# 安装新的crontab
|
||||||
|
if crontab "$temp_cron"; then
|
||||||
|
log_success "DNS 同步定时任务设置成功"
|
||||||
|
log_info " 执行频率: 每1分钟"
|
||||||
|
log_info " 日志文件: $INSTALL_DIR/.dns_sync.log"
|
||||||
|
log_info " 查看定时任务: crontab -l"
|
||||||
|
log_info " 删除定时任务: crontab -e"
|
||||||
|
else
|
||||||
|
log_error "DNS 同步定时任务设置失败"
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
|
||||||
|
log_info "DNS 同步通过crontab自动执行"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 设置版本校验定时任务
|
||||||
|
setup_version_check_cron() {
|
||||||
|
log_info "设置版本校验定时任务..."
|
||||||
|
|
||||||
|
# 使用当前版本目录中的版本校验脚本
|
||||||
|
local check_version_script="$INSTALL_DIR/check_version.sh"
|
||||||
|
|
||||||
|
# 检查脚本是否存在
|
||||||
|
if [[ ! -f "$check_version_script" ]]; then
|
||||||
|
log_warning "版本校验脚本不存在: $check_version_script"
|
||||||
|
log_info "跳过版本校验定时任务设置"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 确保脚本可执行
|
||||||
|
chmod +x "$check_version_script"
|
||||||
|
|
||||||
|
# 创建临时crontab文件
|
||||||
|
local temp_cron="/tmp/crontab_$$"
|
||||||
|
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
||||||
|
|
||||||
|
# 检查是否已存在版本校验定时任务
|
||||||
|
if check_cron_task_exists "check_version.sh" "$temp_cron"; then
|
||||||
|
log_info "发现旧的版本校验定时任务,正在更新..."
|
||||||
|
# 删除所有包含check_version.sh的行
|
||||||
|
grep -v "check_version.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
|
mv "$temp_cron.new" "$temp_cron"
|
||||||
|
log_info "旧的版本校验定时任务已删除"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加新的定时任务(每30分钟执行一次)
|
||||||
|
echo "# Argus-Metrics 版本校验定时任务" >> "$temp_cron"
|
||||||
|
echo "*/1 * * * * $check_version_script >> $INSTALL_DIR/.version_check.log 2>&1" >> "$temp_cron"
|
||||||
|
|
||||||
|
# 安装新的crontab
|
||||||
|
if crontab "$temp_cron"; then
|
||||||
|
log_success "版本校验定时任务设置成功"
|
||||||
|
log_info " 执行频率: 每1分钟"
|
||||||
|
log_info " 日志文件: $INSTALL_DIR/.version_check.log"
|
||||||
|
log_info " 查看定时任务: crontab -l"
|
||||||
|
log_info " 删除定时任务: crontab -e"
|
||||||
|
else
|
||||||
|
log_error "版本校验定时任务设置失败"
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
|
||||||
|
log_info "版本校验通过crontab自动执行"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 设置自动重启定时任务
|
||||||
|
setup_restart_cron() {
|
||||||
|
log_info "设置自动重启定时任务..."
|
||||||
|
|
||||||
|
# 使用当前版本目录中的重启脚本
|
||||||
|
local restart_script="$INSTALL_DIR/restart_unhealthy.sh"
|
||||||
|
|
||||||
|
# 检查脚本是否存在
|
||||||
|
if [[ ! -f "$restart_script" ]]; then
|
||||||
|
log_warning "重启脚本不存在: $restart_script"
|
||||||
|
log_info "跳过自动重启定时任务设置"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 确保脚本可执行
|
||||||
|
chmod +x "$restart_script"
|
||||||
|
|
||||||
|
# 创建临时crontab文件
|
||||||
|
local temp_cron="/tmp/crontab_$$"
|
||||||
|
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
||||||
|
|
||||||
|
# 检查是否已存在自动重启定时任务
|
||||||
|
if check_cron_task_exists "restart_unhealthy.sh" "$temp_cron"; then
|
||||||
|
log_info "发现旧的自动重启定时任务,正在更新..."
|
||||||
|
# 删除所有包含restart_unhealthy.sh的行
|
||||||
|
grep -v "restart_unhealthy.sh" "$temp_cron" > "$temp_cron.new"
|
||||||
|
mv "$temp_cron.new" "$temp_cron"
|
||||||
|
log_info "旧的自动重启定时任务已删除"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加新的定时任务(每2分钟执行一次)
|
||||||
|
echo "# Argus-Metrics 自动重启定时任务" >> "$temp_cron"
|
||||||
|
echo "*/2 * * * * $restart_script >> $INSTALL_DIR/.restart.log 2>&1" >> "$temp_cron"
|
||||||
|
|
||||||
|
# 安装新的crontab
|
||||||
|
if crontab "$temp_cron"; then
|
||||||
|
log_success "自动重启定时任务设置成功"
|
||||||
|
log_info " 执行频率: 每2分钟"
|
||||||
|
log_info " 日志文件: $INSTALL_DIR/.restart.log"
|
||||||
|
log_info " 查看定时任务: crontab -l"
|
||||||
|
log_info " 删除定时任务: crontab -e"
|
||||||
|
else
|
||||||
|
log_error "自动重启定时任务设置失败"
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f "$temp_cron"
|
||||||
|
|
||||||
|
log_info "自动重启检查通过crontab自动执行"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示安装信息
|
||||||
|
show_install_info() {
|
||||||
|
log_success "Argus-Metrics All-in-One 安装完成!"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -d "$TEMP_DIR" ]]; then
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# 主函数
|
||||||
|
main() {
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Argus-Metrics All-in-One 安装脚本 v1.0"
|
||||||
|
echo "=========================================="
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 加载配置文件
|
||||||
|
load_config
|
||||||
|
|
||||||
|
log_info "安装目录: $INSTALL_DIR"
|
||||||
|
echo
|
||||||
|
|
||||||
|
check_root
|
||||||
|
check_system
|
||||||
|
find_version_file
|
||||||
|
create_install_dirs
|
||||||
|
install_system_deps
|
||||||
|
parse_version_info
|
||||||
|
verify_checksums
|
||||||
|
install_components
|
||||||
|
copy_config_files
|
||||||
|
create_install_record
|
||||||
|
setup_health_check_cron
|
||||||
|
setup_dns_sync_cron
|
||||||
|
setup_version_check_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
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
474
src/metric/client-plugins/all-in-one-demo/scripts/package_artifact.sh
Executable file
474
src/metric/client-plugins/all-in-one-demo/scripts/package_artifact.sh
Executable file
@@ -0,0 +1,474 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 日志函数
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示帮助信息
|
||||||
|
show_help() {
|
||||||
|
echo "AIOps All-in-One 打包脚本"
|
||||||
|
echo
|
||||||
|
echo "用法: $0 [选项]"
|
||||||
|
echo
|
||||||
|
echo "选项:"
|
||||||
|
echo " --force 强制重新打包,即使版本已存在"
|
||||||
|
echo " --help 显示此帮助信息"
|
||||||
|
echo
|
||||||
|
echo "示例:"
|
||||||
|
echo " $0 # 正常打包,跳过已存在的版本"
|
||||||
|
echo " $0 --force # 强制重新打包"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
FORCE_PACKAGE=false
|
||||||
|
if [[ "$1" == "--force" ]]; then
|
||||||
|
FORCE_PACKAGE=true
|
||||||
|
log_info "强制重新打包模式"
|
||||||
|
elif [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 获取当前目录和版本
|
||||||
|
CURRENT_DIR=$(pwd)
|
||||||
|
VERSION=$(cat config/VERSION 2>/dev/null || echo "1.0.0")
|
||||||
|
ARTIFACT_DIR="artifact/$VERSION"
|
||||||
|
|
||||||
|
log_info "开始打包 AIOps All-in-One 安装包 v$VERSION"
|
||||||
|
|
||||||
|
# 检查必要文件
|
||||||
|
log_info "检查必要文件..."
|
||||||
|
if [[ ! -f "config/VERSION" ]]; then
|
||||||
|
log_error "VERSION 文件不存在"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "config/checklist" ]]; then
|
||||||
|
log_error "checklist 文件不存在"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查是否已存在该版本
|
||||||
|
if [[ -d "$ARTIFACT_DIR" && "$FORCE_PACKAGE" == "false" ]]; then
|
||||||
|
log_info "检查版本 $VERSION 是否已存在..."
|
||||||
|
|
||||||
|
# 检查 version.json 是否存在
|
||||||
|
if [[ -f "$ARTIFACT_DIR/version.json" ]]; then
|
||||||
|
log_info "找到已存在的版本信息文件"
|
||||||
|
|
||||||
|
# 检查是否所有组件文件都存在
|
||||||
|
missing_files=0
|
||||||
|
existing_components=0
|
||||||
|
|
||||||
|
# 解析已存在的 version.json 来检查文件
|
||||||
|
if command -v jq &> /dev/null; then
|
||||||
|
# 使用 jq 解析
|
||||||
|
while IFS= read -r component; do
|
||||||
|
existing_components=$((existing_components + 1))
|
||||||
|
# 查找对应的 tar 文件
|
||||||
|
found_file=false
|
||||||
|
for file in "$ARTIFACT_DIR/${component}-"*.tar.gz; do
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
found_file=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$found_file" == "false" ]]; then
|
||||||
|
missing_files=$((missing_files + 1))
|
||||||
|
log_warning " 缺少文件: $component"
|
||||||
|
fi
|
||||||
|
done < <(jq -r '.artifact_list | keys[]' "$ARTIFACT_DIR/version.json" 2>/dev/null)
|
||||||
|
else
|
||||||
|
# 简单的文件检查
|
||||||
|
for file in "$ARTIFACT_DIR"/*.tar.gz; do
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
existing_components=$((existing_components + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 如果所有文件都存在,则跳过打包
|
||||||
|
if [[ $missing_files -eq 0 && $existing_components -gt 0 ]]; then
|
||||||
|
log_success "版本 $VERSION 已完整打包,跳过重复打包"
|
||||||
|
echo
|
||||||
|
echo "现有文件:"
|
||||||
|
ls -la "$ARTIFACT_DIR"
|
||||||
|
echo
|
||||||
|
echo "如需强制重新打包,请删除目录: rm -rf $ARTIFACT_DIR"
|
||||||
|
echo "或使用: ./package.sh --force"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
log_warning "版本 $VERSION 存在但不完整,将重新打包"
|
||||||
|
log_info " 现有组件: $existing_components"
|
||||||
|
log_info " 缺少文件: $missing_files"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warning "版本目录存在但缺少 version.json,将重新打包"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建 artifact 目录
|
||||||
|
mkdir -p "$ARTIFACT_DIR"
|
||||||
|
log_info "创建输出目录: $ARTIFACT_DIR"
|
||||||
|
|
||||||
|
# 创建临时文件存储数据
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
COMPONENTS_FILE="$TEMP_DIR/components.txt"
|
||||||
|
VERSIONS_FILE="$TEMP_DIR/versions.txt"
|
||||||
|
DEPENDENCIES_FILE="$TEMP_DIR/dependencies.txt"
|
||||||
|
INSTALL_ORDER_FILE="$TEMP_DIR/install_order.txt"
|
||||||
|
CHECKSUMS_FILE="$TEMP_DIR/checksums.txt"
|
||||||
|
ARTIFACT_LIST_FILE="$TEMP_DIR/artifact_list.txt"
|
||||||
|
|
||||||
|
# 解析 checklist 文件
|
||||||
|
log_info "解析组件清单..."
|
||||||
|
line_num=0
|
||||||
|
component_count=0
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
|
||||||
|
|
||||||
|
line_num=$((line_num + 1))
|
||||||
|
|
||||||
|
# 解析行: 组件名 目录路径 版本 [依赖组件] [安装顺序]
|
||||||
|
read -r component component_path version dep_component order <<< "$line"
|
||||||
|
|
||||||
|
if [[ -z "$component" || -z "$component_path" || -z "$version" ]]; then
|
||||||
|
log_warning "跳过无效行 $line_num: $line"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 存储组件信息
|
||||||
|
echo "$component" >> "$COMPONENTS_FILE"
|
||||||
|
echo "$component:$version" >> "$VERSIONS_FILE"
|
||||||
|
echo "$component:$component_path" >> "$TEMP_DIR/component_paths.txt"
|
||||||
|
|
||||||
|
if [[ -n "$dep_component" && "$dep_component" != "$component" ]]; then
|
||||||
|
echo "$component:$dep_component" >> "$DEPENDENCIES_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$order" && "$order" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "$order:$component" >> "$INSTALL_ORDER_FILE"
|
||||||
|
else
|
||||||
|
# 如果没有指定顺序,按解析顺序分配
|
||||||
|
echo "$line_num:$component" >> "$INSTALL_ORDER_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
component_count=$((component_count + 1))
|
||||||
|
log_info " - $component v$version"
|
||||||
|
done < config/checklist
|
||||||
|
|
||||||
|
if [[ $component_count -eq 0 ]]; then
|
||||||
|
log_error "没有找到有效的组件"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "找到 $component_count 个组件"
|
||||||
|
|
||||||
|
# 检查组件目录是否存在
|
||||||
|
log_info "检查组件目录..."
|
||||||
|
missing_components=()
|
||||||
|
|
||||||
|
while IFS= read -r component; do
|
||||||
|
# 获取组件路径
|
||||||
|
component_path=$(grep "^$component:" "$TEMP_DIR/component_paths.txt" | cut -d':' -f2-)
|
||||||
|
if [[ -z "$component_path" ]]; then
|
||||||
|
log_error "未找到组件 $component 的路径配置"
|
||||||
|
log_info "请检查 component_paths.txt 文件或添加路径配置"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "$component_path" ]]; then
|
||||||
|
missing_components+=("$component:$component_path")
|
||||||
|
fi
|
||||||
|
done < "$COMPONENTS_FILE"
|
||||||
|
|
||||||
|
if [[ ${#missing_components[@]} -gt 0 ]]; then
|
||||||
|
log_error "以下组件目录不存在:"
|
||||||
|
for component_path in "${missing_components[@]}"; do
|
||||||
|
echo " - $component_path"
|
||||||
|
done
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 打包各个组件
|
||||||
|
log_info "开始打包组件..."
|
||||||
|
|
||||||
|
while IFS= read -r component; do
|
||||||
|
# 获取组件版本和路径
|
||||||
|
version=$(grep "^$component:" "$VERSIONS_FILE" | cut -d':' -f2)
|
||||||
|
component_path=$(grep "^$component:" "$TEMP_DIR/component_paths.txt" | cut -d':' -f2-)
|
||||||
|
if [[ -z "$component_path" ]]; then
|
||||||
|
log_error "未找到组件 $component 的路径配置"
|
||||||
|
log_info "请检查 component_paths.txt 文件或添加路径配置"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "打包 $component v$version..."
|
||||||
|
log_info " 组件路径: $component_path"
|
||||||
|
|
||||||
|
# 进入组件目录
|
||||||
|
cd "$component_path"
|
||||||
|
|
||||||
|
# 检查组件是否有 package.sh
|
||||||
|
if [[ ! -f "package.sh" ]]; then
|
||||||
|
log_error "$component 缺少 package.sh 文件"
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 执行组件的打包脚本
|
||||||
|
if ./package.sh; then
|
||||||
|
# 查找生成的 tar 包
|
||||||
|
tar_file=$(find . -name "*.tar.gz" -type f | head -1)
|
||||||
|
if [[ -n "$tar_file" ]]; then
|
||||||
|
# 移动到 artifact 目录
|
||||||
|
mv "$tar_file" "$CURRENT_DIR/$ARTIFACT_DIR/"
|
||||||
|
tar_filename=$(basename "$tar_file")
|
||||||
|
|
||||||
|
# 计算校验和
|
||||||
|
checksum=$(sha256sum "$CURRENT_DIR/$ARTIFACT_DIR/$tar_filename" | cut -d' ' -f1)
|
||||||
|
echo "$component:sha256:$checksum" >> "$CHECKSUMS_FILE"
|
||||||
|
echo "$component:$version" >> "$ARTIFACT_LIST_FILE"
|
||||||
|
|
||||||
|
# 将完整的文件名存储到安装顺序文件中
|
||||||
|
echo "$tar_filename" >> "$TEMP_DIR/install_order_files.txt"
|
||||||
|
|
||||||
|
log_success " $component 打包完成: $tar_filename"
|
||||||
|
else
|
||||||
|
log_error "$component 打包失败,未找到生成的 tar 包"
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "$component 打包失败"
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 返回主目录
|
||||||
|
cd "$CURRENT_DIR"
|
||||||
|
done < "$COMPONENTS_FILE"
|
||||||
|
|
||||||
|
# 生成 version.json
|
||||||
|
log_info "生成版本信息文件..."
|
||||||
|
version_json="$ARTIFACT_DIR/version.json"
|
||||||
|
|
||||||
|
# 构建依赖关系 JSON
|
||||||
|
deps_json=""
|
||||||
|
if [[ -f "$DEPENDENCIES_FILE" ]]; then
|
||||||
|
first=true
|
||||||
|
while IFS= read -r line; do
|
||||||
|
component=$(echo "$line" | cut -d':' -f1)
|
||||||
|
dep=$(echo "$line" | cut -d':' -f2)
|
||||||
|
if [[ "$first" == "true" ]]; then
|
||||||
|
deps_json="\"$component\":[\"$dep\"]"
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
deps_json="$deps_json,\"$component\":[\"$dep\"]"
|
||||||
|
fi
|
||||||
|
done < "$DEPENDENCIES_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建安装顺序数组
|
||||||
|
order_array=""
|
||||||
|
if [[ -f "$TEMP_DIR/install_order_files.txt" ]]; then
|
||||||
|
first=true
|
||||||
|
while IFS= read -r filename; do
|
||||||
|
if [[ "$first" == "true" ]]; then
|
||||||
|
order_array="\"$filename\""
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
order_array="$order_array,\"$filename\""
|
||||||
|
fi
|
||||||
|
done < "$TEMP_DIR/install_order_files.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建 artifact_list JSON
|
||||||
|
artifact_json=""
|
||||||
|
if [[ -f "$ARTIFACT_LIST_FILE" ]]; then
|
||||||
|
first=true
|
||||||
|
while IFS= read -r line; do
|
||||||
|
component=$(echo "$line" | cut -d':' -f1)
|
||||||
|
version=$(echo "$line" | cut -d':' -f2)
|
||||||
|
if [[ "$first" == "true" ]]; then
|
||||||
|
artifact_json="\"$component\":\"$version\""
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
artifact_json="$artifact_json,\"$component\":\"$version\""
|
||||||
|
fi
|
||||||
|
done < "$ARTIFACT_LIST_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建 checksums JSON
|
||||||
|
checksums_json=""
|
||||||
|
if [[ -f "$CHECKSUMS_FILE" ]]; then
|
||||||
|
first=true
|
||||||
|
while IFS= read -r line; do
|
||||||
|
component=$(echo "$line" | cut -d':' -f1)
|
||||||
|
checksum=$(echo "$line" | cut -d':' -f2-)
|
||||||
|
if [[ "$first" == "true" ]]; then
|
||||||
|
checksums_json="\"$component\":\"$checksum\""
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
checksums_json="$checksums_json,\"$component\":\"$checksum\""
|
||||||
|
fi
|
||||||
|
done < "$CHECKSUMS_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 生成完整的 version.json
|
||||||
|
cat > "$version_json" << EOF
|
||||||
|
{
|
||||||
|
"version": "$VERSION",
|
||||||
|
"build_time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
|
"artifact_list": {
|
||||||
|
$artifact_json
|
||||||
|
},
|
||||||
|
"checksums": {
|
||||||
|
$checksums_json
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
$deps_json
|
||||||
|
},
|
||||||
|
"install_order": [
|
||||||
|
$order_array
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_success "版本信息文件生成完成: $version_json"
|
||||||
|
|
||||||
|
# 复制`安装`脚本到 artifact 目录
|
||||||
|
log_info "复制安装脚本..."
|
||||||
|
if [[ -f "scripts/install_artifact.sh" ]]; then
|
||||||
|
cp "scripts/install_artifact.sh" "$ARTIFACT_DIR/install.sh"
|
||||||
|
chmod +x "$ARTIFACT_DIR/install.sh"
|
||||||
|
log_success "安装脚本复制完成: $ARTIFACT_DIR/install.sh"
|
||||||
|
else
|
||||||
|
log_warning "scripts/install_artifact.sh 文件不存在"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制`卸载`脚本到 artifact 目录
|
||||||
|
log_info "复制卸载脚本..."
|
||||||
|
if [[ -f "scripts/uninstall_artifact.sh" ]]; then
|
||||||
|
cp "scripts/uninstall_artifact.sh" "$ARTIFACT_DIR/uninstall.sh"
|
||||||
|
chmod +x "$ARTIFACT_DIR/uninstall.sh"
|
||||||
|
log_success "卸载脚本复制完成: $ARTIFACT_DIR/uninstall.sh"
|
||||||
|
else
|
||||||
|
log_warning "scripts/uninstall_artifact.sh 文件不存在"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制`健康检查`脚本到 artifact 目录
|
||||||
|
log_info "复制健康检查脚本..."
|
||||||
|
if [[ -f "scripts/check_health.sh" ]]; then
|
||||||
|
cp "scripts/check_health.sh" "$ARTIFACT_DIR/check_health.sh"
|
||||||
|
chmod +x "$ARTIFACT_DIR/check_health.sh"
|
||||||
|
log_success "健康检查脚本复制完成: $ARTIFACT_DIR/check_health.sh"
|
||||||
|
else
|
||||||
|
log_warning "scripts/check_health.sh 文件不存在"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制`DNS 同步`脚本到 artifact 目录
|
||||||
|
log_info "复制 DNS 同步脚本..."
|
||||||
|
if [[ -f "scripts/sync_dns.sh" ]]; then
|
||||||
|
cp "scripts/sync_dns.sh" "$ARTIFACT_DIR/sync_dns.sh"
|
||||||
|
chmod +x "$ARTIFACT_DIR/sync_dns.sh"
|
||||||
|
log_success "DNS 同步脚本复制完成: $ARTIFACT_DIR/sync_dns.sh"
|
||||||
|
else
|
||||||
|
log_warning "scripts/sync_dns.sh 文件不存在"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制`版本校验`脚本到 artifact 目录
|
||||||
|
log_info "复制版本校验脚本..."
|
||||||
|
if [[ -f "scripts/check_version.sh" ]]; then
|
||||||
|
cp "scripts/check_version.sh" "$ARTIFACT_DIR/check_version.sh"
|
||||||
|
chmod +x "$ARTIFACT_DIR/check_version.sh"
|
||||||
|
log_success "版本校验脚本复制完成: $ARTIFACT_DIR/check_version.sh"
|
||||||
|
else
|
||||||
|
log_warning "scripts/check_version.sh 文件不存在"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制`自动重启`脚本到 artifact 目录
|
||||||
|
log_info "复制自动重启脚本..."
|
||||||
|
if [[ -f "scripts/restart_unhealthy.sh" ]]; then
|
||||||
|
cp "scripts/restart_unhealthy.sh" "$ARTIFACT_DIR/restart_unhealthy.sh"
|
||||||
|
chmod +x "$ARTIFACT_DIR/restart_unhealthy.sh"
|
||||||
|
log_success "自动重启脚本复制完成: $ARTIFACT_DIR/restart_unhealthy.sh"
|
||||||
|
else
|
||||||
|
log_warning "scripts/restart_unhealthy.sh 文件不存在"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制配置文件到 artifact 目录
|
||||||
|
log_info "复制配置文件..."
|
||||||
|
if [[ -f "config/config.env" ]]; then
|
||||||
|
cp "config/config.env" "$ARTIFACT_DIR/"
|
||||||
|
log_success "配置文件复制完成: $ARTIFACT_DIR/config.env"
|
||||||
|
else
|
||||||
|
log_warning "config 目录不存在,跳过配置文件复制"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# DNS 配置文件不需要复制到版本目录,直接从 FTP 服务器根目录获取
|
||||||
|
|
||||||
|
# 复制 deps 目录到 artifact 目录
|
||||||
|
log_info "复制系统依赖包..."
|
||||||
|
if [[ -d "deps" ]]; then
|
||||||
|
cp -r "deps" "$ARTIFACT_DIR/"
|
||||||
|
log_success "系统依赖包复制完成: $ARTIFACT_DIR/deps"
|
||||||
|
|
||||||
|
# 显示deps目录内容
|
||||||
|
log_info " 依赖包列表:"
|
||||||
|
find "$ARTIFACT_DIR/deps" -name "*.tar.gz" -exec basename {} \; | while read dep_file; do
|
||||||
|
log_info " - $dep_file"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_warning "deps 目录不存在,跳过依赖包复制"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 显示打包结果
|
||||||
|
log_success "打包完成!"
|
||||||
|
echo
|
||||||
|
echo "版本: $VERSION"
|
||||||
|
echo "输出目录: $ARTIFACT_DIR"
|
||||||
|
echo "包含组件:"
|
||||||
|
if [[ -f "$ARTIFACT_LIST_FILE" ]]; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
component=$(echo "$line" | cut -d':' -f1)
|
||||||
|
version=$(echo "$line" | cut -d':' -f2)
|
||||||
|
echo " - $component v$version"
|
||||||
|
done < "$ARTIFACT_LIST_FILE"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "文件列表:"
|
||||||
|
ls -la "$ARTIFACT_DIR"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user