Compare commits
29 Commits
dev_1.0.0_
...
bc016826f1
| Author | SHA1 | Date | |
|---|---|---|---|
| bc016826f1 | |||
| 5e5342ce20 | |||
| f1f4ba6bb6 | |||
| a019aeccca | |||
|
|
d5194f64d0 | ||
|
|
a531383d09 | ||
|
|
4eaeb5f93a | ||
|
|
153a91a458 | ||
|
|
49e01fc472 | ||
|
|
05bfde9d37 | ||
|
|
3e85e50e78 | ||
|
|
f8814cd705 | ||
|
|
6771398b5b | ||
|
|
d6dfdc3796 | ||
|
|
40af0e74b7 | ||
|
|
eb31643a87 | ||
|
|
ddec4cf1b0 | ||
|
|
8391108327 | ||
|
|
2faa13c567 | ||
|
|
23a92d7e9c | ||
|
|
a9281639bf | ||
|
|
1fbf725d50 | ||
|
|
3e9cfa564c | ||
|
|
d61f59b198 | ||
|
|
ee473439d5 | ||
|
|
a520b2a33d | ||
|
|
31a6ab9803 | ||
|
|
653a0c04a6 | ||
| 47af89c54d |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
|||||||
src/metric/client-plugins/all-in-one-full/plugins/*/bin/* filter=lfs diff=lfs merge=lfs -text
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
.idea/
|
|
||||||
@@ -5,10 +5,3 @@
|
|||||||
项目文档:【腾讯文档】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`。
|
|
||||||
|
|||||||
@@ -1,409 +1,138 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 帮助信息
|
||||||
show_help() {
|
show_help() {
|
||||||
cat <<'EOF'
|
cat << EOF
|
||||||
ARGUS Unified Build System - Image Build Tool
|
ARGUS Unified Build System - Image Build Tool
|
||||||
|
|
||||||
Usage: $0 [OPTIONS]
|
Usage: $0 [OPTIONS]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--intranet Use intranet mirror for log/bind builds
|
--intranet Use intranet mirror for Ubuntu 22.04 packages
|
||||||
--master-offline Build master offline image (requires src/master/offline_wheels.tar.gz)
|
-h, --help Show this help message
|
||||||
--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
|
|
||||||
|
|
||||||
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 --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_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
|
||||||
--intranet)
|
--intranet)
|
||||||
use_intranet=true
|
use_intranet=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--master)
|
-h|--help)
|
||||||
build_master=true
|
show_help
|
||||||
shift
|
exit 0
|
||||||
;;
|
;;
|
||||||
--master-offline)
|
*)
|
||||||
build_master=true
|
echo "Unknown option: $1"
|
||||||
build_master_offline=true
|
show_help
|
||||||
shift
|
exit 1
|
||||||
;;
|
;;
|
||||||
--metric)
|
esac
|
||||||
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)
|
|
||||||
show_help
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown option: $1" >&2
|
|
||||||
show_help
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
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_offline_tar="$master_root/offline_wheels.tar.gz"
|
|
||||||
master_offline_dir="$master_root/offline_wheels"
|
|
||||||
|
|
||||||
if [[ "$build_master_offline" == true ]]; then
|
|
||||||
if [[ ! -f "$master_offline_tar" ]]; then
|
|
||||||
echo "❌ offline wheels tar not found: $master_offline_tar" >&2
|
|
||||||
echo " 请提前准备好 offline_wheels.tar.gz 后再执行 --master-offline" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "📦 Preparing offline wheels for master (extracting $master_offline_tar)"
|
|
||||||
rm -rf "$master_offline_dir"
|
|
||||||
mkdir -p "$master_offline_dir"
|
|
||||||
tar -xzf "$master_offline_tar" -C "$master_root"
|
|
||||||
has_wheel=$(find "$master_offline_dir" -maxdepth 1 -type f -name '*.whl' -print -quit)
|
|
||||||
if [[ -z "$has_wheel" ]]; then
|
|
||||||
echo "❌ offline_wheels extraction failed或无 wheel: $master_offline_dir" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
echo "ARGUS Unified Build System"
|
echo "ARGUS Unified Build System"
|
||||||
echo "======================================="
|
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 ""
|
||||||
|
|
||||||
|
# 构建镜像的函数
|
||||||
build_image() {
|
build_image() {
|
||||||
local image_name=$1
|
local image_name=$1
|
||||||
local dockerfile_path=$2
|
local dockerfile_path=$2
|
||||||
local tag=$3
|
local tag=$3
|
||||||
local context="."
|
|
||||||
shift 3
|
|
||||||
|
|
||||||
if [[ $# -gt 0 ]]; then
|
echo "🔄 Building $image_name image..."
|
||||||
context=$1
|
echo " Dockerfile: $dockerfile_path"
|
||||||
shift
|
echo " Tag: $tag"
|
||||||
fi
|
|
||||||
|
|
||||||
local extra_args=("$@")
|
if docker build $build_args -f "$dockerfile_path" -t "$tag" .; then
|
||||||
|
echo "✅ $image_name image built successfully"
|
||||||
echo "🔄 Building $image_name image..."
|
return 0
|
||||||
echo " Dockerfile: $dockerfile_path"
|
else
|
||||||
echo " Tag: $tag"
|
echo "❌ Failed to build $image_name image"
|
||||||
echo " Context: $context"
|
return 1
|
||||||
|
fi
|
||||||
if docker build "${build_args[@]}" "${extra_args[@]}" -f "$dockerfile_path" -t "$tag" "$context"; then
|
|
||||||
echo "✅ $image_name image built successfully"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo "❌ Failed to build $image_name image"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
pull_base_image() {
|
|
||||||
local image_ref=$1
|
|
||||||
local attempts=${2:-3}
|
|
||||||
local delay=${3:-5}
|
|
||||||
|
|
||||||
# If the image already exists locally, skip pulling.
|
|
||||||
if docker image inspect "$image_ref" >/dev/null 2>&1; then
|
|
||||||
echo " Local image present; skip pull: $image_ref"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
for ((i=1; i<=attempts; i++)); do
|
|
||||||
echo " Pulling base image ($i/$attempts): $image_ref"
|
|
||||||
if docker pull "$image_ref" >/dev/null; then
|
|
||||||
echo " Base image ready: $image_ref"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo " Pull failed: $image_ref"
|
|
||||||
if (( i < attempts )); then
|
|
||||||
echo " Retrying in ${delay}s..."
|
|
||||||
sleep "$delay"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "❌ Unable to pull base image after ${attempts} attempts: $image_ref"
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 构建所有镜像
|
||||||
images_built=()
|
images_built=()
|
||||||
build_failed=false
|
build_failed=false
|
||||||
|
|
||||||
if [[ "$build_core" == true ]]; then
|
# 构建 Elasticsearch 镜像
|
||||||
if build_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest"; then
|
if build_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest"; then
|
||||||
images_built+=("argus-elasticsearch:latest")
|
images_built+=("argus-elasticsearch:latest")
|
||||||
else
|
else
|
||||||
build_failed=true
|
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_master" == true ]]; then
|
# 构建 Kibana 镜像
|
||||||
echo ""
|
if build_image "Kibana" "src/log/kibana/build/Dockerfile" "argus-kibana:latest"; then
|
||||||
echo "🔄 Building Master image..."
|
images_built+=("argus-kibana:latest")
|
||||||
pushd "$master_root" >/dev/null
|
else
|
||||||
master_args=("--tag" "argus-master:latest")
|
|
||||||
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
|
|
||||||
build_failed=true
|
build_failed=true
|
||||||
fi
|
|
||||||
popd >/dev/null
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$build_metric" == true ]]; then
|
echo ""
|
||||||
echo ""
|
|
||||||
echo "Building Metric module images..."
|
|
||||||
|
|
||||||
metric_base_images=(
|
# 构建 BIND9 镜像
|
||||||
"ubuntu:22.04"
|
if build_image "BIND9" "src/bind/build/Dockerfile" "argus-bind9:latest"; then
|
||||||
"ubuntu/prometheus:3-24.04_stable"
|
images_built+=("argus-bind9:latest")
|
||||||
"grafana/grafana:11.1.0"
|
else
|
||||||
)
|
build_failed=true
|
||||||
|
|
||||||
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
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
echo "📦 Build Summary"
|
echo "📦 Build Summary"
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
|
|
||||||
if [[ ${#images_built[@]} -gt 0 ]]; then
|
if [[ ${#images_built[@]} -gt 0 ]]; then
|
||||||
echo "✅ Successfully built images:"
|
echo "✅ Successfully built images:"
|
||||||
for image in "${images_built[@]}"; do
|
for image in "${images_built[@]}"; do
|
||||||
echo " • $image"
|
echo " • $image"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$build_failed" == true ]]; then
|
if [[ "$build_failed" == true ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "❌ Some images failed to build. Please check the errors above."
|
echo "❌ Some images failed to build. Please check the errors above."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$use_intranet" == true ]]; then
|
if [[ "$use_intranet" == true ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "🌐 Built with intranet mirror configuration"
|
echo "🌐 Built with intranet mirror configuration"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$build_master_offline" == true ]]; then
|
|
||||||
echo ""
|
|
||||||
echo "🧳 Master offline wheels 已解压到 $master_offline_dir"
|
|
||||||
fi
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🚀 Next steps:"
|
echo "🚀 Next steps:"
|
||||||
echo " ./build/save_images.sh --compress # 导出镜像"
|
echo " cd src/log && ./scripts/save_images.sh # Export log images"
|
||||||
echo " cd src/master/tests && MASTER_IMAGE_TAG=argus-master:offline ./scripts/00_e2e_test.sh"
|
echo " cd src/bind && ./scripts/save_images.sh # Export bind images"
|
||||||
echo ""
|
echo " cd src/log/tests && ./scripts/02_up.sh # Start log services"
|
||||||
|
echo ""
|
||||||
@@ -67,13 +67,6 @@ declare -A images=(
|
|||||||
["argus-elasticsearch:latest"]="argus-elasticsearch-latest.tar"
|
["argus-elasticsearch:latest"]="argus-elasticsearch-latest.tar"
|
||||||
["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-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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 函数:检查镜像是否存在
|
# 函数:检查镜像是否存在
|
||||||
@@ -226,4 +219,4 @@ fi
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ Image export completed successfully!"
|
echo "✅ Image export completed successfully!"
|
||||||
echo ""
|
echo ""
|
||||||
2
configs/.gitignore
vendored
2
configs/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
# Local overrides for build user/group settings
|
|
||||||
build_user.local.conf
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# 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` 强制覆盖。
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
#!/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}"
|
|
||||||
}
|
|
||||||
5
src/agent/.gitignore
vendored
5
src/agent/.gitignore
vendored
@@ -1,5 +0,0 @@
|
|||||||
build/
|
|
||||||
*.egg-info/
|
|
||||||
__pycache__/
|
|
||||||
|
|
||||||
.env
|
|
||||||
23
src/agent/Dockerfile
Normal file
23
src/agent/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
|
ARG PIP_INDEX_URL=
|
||||||
|
ENV PIP_NO_CACHE_DIR=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONPATH=/app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN set -euxo pipefail \
|
||||||
|
&& python -m pip install --upgrade pip \
|
||||||
|
&& if [[ -n "$PIP_INDEX_URL" ]]; then \
|
||||||
|
PIP_INDEX_URL="$PIP_INDEX_URL" python -m pip install -r requirements.txt; \
|
||||||
|
else \
|
||||||
|
python -m pip install -r requirements.txt; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
COPY app ./app
|
||||||
|
|
||||||
|
CMD ["python", "-m", "app.main"]
|
||||||
@@ -1,78 +1,31 @@
|
|||||||
# Argus Agent 模块
|
# Argus Agent Module
|
||||||
|
|
||||||
Argus Agent 是一个轻量级 Python 进程,负责向 Argus Master 注册节点、汇报健康数据,并维护本地持久化信息。模块现以 PyInstaller 打包为独立可执行文件,便于在普通容器或虚机中直接运行。
|
Python agent that registers with the Argus master service, persists node information, gathers host metadata, reads module health files, and periodically reports status.
|
||||||
|
|
||||||
## 构建可执行文件
|
## Build & Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src/agent
|
cd src/agent
|
||||||
./scripts/build_binary.sh # 生成 dist/argus-agent
|
./scripts/build_images.sh # builds argus-agent:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
脚本默认会在 Docker 容器 (`python:3.11-slim-bullseye`) 内执行 PyInstaller,确保产物运行时兼容 glibc 2.31+(覆盖 2.35 环境)。构建流程注意事项:
|
Runtime expects a configuration file (generated by installer) at `/private/argus/agent/<hostname>/config`. Key fields:
|
||||||
|
|
||||||
- 每次构建前会清理 `build/`、`dist/` 并在容器内重新创建虚拟环境。
|
- `HOSTNAME`, `NODE_FILE`, `VERSION`
|
||||||
- 需要使用内网 Python 镜像时,可通过 `PIP_INDEX_URL`、`PIP_EXTRA_INDEX_URL`、`PIP_TRUSTED_HOST` 等环境变量传入,脚本会自动透传给容器。
|
- `MASTER_ENDPOINT` (e.g. `http://master:3000`)
|
||||||
- 如果宿主机无法运行 Docker,可设置 `AGENT_BUILD_USE_DOCKER=0` 回退到本地构建;此时代码必须在 glibc ≤ 2.35 的机器上执行。
|
- `REPORT_INTERVAL_SECONDS`
|
||||||
|
- `SUBMODULE_HEALTH_FILE_DIR` (supports `{hostname}` placeholder)
|
||||||
|
- optional `GPU_NUMBER`
|
||||||
|
|
||||||
构建结束后脚本会在 `build/compat_check/` 下解包关键动态库并输出最高 `GLIBC_x.y` 版本,便于快速核对兼容性。如果结果中缺少 `libssl.so.3` / `libcrypto.so.3`,表示系统会在目标宿主机上使用本地 OpenSSL 库,无需额外处理。
|
Health files live under `/private/argus/agent/health/<hostname>/` and must follow `<prefix>-*.json` naming (e.g. `log-fluentbit.json`). The agent sends parsed JSON objects keyed by file stem.
|
||||||
|
|
||||||
例如:
|
## Tests
|
||||||
|
|
||||||
```bash
|
Docker-based E2E stack (master + agent):
|
||||||
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 包镜像(先设置上述镜像环境变量后重试)。
|
|
||||||
|
|
||||||
## 运行时配置
|
|
||||||
|
|
||||||
Agent 不再依赖配置文件;所有参数均由环境变量与主机名推导:
|
|
||||||
|
|
||||||
| 变量 | 必填 | 默认值 | 说明 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `MASTER_ENDPOINT` | 是 | N/A | Master 基础地址,可写 `http://host:3000` 或 `host:3000`(自动补全 `http://`)。 |
|
|
||||||
| `REPORT_INTERVAL_SECONDS` | 否 | `60` | 状态上报间隔(秒)。必须为正整数。 |
|
|
||||||
| `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>/health/`
|
|
||||||
|
|
||||||
健康目录中的文件需遵循 `<模块前缀>-*.json` 命名(例如 `log-fluentbit.json`、`metric-node-exporter.json`),文件内容会原样并入上报的 `health` 字段。
|
|
||||||
|
|
||||||
## 日志与持久化
|
|
||||||
|
|
||||||
- Agent 会在成功注册、状态上报、异常重试等关键节点输出结构化日志,便于聚合分析。
|
|
||||||
- `node.json` 保存 Master 返回的最新节点对象,用于重启后继续使用既有节点 ID。
|
|
||||||
|
|
||||||
## 端到端测试
|
|
||||||
|
|
||||||
仓库内提供 Docker Compose 测试栈(master + ubuntu 容器):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src/agent/tests
|
cd src/agent/tests
|
||||||
./scripts/00_e2e_test.sh
|
./scripts/00_e2e_test.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
测试脚本会:
|
The scripts provision configs/health directories under `tests/private/` and clean up via `07_down.sh`.
|
||||||
|
|
||||||
1. 构建 master 镜像与 agent 可执行文件。
|
|
||||||
2. 以 `ubuntu:24.04` 启动 agent 容器,并通过环境变量注入 `MASTER_ENDPOINT`、`REPORT_INTERVAL_SECONDS`。
|
|
||||||
3. 验证注册、健康上报、nodes.json 生成、统计接口,以及“容器重启 + IP 变化”重注册流程。
|
|
||||||
4. 清理 `tests/private/` 与临时容器网络。
|
|
||||||
|
|
||||||
如需在真实环境部署,只需将 `dist/argus-agent` 连同健康目录挂载到目标主机,并按上表设置环境变量即可。
|
|
||||||
|
|||||||
@@ -18,15 +18,16 @@ _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": config.environment,
|
"env": env,
|
||||||
"user": config.user,
|
"user": user,
|
||||||
"instance": config.instance,
|
"instance": 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(config),
|
||||||
}
|
}
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
@@ -69,8 +70,11 @@ def _detect_memory_bytes() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _detect_gpu_count() -> int:
|
def _detect_gpu_count(config: AgentConfig) -> int:
|
||||||
"""采集 GPU 数量,如无法探测则默认为 0。"""
|
"""采集 GPU 数量,可被配置覆盖。"""
|
||||||
|
if config.gpu_number_override is not None:
|
||||||
|
return config.gpu_number_override
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["nvidia-smi", "-L"],
|
["nvidia-smi", "-L"],
|
||||||
|
|||||||
@@ -1,141 +1,101 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
|
||||||
|
|
||||||
from .state import load_node_state
|
|
||||||
from .version import VERSION
|
|
||||||
from .log import get_logger
|
|
||||||
|
|
||||||
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
|
||||||
report_interval_seconds: int
|
report_interval_seconds: int
|
||||||
health_dir: str
|
health_dir_template: str
|
||||||
|
gpu_number_override: int | None
|
||||||
request_timeout_seconds: int = 10
|
request_timeout_seconds: int = 10
|
||||||
|
|
||||||
|
@property
|
||||||
def _normalise_master_endpoint(value: str) -> str:
|
def health_dir(self) -> str:
|
||||||
value = value.strip()
|
return self.health_dir_template.format(hostname=self.hostname)
|
||||||
if not value:
|
|
||||||
raise ValueError("MASTER_ENDPOINT environment variable is required")
|
|
||||||
if not value.startswith("http://") and not value.startswith("https://"):
|
|
||||||
value = f"http://{value}"
|
|
||||||
return value.rstrip("/")
|
|
||||||
|
|
||||||
|
|
||||||
def _read_report_interval(raw_value: str | None) -> int:
|
def _parse_config_file(path: str) -> dict[str, str]:
|
||||||
if raw_value is None or raw_value.strip() == "":
|
result: dict[str, str] = {}
|
||||||
return DEFAULT_REPORT_INTERVAL_SECONDS
|
|
||||||
try:
|
try:
|
||||||
interval = int(raw_value)
|
with open(path, "r", encoding="utf-8") as handle:
|
||||||
|
for raw_line in handle:
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
result[key.strip().upper()] = value.strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise FileNotFoundError(f"Agent config file not found: {path}") from None
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(path: str) -> AgentConfig:
|
||||||
|
"""读取配置文件并结合环境变量,返回 AgentConfig。"""
|
||||||
|
config_values = _parse_config_file(path)
|
||||||
|
force_env = os.environ.get("AGENT_FORCE_ENV", "0").lower() in {"1", "true", "yes"}
|
||||||
|
|
||||||
|
def read_key(key: str, default: str | None = None, *, required: bool = False) -> str:
|
||||||
|
env_key = f"AGENT_{key}"
|
||||||
|
if env_key in os.environ:
|
||||||
|
return os.environ[env_key]
|
||||||
|
if force_env and key in os.environ:
|
||||||
|
return os.environ[key]
|
||||||
|
if key in config_values:
|
||||||
|
return config_values[key]
|
||||||
|
if default is not None:
|
||||||
|
return default
|
||||||
|
if required:
|
||||||
|
raise ValueError(f"Missing required configuration key: {key}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
hostname = read_key("HOSTNAME", required=True)
|
||||||
|
node_file = read_key("NODE_FILE", f"/private/argus/agent/{hostname}/node.json")
|
||||||
|
version = read_key("VERSION", "1.0.0")
|
||||||
|
master_endpoint = read_key("MASTER_ENDPOINT", required=True)
|
||||||
|
report_interval_raw = read_key("REPORT_INTERVAL_SECONDS", "60")
|
||||||
|
health_dir_template = read_key(
|
||||||
|
"SUBMODULE_HEALTH_FILE_DIR",
|
||||||
|
f"/private/argus/agent/health/{{hostname}}/",
|
||||||
|
)
|
||||||
|
gpu_override_raw = read_key("GPU_NUMBER", "")
|
||||||
|
|
||||||
|
try:
|
||||||
|
report_interval_seconds = int(report_interval_raw)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("REPORT_INTERVAL_SECONDS must be an integer") from exc
|
raise ValueError("REPORT_INTERVAL_SECONDS must be an integer") from exc
|
||||||
if interval <= 0:
|
if report_interval_seconds <= 0:
|
||||||
raise ValueError("REPORT_INTERVAL_SECONDS must be positive")
|
raise ValueError("REPORT_INTERVAL_SECONDS must be positive")
|
||||||
return interval
|
|
||||||
|
|
||||||
|
gpu_override = None
|
||||||
|
if gpu_override_raw:
|
||||||
|
try:
|
||||||
|
gpu_override = int(gpu_override_raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError("GPU_NUMBER must be an integer when provided") from exc
|
||||||
|
if gpu_override < 0:
|
||||||
|
raise ValueError("GPU_NUMBER must be non-negative")
|
||||||
|
|
||||||
def _resolve_hostname() -> str:
|
if not master_endpoint.startswith("http://") and not master_endpoint.startswith("https://"):
|
||||||
return os.environ.get("AGENT_HOSTNAME") or socket.gethostname()
|
master_endpoint = f"http://{master_endpoint}"
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
"""从环境变量推导配置,移除了外部配置文件依赖。"""
|
|
||||||
|
|
||||||
hostname = _resolve_hostname()
|
|
||||||
node_file = f"/private/argus/agent/{hostname}/node.json"
|
|
||||||
environment, user, instance = _resolve_metadata_fields(hostname, node_file)
|
|
||||||
|
|
||||||
health_dir = f"/private/argus/agent/{hostname}/health/"
|
|
||||||
|
|
||||||
master_endpoint_env = os.environ.get("MASTER_ENDPOINT")
|
|
||||||
if master_endpoint_env is None:
|
|
||||||
raise ValueError("MASTER_ENDPOINT environment variable is not set")
|
|
||||||
master_endpoint = _normalise_master_endpoint(master_endpoint_env)
|
|
||||||
|
|
||||||
report_interval_seconds = _read_report_interval(os.environ.get("REPORT_INTERVAL_SECONDS"))
|
|
||||||
|
|
||||||
Path(node_file).parent.mkdir(parents=True, exist_ok=True)
|
Path(node_file).parent.mkdir(parents=True, exist_ok=True)
|
||||||
Path(health_dir).mkdir(parents=True, exist_ok=True)
|
Path(health_dir_template.format(hostname=hostname)).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
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.rstrip("/"),
|
||||||
report_interval_seconds=report_interval_seconds,
|
report_interval_seconds=report_interval_seconds,
|
||||||
health_dir=health_dir,
|
health_dir_template=health_dir_template,
|
||||||
|
gpu_number_override=gpu_override,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -30,15 +32,28 @@ class StopSignal:
|
|||||||
return self._stop
|
return self._stop
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Optional[list[str]] = None) -> int: # noqa: ARG001 - 保留签名以兼容入口调用
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Argus agent")
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
dest="config_path",
|
||||||
|
default=None,
|
||||||
|
help="Path to agent config file",
|
||||||
|
)
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: Optional[list[str]] = None) -> int:
|
||||||
setup_logging()
|
setup_logging()
|
||||||
|
args = parse_args(argv or sys.argv[1:])
|
||||||
|
|
||||||
stop_signal = StopSignal()
|
stop_signal = StopSignal()
|
||||||
signal.signal(signal.SIGTERM, stop_signal.set)
|
signal.signal(signal.SIGTERM, stop_signal.set)
|
||||||
signal.signal(signal.SIGINT, stop_signal.set)
|
signal.signal(signal.SIGINT, stop_signal.set)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = load_config()
|
config_path = args.config_path or _default_config_path()
|
||||||
|
config = load_config(config_path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOGGER.error("Failed to load configuration", extra={"error": str(exc)})
|
LOGGER.error("Failed to load configuration", extra={"error": str(exc)})
|
||||||
return 1
|
return 1
|
||||||
@@ -74,6 +89,13 @@ def main(argv: Optional[list[str]] = None) -> int: # noqa: ARG001 - 保留签
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _default_config_path() -> str:
|
||||||
|
from socket import gethostname
|
||||||
|
|
||||||
|
hostname = gethostname()
|
||||||
|
return f"/private/argus/agent/{hostname}/config"
|
||||||
|
|
||||||
|
|
||||||
def _register_with_retry(
|
def _register_with_retry(
|
||||||
client: AgentClient,
|
client: AgentClient,
|
||||||
config: AgentConfig,
|
config: AgentConfig,
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import importlib.metadata
|
|
||||||
|
|
||||||
try:
|
|
||||||
import tomllib
|
|
||||||
except ModuleNotFoundError: # pragma: no cover
|
|
||||||
import tomli as tomllib # type: ignore[no-redef]
|
|
||||||
|
|
||||||
|
|
||||||
def _candidate_paths() -> list[Path]:
|
|
||||||
paths = []
|
|
||||||
bundle_dir: Optional[str] = getattr(sys, "_MEIPASS", None)
|
|
||||||
if bundle_dir:
|
|
||||||
paths.append(Path(bundle_dir) / "pyproject.toml")
|
|
||||||
paths.append(Path(__file__).resolve().parent.parent / "pyproject.toml")
|
|
||||||
paths.append(Path(__file__).resolve().parent / "pyproject.toml")
|
|
||||||
paths.append(Path.cwd() / "pyproject.toml")
|
|
||||||
return paths
|
|
||||||
|
|
||||||
|
|
||||||
def _read_from_pyproject() -> Optional[str]:
|
|
||||||
for path in _candidate_paths():
|
|
||||||
if not path.exists():
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
with path.open("rb") as handle:
|
|
||||||
data = tomllib.load(handle)
|
|
||||||
except (OSError, tomllib.TOMLDecodeError):
|
|
||||||
continue
|
|
||||||
project = data.get("project")
|
|
||||||
if isinstance(project, dict):
|
|
||||||
version = project.get("version")
|
|
||||||
if isinstance(version, str):
|
|
||||||
return version
|
|
||||||
tool = data.get("tool")
|
|
||||||
if isinstance(tool, dict):
|
|
||||||
argus_cfg = tool.get("argus")
|
|
||||||
if isinstance(argus_cfg, dict):
|
|
||||||
version = argus_cfg.get("version")
|
|
||||||
if isinstance(version, str):
|
|
||||||
return version
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _detect_version() -> str:
|
|
||||||
try:
|
|
||||||
return importlib.metadata.version("argus-agent")
|
|
||||||
except importlib.metadata.PackageNotFoundError:
|
|
||||||
pass
|
|
||||||
override = os.environ.get("AGENT_VERSION_OVERRIDE")
|
|
||||||
if override:
|
|
||||||
return override
|
|
||||||
fallback = _read_from_pyproject()
|
|
||||||
if fallback:
|
|
||||||
return fallback
|
|
||||||
return "0.0.0"
|
|
||||||
|
|
||||||
|
|
||||||
VERSION: str = _detect_version()
|
|
||||||
|
|
||||||
|
|
||||||
def get_version() -> str:
|
|
||||||
return VERSION
|
|
||||||
BIN
src/agent/dist/argus-agent
vendored
BIN
src/agent/dist/argus-agent
vendored
Binary file not shown.
@@ -1,10 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from app.main import main as agent_main
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(agent_main())
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "argus-agent"
|
|
||||||
version = "1.1.0"
|
|
||||||
description = "Argus agent binary"
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = ">=3.11"
|
|
||||||
dependencies = [
|
|
||||||
"requests==2.31.0"
|
|
||||||
]
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["setuptools>=69", "wheel"]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[tool.argus]
|
|
||||||
entry = "app.main:main"
|
|
||||||
|
|
||||||
[tool.setuptools]
|
|
||||||
packages = ["app"]
|
|
||||||
1
src/agent/requirements.txt
Normal file
1
src/agent/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
requests==2.31.0
|
||||||
@@ -1,690 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
MODULE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
||||||
BUILD_ROOT="$MODULE_ROOT/build"
|
|
||||||
DIST_DIR="$MODULE_ROOT/dist"
|
|
||||||
PYINSTALLER_BUILD="$BUILD_ROOT/pyinstaller"
|
|
||||||
PYINSTALLER_SPEC="$PYINSTALLER_BUILD/spec"
|
|
||||||
PYINSTALLER_WORK="$PYINSTALLER_BUILD/work"
|
|
||||||
VENV_DIR="$BUILD_ROOT/venv"
|
|
||||||
|
|
||||||
AGENT_BUILD_IMAGE="${AGENT_BUILD_IMAGE:-python:3.11-slim-bullseye}"
|
|
||||||
AGENT_BUILD_USE_DOCKER="${AGENT_BUILD_USE_DOCKER:-1}"
|
|
||||||
# 默认在容器内忽略代理以避免公司内网代理在 Docker 网络不可达导致 pip 失败(可用 0 关闭)
|
|
||||||
AGENT_BUILD_IGNORE_PROXY="${AGENT_BUILD_IGNORE_PROXY:-1}"
|
|
||||||
USED_DOCKER=0
|
|
||||||
|
|
||||||
run_host_build() {
|
|
||||||
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
|
|
||||||
pip install --upgrade pip
|
|
||||||
pip install .
|
|
||||||
pip install pyinstaller==6.6.0
|
|
||||||
pyinstaller \
|
|
||||||
--clean \
|
|
||||||
--onefile \
|
|
||||||
--name argus-agent \
|
|
||||||
--distpath dist \
|
|
||||||
--workpath build/pyinstaller/work \
|
|
||||||
--specpath build/pyinstaller/spec \
|
|
||||||
--add-data /workspace/pyproject.toml:. \
|
|
||||||
entry.py
|
|
||||||
chmod +x dist/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
|
|
||||||
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"
|
|
||||||
39
src/agent/scripts/build_images.sh
Executable file
39
src/agent/scripts/build_images.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 [--intranet] [--tag <image_tag>]" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG:-argus-agent:dev}"
|
||||||
|
BUILD_ARGS=()
|
||||||
|
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--intranet)
|
||||||
|
INTRANET_INDEX="${INTRANET_INDEX:-https://pypi.tuna.tsinghua.edu.cn/simple}"
|
||||||
|
BUILD_ARGS+=("--build-arg" "PIP_INDEX_URL=${INTRANET_INDEX}")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--tag)
|
||||||
|
[[ $# -ge 2 ]] || { usage; exit 1; }
|
||||||
|
IMAGE_TAG="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[INFO] Building image $IMAGE_TAG"
|
||||||
|
docker build "${BUILD_ARGS[@]}" -t "$IMAGE_TAG" "$PROJECT_ROOT"
|
||||||
|
echo "[OK] Image $IMAGE_TAG built"
|
||||||
39
src/agent/scripts/load_images.sh
Executable file
39
src/agent/scripts/load_images.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 [--file <tar_path>]" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
DEFAULT_INPUT="$PROJECT_ROOT/images/argus-agent-dev.tar"
|
||||||
|
IMAGE_TAR="$DEFAULT_INPUT"
|
||||||
|
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--file)
|
||||||
|
[[ $# -ge 2 ]] || { usage; exit 1; }
|
||||||
|
IMAGE_TAR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -f "$IMAGE_TAR" ]]; then
|
||||||
|
echo "[ERROR] Image tarball not found: $IMAGE_TAR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Loading image from $IMAGE_TAR"
|
||||||
|
docker image load -i "$IMAGE_TAR"
|
||||||
|
echo "[OK] Image loaded"
|
||||||
41
src/agent/scripts/save_images.sh
Executable file
41
src/agent/scripts/save_images.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 [--tag <image_tag>] [--output <tar_path>]" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
DEFAULT_OUTPUT="$PROJECT_ROOT/images/argus-agent-dev.tar"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG:-argus-agent:dev}"
|
||||||
|
OUTPUT_PATH="$DEFAULT_OUTPUT"
|
||||||
|
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--tag)
|
||||||
|
[[ $# -ge 2 ]] || { usage; exit 1; }
|
||||||
|
IMAGE_TAG="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--output)
|
||||||
|
[[ $# -ge 2 ]] || { usage; exit 1; }
|
||||||
|
OUTPUT_PATH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$OUTPUT_PATH")"
|
||||||
|
echo "[INFO] Saving image $IMAGE_TAG to $OUTPUT_PATH"
|
||||||
|
docker image save "$IMAGE_TAG" -o "$OUTPUT_PATH"
|
||||||
|
echo "[OK] Image saved"
|
||||||
@@ -1,94 +1,26 @@
|
|||||||
services:
|
services:
|
||||||
bind:
|
|
||||||
image: ${BIND_IMAGE_TAG:-argus-bind9:latest}
|
|
||||||
container_name: argus-bind-agent-e2e
|
|
||||||
volumes:
|
|
||||||
- ./private:/private
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
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:latest
|
image: argus-master:dev
|
||||||
container_name: argus-master-agent-e2e
|
container_name: argus-master-agent-e2e
|
||||||
depends_on:
|
|
||||||
- bind
|
|
||||||
environment:
|
environment:
|
||||||
- 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:
|
||||||
- ./private/argus/master:/private/argus/master
|
- ./private/argus/master:/private/argus/master
|
||||||
- ./private/argus/metric/prometheus:/private/argus/metric/prometheus
|
- ./private/argus/metric/prometheus:/private/argus/metric/prometheus
|
||||||
- ./private/argus/etc:/private/argus/etc
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
ipv4_address: 172.28.0.10
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
agent:
|
agent:
|
||||||
image: ubuntu:22.04
|
image: argus-agent:dev
|
||||||
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:
|
||||||
- master
|
- master
|
||||||
- bind
|
|
||||||
environment:
|
|
||||||
- MASTER_ENDPOINT=http://master.argus.com:3000
|
|
||||||
- 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/dev-e2euser-e2einst-pod-0/health:/private/argus/agent/dev-e2euser-e2einst-pod-0/health
|
- ./private/argus/agent/health/dev-e2euser-e2einst-pod-0:/private/argus/agent/health/dev-e2euser-e2einst-pod-0
|
||||||
- ./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.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,10 +7,9 @@ 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_verify_agent.sh"
|
"05_assert_status_on_master.sh"
|
||||||
"06_assert_status_on_master.sh"
|
"06_restart_agent_and_reregister.sh"
|
||||||
"07_restart_agent_and_reregister.sh"
|
"07_down.sh"
|
||||||
"08_down.sh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for script in "${SCRIPTS[@]}"; do
|
for script in "${SCRIPTS[@]}"; do
|
||||||
|
|||||||
@@ -5,59 +5,40 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
AGENT_ROOT="$(cd "$TEST_ROOT/.." && pwd)"
|
AGENT_ROOT="$(cd "$TEST_ROOT/.." && pwd)"
|
||||||
MASTER_ROOT="$(cd "$AGENT_ROOT/../master" && pwd)"
|
MASTER_ROOT="$(cd "$AGENT_ROOT/../master" && pwd)"
|
||||||
REPO_ROOT="$(cd "$AGENT_ROOT/../.." && pwd)"
|
|
||||||
PRIVATE_ROOT="$TEST_ROOT/private"
|
PRIVATE_ROOT="$TEST_ROOT/private"
|
||||||
TMP_ROOT="$TEST_ROOT/tmp"
|
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/$AGENT_HOSTNAME/health"
|
AGENT_HEALTH_DIR="$PRIVATE_ROOT/argus/agent/health/$AGENT_HOSTNAME"
|
||||||
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"
|
|
||||||
BIND_IMAGE_TAG="${BIND_IMAGE_TAG:-argus-bind9:latest}"
|
|
||||||
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"
|
||||||
mkdir -p "$METRIC_PRIVATE_DIR"
|
mkdir -p "$METRIC_PRIVATE_DIR"
|
||||||
mkdir -p "$TMP_ROOT"
|
mkdir -p "$TMP_ROOT"
|
||||||
mkdir -p "$DNS_DIR"
|
|
||||||
|
cat > "$AGENT_CONFIG_DIR/config" <<CONFIG
|
||||||
|
HOSTNAME=$AGENT_HOSTNAME
|
||||||
|
NODE_FILE=/private/argus/agent/$AGENT_HOSTNAME/node.json
|
||||||
|
VERSION=1.1.0
|
||||||
|
MASTER_ENDPOINT=http://master:3000
|
||||||
|
REPORT_INTERVAL_SECONDS=2
|
||||||
|
SUBMODULE_HEALTH_FILE_DIR=/private/argus/agent/health/{hostname}/
|
||||||
|
GPU_NUMBER=
|
||||||
|
IP_OVERRIDE=
|
||||||
|
CONFIG
|
||||||
|
|
||||||
touch "$AGENT_HEALTH_DIR/.keep"
|
touch "$AGENT_HEALTH_DIR/.keep"
|
||||||
|
|
||||||
# 中文提示:准备 bind 模块提供的 update-dns.sh,模拟生产下发
|
pushd "$MASTER_ROOT" >/dev/null
|
||||||
if [[ -f "$BIND_ROOT/build/update-dns.sh" ]]; then
|
./scripts/build_images.sh --tag argus-master:dev
|
||||||
cp "$BIND_ROOT/build/update-dns.sh" "$DNS_DIR/update-dns.sh"
|
|
||||||
chmod +x "$DNS_DIR/update-dns.sh"
|
|
||||||
else
|
|
||||||
echo "[WARN] bind update script missing at $BIND_ROOT/build/update-dns.sh"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ensure_image "argus-master:latest"
|
|
||||||
ensure_image "$BIND_IMAGE_TAG"
|
|
||||||
|
|
||||||
AGENT_BINARY="$AGENT_ROOT/dist/argus-agent"
|
|
||||||
|
|
||||||
pushd "$AGENT_ROOT" >/dev/null
|
|
||||||
./scripts/build_binary.sh
|
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
if [[ ! -x "$AGENT_BINARY" ]]; then
|
pushd "$AGENT_ROOT" >/dev/null
|
||||||
echo "[ERROR] Agent binary not found at $AGENT_BINARY" >&2
|
./scripts/build_images.sh --tag argus-agent:dev
|
||||||
exit 1
|
popd >/dev/null
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$AGENT_BINARY" > "$TMP_ROOT/agent_binary_path"
|
|
||||||
echo "$BIND_IMAGE_TAG" > "$TMP_ROOT/bind_image_tag"
|
|
||||||
|
|
||||||
echo "[INFO] Agent E2E bootstrap complete"
|
echo "[INFO] Agent E2E bootstrap complete"
|
||||||
|
|||||||
@@ -3,35 +3,6 @@ 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"
|
|
||||||
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
|
|
||||||
echo "[ERROR] Agent binary path missing; run 01_bootstrap.sh first" >&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
|
|
||||||
|
|
||||||
BIND_IMAGE_TAG_VALUE="argus-bind9:latest"
|
|
||||||
if [[ -f "$TMP_ROOT/bind_image_tag" ]]; then
|
|
||||||
BIND_IMAGE_TAG_VALUE="$(cat "$TMP_ROOT/bind_image_tag")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
compose() {
|
compose() {
|
||||||
if docker compose version >/dev/null 2>&1; then
|
if docker compose version >/dev/null 2>&1; then
|
||||||
@@ -41,13 +12,13 @@ compose() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
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 container rm -f argus-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
|
||||||
|
|
||||||
pushd "$TEST_ROOT" >/dev/null
|
pushd "$TEST_ROOT" >/dev/null
|
||||||
compose down --remove-orphans || true
|
compose down --remove-orphans || true
|
||||||
BIND_IMAGE_TAG="$BIND_IMAGE_TAG_VALUE" compose up -d
|
compose up -d
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
echo "[INFO] Master+Agent stack started"
|
echo "[INFO] Master+Agent stack started"
|
||||||
|
|||||||
@@ -6,14 +6,11 @@ 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"
|
||||||
|
|
||||||
primary_node_id=""
|
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)
|
||||||
@@ -22,49 +19,24 @@ 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"
|
||||||
readarray -t node_ids < <(python3 - "$list_file" "$AGENT_HOSTNAME" "$ENV_AGENT_HOSTNAME" <<'PY'
|
node_id=$(python3 - "$list_file" <<'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 "$primary_node_id" ]]; then
|
if [[ -z "$node_id" ]]; then
|
||||||
echo "[ERROR] Primary agent did not register within timeout" >&2
|
echo "[ERROR] Agent did not register within timeout" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$env_node_id" ]]; then
|
echo "$node_id" > "$TMP_ROOT/node_id"
|
||||||
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
|
||||||
@@ -78,20 +50,8 @@ 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/$primary_node_id" -o "$detail_file"
|
curl -sS "$API_BASE/nodes/$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:
|
||||||
@@ -102,5 +62,4 @@ 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 $primary_node_id"
|
echo "[INFO] Agent registered with node id $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/dev-e2euser-e2einst-pod-0/health"
|
HEALTH_DIR="$TEST_ROOT/private/argus/agent/health/dev-e2euser-e2einst-pod-0"
|
||||||
|
|
||||||
cat > "$HEALTH_DIR/log-fluentbit.json" <<JSON
|
cat > "$HEALTH_DIR/log-fluentbit.json" <<JSON
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ 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
|
||||||
@@ -43,36 +41,13 @@ if [[ ! -f "$NODES_JSON" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 - "$NODES_JSON" "$NODE_ID" "$ENV_NODE_ID" <<'PY'
|
python3 - "$NODES_JSON" <<'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
|
||||||
expected_primary = sys.argv[2]
|
entry = nodes[0]
|
||||||
expected_env = sys.argv[3]
|
assert entry["node_id"], entry
|
||||||
|
|
||||||
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,60 +0,0 @@
|
|||||||
#!/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
|
|
||||||
108
src/agent/tests/scripts/06_restart_agent_and_reregister.sh
Executable file
108
src/agent/tests/scripts/06_restart_agent_and_reregister.sh
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/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"
|
||||||
|
|
||||||
|
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" \
|
||||||
|
argus-agent:dev \
|
||||||
|
sleep 300 >/dev/null; then
|
||||||
|
echo "[ERROR] Failed to start agent container with custom IP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 在容器内启动真实 agent 进程
|
||||||
|
if ! docker exec -d argus-agent-e2e python -m app.main; then
|
||||||
|
echo "[ERROR] Failed to spawn agent process inside container" >&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"
|
||||||
@@ -3,7 +3,6 @@ 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
|
||||||
@@ -13,24 +12,13 @@ compose() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
docker container rm -f argus-agent-e2e argus-agent-env-e2e >/dev/null 2>&1 || true
|
docker container rm -f argus-agent-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
|
||||||
|
|
||||||
if [[ -d "$TEST_ROOT/private" ]]; then
|
rm -rf "$TEST_ROOT/private"
|
||||||
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"
|
||||||
@@ -1,254 +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")"
|
|
||||||
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"
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
LOG_PREFIX="[AGENT-ENTRYPOINT]"
|
|
||||||
DNS_SCRIPT="/private/argus/etc/update-dns.sh"
|
|
||||||
DNS_CONF="/private/argus/etc/dns.conf"
|
|
||||||
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() {
|
|
||||||
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 脚本
|
|
||||||
for _ in {1..30}; do
|
|
||||||
if [[ -x "$DNS_SCRIPT" ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
log "等待 update-dns.sh 准备就绪..."
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -x "$DNS_SCRIPT" ]]; then
|
|
||||||
log "执行 update-dns.sh 更新容器 DNS"
|
|
||||||
while true; do
|
|
||||||
if "$DNS_SCRIPT"; then
|
|
||||||
log "update-dns.sh 执行成功"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
log "update-dns.sh 执行失败,3 秒后重试"
|
|
||||||
sleep 3
|
|
||||||
done
|
|
||||||
else
|
|
||||||
log "未获取到 update-dns.sh,使用镜像默认 DNS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 中文提示:记录当前 dns.conf 内容,便于排查
|
|
||||||
if [[ -f "$DNS_CONF" ]]; then
|
|
||||||
log "dns.conf 内容: $(tr '\n' ' ' < "$DNS_CONF")"
|
|
||||||
else
|
|
||||||
log "dns.conf 暂未生成"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 中文提示:尝试解析 master 域名,失败不阻塞但会打日志
|
|
||||||
for _ in {1..30}; do
|
|
||||||
if getent hosts "$TARGET_DOMAIN" >/dev/null 2>&1; then
|
|
||||||
MASTER_IP=$(getent hosts "$TARGET_DOMAIN" | awk '{print $1}' | head -n 1)
|
|
||||||
log "master.argus.com 解析成功: $MASTER_IP"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
log "启动 argus-agent"
|
|
||||||
exec su -s /bin/bash -c /usr/local/bin/argus-agent "$RUNTIME_USER"
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
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()
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
# 基于 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.
@@ -1,19 +0,0 @@
|
|||||||
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']
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/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=""
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
[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
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# 告警配置
|
|
||||||
|
|
||||||
> 参考:[自定义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 地址
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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 分钟。"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
DATA_ROOT=/home/argus/tmp/private/argus
|
|
||||||
ARGUS_BUILD_UID=1048
|
|
||||||
ARGUS_BUILD_GID=1048
|
|
||||||
|
|
||||||
USE_INTRANET=false
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
DATA_ROOT=/home/argus/tmp/private/argus
|
|
||||||
ARGUS_UID=1048
|
|
||||||
ARGUS_GID=1048
|
|
||||||
|
|
||||||
USE_INTRANET=false
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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']
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
172.18.0.2
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)"
|
|
||||||
project_root="$(cd "$root/../../.." && pwd)"
|
|
||||||
|
|
||||||
source "$project_root/scripts/common/build_user.sh"
|
|
||||||
load_build_user
|
|
||||||
|
|
||||||
# 创建新的private目录结构 (基于argus目录结构)
|
|
||||||
echo "[INFO] Creating private directory structure for supervisor-based containers..."
|
|
||||||
mkdir -p "$root/private/argus/alert/alertmanager"
|
|
||||||
mkdir -p "$root/private/argus/etc/"
|
|
||||||
|
|
||||||
# 设置数据目录权限
|
|
||||||
echo "[INFO] Setting permissions for data directories..."
|
|
||||||
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/alert/alertmanager" 2>/dev/null || true
|
|
||||||
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/etc" 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "[INFO] Supervisor-based containers will manage their own scripts and configurations"
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
compose_cmd="docker compose"
|
|
||||||
if ! $compose_cmd version >/dev/null 2>&1; then
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
|
||||||
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
|
||||||
fi
|
|
||||||
$compose_cmd -p alert-mvp up -d --remove-orphans
|
|
||||||
echo "[OK] 服务已启动:Alertmanager http://localhost:9093"
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# Alertmanager 测试脚本
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
ALERTMANAGER_URL="http://localhost:9093"
|
|
||||||
TEST_ALERT_NAME_CRITICAL="NodeDown"
|
|
||||||
TEST_ALERT_NAME_WARNING="HighCPU"
|
|
||||||
TMP_LOG="/tmp/test-alertmanager.log"
|
|
||||||
|
|
||||||
# 等待参数
|
|
||||||
am_wait_attempts=30
|
|
||||||
am_wait_interval=2
|
|
||||||
|
|
||||||
GREEN="\033[1;32m"
|
|
||||||
RED="\033[1;31m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
RESET="\033[0m"
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 函数定义
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
wait_for_alertmanager() {
|
|
||||||
local attempt=1
|
|
||||||
echo "[INFO] 等待 Alertmanager 启动中..."
|
|
||||||
while (( attempt <= am_wait_attempts )); do
|
|
||||||
if curl -fsS "${ALERTMANAGER_URL}/api/v2/status" >/dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}[OK] Alertmanager 已就绪 (attempt=${attempt}/${am_wait_attempts})${RESET}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "[..] Alertmanager 尚未就绪 (${attempt}/${am_wait_attempts})"
|
|
||||||
sleep "${am_wait_interval}"
|
|
||||||
(( attempt++ ))
|
|
||||||
done
|
|
||||||
echo -e "${RED}[ERROR] Alertmanager 在 ${am_wait_attempts} 次尝试后仍未就绪${RESET}"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
log_step() {
|
|
||||||
echo -e "${YELLOW}==== $1 ====${RESET}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 主流程
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
log_step "测试 Alertmanager 开始"
|
|
||||||
echo "[INFO] Alertmanager 地址: $ALERTMANAGER_URL"
|
|
||||||
|
|
||||||
# Step 1: 等待 Alertmanager 启动
|
|
||||||
wait_for_alertmanager
|
|
||||||
|
|
||||||
# Step 2: 触发一个critical测试告警
|
|
||||||
echo "[INFO] 发送critical测试告警..."
|
|
||||||
curl -fsS -X POST "${ALERTMANAGER_URL}/api/v2/alerts" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '[
|
|
||||||
{
|
|
||||||
"labels": {
|
|
||||||
"alertname": "'"${TEST_ALERT_NAME_CRITICAL}"'",
|
|
||||||
"instance": "node-1",
|
|
||||||
"severity": "critical"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"summary": "节点 node-1 宕机"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]' \
|
|
||||||
-o "$TMP_LOG"
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}[OK] 已成功发送critical测试告警${RESET}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}[ERROR] critical告警发送失败!${RESET}"
|
|
||||||
cat "$TMP_LOG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 3: 触发一个warning测试告警
|
|
||||||
echo "[INFO] 发送warning测试告警..."
|
|
||||||
curl -fsS -X POST "${ALERTMANAGER_URL}/api/v2/alerts" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '[
|
|
||||||
{
|
|
||||||
"labels": {
|
|
||||||
"alertname": "'"${TEST_ALERT_NAME_WARNING}"'",
|
|
||||||
"instance": "node-1",
|
|
||||||
"severity": "warning"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"summary": "节点 node-1 CPU 使用率过高"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]' \
|
|
||||||
-o "$TMP_LOG"
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}[OK] 已成功发送warning测试告警${RESET}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}[ERROR] warning告警发送失败!${RESET}"
|
|
||||||
cat "$TMP_LOG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# Alertmanager 测试脚本(含启动等待)
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
ALERTMANAGER_URL="http://localhost:9093"
|
|
||||||
TEST_ALERT_NAME_CRITICAL="NodeDown"
|
|
||||||
TEST_ALERT_NAME_WARNING="HighCPU"
|
|
||||||
TMP_LOG="/tmp/test-alertmanager.log"
|
|
||||||
|
|
||||||
# 等待参数
|
|
||||||
am_wait_attempts=30
|
|
||||||
am_wait_interval=2
|
|
||||||
|
|
||||||
GREEN="\033[1;32m"
|
|
||||||
RED="\033[1;31m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
RESET="\033[0m"
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 函数定义
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
wait_for_alertmanager() {
|
|
||||||
local attempt=1
|
|
||||||
echo "[INFO] 等待 Alertmanager 启动中..."
|
|
||||||
while (( attempt <= am_wait_attempts )); do
|
|
||||||
if curl -fsS "${ALERTMANAGER_URL}/api/v2/status" >/dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}[OK] Alertmanager 已就绪 (attempt=${attempt}/${am_wait_attempts})${RESET}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "[..] Alertmanager 尚未就绪 (${attempt}/${am_wait_attempts})"
|
|
||||||
sleep "${am_wait_interval}"
|
|
||||||
(( attempt++ ))
|
|
||||||
done
|
|
||||||
echo -e "${RED}[ERROR] Alertmanager 在 ${am_wait_attempts} 次尝试后仍未就绪${RESET}"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
log_step() {
|
|
||||||
echo -e "${YELLOW}==== $1 ====${RESET}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 主流程
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
log_step "查询 Alertmanager 当前告警列表开始"
|
|
||||||
echo "[INFO] Alertmanager 地址: $ALERTMANAGER_URL"
|
|
||||||
|
|
||||||
# Step 1: 等待 Alertmanager 启动
|
|
||||||
wait_for_alertmanager
|
|
||||||
|
|
||||||
# Step 2: 查询当前告警列表
|
|
||||||
echo "[INFO] 查询当前告警..."
|
|
||||||
sleep 1
|
|
||||||
curl -fsS "${ALERTMANAGER_URL}/api/v2/alerts" | jq '.' || {
|
|
||||||
echo -e "${RED}[WARN] 无法解析返回 JSON,请检查 jq 是否安装${RESET}"
|
|
||||||
curl -s "${ALERTMANAGER_URL}/api/v2/alerts"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Step 3: 检查告警是否包含 NodeDown
|
|
||||||
if curl -fsS "${ALERTMANAGER_URL}/api/v2/alerts" | grep -q "${TEST_ALERT_NAME_CRITICAL}"; then
|
|
||||||
echo -e "${GREEN}✅ 测试通过:Alertmanager 已成功接收告警 ${TEST_ALERT_NAME_CRITICAL}${RESET}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ 测试失败:未检测到告警 ${TEST_ALERT_NAME_CRITICAL}${RESET}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_step "测试结束"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
compose_cmd="docker compose"
|
|
||||||
if ! $compose_cmd version >/dev/null 2>&1; then
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
|
||||||
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
|
||||||
fi
|
|
||||||
$compose_cmd -p alert-mvp down
|
|
||||||
echo "[OK] 已停止所有容器"
|
|
||||||
|
|
||||||
# 清理private目录内容
|
|
||||||
echo "[INFO] 清理private目录内容..."
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
if [ -d "private" ]; then
|
|
||||||
# 删除private目录及其所有内容
|
|
||||||
rm -rf private
|
|
||||||
echo "[OK] 已清理private目录"
|
|
||||||
else
|
|
||||||
echo "[INFO] private目录不存在,无需清理"
|
|
||||||
fi
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "======================================="
|
|
||||||
echo "ARGUS Alert System End-to-End Test"
|
|
||||||
echo "======================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 记录测试开始时间
|
|
||||||
test_start_time=$(date +%s)
|
|
||||||
|
|
||||||
# 函数:等待服务就绪
|
|
||||||
wait_for_services() {
|
|
||||||
echo "[INFO] Waiting for all services to be ready..."
|
|
||||||
local max_attempts=${SERVICE_WAIT_ATTEMPTS:-120}
|
|
||||||
local attempt=1
|
|
||||||
|
|
||||||
while [ $attempt -le $max_attempts ]; do
|
|
||||||
if curl -fs http://localhost:9093/api/v2/status >/dev/null 2>&1; then
|
|
||||||
echo "[OK] All services are ready!"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo " Waiting for services... ($attempt/$max_attempts)"
|
|
||||||
sleep 5
|
|
||||||
((attempt++))
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[ERROR] Services not ready after $max_attempts attempts"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:显示测试步骤
|
|
||||||
show_step() {
|
|
||||||
echo ""
|
|
||||||
echo "🔄 Step $1: $2"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:验证步骤结果
|
|
||||||
verify_step() {
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "✅ $1 - SUCCESS"
|
|
||||||
else
|
|
||||||
echo "❌ $1 - FAILED"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 开始端到端测试
|
|
||||||
show_step "1" "Bootstrap - Initialize environment"
|
|
||||||
./scripts/01_bootstrap.sh
|
|
||||||
verify_step "Bootstrap"
|
|
||||||
|
|
||||||
show_step "2" "Startup - Start all services"
|
|
||||||
./scripts/02_up.sh
|
|
||||||
verify_step "Service startup"
|
|
||||||
|
|
||||||
# 等待服务完全就绪
|
|
||||||
wait_for_services || exit 1
|
|
||||||
|
|
||||||
# 发送告警数据
|
|
||||||
show_step "3" "Add alerts - Send test alerts to Alertmanager"
|
|
||||||
./scripts/03_alertmanager_add_alert.sh
|
|
||||||
verify_step "Send test alerts"
|
|
||||||
|
|
||||||
# 查询告警数据
|
|
||||||
show_step "4" "Verify data - Query Alertmanager"
|
|
||||||
./scripts/04_query_alerts.sh
|
|
||||||
verify_step "Data verification"
|
|
||||||
|
|
||||||
|
|
||||||
# 检查服务健康状态
|
|
||||||
show_step "Health" "Check service health"
|
|
||||||
echo "[INFO] Checking service health..."
|
|
||||||
|
|
||||||
# 检查 Alertmanager 状态
|
|
||||||
if curl -fs "http://localhost:9093/api/v2/status" >/dev/null 2>&1; then
|
|
||||||
am_status="available"
|
|
||||||
echo "✅ Alertmanager status: $am_status"
|
|
||||||
else
|
|
||||||
am_status="unavailable"
|
|
||||||
echo "⚠️ Alertmanager status: $am_status"
|
|
||||||
fi
|
|
||||||
verify_step "Service health check"
|
|
||||||
|
|
||||||
# 清理环境
|
|
||||||
show_step "5" "Cleanup - Stop all services"
|
|
||||||
./scripts/05_down.sh
|
|
||||||
verify_step "Service cleanup"
|
|
||||||
|
|
||||||
# 计算总测试时间
|
|
||||||
test_end_time=$(date +%s)
|
|
||||||
total_time=$((test_end_time - test_start_time))
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "======================================="
|
|
||||||
echo "🎉 END-TO-END TEST COMPLETED SUCCESSFULLY!"
|
|
||||||
echo "======================================="
|
|
||||||
echo "📊 Test Summary:"
|
|
||||||
echo " • Total time: ${total_time}s"
|
|
||||||
echo " • Alertmanager status: $am_status"
|
|
||||||
echo " • All services started and stopped successfully"
|
|
||||||
echo ""
|
|
||||||
echo "✅ The ARGUS Alert system is working correctly!"
|
|
||||||
echo ""
|
|
||||||
@@ -6,11 +6,6 @@ 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 \
|
||||||
@@ -26,7 +21,6 @@ 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 \
|
||||||
@@ -35,24 +29,6 @@ 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,9 +9,6 @@ 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"
|
||||||
@@ -36,7 +33,6 @@ 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)"
|
||||||
@@ -65,10 +61,7 @@ upsert_record() {
|
|||||||
echo "[SKIP] ${name} unchanged (${new_ip})"
|
echo "[SKIP] ${name} unchanged (${new_ip})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $changed -eq 1 ]]; then
|
return $changed
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
@@ -77,7 +70,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)"
|
||||||
@@ -104,3 +97,4 @@ while true; do
|
|||||||
|
|
||||||
sleep "$SLEEP_SECONDS"
|
sleep "$SLEEP_SECONDS"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ 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,8 +6,6 @@ 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:
|
||||||
- "${HOST_DNS_PORT:-1053}:53/tcp"
|
- "53:53/tcp"
|
||||||
- "${HOST_DNS_PORT:-1053}:53/udp"
|
- "53:53/udp"
|
||||||
volumes:
|
volumes:
|
||||||
- ./private:/private
|
- ./private:/private
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -13,4 +13,4 @@ services:
|
|||||||
|
|
||||||
networks:
|
networks:
|
||||||
bind-test-network:
|
bind-test-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
@@ -7,9 +7,6 @@
|
|||||||
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"
|
||||||
@@ -115,4 +112,4 @@ else
|
|||||||
echo " - Review BIND9 configuration files"
|
echo " - Review BIND9 configuration files"
|
||||||
echo " - Check system resources and port availability"
|
echo " - Check system resources and port availability"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -7,17 +7,13 @@ 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/argus/bind
|
mkdir -p private
|
||||||
mkdir -p private/argus/etc
|
|
||||||
chmod 777 private
|
chmod 777 private
|
||||||
|
|
||||||
# Start the container
|
# Start the container
|
||||||
@@ -39,4 +35,4 @@ fi
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "BIND9 test environment is ready!"
|
echo "BIND9 test environment is ready!"
|
||||||
echo "DNS server listening on localhost:${HOST_DNS_PORT}"
|
echo "DNS server listening on localhost:53"
|
||||||
@@ -5,10 +5,7 @@
|
|||||||
|
|
||||||
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() {
|
||||||
@@ -22,7 +19,7 @@ test_dns_query() {
|
|||||||
echo "Expected IP: $expected_ip"
|
echo "Expected IP: $expected_ip"
|
||||||
|
|
||||||
# Perform dig query
|
# Perform dig query
|
||||||
result=$(dig @localhost -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost $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"
|
||||||
@@ -72,4 +69,4 @@ if [ $failed_tests -eq 0 ]; then
|
|||||||
else
|
else
|
||||||
echo "✗ $failed_tests test(s) failed"
|
echo "✗ $failed_tests test(s) failed"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -6,13 +6,10 @@
|
|||||||
|
|
||||||
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
|
||||||
@@ -39,7 +36,7 @@ test_dns_query() {
|
|||||||
# Wait a moment for DNS cache
|
# Wait a moment for DNS cache
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
result=$(dig @localhost -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost $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"
|
||||||
@@ -93,7 +90,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 -p "$HOST_DNS_PORT" web.argus.com A +short 2>/dev/null || echo "UNKNOWN")
|
current_web_ip=$(dig @localhost 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)
|
||||||
@@ -188,7 +185,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 -p "$HOST_DNS_PORT" invalid.argus.com A +short 2>/dev/null || echo "NO_RESULT")
|
result=$(dig @localhost 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,13 +5,10 @@
|
|||||||
|
|
||||||
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
|
||||||
@@ -35,7 +32,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 -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost $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"
|
||||||
@@ -112,4 +109,4 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo "✓ DNS configuration reload test completed successfully!"
|
echo "✓ DNS configuration reload test completed successfully!"
|
||||||
echo "✓ IP address changed from 12.4.5.6 to 192.168.1.100"
|
echo "✓ IP address changed from 12.4.5.6 to 192.168.1.100"
|
||||||
echo "✓ Configuration persisted and reloaded correctly"
|
echo "✓ Configuration persisted and reloaded correctly"
|
||||||
@@ -5,13 +5,10 @@
|
|||||||
|
|
||||||
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
|
||||||
@@ -28,7 +25,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 -p "$HOST_DNS_PORT" "$hostname".argus.com A +short 2>/dev/null || echo "QUERY_FAILED")
|
result=$(dig @localhost $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"
|
||||||
@@ -115,4 +112,4 @@ echo ""
|
|||||||
echo "✓ Configuration persistence test completed successfully!"
|
echo "✓ Configuration persistence test completed successfully!"
|
||||||
echo "✓ Modified IP (192.168.1.100) persisted after container restart"
|
echo "✓ Modified IP (192.168.1.100) persisted after container restart"
|
||||||
echo "✓ Configuration files properly linked to persistent storage"
|
echo "✓ Configuration files properly linked to persistent storage"
|
||||||
echo "✓ DNS resolution working correctly with persisted configuration"
|
echo "✓ DNS resolution working correctly with persisted configuration"
|
||||||
@@ -7,9 +7,6 @@ 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,29 +3,10 @@ FROM docker.elastic.co/elasticsearch/elasticsearch:8.13.4
|
|||||||
# 切换到 root 用户进行系统级安装
|
# 切换到 root 用户进行系统级安装
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
ARG ARGUS_BUILD_UID=2133
|
# 修改elasticsearch用户的UID和GID
|
||||||
ARG ARGUS_BUILD_GID=2015
|
RUN usermod -u 2133 elasticsearch && \
|
||||||
|
groupmod -g 2015 elasticsearch && \
|
||||||
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
chown -R elasticsearch:elasticsearch /usr/share/elasticsearch
|
||||||
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,96 +1,47 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "[INFO] Starting Fluent Bit setup in Ubuntu container (offline-first)..."
|
echo "[INFO] Starting Fluent Bit setup in Ubuntu container..."
|
||||||
|
|
||||||
|
# 安装必要的工具
|
||||||
|
echo "[INFO] Installing required packages..."
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq curl
|
||||||
|
|
||||||
# Stage bundle to /tmp (read-only mount under /private)
|
# 解压bundle到/tmp
|
||||||
echo "[INFO] Staging fluent-bit bundle..."
|
echo "[INFO] Extracting fluent-bit bundle..."
|
||||||
rm -rf /tmp/flb && mkdir -p /tmp/flb
|
cp -r /private/etc /tmp
|
||||||
cp -r /private/etc /tmp/flb/
|
cp -r /private/packages /tmp
|
||||||
mkdir -p /tmp/flb/packages
|
cd /tmp
|
||||||
cp -r /private/packages/* /tmp/flb/packages/ 2>/dev/null || true
|
|
||||||
|
|
||||||
# Helper: check and install a local deb if not already satisfied
|
# 安装 Fluent Bit 从 deb 包
|
||||||
ensure_lib() {
|
echo "[INFO] Installing Fluent Bit from deb package..."
|
||||||
local soname="$1"; shift
|
dpkg -i /tmp/packages/fluent-bit_3.1.9_amd64.deb || true
|
||||||
local pattern="$1"; shift
|
apt-get install -f -y -qq # 解决依赖问题
|
||||||
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 || { echo "[ERROR] fluent-bit not installed or libraries missing" >&2; exit 1; }
|
/opt/fluent-bit/bin/fluent-bit --version
|
||||||
|
|
||||||
# Place configuration
|
# 创建配置目录
|
||||||
mkdir -p /etc/fluent-bit
|
mkdir -p /etc/fluent-bit
|
||||||
cp -r /tmp/flb/etc/* /etc/fluent-bit/
|
cp -r /tmp/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
|
||||||
|
|
||||||
# Wait for Elasticsearch via bash /dev/tcp to avoid curl dependency
|
# 等待 Elasticsearch 就绪
|
||||||
echo "[INFO] Waiting for Elasticsearch to be ready (tcp ${ES_HOST}:${ES_PORT})..."
|
echo "[INFO] Waiting for Elasticsearch to be ready..."
|
||||||
for i in $(seq 1 120); do
|
while ! curl -fs http://${ES_HOST}:${ES_PORT}/_cluster/health >/dev/null 2>&1; do
|
||||||
if exec 3<>/dev/tcp/${ES_HOST}/${ES_PORT}; then
|
echo " Waiting for ES at ${ES_HOST}:${ES_PORT}..."
|
||||||
exec 3<&- 3>&-
|
sleep 5
|
||||||
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 --config=/etc/fluent-bit/fluent-bit.conf
|
exec /opt/fluent-bit/bin/fluent-bit \
|
||||||
|
--config=/etc/fluent-bit/fluent-bit.conf
|
||||||
|
|||||||
@@ -3,29 +3,10 @@ FROM docker.elastic.co/kibana/kibana:8.13.4
|
|||||||
# 切换到 root 用户进行系统级安装
|
# 切换到 root 用户进行系统级安装
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
ARG ARGUS_BUILD_UID=2133
|
# 修改kibana用户的UID和GID
|
||||||
ARG ARGUS_BUILD_GID=2015
|
RUN usermod -u 2133 kibana && \
|
||||||
|
groupmod -g 2015 kibana && \
|
||||||
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
chown -R kibana:kibana /usr/share/kibana
|
||||||
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="${KB_HOST:-http://127.0.0.1:5601}"
|
KB_HOST="http://localhost:5601"
|
||||||
|
|
||||||
echo "[INFO] Starting Kibana post-start configuration..."
|
echo "[INFO] Starting Kibana post-start configuration..."
|
||||||
|
|
||||||
@@ -83,37 +83,50 @@ 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-*"
|
# 检查是否存在匹配的索引
|
||||||
create_or_ensure_data_view "infer" "infer-*"
|
local train_indices=$(curl -s "$ES_HOST/_cat/indices/train-*?h=index" 2>/dev/null | wc -l || echo "0")
|
||||||
|
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,7 +17,6 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 30
|
retries: 30
|
||||||
restart: always
|
|
||||||
|
|
||||||
kibana:
|
kibana:
|
||||||
build:
|
build:
|
||||||
@@ -74,11 +73,13 @@ 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: always
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
#!/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..."
|
||||||
@@ -15,9 +11,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..."
|
||||||
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/elasticsearch" 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/log/kibana" 2>/dev/null || true
|
||||||
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/etc" 2>/dev/null || true
|
sudo chown -R 2133:2015 "$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"
|
||||||
|
|
||||||
@@ -32,42 +28,3 @@ 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,22 +4,8 @@ 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() {
|
# 检查容器是否存在并运行
|
||||||
local name="$1"
|
if ! docker ps | grep -q "$container_name"; then
|
||||||
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
|
||||||
@@ -42,4 +28,4 @@ STACK"
|
|||||||
|
|
||||||
echo "[OK] 已通过docker exec写入测试日志到 host01 容器内:"
|
echo "[OK] 已通过docker exec写入测试日志到 host01 容器内:"
|
||||||
echo " - /logs/train/train-demo.log"
|
echo " - /logs/train/train-demo.log"
|
||||||
echo " - /logs/infer/infer-demo.log"
|
echo " - /logs/infer/infer-demo.log"
|
||||||
@@ -4,22 +4,8 @@ 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() {
|
# 检查容器是否存在并运行
|
||||||
local name="$1"
|
if ! docker ps | grep -q "$container_name"; then
|
||||||
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
|
||||||
@@ -38,4 +24,4 @@ docker exec "$container_name" sh -c "printf '%s INFO [host02] inference complete
|
|||||||
|
|
||||||
echo "[OK] 已通过docker exec写入测试日志到 host02 容器内:"
|
echo "[OK] 已通过docker exec写入测试日志到 host02 容器内:"
|
||||||
echo " - /logs/train/train-demo.log"
|
echo " - /logs/train/train-demo.log"
|
||||||
echo " - /logs/infer/infer-demo.log"
|
echo " - /logs/infer/infer-demo.log"
|
||||||
@@ -1,42 +1,7 @@
|
|||||||
#!/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
|
||||||
# 打印计数,缺失索引按 0 处理
|
printf "infer-* 计数:"; curl -fsS "$ES/infer-*/_count" | sed -E 's/.*"count":([0-9]+).*/\1/'; echo
|
||||||
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=${SERVICE_WAIT_ATTEMPTS:-120}
|
local max_attempts=60
|
||||||
local attempt=1
|
local attempt=1
|
||||||
|
|
||||||
while [ $attempt -le $max_attempts ]; do
|
while [ $attempt -le $max_attempts ]; do
|
||||||
@@ -115,32 +115,20 @@ 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 指标
|
||||||
@@ -151,13 +139,6 @@ if [ "$fb_host01_uptime" -gt 0 ] && [ "$fb_host02_uptime" -gt 0 ]; then
|
|||||||
echo "✅ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
echo "✅ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
||||||
else
|
else
|
||||||
echo "⚠️ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
echo "⚠️ Fluent-Bit services: host01 uptime=${fb_host01_uptime}s, host02 uptime=${fb_host02_uptime}s"
|
||||||
health_check_ok=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$health_check_ok" -eq 1 ]; then
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
verify_step "Service health check"
|
verify_step "Service health check"
|
||||||
|
|||||||
@@ -3,79 +3,23 @@ FROM python:3.11-slim
|
|||||||
SHELL ["/bin/bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
ARG PIP_INDEX_URL=
|
ARG PIP_INDEX_URL=
|
||||||
ARG USE_OFFLINE=0
|
|
||||||
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 \
|
||||||
PYTHONPATH=/app
|
PYTHONPATH=/app
|
||||||
|
|
||||||
USER root
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./src/master/requirements.txt ./requirements.txt
|
COPY requirements.txt ./
|
||||||
COPY ./src/master/offline_wheels/ /opt/offline_wheels/
|
|
||||||
|
|
||||||
RUN set -euxo pipefail \
|
RUN set -euxo pipefail \
|
||||||
&& if [[ "$USE_OFFLINE" == "1" ]]; then \
|
&& python -m pip install --upgrade pip \
|
||||||
python -m pip install --no-index --find-links /opt/offline_wheels pip && \
|
&& if [[ -n "$PIP_INDEX_URL" ]]; then \
|
||||||
python -m pip install --no-index --find-links /opt/offline_wheels -r requirements.txt; \
|
PIP_INDEX_URL="$PIP_INDEX_URL" python -m pip install -r requirements.txt; \
|
||||||
else \
|
else \
|
||||||
python -m pip install --upgrade pip && \
|
python -m pip install -r requirements.txt; \
|
||||||
if [[ -n "$PIP_INDEX_URL" ]]; then \
|
|
||||||
PIP_INDEX_URL="$PIP_INDEX_URL" python -m pip install -r requirements.txt; \
|
|
||||||
else \
|
|
||||||
python -m pip install -r requirements.txt; \
|
|
||||||
fi; \
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 配置内网 apt 源并安装常用工具
|
COPY app ./app
|
||||||
RUN if [[ "$USE_INTRANET" == "true" ]]; then \
|
|
||||||
echo "Configuring intranet apt sources" && \
|
|
||||||
if [[ -f /etc/apt/sources.list ]]; then cp /etc/apt/sources.list /etc/apt/sources.list.bak; fi && \
|
|
||||||
mkdir -p /etc/apt && \
|
|
||||||
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-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
|
||||||
fi && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y supervisor net-tools inetutils-ping && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 运行期切换到运行所需的 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
|
|
||||||
|
|
||||||
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/start-master.sh /usr/local/bin/start-master.sh
|
|
||||||
COPY ./src/master/build/dns-monitor.sh /usr/local/bin/dns-monitor.sh
|
|
||||||
RUN chmod +x /usr/local/bin/start-master.sh /usr/local/bin/dns-monitor.sh
|
|
||||||
|
|
||||||
COPY ./src/master/app ./app
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
CMD ["gunicorn", "--bind", "0.0.0.0:3000", "app:create_app()"]
|
||||||
|
|||||||
@@ -1,186 +1,48 @@
|
|||||||
# Argus Master 模块
|
# Argus Master Module
|
||||||
|
|
||||||
Argus Master 是基于 Flask + SQLite 的节点管理服务,负责:
|
A lightweight Flask + SQLite service that manages Argus agent nodes. It exposes node registration, status updates, configuration management, statistics, and generates `nodes.json` for the metric module.
|
||||||
|
|
||||||
- 接收 agent 的注册与重注册请求,分配/校验节点 ID。
|
## Build & Run
|
||||||
- 存储节点元数据、配置、健康状态,并根据上报时间计算在线状态。
|
|
||||||
- 输出仅包含在线节点的 `nodes.json`,供其他模块(如 metric)消费。
|
|
||||||
- 提供查询、配置更新、统计等 REST API。
|
|
||||||
|
|
||||||
## 构建与运行
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src/master
|
cd src/master
|
||||||
./scripts/build_images.sh # 生成 argus-master:latest 镜像
|
./scripts/build_images.sh # builds argus-master:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
如需离线构建,先在有网环境运行准备脚本:
|
Local run via Docker compose (test stack):
|
||||||
|
|
||||||
```bash
|
|
||||||
cd src/master
|
|
||||||
./scripts/prepare_offline_wheels.sh --pip-version 25.2 # 可选 --clean
|
|
||||||
```
|
|
||||||
|
|
||||||
脚本会把 `requirements.txt` 及 pip 指定版本全部下载到 `offline_wheels/`。随后将源码目录(含该子目录)与基础镜像一并拷贝到内网,执行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd src/master
|
|
||||||
./scripts/build_images.sh --offline --tag argus-master:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
若内网缺少 `python:3.11-slim`,请提前在外网 `docker save` 后通过离线介质 `docker load`。
|
|
||||||
|
|
||||||
本仓库提供的端到端测试会使用 `src/master/tests/docker-compose.yml` 启动示例环境:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src/master/tests
|
cd src/master/tests
|
||||||
./scripts/01_up_master.sh # 构建镜像并启动容器,监听 http://localhost:31300
|
./scripts/01_up_master.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
服务日志与数据默认写入 `tests/private/argus/master/`(或自定义的挂载目录)。
|
Service listens on port `3000` (`31300` when using the provided test compose). Key environment variables:
|
||||||
|
|
||||||
## 运行时环境变量
|
- `DB_PATH` (default `/private/argus/master/db.sqlite3`)
|
||||||
|
- `METRIC_NODES_JSON_PATH` (default `/private/argus/metric/prometheus/nodes.json`)
|
||||||
|
- `OFFLINE_THRESHOLD_SECONDS`, `ONLINE_THRESHOLD_SECONDS`, `SCHEDULER_INTERVAL_SECONDS`
|
||||||
|
|
||||||
| 变量 | 默认值 | 说明 |
|
## REST API Summary
|
||||||
| --- | --- | --- |
|
|
||||||
| `DB_PATH` | `/private/argus/master/db.sqlite3` | SQLite 数据库存放路径。目录会在启动时自动创建。 |
|
|
||||||
| `METRIC_NODES_JSON_PATH` | `/private/argus/metric/prometheus/nodes.json` | `nodes.json` 输出位置,仅包含在线节点。采用原子写入避免部分文件。 |
|
|
||||||
| `OFFLINE_THRESHOLD_SECONDS` | `180` | 若距离最近一次上报时间超过该值,调度器会将节点标记为 `offline`。 |
|
|
||||||
| `ONLINE_THRESHOLD_SECONDS` | `120` | 若最新上报时间距当前不超过该值,则标记为 `online`。范围处于两个阈值之间时保持原状态。 |
|
|
||||||
| `SCHEDULER_INTERVAL_SECONDS` | `30` | 调度器检查节点状态与刷新 `nodes.json` 的周期。 |
|
|
||||||
| `NODE_ID_PREFIX` | `A` | 新节点 ID 的前缀,实际 ID 形如 `A1`、`A2`。 |
|
|
||||||
| `AUTH_MODE` | `disabled` | 预留的认证开关,当前固定为禁用。 |
|
|
||||||
|
|
||||||
## 进程与监控
|
Base path: `/api/v1/master`
|
||||||
|
|
||||||
镜像内通过 `supervisord` 管理进程:
|
- `GET /nodes` — list node summaries
|
||||||
|
- `GET /nodes/{id}` — node detail
|
||||||
|
- `POST /nodes` — register/re-register
|
||||||
|
- `PUT /nodes/{id}/status` — status report (timestamp + health map)
|
||||||
|
- `PUT /nodes/{id}/config` — update config/labels (partial)
|
||||||
|
- `GET /nodes/statistics` — totals + grouped counts
|
||||||
|
- `GET /healthz`, `GET /readyz` — health checks
|
||||||
|
|
||||||
- `master`:执行 `/usr/local/bin/start-master.sh`,默认以 4 个 Gunicorn worker 监听 `0.0.0.0:3000`;可通过环境变量 `GUNICORN_WORKERS`、`GUNICORN_BIND`、`GUNICORN_EXTRA_ARGS` 调整。
|
`nodes.json` only contains nodes whose status is `online`.
|
||||||
- `dns-monitor`:轮询 `/private/argus/etc/dns.conf`,若发现变更则调用 `/private/argus/etc/update-dns.sh`,日志输出在 `/var/log/supervisor/dns-monitor.log`。
|
|
||||||
|
|
||||||
镜像构建阶段会安装 `supervisor`/`net-tools`/`inetutils-ping`/`vim` 等基础工具,并在运行前把 apt 源切换到内网镜像,方便容器内进一步运维。
|
## Tests
|
||||||
|
|
||||||
## 域名注册与 DNS 联动
|
End-to-end tests are Docker based:
|
||||||
|
|
||||||
- Master 容器启动时会主动执行 `/private/argus/etc/update-dns.sh`(若存在),把自身 `/etc/resolv.conf` 指向 bind 服务提供的 DNS;随后解析 `eth0` 的 IPv4 地址并写入 `/private/argus/etc/master.argus.com`。该文件会被 bind 模块的 `argus_dns_sync.sh` 监控,用于生成 `master.argus.com` → 当前容器 IP 的 A 记录。
|
|
||||||
- 测试与生产都需要将 bind 下发的 `update-dns.sh`、`dns.conf` 等文件挂载到 `/private/argus/etc/`。在 E2E 场景中,`tests/private/argus/etc` 会由脚本自动准备。
|
|
||||||
- 其他模块(如 agent)在启动脚本中只需执行同一份 `update-dns.sh`,即可使用域名访问 master;若域名注册异常,agent 将无法成功上报,可据此快速定位问题。
|
|
||||||
|
|
||||||
## REST API 详解
|
|
||||||
|
|
||||||
基础路径:`/api/v1/master`,全部返回 JSON。
|
|
||||||
|
|
||||||
### 1. `GET /nodes`
|
|
||||||
- **用途**:获取所有节点的简要信息。
|
|
||||||
- **响应示例**:
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"id": "A1", "name": "dev-user-inst-pod-0", "status": "online", "type": "agent", "version": "1.1.0"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. `GET /nodes/{id}`
|
|
||||||
- **用途**:获取节点详情(包含配置、健康、持久化时间戳等)。
|
|
||||||
- **错误**:`404` 表示节点不存在。
|
|
||||||
|
|
||||||
### 3. `POST /nodes`
|
|
||||||
- **用途**:注册或重注册节点。
|
|
||||||
- **请求体**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "A1", // 可选,重注册时携带
|
|
||||||
"name": "dev-user-inst-pod-0",
|
|
||||||
"type": "agent",
|
|
||||||
"version": "1.1.0",
|
|
||||||
"meta_data": {
|
|
||||||
"hostname": "dev-user-inst-pod-0",
|
|
||||||
"ip": "10.0.0.10",
|
|
||||||
"env": "dev",
|
|
||||||
"user": "testuser",
|
|
||||||
"instance": "testinst",
|
|
||||||
"cpu_number": 4,
|
|
||||||
"memory_in_bytes": 2147483648,
|
|
||||||
"gpu_number": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- **成功返回**:
|
|
||||||
- 新节点:`201 Created`,返回完整节点对象。
|
|
||||||
- 重注册:`200 OK`,返回更新后的节点对象。
|
|
||||||
- **错误情况**:
|
|
||||||
- `404 Not Found`:携带的 ID 在 Master 中不存在。
|
|
||||||
- `500 Internal Server Error`:携带的 ID 与已有名称不匹配。
|
|
||||||
- `400 Bad Request`:请求体缺字段或类型不正确。
|
|
||||||
|
|
||||||
### 4. `PUT /nodes/{id}/status`
|
|
||||||
- **用途**:Agent 上报状态。Master 记录 `last_report`(服务器时间)与 `agent_last_report`(上报内时间),并更新 `health` 字段。
|
|
||||||
- **请求体示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"timestamp": "2025-09-24T03:24:59Z",
|
|
||||||
"health": {
|
|
||||||
"log-fluentbit": {"status": "healthy"},
|
|
||||||
"metric-node-exporter": {"status": "healthy"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- **响应**:`200 OK`,返回最新节点对象。`404` 表示节点不存在。
|
|
||||||
|
|
||||||
### 5. `PUT /nodes/{id}/config`
|
|
||||||
- **用途**:局部更新节点配置与标签。
|
|
||||||
- **请求体示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"config": {"log_level": "debug"},
|
|
||||||
"label": ["gpu", "exp001"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- **说明**:字段可任选其一;未提供的配置保持原值。更新标签会触发 `nodes.json` 重新生成。
|
|
||||||
- **错误**:`404` 表示节点不存在;`400` 表示请求体不合法。
|
|
||||||
|
|
||||||
### 6. `GET /nodes/statistics`
|
|
||||||
- **用途**:统计节点总数及按状态分布。
|
|
||||||
- **响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"total": 2,
|
|
||||||
"status_statistics": [
|
|
||||||
{"status": "online", "count": 1},
|
|
||||||
{"status": "offline", "count": 1}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. 健康探针
|
|
||||||
- `GET /healthz`:进程存活检查。
|
|
||||||
- `GET /readyz`:数据库可用性检查(会尝试访问 `DB_PATH`)。
|
|
||||||
|
|
||||||
|
|
||||||
如需验证离线镜像,可使用自动化脚本:
|
|
||||||
```bash
|
```bash
|
||||||
cd src/master/tests
|
cd src/master/tests
|
||||||
./scripts/00_e2e_test_offline.sh # 构建离线镜像并执行完整 E2E
|
./scripts/00_e2e_test.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 端到端测试场景
|
Scripts create temporary `private/` and `tmp/` directories under `tests/`; they are cleaned automatically by `06_down.sh`.
|
||||||
|
|
||||||
执行 `src/master/tests/scripts/00_e2e_test.sh` 会串联以下用例(脚本 01–10):
|
|
||||||
|
|
||||||
1. **01_up_master**:构建镜像、启动容器、初始化目录与卷。
|
|
||||||
2. **02_verify_ready_and_nodes_json**:轮询 `/readyz`,校验初始 `nodes.json` 为 `[]`。
|
|
||||||
3. **03_register_via_curl**:模拟 agent 注册,保存返回的节点 ID,并确认节点出现在列表接口中。
|
|
||||||
4. **04_reregister_and_error_cases**:覆盖重注册成功、携带未知 ID 的 `404`、ID/名称不匹配触发 `500` 等场景。
|
|
||||||
5. **05_status_report_via_curl**:上报健康信息并验证状态自动从 `initialized`→`online`→`offline`→`online` 的转换。
|
|
||||||
6. **06_config_update_and_nodes_json**:更新配置/标签,检查 `nodes.json` 中的标签同步,并确保离线节点不会出现在文件里。
|
|
||||||
7. **07_stats_single_node**:等待节点掉线,验证统计接口与 `nodes.json` 为空列表。
|
|
||||||
8. **08_multi_node_stats**:注册第二节点,使一在线一离线,校验统计聚合和 `nodes.json` 仅包含在线节点。
|
|
||||||
9. **09_restart_persistence**:重启 master 容器,确认节点数据、统计结果与 `nodes.json` 在持久化目录中保持不变。
|
|
||||||
10. **10_down**:停止并清理容器、网络与临时目录。
|
|
||||||
|
|
||||||
## 相关持久化文件
|
|
||||||
|
|
||||||
- SQLite:默认位于 `DB_PATH`,包含 `nodes` 与 `kv` 两张表。
|
|
||||||
- `nodes.json`:由调度器周期生成,仅保留状态为 `online` 的节点信息。
|
|
||||||
- 测试用例中的 `tests/private/`、`tests/tmp/` 会随脚本自动清理,避免污染后续运行。
|
|
||||||
|
|
||||||
如需在生产环境运行,可将镜像推送到私有仓库,或参考测试 Compose 配置自行部署;只需确保上述环境变量在容器内正确设置即可。
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../bind/build/dns-monitor.sh
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# 中文提示:确保共享目录与 DNS 相关脚本存在
|
|
||||||
DNS_DIR="/private/argus/etc"
|
|
||||||
DNS_SCRIPT="${DNS_DIR}/update-dns.sh"
|
|
||||||
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"
|
|
||||||
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
|
|
||||||
echo "[INFO] Running update-dns.sh before master starts"
|
|
||||||
# 中文提示:若脚本存在则执行,保证容器使用 bind 作为 DNS
|
|
||||||
"$DNS_SCRIPT" || echo "[WARN] update-dns.sh execution failed"
|
|
||||||
else
|
|
||||||
echo "[WARN] DNS update script not found or not executable: $DNS_SCRIPT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 中文提示:记录 master 当前 IP,供 bind 服务同步
|
|
||||||
MASTER_IP=$(ifconfig | grep -A 1 eth0 | grep inet | awk '{print $2}' || true)
|
|
||||||
if [[ -n "${MASTER_IP}" ]]; then
|
|
||||||
echo "current IP: ${MASTER_IP}"
|
|
||||||
echo "${MASTER_IP}" > "$MASTER_DOMAIN_FILE"
|
|
||||||
chown "$RUNTIME_UID:$RUNTIME_GID" "$MASTER_DOMAIN_FILE" 2>/dev/null || true
|
|
||||||
else
|
|
||||||
echo "[WARN] Failed to detect master IP via ifconfig"
|
|
||||||
fi
|
|
||||||
|
|
||||||
WORKERS=${GUNICORN_WORKERS:-4}
|
|
||||||
BIND_ADDR=${GUNICORN_BIND:-0.0.0.0:3000}
|
|
||||||
EXTRA_OPTS=${GUNICORN_EXTRA_ARGS:-}
|
|
||||||
|
|
||||||
if [[ -n "$EXTRA_OPTS" ]]; then
|
|
||||||
read -r -a EXTRA_ARRAY <<< "$EXTRA_OPTS"
|
|
||||||
else
|
|
||||||
EXTRA_ARRAY=()
|
|
||||||
fi
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
[supervisord]
|
|
||||||
nodaemon=true
|
|
||||||
logfile=/var/log/supervisor/supervisord.log
|
|
||||||
pidfile=/var/run/supervisord.pid
|
|
||||||
user=root
|
|
||||||
|
|
||||||
[program:master]
|
|
||||||
command=/usr/local/bin/start-master.sh
|
|
||||||
user=root
|
|
||||||
stdout_logfile=/var/log/supervisor/master.log
|
|
||||||
stderr_logfile=/var/log/supervisor/master_error.log
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startsecs=5
|
|
||||||
stopwaitsecs=30
|
|
||||||
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
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
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
|
|
||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user