Compare commits
12 Commits
dev_1.0.0_
...
dev_1.0.0_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bab771b5e4 | ||
|
|
9032608e79 | ||
|
|
2b367f9ba1 | ||
|
|
084c0a3719 | ||
|
|
abc739b1be | ||
|
|
cb213df6f8 | ||
|
|
ade9dd7d62 | ||
|
|
54f99b854c | ||
|
|
ac15595c8e | ||
|
|
f17bc6d312 | ||
|
|
ac0eb558e9 | ||
|
|
ef89e5d7e6 |
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
|
||||
@@ -10,29 +10,19 @@ Usage: $0 [OPTIONS]
|
||||
Options:
|
||||
--intranet Use intranet mirror for log/bind builds
|
||||
--master-offline Build master offline image (requires src/master/offline_wheels.tar.gz)
|
||||
--metric Build metric module images (ftp, prometheus, grafana, test nodes)
|
||||
--no-cache Build all images without using Docker layer cache
|
||||
--only LIST Comma-separated targets to build: core,master,metric,web,alert,sys,all
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
$0 # Build with default sources
|
||||
$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
|
||||
$0 --intranet --master-offline
|
||||
EOF
|
||||
}
|
||||
|
||||
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
|
||||
case $1 in
|
||||
@@ -49,35 +39,6 @@ while [[ $# -gt 0 ]]; do
|
||||
build_master_offline=true
|
||||
shift
|
||||
;;
|
||||
--metric)
|
||||
build_metric=true
|
||||
shift
|
||||
;;
|
||||
--no-cache)
|
||||
no_cache=true
|
||||
shift
|
||||
;;
|
||||
--only)
|
||||
if [[ -z ${2:-} ]]; then
|
||||
echo "--only requires a target list" >&2; exit 1
|
||||
fi
|
||||
sel="$2"; shift 2
|
||||
# reset all, then enable selected
|
||||
build_core=false; build_master=false; build_metric=false; build_web=false; build_alert=false; build_sys=false
|
||||
IFS=',' read -ra parts <<< "$sel"
|
||||
for p in "${parts[@]}"; do
|
||||
case "$p" in
|
||||
core) build_core=true ;;
|
||||
master) build_master=true ;;
|
||||
metric) build_metric=true ;;
|
||||
web) build_web=true ;;
|
||||
alert) build_alert=true ;;
|
||||
sys) build_sys=true ;;
|
||||
all) build_core=true; build_master=true; build_metric=true; build_web=true; build_alert=true; build_sys=true ;;
|
||||
*) echo "Unknown --only target: $p" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
@@ -104,10 +65,6 @@ 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"
|
||||
@@ -148,22 +105,14 @@ build_image() {
|
||||
local image_name=$1
|
||||
local dockerfile_path=$2
|
||||
local tag=$3
|
||||
local context="."
|
||||
shift 3
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
context=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
local extra_args=("$@")
|
||||
|
||||
echo "🔄 Building $image_name image..."
|
||||
echo " Dockerfile: $dockerfile_path"
|
||||
echo " Tag: $tag"
|
||||
echo " Context: $context"
|
||||
|
||||
if docker build "${build_args[@]}" "${extra_args[@]}" -f "$dockerfile_path" -t "$tag" "$context"; then
|
||||
if docker build "${build_args[@]}" "${extra_args[@]}" -f "$dockerfile_path" -t "$tag" .; then
|
||||
echo "✅ $image_name image built successfully"
|
||||
return 0
|
||||
else
|
||||
@@ -172,59 +121,29 @@ build_image() {
|
||||
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=()
|
||||
build_failed=false
|
||||
|
||||
if [[ "$build_core" == true ]]; then
|
||||
if build_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest"; then
|
||||
images_built+=("argus-elasticsearch:latest")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
if build_image "Elasticsearch" "src/log/elasticsearch/build/Dockerfile" "argus-elasticsearch:latest"; then
|
||||
images_built+=("argus-elasticsearch:latest")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
if build_image "Kibana" "src/log/kibana/build/Dockerfile" "argus-kibana:latest"; then
|
||||
images_built+=("argus-kibana:latest")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
if build_image "Kibana" "src/log/kibana/build/Dockerfile" "argus-kibana:latest"; then
|
||||
images_built+=("argus-kibana:latest")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
if build_image "BIND9" "src/bind/build/Dockerfile" "argus-bind9:latest"; then
|
||||
images_built+=("argus-bind9:latest")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
if build_image "BIND9" "src/bind/build/Dockerfile" "argus-bind9:latest"; then
|
||||
images_built+=("argus-bind9:latest")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
@@ -240,9 +159,6 @@ if [[ "$build_master" == true ]]; then
|
||||
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")
|
||||
@@ -255,127 +171,6 @@ if [[ "$build_master" == true ]]; then
|
||||
popd >/dev/null
|
||||
fi
|
||||
|
||||
if [[ "$build_metric" == true ]]; then
|
||||
echo ""
|
||||
echo "Building Metric module images..."
|
||||
|
||||
metric_base_images=(
|
||||
"ubuntu:22.04"
|
||||
"ubuntu/prometheus:3-24.04_stable"
|
||||
"grafana/grafana:11.1.0"
|
||||
)
|
||||
|
||||
for base_image in "${metric_base_images[@]}"; do
|
||||
if ! pull_base_image "$base_image"; then
|
||||
build_failed=true
|
||||
fi
|
||||
done
|
||||
|
||||
metric_builds=(
|
||||
"Metric FTP|src/metric/ftp/build/Dockerfile|argus-metric-ftp:latest|src/metric/ftp/build"
|
||||
"Metric Prometheus|src/metric/prometheus/build/Dockerfile|argus-metric-prometheus:latest|src/metric/prometheus/build"
|
||||
"Metric Grafana|src/metric/grafana/build/Dockerfile|argus-metric-grafana:latest|src/metric/grafana/build"
|
||||
)
|
||||
|
||||
for build_spec in "${metric_builds[@]}"; do
|
||||
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||
images_built+=("$image_tag")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
|
||||
# =======================================
|
||||
# Sys (system tests) node images
|
||||
# =======================================
|
||||
|
||||
if [[ "$build_sys" == true ]]; then
|
||||
echo ""
|
||||
echo "Building Sys node images..."
|
||||
|
||||
sys_base_images=(
|
||||
"ubuntu:22.04"
|
||||
"nvidia/cuda:12.2.2-runtime-ubuntu22.04"
|
||||
)
|
||||
|
||||
for base_image in "${sys_base_images[@]}"; do
|
||||
if ! pull_base_image "$base_image"; then
|
||||
build_failed=true
|
||||
fi
|
||||
done
|
||||
|
||||
sys_builds=(
|
||||
"Sys Node|src/sys/build/node/Dockerfile|argus-sys-node:latest|."
|
||||
"Sys Metric Test Node|src/sys/build/test-node/Dockerfile|argus-sys-metric-test-node:latest|."
|
||||
"Sys Metric Test GPU Node|src/sys/build/test-gpu-node/Dockerfile|argus-sys-metric-test-gpu-node:latest|."
|
||||
)
|
||||
|
||||
for build_spec in "${sys_builds[@]}"; do
|
||||
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||
images_built+=("$image_tag")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
|
||||
# =======================================
|
||||
# Web & Alert module images
|
||||
# =======================================
|
||||
|
||||
if [[ "$build_web" == true || "$build_alert" == true ]]; then
|
||||
echo ""
|
||||
echo "Building Web and Alert module images..."
|
||||
|
||||
# Pre-pull commonly used base images for stability
|
||||
web_alert_base_images=(
|
||||
"node:20"
|
||||
"ubuntu:24.04"
|
||||
)
|
||||
|
||||
for base_image in "${web_alert_base_images[@]}"; do
|
||||
if ! pull_base_image "$base_image"; then
|
||||
build_failed=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$build_web" == true ]]; then
|
||||
web_builds=(
|
||||
"Web Frontend|src/web/build_tools/frontend/Dockerfile|argus-web-frontend:latest|."
|
||||
"Web Proxy|src/web/build_tools/proxy/Dockerfile|argus-web-proxy:latest|."
|
||||
)
|
||||
for build_spec in "${web_builds[@]}"; do
|
||||
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||
images_built+=("$image_tag")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ "$build_alert" == true ]]; then
|
||||
alert_builds=(
|
||||
"Alertmanager|src/alert/alertmanager/build/Dockerfile|argus-alertmanager:latest|."
|
||||
)
|
||||
for build_spec in "${alert_builds[@]}"; do
|
||||
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||
images_built+=("$image_tag")
|
||||
else
|
||||
build_failed=true
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "======================================="
|
||||
echo "📦 Build Summary"
|
||||
echo "======================================="
|
||||
@@ -402,6 +197,7 @@ if [[ "$build_master_offline" == true ]]; then
|
||||
echo ""
|
||||
echo "🧳 Master offline wheels 已解压到 $master_offline_dir"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🚀 Next steps:"
|
||||
echo " ./build/save_images.sh --compress # 导出镜像"
|
||||
|
||||
@@ -68,12 +68,6 @@ declare -A images=(
|
||||
["argus-kibana:latest"]="argus-kibana-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 +220,4 @@ fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Image export completed successfully!"
|
||||
echo ""
|
||||
echo ""
|
||||
@@ -34,18 +34,6 @@ 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`,后续重启无需再次提供环境变量就能保持一致性。
|
||||
|
||||
派生路径:
|
||||
|
||||
|
||||
@@ -18,12 +18,13 @@ _HOSTNAME_PATTERN = re.compile(r"^([^-]+)-([^-]+)-([^-]+)-.*$")
|
||||
def collect_metadata(config: AgentConfig) -> Dict[str, Any]:
|
||||
"""汇总节点注册需要的静态信息。"""
|
||||
hostname = config.hostname
|
||||
env, user, instance = _parse_hostname(hostname)
|
||||
meta = {
|
||||
"hostname": hostname,
|
||||
"ip": _detect_ip_address(),
|
||||
"env": config.environment,
|
||||
"user": config.user,
|
||||
"instance": config.instance,
|
||||
"env": env,
|
||||
"user": user,
|
||||
"instance": instance,
|
||||
"cpu_number": _detect_cpu_count(),
|
||||
"memory_in_bytes": _detect_memory_bytes(),
|
||||
"gpu_number": _detect_gpu_count(),
|
||||
|
||||
@@ -6,21 +6,14 @@ from dataclasses import dataclass
|
||||
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)
|
||||
class AgentConfig:
|
||||
hostname: str
|
||||
environment: str
|
||||
user: str
|
||||
instance: str
|
||||
node_file: str
|
||||
version: str
|
||||
master_endpoint: str
|
||||
@@ -54,68 +47,11 @@ def _resolve_hostname() -> str:
|
||||
return os.environ.get("AGENT_HOSTNAME") or socket.gethostname()
|
||||
|
||||
|
||||
def _load_metadata_from_state(node_file: str) -> tuple[str, str, str] | None:
|
||||
state = load_node_state(node_file)
|
||||
if not state:
|
||||
return None
|
||||
|
||||
meta = state.get("meta_data") or {}
|
||||
env = meta.get("env") or state.get("env")
|
||||
user = meta.get("user") or state.get("user")
|
||||
instance = meta.get("instance") or state.get("instance")
|
||||
|
||||
if env and user and instance:
|
||||
LOGGER.debug("Metadata resolved from node state", extra={"node_file": node_file})
|
||||
return env, user, instance
|
||||
|
||||
LOGGER.warning(
|
||||
"node.json missing metadata fields; ignoring",
|
||||
extra={"node_file": node_file, "meta_data": meta},
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_metadata_fields(hostname: str, node_file: str) -> tuple[str, str, str]:
|
||||
env = os.environ.get("AGENT_ENV")
|
||||
user = os.environ.get("AGENT_USER")
|
||||
instance = os.environ.get("AGENT_INSTANCE")
|
||||
|
||||
if env and user and instance:
|
||||
return env, user, instance
|
||||
|
||||
if any([env, user, instance]):
|
||||
LOGGER.warning(
|
||||
"Incomplete metadata environment variables; falling back to persisted metadata",
|
||||
extra={
|
||||
"has_env": bool(env),
|
||||
"has_user": bool(user),
|
||||
"has_instance": bool(instance),
|
||||
},
|
||||
)
|
||||
|
||||
state_metadata = _load_metadata_from_state(node_file)
|
||||
if state_metadata is not None:
|
||||
return state_metadata
|
||||
|
||||
from .collector import _parse_hostname # Local import to avoid circular dependency
|
||||
|
||||
env, user, instance = _parse_hostname(hostname)
|
||||
|
||||
if not all([env, user, instance]):
|
||||
raise ValueError(
|
||||
"Failed to determine metadata fields; set AGENT_ENV/USER/INSTANCE or use supported hostname pattern"
|
||||
)
|
||||
|
||||
return env, user, instance
|
||||
|
||||
|
||||
def load_config() -> AgentConfig:
|
||||
"""从环境变量推导配置,移除了外部配置文件依赖。"""
|
||||
|
||||
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")
|
||||
@@ -130,9 +66,6 @@ def load_config() -> AgentConfig:
|
||||
|
||||
return AgentConfig(
|
||||
hostname=hostname,
|
||||
environment=environment,
|
||||
user=user,
|
||||
instance=instance,
|
||||
node_file=node_file,
|
||||
version=VERSION,
|
||||
master_endpoint=master_endpoint,
|
||||
|
||||
BIN
src/agent/dist/argus-agent
vendored
BIN
src/agent/dist/argus-agent
vendored
Binary file not shown.
@@ -12,8 +12,6 @@ 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() {
|
||||
@@ -73,7 +71,6 @@ run_docker_build() {
|
||||
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
|
||||
@@ -85,10 +82,6 @@ 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
|
||||
|
||||
@@ -60,36 +60,6 @@ services:
|
||||
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:
|
||||
default:
|
||||
driver: bridge
|
||||
|
||||
@@ -7,10 +7,10 @@ SCRIPTS=(
|
||||
"02_up.sh"
|
||||
"03_wait_and_assert_registration.sh"
|
||||
"04_write_health_files.sh"
|
||||
"05_verify_agent.sh"
|
||||
"06_assert_status_on_master.sh"
|
||||
"07_restart_agent_and_reregister.sh"
|
||||
"08_down.sh"
|
||||
"08_verify_agent.sh"
|
||||
"05_assert_status_on_master.sh"
|
||||
"06_restart_agent_and_reregister.sh"
|
||||
"07_down.sh"
|
||||
)
|
||||
|
||||
for script in "${SCRIPTS[@]}"; do
|
||||
|
||||
@@ -41,7 +41,7 @@ compose() {
|
||||
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 argus-master-agent-e2e argus-bind-agent-e2e >/dev/null 2>&1 || true
|
||||
|
||||
docker network rm tests_default >/dev/null 2>&1 || true
|
||||
|
||||
|
||||
@@ -6,14 +6,11 @@ TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TMP_ROOT="$TEST_ROOT/tmp"
|
||||
API_BASE="http://localhost:32300/api/v1/master"
|
||||
AGENT_HOSTNAME="dev-e2euser-e2einst-pod-0"
|
||||
ENV_AGENT_HOSTNAME="host_abc"
|
||||
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"
|
||||
|
||||
primary_node_id=""
|
||||
env_node_id=""
|
||||
node_id=""
|
||||
for _ in {1..30}; do
|
||||
sleep 2
|
||||
response=$(curl -sS "$API_BASE/nodes" || true)
|
||||
@@ -22,49 +19,24 @@ for _ in {1..30}; do
|
||||
fi
|
||||
list_file="$TMP_ROOT/nodes_list.json"
|
||||
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
|
||||
|
||||
with open(sys.argv[1]) as handle:
|
||||
nodes = json.load(handle)
|
||||
|
||||
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)
|
||||
print(nodes[0]["id"] if nodes else "")
|
||||
PY
|
||||
)
|
||||
|
||||
primary_node_id="${node_ids[0]}"
|
||||
env_node_id="${node_ids[1]}"
|
||||
|
||||
if [[ -n "$primary_node_id" && -n "$env_node_id" ]]; then
|
||||
)
|
||||
if [[ -n "$node_id" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$primary_node_id" ]]; then
|
||||
echo "[ERROR] Primary agent did not register within timeout" >&2
|
||||
if [[ -z "$node_id" ]]; then
|
||||
echo "[ERROR] Agent did not register within timeout" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$env_node_id" ]]; then
|
||||
echo "[ERROR] Env-variable agent did not register within timeout" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$primary_node_id" > "$TMP_ROOT/node_id"
|
||||
echo "$env_node_id" > "$TMP_ROOT/node_id_host_abc"
|
||||
echo "$node_id" > "$TMP_ROOT/node_id"
|
||||
|
||||
if [[ ! -f "$NODE_FILE" ]]; then
|
||||
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"
|
||||
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"
|
||||
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'
|
||||
import json, sys, pathlib
|
||||
with open(sys.argv[1]) as handle:
|
||||
@@ -102,5 +62,4 @@ if not ip:
|
||||
pathlib.Path(sys.argv[2]).write_text(ip)
|
||||
PY
|
||||
|
||||
echo "[INFO] Agent registered with node id $primary_node_id"
|
||||
echo "[INFO] Env-variable agent registered with node id $env_node_id"
|
||||
echo "[INFO] Agent registered with node id $node_id"
|
||||
|
||||
@@ -6,8 +6,6 @@ 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="$(cat "$TMP_ROOT/node_id_host_abc")"
|
||||
ENV_HOSTNAME="host_abc"
|
||||
NODES_JSON="$TEST_ROOT/private/argus/metric/prometheus/nodes.json"
|
||||
|
||||
success=false
|
||||
@@ -43,36 +41,13 @@ if [[ ! -f "$NODES_JSON" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - "$NODES_JSON" "$NODE_ID" "$ENV_NODE_ID" <<'PY'
|
||||
python3 - "$NODES_JSON" <<'PY'
|
||||
import json, sys
|
||||
with open(sys.argv[1]) as handle:
|
||||
nodes = json.load(handle)
|
||||
|
||||
expected_primary = sys.argv[2]
|
||||
expected_env = sys.argv[3]
|
||||
|
||||
ids = {entry.get("node_id") for entry in nodes}
|
||||
assert expected_primary in ids, nodes
|
||||
assert expected_env in ids, nodes
|
||||
assert len(nodes) >= 2, nodes
|
||||
assert len(nodes) == 1, nodes
|
||||
entry = nodes[0]
|
||||
assert entry["node_id"], entry
|
||||
PY
|
||||
|
||||
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
|
||||
@@ -6,20 +6,10 @@ 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 注册逻辑一致
|
||||
@@ -28,11 +18,6 @@ if [[ ! -f "$ENTRYPOINT_SCRIPT" ]]; then
|
||||
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
|
||||
@@ -89,37 +74,15 @@ if [[ "$prev_ip" != "$initial_ip" ]]; then
|
||||
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 \
|
||||
@@ -131,7 +94,6 @@ if ! docker run -d \
|
||||
-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" \
|
||||
@@ -179,76 +141,3 @@ if [[ "$success" != true ]]; then
|
||||
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"
|
||||
@@ -13,7 +13,7 @@ compose() {
|
||||
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
|
||||
compose down --remove-orphans
|
||||
26
src/agent/tests/scripts/08_verify_agent.sh
Executable file
26
src/agent/tests/scripts/08_verify_agent.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
VERIFY_SCRIPT="$(cd "$TEST_ROOT/.." && pwd)/scripts/agent_deployment_verify.sh"
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q '^argus-agent-e2e$'; then
|
||||
echo "[WARN] agent container not running; skip verification"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if docker exec -i argus-agent-e2e bash -lc 'command -v curl >/dev/null && command -v jq >/dev/null'; then
|
||||
echo "[INFO] curl/jq already installed in agent container"
|
||||
else
|
||||
echo "[INFO] Installing curl/jq in agent container"
|
||||
docker exec -i argus-agent-e2e bash -lc 'apt-get update >/dev/null 2>&1 && apt-get install -y curl jq >/dev/null 2>&1' || true
|
||||
fi
|
||||
|
||||
if docker exec -i argus-agent-e2e bash -lc 'command -v /usr/local/bin/agent_deployment_verify.sh >/dev/null'; then
|
||||
docker exec -i argus-agent-e2e /usr/local/bin/agent_deployment_verify.sh
|
||||
elif [[ -x "$VERIFY_SCRIPT" ]]; then
|
||||
docker exec -i argus-agent-e2e "$VERIFY_SCRIPT"
|
||||
else
|
||||
echo "[WARN] agent_deployment_verify.sh not found"
|
||||
fi
|
||||
@@ -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()
|
||||
@@ -9,21 +9,21 @@ 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 版本(与本地离线包保持一致)
|
||||
# 设置 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
|
||||
# 下载并解压 Alertmanager 二进制
|
||||
RUN wget https://github.com/prometheus/alertmanager/releases/download/v${ALERTMANAGER_VERSION}/alertmanager-${ALERTMANAGER_VERSION}.linux-amd64.tar.gz && \
|
||||
tar xvf alertmanager-${ALERTMANAGER_VERSION}.linux-amd64.tar.gz && \
|
||||
mv alertmanager-${ALERTMANAGER_VERSION}.linux-amd64 /usr/local/alertmanager && \
|
||||
rm 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}
|
||||
ARG ARGUS_UID=2133
|
||||
ARG ARGUS_GID=2015
|
||||
ENV ARGUS_UID=${ARGUS_UID}
|
||||
ENV ARGUS_GID=${ARGUS_GID}
|
||||
|
||||
RUN mkdir -p /usr/share/alertmanager && \
|
||||
mkdir -p ${ALERTMANAGER_BASE_PATH} && \
|
||||
@@ -33,24 +33,16 @@ RUN mkdir -p /usr/share/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 groupadd -g ${ARGUS_GID} alertmanager
|
||||
|
||||
RUN chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" /usr/share/alertmanager /alertmanager ${ALERTMANAGER_BASE_PATH} /private/argus/etc /usr/local/bin || true
|
||||
# 创建 alertmanager 用户并指定组
|
||||
RUN useradd -M -s /usr/sbin/nologin -u ${ARGUS_UID} -g ${ARGUS_GID} alertmanager
|
||||
|
||||
RUN chown -R alertmanager:alertmanager /usr/share/alertmanager && \
|
||||
chown -R alertmanager:alertmanager /alertmanager && \
|
||||
chown -R alertmanager:alertmanager ${ALERTMANAGER_BASE_PATH} && \
|
||||
chown -R alertmanager:alertmanager /private/argus/etc && \
|
||||
chown -R alertmanager:alertmanager /usr/local/bin
|
||||
|
||||
# 配置内网 apt 源 (如果指定了内网选项)
|
||||
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||
@@ -94,3 +86,4 @@ EXPOSE 9093
|
||||
|
||||
# 使用 supervisor 作为入口点
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
|
||||
|
||||
Binary file not shown.
@@ -5,9 +5,9 @@ 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} \
|
||||
--build-arg ARGUS_UID=${ARGUS_UID} \
|
||||
--build-arg ARGUS_GID=${ARGUS_GID} \
|
||||
-f src/alert/alertmanager/build/Dockerfile \
|
||||
-t argus-alertmanager:latest .
|
||||
|
||||
docker save -o argus-alertmanager-latest.tar argus-alertmanager:latest
|
||||
docker save -o argus-alertmanager-latest.tar argus-alertmanager:latest
|
||||
@@ -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"
|
||||
|
||||
@@ -7,8 +7,10 @@ ALERTMANAGER_BASE_PATH=${ALERTMANAGER_BASE_PATH:-/private/argus/alert/alertmanag
|
||||
|
||||
echo "[INFO] Alertmanager base path: ${ALERTMANAGER_BASE_PATH}"
|
||||
|
||||
# 使用容器内的 /etc/alertmanager/alertmanager.yml 作为配置文件,避免写入挂载卷导致的权限问题
|
||||
echo "[INFO] Using /etc/alertmanager/alertmanager.yml as configuration"
|
||||
# 生成配置文件
|
||||
echo "[INFO] Generating Alertmanager configuration file..."
|
||||
sed "s|\${ALERTMANAGER_BASE_PATH}|${ALERTMANAGER_BASE_PATH}|g" \
|
||||
/etc/alertmanager/alertmanager.yml > ${ALERTMANAGER_BASE_PATH}/alertmanager.yml
|
||||
|
||||
|
||||
# 记录容器 IP 地址
|
||||
|
||||
@@ -6,7 +6,7 @@ user=root
|
||||
|
||||
[program:alertmanager]
|
||||
command=/usr/local/bin/start-am-supervised.sh
|
||||
user=ubuntu
|
||||
user=alertmanager
|
||||
stdout_logfile=/var/log/supervisor/alertmanager.log
|
||||
stderr_logfile=/var/log/supervisor/alertmanager_error.log
|
||||
autorestart=true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
DATA_ROOT=/home/argus/tmp/private/argus
|
||||
ARGUS_BUILD_UID=1048
|
||||
ARGUS_BUILD_GID=1048
|
||||
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
|
||||
@@ -4,15 +4,15 @@ services:
|
||||
context: ../../../
|
||||
dockerfile: src/alert/alertmanager/build/Dockerfile
|
||||
args:
|
||||
ARGUS_BUILD_UID: ${ARGUS_BUILD_UID:-2133}
|
||||
ARGUS_BUILD_GID: ${ARGUS_BUILD_GID:-2015}
|
||||
ARGUS_UID: ${ARGUS_UID:-2133}
|
||||
ARGUS_GID: ${ARGUS_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}
|
||||
- ARGUS_UID=${ARGUS_UID:-2133}
|
||||
- ARGUS_GID=${ARGUS_GID:-2015}
|
||||
ports:
|
||||
- "${ARGUS_PORT:-9093}:9093"
|
||||
volumes:
|
||||
|
||||
@@ -26,7 +26,6 @@ RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
bind9 \
|
||||
bind9utils \
|
||||
dnsutils \
|
||||
bind9-doc \
|
||||
supervisor \
|
||||
net-tools \
|
||||
|
||||
@@ -17,9 +17,6 @@ log_message() {
|
||||
|
||||
log_message "DNS监控脚本启动"
|
||||
|
||||
log_message "删除DNS备份文件(如果存在)"
|
||||
rm -f $DNS_BACKUP
|
||||
|
||||
while true; do
|
||||
if [ -f "$DNS_CONF" ]; then
|
||||
if [ -f "$DNS_BACKUP" ]; then
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,96 +1,47 @@
|
||||
#!/bin/bash
|
||||
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
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl
|
||||
|
||||
# Stage bundle to /tmp (read-only mount under /private)
|
||||
echo "[INFO] Staging fluent-bit bundle..."
|
||||
rm -rf /tmp/flb && mkdir -p /tmp/flb
|
||||
cp -r /private/etc /tmp/flb/
|
||||
mkdir -p /tmp/flb/packages
|
||||
cp -r /private/packages/* /tmp/flb/packages/ 2>/dev/null || true
|
||||
# 解压bundle到/tmp
|
||||
echo "[INFO] Extracting fluent-bit bundle..."
|
||||
cp -r /private/etc /tmp
|
||||
cp -r /private/packages /tmp
|
||||
cd /tmp
|
||||
|
||||
# Helper: check and install a local deb if not already satisfied
|
||||
ensure_lib() {
|
||||
local soname="$1"; shift
|
||||
local pattern="$1"; shift
|
||||
if ldconfig -p 2>/dev/null | grep -q "$soname"; then
|
||||
echo "[OK] $soname already present"
|
||||
return 0
|
||||
fi
|
||||
local deb="$(ls /tmp/flb/packages/$pattern 2>/dev/null | head -n1 || true)"
|
||||
if [[ -n "$deb" ]]; then
|
||||
echo "[INFO] Installing local dependency: $(basename "$deb")"
|
||||
dpkg -i "$deb" >/dev/null 2>&1 || true
|
||||
else
|
||||
echo "[WARN] Local deb for $soname not found (pattern=$pattern)"
|
||||
fi
|
||||
if ! ldconfig -p 2>/dev/null | grep -q "$soname"; then
|
||||
echo "[WARN] $soname still missing after local install; attempting apt fallback"
|
||||
apt-get update -qq || true
|
||||
case "$soname" in
|
||||
libpq.so.5) apt-get install -y -qq libpq5 || true ;;
|
||||
libyaml-0.so.2) apt-get install -y -qq libyaml-0-2 || true ;;
|
||||
esac
|
||||
fi
|
||||
ldconfig 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Offline-first: satisfy runtime deps from local debs, fallback to apt only if necessary
|
||||
ensure_lib "libpq.so.5" "libpq5_*_amd64.deb"
|
||||
ensure_lib "libyaml-0.so.2" "libyaml-0-2_*_amd64.deb"
|
||||
ensure_lib "libsasl2.so.2" "libsasl2-2_*_amd64.deb"
|
||||
ensure_lib "libldap-2.5.so.0" "libldap-2.5-0_*_amd64.deb"
|
||||
|
||||
# Install fluent-bit main package from local bundle
|
||||
FLB_DEB="$(ls /tmp/flb/packages/fluent-bit_*_amd64.deb 2>/dev/null | head -n1 || true)"
|
||||
if [[ -z "$FLB_DEB" ]]; then
|
||||
echo "[ERROR] fluent-bit deb not found under /private/packages" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[INFO] Installing Fluent Bit: $(basename "$FLB_DEB")"
|
||||
dpkg -i "$FLB_DEB" >/dev/null 2>&1 || true
|
||||
|
||||
# If dpkg reported unresolved dependencies, try apt -f only as last resort
|
||||
if ! command -v /opt/fluent-bit/bin/fluent-bit >/dev/null 2>&1; then
|
||||
echo "[WARN] fluent-bit binary missing after dpkg; attempting apt --fix-broken"
|
||||
apt-get install -f -y -qq || true
|
||||
fi
|
||||
|
||||
# Ensure runtime library dependencies are satisfied (libsasl2, libldap are required via libpq/curl)
|
||||
MISSING=$(ldd /opt/fluent-bit/bin/fluent-bit 2>/dev/null | awk '/not found/{print $1}' | xargs -r echo || true)
|
||||
if [[ -n "$MISSING" ]]; then
|
||||
echo "[WARN] missing shared libs: $MISSING"
|
||||
apt-get update -qq || true
|
||||
apt-get install -y -qq libsasl2-2 libldap-2.5-0 || true
|
||||
apt-get install -f -y -qq || true
|
||||
fi
|
||||
# 安装 Fluent Bit 从 deb 包
|
||||
echo "[INFO] Installing Fluent Bit from deb package..."
|
||||
dpkg -i /tmp/packages/fluent-bit_3.1.9_amd64.deb || true
|
||||
apt-get install -f -y -qq # 解决依赖问题
|
||||
|
||||
# 验证 Fluent Bit 可以运行
|
||||
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
|
||||
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
|
||||
chmod 755 /logs/train /logs/infer /buffers
|
||||
|
||||
# Wait for Elasticsearch via bash /dev/tcp to avoid curl dependency
|
||||
echo "[INFO] Waiting for Elasticsearch to be ready (tcp ${ES_HOST}:${ES_PORT})..."
|
||||
for i in $(seq 1 120); do
|
||||
if exec 3<>/dev/tcp/${ES_HOST}/${ES_PORT}; then
|
||||
exec 3<&- 3>&-
|
||||
echo "[INFO] Elasticsearch is ready"
|
||||
break
|
||||
fi
|
||||
[[ $i -eq 120 ]] && { echo "[ERROR] ES not reachable" >&2; exit 1; }
|
||||
sleep 1
|
||||
# 等待 Elasticsearch 就绪
|
||||
echo "[INFO] Waiting for Elasticsearch to be ready..."
|
||||
while ! curl -fs http://${ES_HOST}:${ES_PORT}/_cluster/health >/dev/null 2>&1; do
|
||||
echo " Waiting for ES at ${ES_HOST}:${ES_PORT}..."
|
||||
sleep 5
|
||||
done
|
||||
echo "[INFO] Elasticsearch is ready"
|
||||
|
||||
# 启动 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"
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
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..."
|
||||
|
||||
@@ -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() {
|
||||
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
|
||||
}
|
||||
|
||||
# 主逻辑
|
||||
|
||||
@@ -32,42 +32,3 @@ fi
|
||||
|
||||
echo "[OK] 初始化完成: private/argus/log/{elasticsearch,kibana}"
|
||||
echo "[INFO] Fluent-bit files should be in fluent-bit/ directory"
|
||||
|
||||
# 准备 Fluent Bit 离线依赖(从 metric all-in-one-full 复制 deb 到 ../fluent-bit/build/packages)
|
||||
FLB_BUILD_PACKAGES_DIR="$root/../fluent-bit/build/packages"
|
||||
mkdir -p "$FLB_BUILD_PACKAGES_DIR"
|
||||
for deb in \
|
||||
"$project_root/src/metric/client-plugins/all-in-one-full/plugins/fluent-bit/bin/libyaml-0-2_"*_amd64.deb \
|
||||
"$project_root/src/metric/client-plugins/all-in-one-full/plugins/fluent-bit/bin/libpq5_"*_amd64.deb ; do
|
||||
if ls $deb >/dev/null 2>&1; then
|
||||
for f in $deb; do
|
||||
base="$(basename "$f")"
|
||||
if [[ ! -f "$FLB_BUILD_PACKAGES_DIR/$base" ]]; then
|
||||
cp "$f" "$FLB_BUILD_PACKAGES_DIR/"
|
||||
echo " [+] copied $base"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
# 额外:从 all-in-one-full 的 ubuntu22/curl.tar.gz 解包必要依赖(libsasl2/ldap),便于离线安装
|
||||
CURLOPT_TAR="$project_root/src/metric/client-plugins/all-in-one-full/deps/ubuntu22/curl.tar.gz"
|
||||
if [[ -f "$CURLOPT_TAR" ]]; then
|
||||
tmpdir=$(mktemp -d)
|
||||
if tar -xzf "$CURLOPT_TAR" -C "$tmpdir" 2>/dev/null; then
|
||||
for p in \
|
||||
libsasl2-2_*_amd64.deb \
|
||||
libsasl2-modules-db_*_amd64.deb \
|
||||
libldap-2.5-0_*_amd64.deb \
|
||||
libidn2-0_*_amd64.deb \
|
||||
libbrotli1_*_amd64.deb \
|
||||
libssl3_*_amd64.deb ; do
|
||||
src=$(ls "$tmpdir"/curl/$p 2>/dev/null | head -n1 || true)
|
||||
if [[ -n "$src" ]]; then
|
||||
base="$(basename "$src")"
|
||||
[[ -f "$FLB_BUILD_PACKAGES_DIR/$base" ]] || cp "$src" "$FLB_BUILD_PACKAGES_DIR/" && echo " [+] staged $base"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
rm -rf "$tmpdir"
|
||||
fi
|
||||
|
||||
@@ -115,32 +115,20 @@ show_step "Health" "Check service health"
|
||||
echo "[INFO] Checking service health..."
|
||||
|
||||
# 检查 Elasticsearch 健康状态
|
||||
health_check_ok=1
|
||||
es_health=$(curl -s "http://localhost:9200/_cluster/health" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
|
||||
if [ "$es_health" = "green" ] || [ "$es_health" = "yellow" ]; then
|
||||
echo "✅ Elasticsearch health: $es_health"
|
||||
else
|
||||
echo "❌ Elasticsearch health: $es_health"
|
||||
health_check_ok=0
|
||||
fi
|
||||
|
||||
# 检查 Kibana 状态
|
||||
if curl -fs "http://localhost:5601/api/status" >/dev/null 2>&1; then
|
||||
kb_status="available"
|
||||
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
|
||||
kb_status="unavailable"
|
||||
echo "⚠️ Kibana status: $kb_status"
|
||||
health_check_ok=0
|
||||
fi
|
||||
|
||||
# 检查 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"
|
||||
else
|
||||
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
|
||||
|
||||
verify_step "Service health check"
|
||||
|
||||
@@ -3,13 +3,12 @@ set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'USAGE'
|
||||
Usage: $0 [--intranet] [--offline] [--tag <image_tag>] [--no-cache]
|
||||
Usage: $0 [--intranet] [--offline] [--tag <image_tag>]
|
||||
|
||||
Options:
|
||||
--intranet 使用指定的 PyPI 镜像源(默认清华镜像)。
|
||||
--offline 完全离线构建,依赖 offline_wheels/ 目录中的离线依赖包。
|
||||
--tag <image_tag> 自定义镜像标签,默认 argus-master:latest。
|
||||
--no-cache 不使用 Docker 构建缓存。
|
||||
USAGE
|
||||
}
|
||||
|
||||
@@ -20,7 +19,6 @@ IMAGE_TAG="${IMAGE_TAG:-argus-master:latest}"
|
||||
DOCKERFILE="src/master/Dockerfile"
|
||||
BUILD_ARGS=()
|
||||
OFFLINE_MODE=0
|
||||
NO_CACHE=0
|
||||
|
||||
source "$PROJECT_ROOT/scripts/common/build_user.sh"
|
||||
load_build_user
|
||||
@@ -47,11 +45,6 @@ while [[ "$#" -gt 0 ]]; do
|
||||
IMAGE_TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-cache)
|
||||
NO_CACHE=1
|
||||
BUILD_ARGS+=("--no-cache")
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
|
||||
2
src/metric/.gitignore
vendored
2
src/metric/.gitignore
vendored
@@ -4,4 +4,4 @@
|
||||
/client-plugins/demo-all-in-one/publish/
|
||||
/client-plugins/demo-all-in-one/checklist
|
||||
/client-plugins/demo-all-in-one/VERSION
|
||||
/client-plugins/all-in-one-full/artifact/
|
||||
/client-plugins/all-in-one-full/
|
||||
|
||||
@@ -4,15 +4,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
# PID 文件检测,防止重复执行
|
||||
PIDFILE="/var/run/check_health.pid"
|
||||
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
|
||||
echo "健康检查脚本已在运行中,跳过本次执行" >&2
|
||||
exit 0
|
||||
fi
|
||||
echo $$ > "$PIDFILE"
|
||||
trap "rm -f $PIDFILE" EXIT
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
HEALTH_LOG_FILE="$SCRIPT_DIR/.health_log"
|
||||
|
||||
@@ -200,22 +200,22 @@ parse_version_info() {
|
||||
VERSION=$(grep '"version"' "$VERSION_FILE_PATH" | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
||||
BUILD_TIME=$(grep '"build_time"' "$VERSION_FILE_PATH" | sed 's/.*"build_time": *"\([^"]*\)".*/\1/')
|
||||
|
||||
# 解析 artifact_list(跳过字段名本身)
|
||||
grep -A 100 '"artifact_list"' "$VERSION_FILE_PATH" | grep -v '"artifact_list"' | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
||||
# 解析 artifact_list
|
||||
grep -A 100 '"artifact_list"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
||||
component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/')
|
||||
version=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
||||
echo "$component:$version" >> "$TEMP_DIR/components.txt"
|
||||
done
|
||||
|
||||
# 解析 checksums(跳过字段名本身)
|
||||
grep -A 100 '"checksums"' "$VERSION_FILE_PATH" | grep -v '"checksums"' | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
||||
# 解析 checksums
|
||||
grep -A 100 '"checksums"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+":\s*"[^"]+"' | while read line; do
|
||||
component=$(echo "$line" | sed 's/.*"\([^"]*\)":\s*"[^"]*".*/\1/')
|
||||
checksum=$(echo "$line" | sed 's/.*"[^"]*":\s*"\([^"]*\)".*/\1/')
|
||||
echo "$component:$checksum" >> "$TEMP_DIR/checksums.txt"
|
||||
done
|
||||
|
||||
# 解析 install_order(跳过字段名本身,只取数组元素)
|
||||
grep -A 100 '"install_order"' "$VERSION_FILE_PATH" | grep -v '"install_order"' | grep -E '^\s*"[^"]+"' | while read line; do
|
||||
# 解析 install_order
|
||||
grep -A 100 '"install_order"' "$VERSION_FILE_PATH" | grep -E '^\s*"[^"]+"' | while read line; do
|
||||
component=$(echo "$line" | sed 's/.*"\([^"]*\)".*/\1/')
|
||||
echo "$component" >> "$TEMP_DIR/install_order.txt"
|
||||
done
|
||||
@@ -317,152 +317,85 @@ create_install_dirs() {
|
||||
log_success "安装目录创建完成: $INSTALL_DIR"
|
||||
}
|
||||
|
||||
# 获取系统版本
|
||||
get_system_version() {
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
log_error "无法检测操作系统版本"
|
||||
return 1
|
||||
fi
|
||||
|
||||
source /etc/os-release
|
||||
|
||||
# 提取主版本号
|
||||
case "$VERSION_ID" in
|
||||
"20.04")
|
||||
echo "ubuntu20"
|
||||
;;
|
||||
"22.04")
|
||||
echo "ubuntu22"
|
||||
;;
|
||||
*)
|
||||
log_warning "未识别的Ubuntu版本: $VERSION_ID,尝试使用ubuntu22"
|
||||
echo "ubuntu22"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 安装系统依赖包
|
||||
install_system_deps() {
|
||||
log_info "检查系统依赖包..."
|
||||
|
||||
|
||||
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local deps_dir="$script_dir/deps"
|
||||
|
||||
|
||||
# 检查deps目录是否存在
|
||||
if [[ ! -d "$deps_dir" ]]; then
|
||||
log_info "deps 目录不存在,跳过系统依赖包安装"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 获取系统版本对应的依赖目录
|
||||
local system_version=$(get_system_version)
|
||||
local version_deps_dir="$deps_dir/$system_version"
|
||||
|
||||
log_info "检测到系统版本: $system_version"
|
||||
|
||||
# 检查版本特定的依赖目录是否存在
|
||||
if [[ ! -d "$version_deps_dir" ]]; then
|
||||
log_warning "未找到 $system_version 版本的依赖目录: $version_deps_dir"
|
||||
# 回退到旧的逻辑,检查根deps目录
|
||||
local deps_count=$(find "$deps_dir" -name "*.tar.gz" | wc -l)
|
||||
if [[ $deps_count -eq 0 ]]; then
|
||||
log_info "deps 目录中没有 tar.gz 文件,跳过系统依赖包安装"
|
||||
return 0
|
||||
fi
|
||||
version_deps_dir="$deps_dir"
|
||||
else
|
||||
# 检查版本目录中是否有tar.gz文件
|
||||
local deps_count=$(find "$version_deps_dir" -name "*.tar.gz" | wc -l)
|
||||
if [[ $deps_count -eq 0 ]]; then
|
||||
log_info "$system_version 版本目录中没有 tar.gz 文件,跳过系统依赖包安装"
|
||||
return 0
|
||||
fi
|
||||
# 检查是否有tar.gz文件
|
||||
local deps_count=$(find "$deps_dir" -name "*.tar.gz" | wc -l)
|
||||
if [[ $deps_count -eq 0 ]]; then
|
||||
log_info "deps 目录中没有 tar.gz 文件,跳过系统依赖包安装"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "找到 $system_version 版本的依赖包,开始安装..."
|
||||
|
||||
|
||||
log_info "找到 $deps_count 个系统依赖包,开始安装..."
|
||||
|
||||
# 创建临时目录用于解压依赖包
|
||||
local deps_temp_dir="${TEMP_DIR:-/tmp}/deps"
|
||||
local deps_temp_dir="$TEMP_DIR/deps"
|
||||
mkdir -p "$deps_temp_dir"
|
||||
|
||||
# 定义要检查的核心依赖
|
||||
local CORE_DEPS=(jq cron curl)
|
||||
local FAILED_DEPS=()
|
||||
|
||||
|
||||
# 处理每个tar.gz文件
|
||||
find "$version_deps_dir" -name "*.tar.gz" | while read tar_file; do
|
||||
find "$deps_dir" -name "*.tar.gz" | while read tar_file; do
|
||||
local tar_basename=$(basename "$tar_file")
|
||||
local extract_name="${tar_basename%.tar.gz}"
|
||||
|
||||
|
||||
log_info "处理依赖包: $tar_basename"
|
||||
|
||||
|
||||
# 解压到临时目录
|
||||
local extract_dir="$deps_temp_dir/$extract_name"
|
||||
mkdir -p "$extract_dir"
|
||||
|
||||
|
||||
if tar -xzf "$tar_file" -C "$extract_dir" 2>/dev/null; then
|
||||
log_success " $tar_basename 解压完成"
|
||||
else
|
||||
log_error " $tar_basename 解压失败"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
# 进入解压目录,查找deb包
|
||||
cd "$extract_dir" || continue
|
||||
local deb_files=(*.deb)
|
||||
if [[ ${#deb_files[@]} -gt 0 ]]; then
|
||||
log_info " 找到 ${#deb_files[@]} 个 deb 包,开始安装..."
|
||||
|
||||
for deb in "${deb_files[@]}"; do
|
||||
local pkg_name
|
||||
pkg_name=$(dpkg-deb -f "$deb" Package 2>/dev/null)
|
||||
|
||||
# 如果已安装,则跳过
|
||||
if dpkg -s "$pkg_name" &>/dev/null; then
|
||||
log_success " $pkg_name 已安装,跳过"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 尝试安装
|
||||
log_info " 安装 $pkg_name..."
|
||||
if DEBIAN_FRONTEND=noninteractive dpkg -i "$deb" &>/dev/null; then
|
||||
log_success " $pkg_name 安装成功"
|
||||
cd "$extract_dir"
|
||||
local deb_count=$(find . -name "*.deb" | wc -l)
|
||||
|
||||
if [[ $deb_count -gt 0 ]]; then
|
||||
log_info " 找到 $deb_count 个 deb 包,开始安装..."
|
||||
|
||||
# 1. 先尝试安装所有deb包
|
||||
log_info " 第1步:批量安装deb包..."
|
||||
if dpkg -i *.deb 2>/dev/null; then
|
||||
log_success " 所有deb包安装成功"
|
||||
else
|
||||
log_warning " 部分deb包安装失败,可能存在依赖问题"
|
||||
|
||||
# 2. 使用apt-get修复依赖
|
||||
log_info " 第2步:修复依赖关系..."
|
||||
if apt-get install -f -y; then
|
||||
log_success " 依赖关系修复完成"
|
||||
else
|
||||
log_warning " $pkg_name 安装失败,尝试修复依赖..."
|
||||
if DEBIAN_FRONTEND=noninteractive apt-get install -f -y &>/dev/null; then
|
||||
if dpkg -s "$pkg_name" &>/dev/null; then
|
||||
log_success " $pkg_name 修复安装成功"
|
||||
else
|
||||
log_error " $pkg_name 仍未安装成功"
|
||||
FAILED_DEPS+=("$pkg_name")
|
||||
fi
|
||||
else
|
||||
log_error " $pkg_name 自动修复失败"
|
||||
FAILED_DEPS+=("$pkg_name")
|
||||
fi
|
||||
log_error " 依赖关系修复失败"
|
||||
# 继续处理其他包,不退出
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
log_info " $tar_basename 中没有找到deb包,跳过"
|
||||
fi
|
||||
|
||||
|
||||
# 返回到依赖临时目录
|
||||
cd "$deps_temp_dir" || continue
|
||||
cd "$deps_temp_dir"
|
||||
done
|
||||
|
||||
|
||||
# 检查并启动 cron 服务
|
||||
start_cron_service
|
||||
|
||||
# 总结安装结果
|
||||
if [[ ${#FAILED_DEPS[@]} -gt 0 ]]; then
|
||||
log_error "以下系统依赖未能成功安装,安装终止,请手动安装后重试:"
|
||||
for f in "${FAILED_DEPS[@]}"; do
|
||||
echo " - $f"
|
||||
done
|
||||
exit 1
|
||||
else
|
||||
log_success "系统依赖包安装完成,全部就绪"
|
||||
fi
|
||||
|
||||
log_success "系统依赖包安装完成"
|
||||
}
|
||||
|
||||
# 启动 cron 服务
|
||||
@@ -704,18 +637,6 @@ EOF
|
||||
log_success "安装记录已创建: $install_record_file"
|
||||
}
|
||||
|
||||
# 检查cron任务是否已存在
|
||||
check_cron_task_exists() {
|
||||
local task_pattern="$1"
|
||||
local temp_cron="$2"
|
||||
|
||||
if grep -q "$task_pattern" "$temp_cron"; then
|
||||
return 0 # 任务已存在
|
||||
else
|
||||
return 1 # 任务不存在
|
||||
fi
|
||||
}
|
||||
|
||||
# 设置健康检查定时任务
|
||||
setup_health_check_cron() {
|
||||
log_info "设置健康检查定时任务..."
|
||||
@@ -740,7 +661,7 @@ setup_health_check_cron() {
|
||||
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
||||
|
||||
# 检查并删除旧的健康检查任务
|
||||
if check_cron_task_exists "check_health.sh" "$temp_cron"; then
|
||||
if grep -q "check_health.sh" "$temp_cron"; then
|
||||
log_info "发现旧的健康检查定时任务,正在更新..."
|
||||
# 删除所有包含check_health.sh的行
|
||||
grep -v "check_health.sh" "$temp_cron" > "$temp_cron.new"
|
||||
@@ -795,7 +716,7 @@ setup_dns_sync_cron() {
|
||||
crontab -l 2>/dev/null > "$temp_cron" || touch "$temp_cron"
|
||||
|
||||
# 检查并删除旧的 DNS 同步任务
|
||||
if check_cron_task_exists "sync_dns.sh" "$temp_cron"; then
|
||||
if grep -q "sync_dns.sh" "$temp_cron"; then
|
||||
log_info "发现旧的 DNS 同步定时任务,正在更新..."
|
||||
# 删除所有包含sync_dns.sh的行
|
||||
grep -v "sync_dns.sh" "$temp_cron" > "$temp_cron.new"
|
||||
@@ -803,15 +724,16 @@ setup_dns_sync_cron() {
|
||||
log_info "旧的 DNS 同步定时任务已删除"
|
||||
fi
|
||||
|
||||
# 添加新的定时任务(每1分钟执行一次)
|
||||
# 添加新的定时任务(每30秒执行一次)
|
||||
# 直接使用版本目录中的 DNS 同步脚本
|
||||
echo "# Argus-Metrics DNS 同步定时任务" >> "$temp_cron"
|
||||
echo "* * * * * $sync_dns_script >> $INSTALL_DIR/.dns_sync.log 2>&1" >> "$temp_cron"
|
||||
echo "* * * * * sleep 30; $sync_dns_script >> $INSTALL_DIR/.dns_sync.log 2>&1" >> "$temp_cron"
|
||||
|
||||
# 安装新的crontab
|
||||
if crontab "$temp_cron"; then
|
||||
log_success "DNS 同步定时任务设置成功"
|
||||
log_info " 执行频率: 每1分钟"
|
||||
log_info " 执行频率: 每30秒"
|
||||
log_info " 日志文件: $INSTALL_DIR/.dns_sync.log"
|
||||
log_info " 查看定时任务: crontab -l"
|
||||
log_info " 删除定时任务: crontab -e"
|
||||
@@ -849,7 +771,7 @@ setup_version_check_cron() {
|
||||
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
||||
|
||||
# 检查是否已存在版本校验定时任务
|
||||
if check_cron_task_exists "check_version.sh" "$temp_cron"; then
|
||||
if grep -q "check_version.sh" "$temp_cron"; then
|
||||
log_info "发现旧的版本校验定时任务,正在更新..."
|
||||
# 删除所有包含check_version.sh的行
|
||||
grep -v "check_version.sh" "$temp_cron" > "$temp_cron.new"
|
||||
@@ -902,7 +824,7 @@ setup_restart_cron() {
|
||||
crontab -l > "$temp_cron" 2>/dev/null || touch "$temp_cron"
|
||||
|
||||
# 检查是否已存在自动重启定时任务
|
||||
if check_cron_task_exists "restart_unhealthy.sh" "$temp_cron"; then
|
||||
if grep -q "restart_unhealthy.sh" "$temp_cron"; then
|
||||
log_info "发现旧的自动重启定时任务,正在更新..."
|
||||
# 删除所有包含restart_unhealthy.sh的行
|
||||
grep -v "restart_unhealthy.sh" "$temp_cron" > "$temp_cron.new"
|
||||
@@ -963,9 +885,9 @@ main() {
|
||||
check_system
|
||||
find_version_file
|
||||
create_install_dirs
|
||||
install_system_deps
|
||||
parse_version_info
|
||||
parse_version_info
|
||||
verify_checksums
|
||||
install_system_deps
|
||||
install_components
|
||||
copy_config_files
|
||||
create_install_record
|
||||
@@ -973,20 +895,6 @@ main() {
|
||||
setup_dns_sync_cron
|
||||
setup_version_check_cron
|
||||
setup_restart_cron
|
||||
|
||||
# 注释掉立即执行健康检查,避免与cron任务重复执行
|
||||
# log_info "立即执行一次健康检查..."
|
||||
# local check_health_script="$INSTALL_DIR/check_health.sh"
|
||||
# if [[ -f "$check_health_script" ]]; then
|
||||
# if "$check_health_script" >> "$INSTALL_DIR/.health_check.log" 2>&1; then
|
||||
# log_success "健康检查执行完成"
|
||||
# else
|
||||
# log_warning "健康检查执行失败,请检查日志: $INSTALL_DIR/.health_check.log"
|
||||
# fi
|
||||
# else
|
||||
# log_warning "健康检查脚本不存在: $check_health_script"
|
||||
# fi
|
||||
|
||||
show_install_info
|
||||
}
|
||||
|
||||
|
||||
@@ -29,68 +29,26 @@ log_error() {
|
||||
show_help() {
|
||||
echo "Argus-Metric Artifact 发布脚本"
|
||||
echo
|
||||
echo "用法: $0 <版本号> [选项]"
|
||||
echo "用法: $0 <版本号>"
|
||||
echo
|
||||
echo "参数:"
|
||||
echo " <版本号> 要发布的版本号,对应 artifact 目录中的版本"
|
||||
echo
|
||||
echo "选项:"
|
||||
echo " --output-dir <路径> 指定输出目录 (默认: /private/argus/ftp/share/)"
|
||||
echo " --owner <uid:gid> 指定文件所有者 (默认: 2133:2015)"
|
||||
echo " -h, --help 显示此帮助信息"
|
||||
echo " <版本号> 要发布的版本号,对应 artifact 目录中的版本"
|
||||
echo
|
||||
echo "示例:"
|
||||
echo " $0 1.20.0 # 使用默认配置发布"
|
||||
echo " $0 1.20.0 --output-dir /tmp/publish # 指定输出目录"
|
||||
echo " $0 1.20.0 --owner 1000:1000 # 指定文件所有者"
|
||||
echo " $0 1.20.0 --output-dir /srv/ftp --owner root:root # 同时指定两者"
|
||||
echo " $0 1.20.0 # 发布 1.20.0 版本"
|
||||
echo
|
||||
}
|
||||
|
||||
# 默认配置
|
||||
DEFAULT_PUBLISH_DIR="/private/argus/ftp/share/"
|
||||
DEFAULT_OWNER="2133:2015"
|
||||
|
||||
# 解析参数
|
||||
VERSION=""
|
||||
PUBLISH_DIR="$DEFAULT_PUBLISH_DIR"
|
||||
OWNER="$DEFAULT_OWNER"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
--output-dir)
|
||||
PUBLISH_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--owner)
|
||||
OWNER="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
VERSION="$1"
|
||||
shift
|
||||
else
|
||||
log_error "未知参数: $1"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查版本号是否提供
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
# 检查参数
|
||||
if [[ $# -ne 1 ]]; then
|
||||
log_error "请提供版本号参数"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="$1"
|
||||
ARTIFACT_DIR="artifact/$VERSION"
|
||||
PUBLISH_DIR="/Users/sundapeng/Project/nlp/aiops/client-plugins/all-in-one/publish/"
|
||||
|
||||
# 检查版本目录是否存在
|
||||
if [[ ! -d "$ARTIFACT_DIR" ]]; then
|
||||
@@ -99,32 +57,11 @@ if [[ ! -d "$ARTIFACT_DIR" ]]; then
|
||||
fi
|
||||
|
||||
log_info "开始发布版本: $VERSION"
|
||||
log_info "输出目录: $PUBLISH_DIR"
|
||||
log_info "文件所有者: $OWNER"
|
||||
|
||||
# 确保发布目录存在
|
||||
log_info "确保发布目录存在: $PUBLISH_DIR"
|
||||
mkdir -p "$PUBLISH_DIR"
|
||||
|
||||
IFS=':' read -r OWNER_UID OWNER_GID <<< "$OWNER"
|
||||
if [[ -z "$OWNER_UID" || -z "$OWNER_GID" ]]; then
|
||||
log_error "--owner 格式不正确,应为 uid:gid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_UID=$(id -u)
|
||||
CURRENT_GID=$(id -g)
|
||||
if [[ "$OWNER_UID" != "$CURRENT_UID" || "$OWNER_GID" != "$CURRENT_GID" ]]; then
|
||||
if [[ "$CURRENT_UID" -ne 0 ]]; then
|
||||
log_error "当前用户 (${CURRENT_UID}:${CURRENT_GID}) 无法设置所有者为 ${OWNER_UID}:${OWNER_GID}"
|
||||
log_error "请以目标用户运行脚本或预先调整目录权限"
|
||||
exit 1
|
||||
fi
|
||||
NEED_CHOWN=true
|
||||
else
|
||||
NEED_CHOWN=false
|
||||
fi
|
||||
|
||||
# 创建临时目录用于打包
|
||||
TEMP_PACKAGE_DIR="/tmp/argus-metric-package-$$"
|
||||
mkdir -p "$TEMP_PACKAGE_DIR"
|
||||
@@ -230,28 +167,17 @@ cd "$TEMP_PACKAGE_DIR"
|
||||
tar -czf "$PUBLISH_DIR/$TAR_NAME" *
|
||||
cd - > /dev/null
|
||||
|
||||
if [[ "$NEED_CHOWN" == true ]]; then
|
||||
log_info "设置文件所有者为: $OWNER"
|
||||
chown "$OWNER" "$PUBLISH_DIR/$TAR_NAME"
|
||||
fi
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_PACKAGE_DIR"
|
||||
|
||||
# 更新 LATEST_VERSION 文件
|
||||
log_info "更新 LATEST_VERSION 文件..."
|
||||
echo "$VERSION" > "$PUBLISH_DIR/LATEST_VERSION"
|
||||
if [[ "$NEED_CHOWN" == true ]]; then
|
||||
chown "$OWNER" "$PUBLISH_DIR/LATEST_VERSION"
|
||||
fi
|
||||
|
||||
# 复制 DNS 配置文件到发布目录根目录(直接从 config 目录复制)
|
||||
if [[ -f "config/dns.conf" ]]; then
|
||||
log_info "复制 DNS 配置文件到发布目录根目录..."
|
||||
cp "config/dns.conf" "$PUBLISH_DIR/"
|
||||
if [[ "$NEED_CHOWN" == true ]]; then
|
||||
chown "$OWNER" "$PUBLISH_DIR/dns.conf"
|
||||
fi
|
||||
log_success "DNS 配置文件复制完成: $PUBLISH_DIR/dns.conf"
|
||||
else
|
||||
log_warning "未找到 config/dns.conf 文件,跳过 DNS 配置文件复制"
|
||||
@@ -261,9 +187,6 @@ fi
|
||||
if [[ -f "scripts/setup.sh" ]]; then
|
||||
log_info "复制 setup.sh 到发布目录..."
|
||||
cp "scripts/setup.sh" "$PUBLISH_DIR/"
|
||||
if [[ "$NEED_CHOWN" == true ]]; then
|
||||
chown "$OWNER" "$PUBLISH_DIR/setup.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 显示发布结果
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
|
||||
# 此脚本会检查各组件的健康状态,并重启不健康的组件
|
||||
|
||||
# PID 文件检测,防止重复执行
|
||||
PIDFILE="/var/run/restart_unhealthy.pid"
|
||||
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
|
||||
echo "自动重启脚本已在运行中,跳过本次执行" >&2
|
||||
exit 0
|
||||
fi
|
||||
echo $$ > "$PIDFILE"
|
||||
trap "rm -f $PIDFILE" EXIT
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
INSTALL_RECORD_FILE="$SCRIPT_DIR/.install_record"
|
||||
|
||||
@@ -1,143 +1,244 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DNS 同步脚本
|
||||
# 比较 FTP 根目录的 dns.conf 和本地的 dns.conf,如果有变化则同步到 /etc/resolv.conf
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||
# 日志函数 - 输出到 stderr 避免影响函数返回值
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1" >&2
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||
}
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOCAL_DNS_CONF="/opt/argus-metric/dns.conf"
|
||||
RESOLV_CONF="/etc/resolv.conf"
|
||||
ALT_RESOLV_CONF="/run/resolv.conf"
|
||||
LOG_FILE="/opt/argus-metric/.dns_sync.log"
|
||||
REMOTE_DNS_CONF_URL=""
|
||||
RESOLV_CONF="/etc/resolv.conf"
|
||||
LOG_FILE="/opt/argus-metric/.dns_sync.log"
|
||||
|
||||
# 获取 FTP 配置
|
||||
# 从环境变量或配置文件获取 FTP 服务器信息
|
||||
get_ftp_config() {
|
||||
# 优先从环境变量获取配置
|
||||
log_info "获取 FTP 配置信息..."
|
||||
|
||||
# 如果环境变量中没有设置,则尝试从配置文件读取
|
||||
if [[ -z "$FTP_SERVER" || -z "$FTP_USER" || -z "$FTP_PASSWORD" ]]; then
|
||||
[[ -f "$SCRIPT_DIR/config.env" ]] && source "$SCRIPT_DIR/config.env"
|
||||
local config_file="$SCRIPT_DIR/config.env"
|
||||
if [[ -f "$config_file" ]]; then
|
||||
log_info "从配置文件读取 FTP 配置: $config_file"
|
||||
source "$config_file"
|
||||
fi
|
||||
else
|
||||
log_info "使用环境变量中的 FTP 配置"
|
||||
fi
|
||||
|
||||
# 设置默认值(如果环境变量和配置文件都没有设置)
|
||||
FTP_SERVER="${FTP_SERVER:-localhost}"
|
||||
FTP_USER="${FTP_USER:-ftpuser}"
|
||||
FTP_PASSWORD="${FTP_PASSWORD:-ZGClab1234!}"
|
||||
|
||||
# 构建远程 DNS 配置文件 URL
|
||||
REMOTE_DNS_CONF_URL="ftp://${FTP_USER}:${FTP_PASSWORD}@${FTP_SERVER}/dns.conf"
|
||||
|
||||
log_info "FTP 配置来源: ${FTP_CONFIG_SOURCE:-环境变量/配置文件}"
|
||||
}
|
||||
|
||||
# 下载远程 dns.conf
|
||||
# 下载远程 DNS 配置文件
|
||||
download_remote_dns_conf() {
|
||||
local tmp="/tmp/dns.remote.$$"
|
||||
local temp_file="/tmp/dns.conf.remote.$$"
|
||||
|
||||
log_info "从 FTP 服务器下载 DNS 配置文件..."
|
||||
log_info "远程地址: $REMOTE_DNS_CONF_URL"
|
||||
log_info "FTP 服务器: $FTP_SERVER"
|
||||
log_info "FTP 用户: $FTP_USER"
|
||||
|
||||
# 先测试 FTP 连接
|
||||
log_info "测试 FTP 连接..."
|
||||
if ! curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/" >/dev/null; then
|
||||
log_error "无法连接到 FTP 服务器: $FTP_SERVER"; return 1
|
||||
fi
|
||||
if ! curl -u "${FTP_USER}:${FTP_PASSWORD}" -sf "ftp://${FTP_SERVER}/dns.conf" -o "$tmp" 2>/dev/null; then
|
||||
log_error "下载 dns.conf 失败"; rm -f "$tmp"; return 1
|
||||
fi
|
||||
echo "$tmp"
|
||||
}
|
||||
|
||||
# 文件比较
|
||||
compare_files() { diff -q "$1" "$2" >/dev/null 2>&1; }
|
||||
|
||||
# 从 dns.conf 提取有效 IP
|
||||
get_dns_ips() {
|
||||
grep -Eo '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' "$1" | sort -u
|
||||
}
|
||||
|
||||
# 安全更新 resolv.conf(保留符号链接)
|
||||
update_resolv_conf() {
|
||||
local dns_conf="$1"
|
||||
local dns_ips
|
||||
mapfile -t dns_ips < <(get_dns_ips "$dns_conf")
|
||||
[[ ${#dns_ips[@]} -eq 0 ]] && { log_warning "未检测到有效 DNS"; return; }
|
||||
|
||||
local target_file="$RESOLV_CONF"
|
||||
if [[ ! -w "$RESOLV_CONF" ]]; then
|
||||
log_warning "/etc/resolv.conf 不可写,使用兜底路径 $ALT_RESOLV_CONF"
|
||||
target_file="$ALT_RESOLV_CONF"
|
||||
fi
|
||||
|
||||
local temp="/tmp/resolv.new.$$"
|
||||
cp "$target_file" "${target_file}.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||||
log_info "更新 DNS 配置文件: $target_file"
|
||||
|
||||
# 写入新的 nameserver 行
|
||||
for ip in "${dns_ips[@]}"; do
|
||||
echo "nameserver $ip"
|
||||
done >"$temp"
|
||||
|
||||
# 追加原内容(去掉重复 nameserver)
|
||||
grep -v '^nameserver' "$target_file" >>"$temp" 2>/dev/null || true
|
||||
awk '!a[$0]++' "$temp" >"${temp}.uniq"
|
||||
|
||||
# ⚙️ 使用 cat 原地覆盖,避免 mv 引发 “设备忙”
|
||||
if cat "${temp}.uniq" >"$target_file" 2>/dev/null; then
|
||||
chmod 644 "$target_file"
|
||||
log_success "DNS 更新完成: ${dns_ips[*]}"
|
||||
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/" >/dev/null 2>&1; then
|
||||
log_success "FTP 服务器连接成功"
|
||||
else
|
||||
log_error "无法写入 $target_file,可能被系统锁定"
|
||||
log_error "无法连接到 FTP 服务器: $FTP_SERVER"
|
||||
log_error "请检查:"
|
||||
log_error " 1. FTP 服务器是否运行"
|
||||
log_error " 2. 网络连接是否正常"
|
||||
log_error " 3. 服务器地址是否正确"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 测试 dns.conf 文件是否存在
|
||||
log_info "检查远程 dns.conf 文件是否存在..."
|
||||
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sfI "ftp://${FTP_SERVER}/dns.conf" >/dev/null 2>&1; then
|
||||
log_success "远程 dns.conf 文件存在"
|
||||
else
|
||||
log_error "远程 dns.conf 文件不存在或无法访问"
|
||||
log_error "请检查 FTP 服务器根目录下是否有 dns.conf 文件"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 尝试下载文件
|
||||
log_info "开始下载 dns.conf 文件..."
|
||||
if curl -u "${FTP_USER}:${FTP_PASSWORD}" -sf "ftp://${FTP_SERVER}/dns.conf" -o "$temp_file" 2>/dev/null; then
|
||||
log_success "远程 DNS 配置文件下载成功"
|
||||
echo "$temp_file"
|
||||
else
|
||||
log_error "下载 dns.conf 文件失败"
|
||||
log_error "尝试手动测试命令:"
|
||||
log_error " curl -u ${FTP_USER}:${FTP_PASSWORD} ftp://${FTP_SERVER}/dns.conf"
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$temp" "${temp}.uniq"
|
||||
}
|
||||
|
||||
# 检查 resolv.conf 是否包含 dns.conf 内容
|
||||
ensure_dns_in_resolv() {
|
||||
local dns_conf="$1"
|
||||
local dns_ips
|
||||
mapfile -t dns_ips < <(get_dns_ips "$dns_conf")
|
||||
[[ ${#dns_ips[@]} -eq 0 ]] && return
|
||||
# 比较两个文件是否相同
|
||||
compare_files() {
|
||||
local file1="$1"
|
||||
local file2="$2"
|
||||
|
||||
if [[ ! -f "$file1" || ! -f "$file2" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 使用 diff 比较文件内容
|
||||
if diff -q "$file1" "$file2" >/dev/null 2>&1; then
|
||||
return 0 # 文件相同
|
||||
else
|
||||
return 1 # 文件不同
|
||||
fi
|
||||
}
|
||||
|
||||
for ip in "${dns_ips[@]}"; do
|
||||
if ! grep -q "nameserver $ip" "$RESOLV_CONF" 2>/dev/null; then
|
||||
log_warning "检测到 /etc/resolv.conf 缺少 $ip,执行兜底修复"
|
||||
update_resolv_conf "$dns_conf"
|
||||
return
|
||||
# 将 DNS 配置追加到 /etc/resolv.conf
|
||||
update_resolv_conf() {
|
||||
local dns_conf_file="$1"
|
||||
|
||||
log_info "更新 /etc/resolv.conf 文件..."
|
||||
|
||||
# 备份原始文件
|
||||
if [[ -f "$RESOLV_CONF" ]]; then
|
||||
cp "$RESOLV_CONF" "${RESOLV_CONF}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
log_info "已备份原始 resolv.conf 文件"
|
||||
fi
|
||||
|
||||
# 读取 DNS 配置文件并追加到 resolv.conf
|
||||
while IFS= read -r line; do
|
||||
# 跳过空行和注释行
|
||||
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
|
||||
# 验证是否为有效的 IP 地址
|
||||
if [[ "$line" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||
# 检查是否已存在相同的 nameserver 行
|
||||
if ! grep -q "nameserver $line" "$RESOLV_CONF" 2>/dev/null; then
|
||||
echo "nameserver $line" >> "$RESOLV_CONF"
|
||||
log_info "添加 DNS 服务器: $line"
|
||||
else
|
||||
log_info "DNS 服务器已存在,跳过: $line"
|
||||
fi
|
||||
else
|
||||
log_warning "跳过无效的 DNS 地址: $line"
|
||||
fi
|
||||
done
|
||||
log_info "/etc/resolv.conf 已包含所有 DNS"
|
||||
done < "$dns_conf_file"
|
||||
|
||||
# 设置文件权限
|
||||
chmod 644 "$RESOLV_CONF"
|
||||
|
||||
log_success "/etc/resolv.conf 文件更新完成"
|
||||
}
|
||||
|
||||
log_sync() { echo "[$(date '+%F %T')] $1" >>"$LOG_FILE"; }
|
||||
# 记录同步日志
|
||||
log_sync() {
|
||||
local message="$1"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo "[$timestamp] $message" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log_info "开始 DNS 同步检查..."
|
||||
mkdir -p /opt/argus-metric
|
||||
|
||||
log_sync "DNS 同步检查开始"
|
||||
|
||||
# 确保系统目录存在
|
||||
mkdir -p "/opt/argus-metric"
|
||||
|
||||
# 获取 FTP 配置
|
||||
get_ftp_config
|
||||
local remote_file
|
||||
if ! remote_file=$(download_remote_dns_conf); then
|
||||
log_error "下载失败"; log_sync "同步失败"; exit 1
|
||||
fi
|
||||
|
||||
|
||||
# 检查本地 DNS 配置文件是否存在
|
||||
if [[ ! -f "$LOCAL_DNS_CONF" ]]; then
|
||||
log_info "本地 dns.conf 不存在,初始化..."
|
||||
cp "$remote_file" "$LOCAL_DNS_CONF"
|
||||
update_resolv_conf "$LOCAL_DNS_CONF"
|
||||
log_sync "首次同步完成"
|
||||
else
|
||||
if compare_files "$LOCAL_DNS_CONF" "$remote_file"; then
|
||||
log_info "dns.conf 无变化"
|
||||
ensure_dns_in_resolv "$LOCAL_DNS_CONF"
|
||||
log_sync "dns.conf 无变化,执行兜底检查"
|
||||
else
|
||||
log_info "检测到 DNS 配置更新"
|
||||
log_warning "本地 DNS 配置文件不存在: $LOCAL_DNS_CONF"
|
||||
log_warning "将下载远程配置文件并更新系统 DNS 设置"
|
||||
|
||||
# 下载远程配置文件
|
||||
if remote_file=$(download_remote_dns_conf); then
|
||||
# 复制到本地
|
||||
cp "$remote_file" "$LOCAL_DNS_CONF"
|
||||
log_success "远程 DNS 配置文件已保存到本地"
|
||||
|
||||
# 更新 resolv.conf
|
||||
update_resolv_conf "$LOCAL_DNS_CONF"
|
||||
log_sync "DNS 配置同步完成"
|
||||
log_sync "首次同步完成,DNS 配置已更新"
|
||||
|
||||
# 清理临时文件
|
||||
rm -f "$remote_file"
|
||||
else
|
||||
log_error "无法下载远程 DNS 配置文件,同步失败"
|
||||
log_sync "同步失败:无法下载远程配置文件"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_info "本地 DNS 配置文件存在: $LOCAL_DNS_CONF"
|
||||
|
||||
# 下载远程配置文件进行比较
|
||||
if remote_file=$(download_remote_dns_conf); then
|
||||
# 比较文件
|
||||
if compare_files "$LOCAL_DNS_CONF" "$remote_file"; then
|
||||
log_info "DNS 配置文件无变化,无需更新"
|
||||
log_sync "DNS 配置文件无变化"
|
||||
else
|
||||
log_info "检测到 DNS 配置文件有变化,开始同步..."
|
||||
log_sync "检测到 DNS 配置文件变化,开始同步"
|
||||
|
||||
# 更新本地配置文件
|
||||
cp "$remote_file" "$LOCAL_DNS_CONF"
|
||||
log_success "本地 DNS 配置文件已更新"
|
||||
|
||||
# 更新 resolv.conf
|
||||
update_resolv_conf "$LOCAL_DNS_CONF"
|
||||
log_sync "DNS 配置同步完成"
|
||||
fi
|
||||
|
||||
# 清理临时文件
|
||||
rm -f "$remote_file"
|
||||
else
|
||||
log_error "无法下载远程 DNS 配置文件,跳过本次同步"
|
||||
log_sync "同步失败:无法下载远程配置文件"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$remote_file"
|
||||
log_success "DNS 同步流程完成"
|
||||
|
||||
log_success "DNS 同步检查完成"
|
||||
log_sync "DNS 同步检查完成"
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# 客户侧组件安装包构建、发布流程
|
||||
|
||||
## 第一步:配置版本和组件
|
||||
|
||||
首先搞定配置文件:
|
||||
|
||||
1. 把 `.checklist.example` 重命名成 `checklist`
|
||||
2. 把 `.VERSION.example` 重命名成 `VERSION`
|
||||
|
||||
### checklist 文件格式
|
||||
```
|
||||
# 组件名称 目录路径 版本号 [依赖组件] [安装顺序]
|
||||
dcgm-exporter-installer /path/to/dcgm-exporter-installer 1.1.0
|
||||
node-exporter-installer /path/to/node-exporter-installer 1.1.0
|
||||
```
|
||||
|
||||
### VERSION 文件
|
||||
设置需要发布的版本号,比如 `1.29.0`
|
||||
|
||||
> 建议用 `version-manager.sh` 来管理版本
|
||||
|
||||
## 第二步:构建安装包
|
||||
|
||||
直接跑脚本:
|
||||
```bash
|
||||
./package_artifact.sh
|
||||
```
|
||||
|
||||
构建完的东西会放在 `artifact/` 目录下,按版本分文件夹。
|
||||
|
||||
如果版本已经存在了,想要覆盖重新构建:
|
||||
```bash
|
||||
./package_artifact.sh --force
|
||||
```
|
||||
|
||||
构建完可以手工测试安装包。
|
||||
|
||||
## 第三步:发布安装包
|
||||
|
||||
用这个脚本发布:
|
||||
```bash
|
||||
./publish_artifact.sh
|
||||
```
|
||||
|
||||
发布后的内容在 `publish/` 目录里,包含:
|
||||
- 压缩版本的安装包
|
||||
- 一键安装的bash脚本
|
||||
|
||||
## 第四步:部署到FTP服务器
|
||||
|
||||
把发布的内容上传到FTP服务器,客户端就可以通过一键命令安装:
|
||||
|
||||
```bash
|
||||
curl -fsSL http://your-ftp-server/install.sh | sh -
|
||||
|
||||
curl -fsSL "ftp://ftpuser:{PASSWD}!@10.211.55.4/share/setup.sh" | sudo bash -s -- --server 10.211.55.4 --user ftpuser --password {PASSWD}
|
||||
```
|
||||
|
||||
这样客户就能直接从FTP服务器下载并安装组件了。
|
||||
@@ -1 +0,0 @@
|
||||
1.29.0
|
||||
@@ -1,3 +0,0 @@
|
||||
# 组件名称 目录路径 版本号 [依赖组件] [安装顺序]
|
||||
dcgm-exporter-installer /Users/sundapeng/Project/nlp/aiops/client-plugins/dcgm-exporter-installer 1.1.0
|
||||
node-exporter-installer /Users/sundapeng/Project/nlp/aiops/client-plugins/node-exporter-installer 1.1.0
|
||||
@@ -1 +0,0 @@
|
||||
1.35.0
|
||||
@@ -1,5 +0,0 @@
|
||||
# 组件名称 目录路径 版本号 [依赖组件] [安装顺序]
|
||||
argus-agent plugins/argus-agent 1.0.0
|
||||
node-exporter plugins/node-exporter 1.0.0
|
||||
dcgm-exporter plugins/dcgm-exporter 1.0.0
|
||||
fluent-bit plugins/fluent-bit 1.0.0
|
||||
@@ -1,14 +0,0 @@
|
||||
# Elasticsearch
|
||||
ES_HOST=es.log.argus.com
|
||||
ES_PORT=9200
|
||||
|
||||
# Argus-Agent
|
||||
# 连接master服务
|
||||
MASTER_ENDPOINT=master.argus.com:3000
|
||||
# 上报状态间隔描述
|
||||
REPORT_INTERVAL_SECONDS=5
|
||||
|
||||
# FTP
|
||||
FTP_SERVER=172.31.0.40
|
||||
FTP_USER=ftpuser
|
||||
FTP_PASSWORD=ZGClab1234!
|
||||
@@ -1,8 +0,0 @@
|
||||
# Argus Metric 配置文件示例
|
||||
# 复制此文件为 config.env 并根据需要修改配置
|
||||
|
||||
# 连接master服务
|
||||
MASTER_ENDPOINT=master.argus.com:3000
|
||||
|
||||
# 上报状态间隔描述(秒)
|
||||
REPORT_INTERVAL_SECONDS=60
|
||||
@@ -1 +0,0 @@
|
||||
172.31.0.2
|
||||
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,94 +0,0 @@
|
||||
# Argus Agent 插件
|
||||
|
||||
这是 Argus Agent 的安装和管理插件,提供了完整的安装、卸载、健康检查功能。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
argus-agent/
|
||||
├── bin/
|
||||
│ └── argus-agent # Argus Agent 二进制文件
|
||||
├── config/ # 配置文件目录
|
||||
├── install.sh # 安装脚本
|
||||
├── uninstall.sh # 卸载脚本
|
||||
├── check_health.sh # 健康检查脚本
|
||||
├── package.sh # 打包脚本
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
安装脚本会:
|
||||
- 检查系统要求
|
||||
- 停止可能运行的服务
|
||||
- 安装二进制文件到 `/usr/local/bin/argus-agent`
|
||||
- 创建 `argus-agent` 用户
|
||||
- 创建配置和数据目录
|
||||
- 启动服务并记录 PID
|
||||
|
||||
### 卸载
|
||||
|
||||
```bash
|
||||
sudo ./uninstall.sh
|
||||
```
|
||||
|
||||
卸载脚本会:
|
||||
- 停止所有 argus-agent 进程
|
||||
- 删除二进制文件
|
||||
- 删除配置和数据目录
|
||||
- 清理日志文件
|
||||
- 更新安装记录
|
||||
|
||||
### 健康检查
|
||||
|
||||
```bash
|
||||
./check_health.sh
|
||||
```
|
||||
|
||||
健康检查脚本会:
|
||||
- 检查安装记录中的 PID
|
||||
- 验证进程是否正在运行
|
||||
- 输出 JSON 格式的健康状态
|
||||
|
||||
### 打包
|
||||
|
||||
```bash
|
||||
./package.sh
|
||||
```
|
||||
|
||||
打包脚本会:
|
||||
- 检查所有必要文件
|
||||
- 创建时间戳命名的压缩包
|
||||
- 输出安装包信息
|
||||
|
||||
## 安装后的文件位置
|
||||
|
||||
- 二进制文件: `/usr/local/bin/argus-agent`
|
||||
- 配置目录: `/etc/argus-agent/`
|
||||
- 数据目录: `/var/lib/argus-agent/`
|
||||
- 日志文件: `/var/log/argus-agent.log`
|
||||
- PID 文件: `/var/run/argus-agent.pid`
|
||||
- 安装记录: `/opt/argus-metric/current/.install_record`
|
||||
|
||||
## 健康检查输出格式
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "argus-agent",
|
||||
"status": "health|unhealth",
|
||||
"reason": "状态说明"
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 安装和卸载脚本需要 root 权限
|
||||
2. 健康检查脚本使用安装记录中的 PID 来验证进程状态
|
||||
3. 如果 jq 命令不可用,健康检查会使用简单的文本解析
|
||||
4. 卸载时会保留 `argus-agent` 用户,避免影响其他服务
|
||||
Binary file not shown.
@@ -1,69 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Argus Agent 健康检查脚本
|
||||
# 输出 JSON 格式结果
|
||||
|
||||
set -e
|
||||
|
||||
# 检查 Argus Agent 健康状态
|
||||
check_health() {
|
||||
local name="argus-agent"
|
||||
local status="unhealth"
|
||||
local reason=""
|
||||
local install_record="/opt/argus-metric/current/.install_record"
|
||||
|
||||
# 首先尝试通过安装记录文件检查进程
|
||||
if [[ -f "$install_record" ]]; then
|
||||
# 尝试使用jq解析JSON格式的安装记录文件
|
||||
local pid=""
|
||||
if command -v jq &> /dev/null; then
|
||||
pid=$(jq -r '.components."argus-agent".pid // empty' "$install_record" 2>/dev/null || echo "")
|
||||
else
|
||||
# 如果没有jq,使用简单的文本解析方法
|
||||
pid=$(grep -A 10 '"argus-agent"' "$install_record" | grep '"pid"' | cut -d'"' -f4 | head -1)
|
||||
fi
|
||||
|
||||
if [[ -n "$pid" && "$pid" =~ ^[0-9]+$ ]]; then
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
# 进程存在且运行正常
|
||||
status="health"
|
||||
reason="进程运行正常 (PID: $pid)"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 0
|
||||
else
|
||||
reason="安装记录中的 PID $pid 进程不存在"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
reason="安装记录文件中未找到有效的 argus-agent PID"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# 如果安装记录文件不存在,尝试查找 argus-agent 进程
|
||||
local pids=$(pgrep -f "argus-agent" 2>/dev/null || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
# 取第一个找到的 PID
|
||||
local pid=$(echo "$pids" | head -1)
|
||||
status="health"
|
||||
reason="发现 argus-agent 进程运行 (PID: $pid),但未找到安装记录"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 0
|
||||
else
|
||||
reason="未找到 argus-agent 进程,且安装记录文件不存在"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
check_health
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,289 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "Argus Agent 安装脚本"
|
||||
echo
|
||||
echo "用法: $0 [选项]"
|
||||
echo
|
||||
echo "选项:"
|
||||
echo " --help 显示此帮助信息"
|
||||
echo
|
||||
echo "示例:"
|
||||
echo " $0 # 安装 Argus Agent"
|
||||
echo
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
INSTALL_DIR=""
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# 如果参数不是以--开头,则认为是安装目录
|
||||
if [[ ! "$arg" =~ ^-- ]]; then
|
||||
INSTALL_DIR="$arg"
|
||||
else
|
||||
log_error "未知参数: $arg"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查是否为 root 用户
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要 root 权限运行"
|
||||
log_info "请使用: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查系统要求
|
||||
check_system() {
|
||||
log_info "检查系统要求..."
|
||||
|
||||
# 检查操作系统
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
log_error "无法检测操作系统版本"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source /etc/os-release
|
||||
log_info "检测到操作系统: $NAME $VERSION"
|
||||
|
||||
# 检查是否为 Linux 系统
|
||||
if [[ "$ID" != "ubuntu" && "$ID" != "debian" && "$ID" != "centos" && "$ID" != "rhel" && "$ID" != "fedora" ]]; then
|
||||
log_warning "此脚本主要针对常见 Linux 发行版,其他系统可能需要调整"
|
||||
fi
|
||||
|
||||
# 检查系统架构
|
||||
local arch=$(uname -m)
|
||||
log_info "系统架构: $arch"
|
||||
|
||||
if [[ "$arch" != "x86_64" && "$arch" != "amd64" ]]; then
|
||||
log_warning "当前架构为 $arch,argus-agent 主要支持 x86_64/amd64"
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止可能运行的服务
|
||||
stop_existing_service() {
|
||||
log_info "检查并停止可能运行的服务..."
|
||||
local pid_file="/var/run/argus-agent.pid"
|
||||
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p "$pid" -o comm= | grep -q "^argus-agent$"; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 2
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
log_success "服务已停止"
|
||||
fi
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
|
||||
local pids=$(pgrep -x argus-agent 2>/dev/null || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
for pid in $pids; do kill -9 "$pid" 2>/dev/null || true; done
|
||||
fi
|
||||
|
||||
# 检查僵尸进程
|
||||
local zombies=$(ps -eo pid,stat,comm | grep '[a]rgus-agent' | awk '$2 ~ /Z/ {print $1}')
|
||||
if [[ -n "$zombies" ]]; then
|
||||
for pid in $zombies; do
|
||||
local ppid=$(ps -o ppid= -p $pid)
|
||||
log_warning "检测到僵尸 argus-agent (PID=$pid, PPID=$ppid),尝试清理"
|
||||
[[ "$ppid" -ne 1 ]] && kill -9 "$ppid" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# 安装 Argus Agent 二进制文件
|
||||
install_argus_agent() {
|
||||
log_info "安装 Argus Agent..."
|
||||
local binary_file="bin/argus-agent"
|
||||
local install_dir="/usr/local/bin"
|
||||
local target_file="$install_dir/argus-agent"
|
||||
|
||||
[[ ! -f "$binary_file" ]] && log_error "找不到 Argus Agent 二进制文件: $binary_file" && exit 1
|
||||
|
||||
stop_existing_service
|
||||
|
||||
local timeout=10
|
||||
while [[ $timeout -gt 0 ]]; do
|
||||
remaining_pids=$(pgrep -x argus-agent | grep -vw $$ || true)
|
||||
[[ -z "$remaining_pids" ]] && break
|
||||
if ps -eo pid,stat,comm | grep -E 'argus-agent' | grep -q 'Z'; then
|
||||
log_warning "检测到僵尸 argus-agent,跳过等待"
|
||||
break
|
||||
fi
|
||||
log_warning "等待 argus-agent 完全退出... ($timeout)"
|
||||
sleep 1
|
||||
((timeout--))
|
||||
done
|
||||
|
||||
cp "$binary_file" "${target_file}.new"
|
||||
chmod +x "${target_file}.new"
|
||||
mv -f "${target_file}.new" "$target_file"
|
||||
log_success "Argus Agent 二进制文件安装完成"
|
||||
}
|
||||
|
||||
|
||||
# 创建用户和组
|
||||
create_user() {
|
||||
log_info "创建 argus-agent 用户..."
|
||||
|
||||
# 检查用户是否已存在
|
||||
if id "argus-agent" &>/dev/null; then
|
||||
log_info "用户 argus-agent 已存在"
|
||||
else
|
||||
useradd --no-create-home --shell /bin/false argus-agent
|
||||
log_success "用户 argus-agent 创建完成"
|
||||
fi
|
||||
}
|
||||
|
||||
# 安装配置文件
|
||||
install_config() {
|
||||
log_info "安装配置文件..."
|
||||
|
||||
local config_dir="/etc/argus-agent"
|
||||
|
||||
# 创建配置目录
|
||||
mkdir -p "$config_dir"
|
||||
|
||||
# 创建健康检查目录
|
||||
mkdir -p "/var/lib/argus-agent/health"
|
||||
chown argus-agent:argus-agent "/var/lib/argus-agent/health"
|
||||
}
|
||||
|
||||
# 启动 Argus Agent 服务
|
||||
start_argus_agent() {
|
||||
log_info "启动 Argus Agent 服务..."
|
||||
local binary_path="/usr/local/bin/argus-agent"
|
||||
local log_file="/var/log/argus-agent.log"
|
||||
local pid_file="/var/run/argus-agent.pid"
|
||||
|
||||
[[ -f "$pid_file" ]] && rm -f "$pid_file"
|
||||
|
||||
log_info "正在启动 Argus Agent..."
|
||||
setsid "$binary_path" > "$log_file" 2>&1 < /dev/null &
|
||||
local pid=$!
|
||||
echo "$pid" > "$pid_file"
|
||||
sleep 2
|
||||
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_success "Argus Agent 服务启动成功 (PID: $pid)"
|
||||
else
|
||||
log_error "Argus Agent 启动失败"
|
||||
[[ -f "$log_file" ]] && tail -n 10 "$log_file"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# 更新安装记录
|
||||
update_install_record() {
|
||||
local pid="$1"
|
||||
# 使用传入的安装目录参数,如果没有则使用默认值
|
||||
local install_base_dir="${2:-/opt/argus-metric/current}"
|
||||
local install_record="$install_base_dir/.install_record"
|
||||
|
||||
# 如果安装记录文件不存在,说明是首次安装,由主安装脚本统一创建
|
||||
if [[ ! -f "$install_record" ]]; then
|
||||
log_info "安装记录文件不存在,将由主安装脚本创建"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 如果文件存在,说明是重启场景,只更新 PID 字段
|
||||
if command -v jq &> /dev/null; then
|
||||
# 读取当前 PID
|
||||
local current_pid=$(jq -r '.components."argus-agent".pid // ""' "$install_record" 2>/dev/null)
|
||||
|
||||
if [[ -z "$current_pid" ]]; then
|
||||
log_warning "无法读取当前 PID,跳过更新"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 使用 jq 只更新 pid 字段,保持字符串类型,保留其他字段
|
||||
jq --arg new_pid "$pid" '.components."argus-agent".pid = $new_pid' "$install_record" > "$install_record.tmp" && mv "$install_record.tmp" "$install_record"
|
||||
log_info "PID 已更新: $current_pid -> $pid"
|
||||
else
|
||||
log_warning "jq 命令不可用,无法更新安装记录文件"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示安装信息
|
||||
show_install_info() {
|
||||
log_success "Argus Agent 安装完成!"
|
||||
echo
|
||||
echo "安装信息:"
|
||||
echo " 二进制文件: /usr/local/bin/argus-agent"
|
||||
echo " 运行用户: argus-agent"
|
||||
echo " 配置目录: /etc/argus-agent/"
|
||||
echo " 健康检查目录: /var/lib/argus-agent/health"
|
||||
echo
|
||||
echo "使用方法:"
|
||||
echo " 手动启动: /usr/local/bin/argus-agent"
|
||||
echo " 后台启动: nohup /usr/local/bin/argus-agent &"
|
||||
echo
|
||||
echo "健康检查:"
|
||||
echo " ./check_health.sh"
|
||||
echo
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " Argus Agent 安装脚本 v1.0"
|
||||
echo "=========================================="
|
||||
echo
|
||||
|
||||
check_root
|
||||
check_system
|
||||
|
||||
log_info "开始安装 Argus Agent..."
|
||||
|
||||
install_argus_agent
|
||||
create_user
|
||||
install_config
|
||||
start_argus_agent
|
||||
|
||||
show_install_info
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
# 获取当前目录
|
||||
CURRENT_DIR=$(pwd)
|
||||
PACKAGE_NAME="argus-agent-$(date +%Y%m%d-%H%M%S)"
|
||||
PACKAGE_FILE="${PACKAGE_NAME}.tar.gz"
|
||||
|
||||
log_info "开始打包 Argus Agent 安装包..."
|
||||
|
||||
# 检查必要文件
|
||||
log_info "检查必要文件..."
|
||||
|
||||
required_files=(
|
||||
"install.sh"
|
||||
"uninstall.sh"
|
||||
"bin/argus-agent"
|
||||
"check_health.sh"
|
||||
)
|
||||
|
||||
missing_files=()
|
||||
for file in "${required_files[@]}"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
missing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_files[@]} -gt 0 ]]; then
|
||||
echo "缺少以下文件:"
|
||||
for file in "${missing_files[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "所有必要文件检查完成"
|
||||
|
||||
# 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
log_info "创建临时目录: $TEMP_DIR"
|
||||
|
||||
# 复制文件到临时目录
|
||||
cp -r . "$TEMP_DIR/$PACKAGE_NAME"
|
||||
|
||||
# 进入临时目录
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# 创建压缩包
|
||||
log_info "创建压缩包: $PACKAGE_FILE"
|
||||
tar -czf "$PACKAGE_FILE" "$PACKAGE_NAME"
|
||||
|
||||
# 移动压缩包到原目录
|
||||
mv "$PACKAGE_FILE" "$CURRENT_DIR/"
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# 返回原目录
|
||||
cd "$CURRENT_DIR"
|
||||
|
||||
# 显示结果
|
||||
log_success "打包完成!"
|
||||
echo
|
||||
echo "安装包文件: $PACKAGE_FILE"
|
||||
echo "文件大小: $(du -h "$PACKAGE_FILE" | cut -f1)"
|
||||
echo
|
||||
echo "使用方法:"
|
||||
echo "1. 将 $PACKAGE_FILE 传输到目标服务器"
|
||||
echo "2. 解压: tar -xzf $PACKAGE_FILE"
|
||||
echo "3. 进入目录: cd $PACKAGE_NAME"
|
||||
echo "4. 运行安装: sudo ./install.sh"
|
||||
echo
|
||||
echo "注意: 请确保所有必要文件都存在"
|
||||
@@ -1,255 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Argus Agent 卸载脚本
|
||||
# 版本: 1.0
|
||||
# 作者: AIOps Team
|
||||
# 日期: $(date +%Y-%m-%d)
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查是否为 root 用户
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要 root 权限运行"
|
||||
log_info "请使用: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止运行中的进程
|
||||
stop_processes() {
|
||||
log_info "停止 Argus Agent 进程..."
|
||||
|
||||
local pid_file="/var/run/argus-agent.pid"
|
||||
local stopped=false
|
||||
|
||||
# 首先尝试通过 PID 文件停止服务
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "通过 PID 文件停止服务 (PID: $pid)..."
|
||||
kill "$pid"
|
||||
sleep 3
|
||||
|
||||
# 检查进程是否已停止
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_warning "进程未响应,强制终止..."
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
log_success "Argus Agent 进程已停止"
|
||||
stopped=true
|
||||
else
|
||||
log_warning "PID 文件存在但进程已不存在,清理 PID 文件"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 查找并杀死所有 argus-agent 进程
|
||||
local pids=$(pgrep -f "argus-agent" 2>/dev/null || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
log_info "发现 argus-agent 进程,正在停止..."
|
||||
for pid in $pids; do
|
||||
log_info "停止进程 PID: $pid"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 2
|
||||
|
||||
# 检查是否还有进程在运行,如果有则强制终止
|
||||
local remaining_pids=$(pgrep -f "argus-agent" 2>/dev/null || true)
|
||||
if [[ -n "$remaining_pids" ]]; then
|
||||
log_warning "进程未响应,强制终止..."
|
||||
for pid in $remaining_pids; do
|
||||
log_info "强制终止进程 PID: $pid"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# 最终检查
|
||||
if pgrep -f "argus-agent" > /dev/null; then
|
||||
log_error "无法停止所有 argus-agent 进程"
|
||||
else
|
||||
log_success "所有 Argus Agent 进程已停止"
|
||||
stopped=true
|
||||
fi
|
||||
else
|
||||
log_info "Argus Agent 进程未运行"
|
||||
fi
|
||||
|
||||
# 清理 PID 文件
|
||||
rm -f "$pid_file"
|
||||
|
||||
if [[ "$stopped" == "false" ]]; then
|
||||
log_warning "未发现需要停止的 Argus Agent 进程"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除二进制文件
|
||||
remove_binary() {
|
||||
log_info "删除 Argus Agent 二进制文件..."
|
||||
|
||||
local binary_files=(
|
||||
"/usr/local/bin/argus-agent"
|
||||
)
|
||||
|
||||
local deleted=false
|
||||
for binary_file in "${binary_files[@]}"; do
|
||||
if [[ -f "$binary_file" ]]; then
|
||||
rm -f "$binary_file"
|
||||
log_success "二进制文件已删除: $binary_file"
|
||||
deleted=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$deleted" == "false" ]]; then
|
||||
log_info "二进制文件不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除配置文件
|
||||
remove_config() {
|
||||
log_info "删除配置文件..."
|
||||
|
||||
local config_dir="/etc/argus-agent"
|
||||
|
||||
if [[ -d "$config_dir" ]]; then
|
||||
rm -rf "$config_dir"
|
||||
log_success "配置目录已删除"
|
||||
else
|
||||
log_info "配置目录不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除数据目录
|
||||
remove_data_dir() {
|
||||
log_info "删除数据目录..."
|
||||
|
||||
local data_dir="/var/lib/argus-agent"
|
||||
|
||||
if [[ -d "$data_dir" ]]; then
|
||||
rm -rf "$data_dir"
|
||||
log_success "数据目录已删除"
|
||||
else
|
||||
log_info "数据目录不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查用户状态(可选)
|
||||
check_user_status() {
|
||||
log_info "检查 argus-agent 用户状态..."
|
||||
|
||||
if id "argus-agent" &>/dev/null; then
|
||||
log_info "检测到 argus-agent 用户存在"
|
||||
log_warning "argus-agent 是系统用户,可能被其他服务使用"
|
||||
log_info "为了系统稳定性,将保留 argus-agent 用户"
|
||||
log_info "如需手动删除,请运行: sudo userdel argus-agent"
|
||||
else
|
||||
log_info "argus-agent 用户不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理日志文件
|
||||
cleanup_logs() {
|
||||
log_info "清理日志文件..."
|
||||
|
||||
# 删除安装脚本创建的日志文件
|
||||
rm -f /var/log/argus-agent.log
|
||||
|
||||
log_success "日志文件已清理"
|
||||
}
|
||||
|
||||
# 清理安装记录
|
||||
cleanup_install_record() {
|
||||
log_info "清理安装记录..."
|
||||
|
||||
local install_record="/opt/argus-metric/current/.install_record"
|
||||
|
||||
if [[ -f "$install_record" ]]; then
|
||||
if command -v jq &> /dev/null; then
|
||||
# 使用 jq 删除 argus-agent 记录
|
||||
jq 'del(.components."argus-agent")' "$install_record" > "$install_record.tmp" && mv "$install_record.tmp" "$install_record"
|
||||
log_success "安装记录已更新"
|
||||
else
|
||||
log_warning "jq 命令不可用,无法清理安装记录"
|
||||
fi
|
||||
else
|
||||
log_info "安装记录文件不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示卸载信息
|
||||
show_uninstall_info() {
|
||||
log_success "Argus Agent 卸载完成!"
|
||||
echo
|
||||
echo "已删除的内容:"
|
||||
echo " - 二进制文件: /usr/local/bin/argus-agent"
|
||||
echo " - 配置目录: /etc/argus-agent"
|
||||
echo " - 数据目录: /var/lib/argus-agent"
|
||||
echo " - 相关日志文件"
|
||||
echo
|
||||
echo "注意:"
|
||||
echo " - argus-agent 用户已保留(系统用户,可能被其他服务使用)"
|
||||
echo " - 如需完全清理,请手动检查并删除相关文件"
|
||||
echo
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " Argus Agent 卸载脚本 v1.0"
|
||||
echo "=========================================="
|
||||
echo
|
||||
|
||||
check_root
|
||||
|
||||
log_warning "此操作将完全卸载 Argus Agent"
|
||||
read -p "确认继续?(y/N): " confirm
|
||||
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
log_info "取消卸载操作"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_info "开始卸载 Argus Agent..."
|
||||
|
||||
stop_processes
|
||||
remove_binary
|
||||
remove_config
|
||||
remove_data_dir
|
||||
cleanup_logs
|
||||
cleanup_install_record
|
||||
|
||||
# 检查用户状态
|
||||
check_user_status
|
||||
|
||||
show_uninstall_info
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,55 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DCGM Exporter 健康检查脚本
|
||||
# 输出 JSON 格式结果
|
||||
|
||||
set -e
|
||||
|
||||
# 检查 DCGM Exporter 健康状态
|
||||
check_health() {
|
||||
local url="http://localhost:9400"
|
||||
local metrics_url="$url/metrics"
|
||||
local name="dcgm-exporter"
|
||||
local status="unhealth"
|
||||
local reason=""
|
||||
|
||||
# 检查 curl 是否可用
|
||||
if ! command -v curl &> /dev/null; then
|
||||
reason="curl 命令不可用,无法进行健康检查"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试根路径连接
|
||||
local http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$http_code" == "200" ]]; then
|
||||
# 测试 metrics 端点
|
||||
local metrics_code=$(curl -s -o /dev/null -w "%{http_code}" "$metrics_url" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$metrics_code" == "200" ]]; then
|
||||
status="health"
|
||||
reason="success"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 0
|
||||
else
|
||||
reason="Metrics 端点异常 (HTTP $metrics_code)"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
reason="HTTP 服务异常 (HTTP $http_code),请检查 DCGM Exporter 是否正在运行在端口 9400"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
check_health
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,77 +0,0 @@
|
||||
# Format
|
||||
# If line starts with a '#' it is considered a comment
|
||||
# DCGM FIELD, Prometheus metric type, help message
|
||||
|
||||
# Clocks
|
||||
DCGM_FI_DEV_SM_CLOCK, gauge, SM clock frequency (in MHz).
|
||||
DCGM_FI_DEV_MEM_CLOCK, gauge, Memory clock frequency (in MHz).
|
||||
# DCGM_EXP_CLOCK_EVENTS_COUNT, gauge, Count of clock events within the user-specified time window (see clock-events-count-window-size param).
|
||||
|
||||
# Temperature
|
||||
DCGM_FI_DEV_MEMORY_TEMP, gauge, Memory temperature (in C).
|
||||
DCGM_FI_DEV_GPU_TEMP, gauge, GPU temperature (in C).
|
||||
|
||||
# Power
|
||||
DCGM_FI_DEV_POWER_USAGE, gauge, Power draw (in W).
|
||||
DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION, counter, Total energy consumption since boot (in mJ).
|
||||
|
||||
# PCIE
|
||||
DCGM_FI_PROF_PCIE_TX_BYTES, counter, Total number of bytes transmitted through PCIe TX via NVML.
|
||||
DCGM_FI_PROF_PCIE_RX_BYTES, counter, Total number of bytes received through PCIe RX via NVML.
|
||||
DCGM_FI_DEV_PCIE_REPLAY_COUNTER, counter, Total number of PCIe retries.
|
||||
|
||||
# Utilization (the sample period varies depending on the product)
|
||||
DCGM_FI_DEV_GPU_UTIL, gauge, GPU utilization (in %).
|
||||
DCGM_FI_DEV_MEM_COPY_UTIL, gauge, Memory utilization (in %).
|
||||
DCGM_FI_DEV_ENC_UTIL, gauge, Encoder utilization (in %).
|
||||
DCGM_FI_DEV_DEC_UTIL , gauge, Decoder utilization (in %).
|
||||
|
||||
# Errors and violations
|
||||
DCGM_FI_DEV_XID_ERRORS, gauge, Value of the last XID error encountered.
|
||||
# DCGM_FI_DEV_POWER_VIOLATION, counter, Throttling duration due to power constraints (in us).
|
||||
# DCGM_FI_DEV_THERMAL_VIOLATION, counter, Throttling duration due to thermal constraints (in us).
|
||||
# DCGM_FI_DEV_SYNC_BOOST_VIOLATION, counter, Throttling duration due to sync-boost constraints (in us).
|
||||
# DCGM_FI_DEV_BOARD_LIMIT_VIOLATION, counter, Throttling duration due to board limit constraints (in us).
|
||||
# DCGM_FI_DEV_LOW_UTIL_VIOLATION, counter, Throttling duration due to low utilization (in us).
|
||||
# DCGM_FI_DEV_RELIABILITY_VIOLATION, counter, Throttling duration due to reliability constraints (in us).
|
||||
# DCGM_EXP_XID_ERRORS_COUNT, gauge, Count of XID Errors within user-specified time window (see xid-count-window-size param).
|
||||
# Memory usage
|
||||
DCGM_FI_DEV_FB_FREE, gauge, Frame buffer memory free (in MB).
|
||||
DCGM_FI_DEV_FB_USED, gauge, Frame buffer memory used (in MB).
|
||||
|
||||
# ECC
|
||||
# DCGM_FI_DEV_ECC_SBE_VOL_TOTAL, counter, Total number of single-bit volatile ECC errors.
|
||||
# DCGM_FI_DEV_ECC_DBE_VOL_TOTAL, counter, Total number of double-bit volatile ECC errors.
|
||||
# DCGM_FI_DEV_ECC_SBE_AGG_TOTAL, counter, Total number of single-bit persistent ECC errors.
|
||||
# DCGM_FI_DEV_ECC_DBE_AGG_TOTAL, counter, Total number of double-bit persistent ECC errors.
|
||||
|
||||
# Retired pages
|
||||
# DCGM_FI_DEV_RETIRED_SBE, counter, Total number of retired pages due to single-bit errors.
|
||||
# DCGM_FI_DEV_RETIRED_DBE, counter, Total number of retired pages due to double-bit errors.
|
||||
# DCGM_FI_DEV_RETIRED_PENDING, counter, Total number of pages pending retirement.
|
||||
|
||||
# NVLink
|
||||
# DCGM_FI_DEV_NVLINK_CRC_FLIT_ERROR_COUNT_TOTAL, counter, Total number of NVLink flow-control CRC errors.
|
||||
# DCGM_FI_DEV_NVLINK_CRC_DATA_ERROR_COUNT_TOTAL, counter, Total number of NVLink data CRC errors.
|
||||
# DCGM_FI_DEV_NVLINK_REPLAY_ERROR_COUNT_TOTAL, counter, Total number of NVLink retries.
|
||||
# DCGM_FI_DEV_NVLINK_RECOVERY_ERROR_COUNT_TOTAL, counter, Total number of NVLink recovery errors.
|
||||
DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL, counter, Total number of NVLink bandwidth counters for all lanes
|
||||
|
||||
# VGPU License status
|
||||
DCGM_FI_DEV_VGPU_LICENSE_STATUS, gauge, vGPU License status
|
||||
|
||||
# Remapped rows
|
||||
DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for uncorrectable errors
|
||||
DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for correctable errors
|
||||
DCGM_FI_DEV_ROW_REMAP_FAILURE, gauge, Whether remapping of rows has failed
|
||||
|
||||
# Static configuration information. These appear as labels on the other metrics
|
||||
DCGM_FI_DRIVER_VERSION, label, Driver Version
|
||||
# DCGM_FI_NVML_VERSION, label, NVML Version
|
||||
# DCGM_FI_DEV_BRAND, label, Device Brand
|
||||
# DCGM_FI_DEV_SERIAL, label, Device Serial Number
|
||||
# DCGM_FI_DEV_OEM_INFOROM_VER, label, OEM inforom version
|
||||
# DCGM_FI_DEV_ECC_INFOROM_VER, label, ECC inforom version
|
||||
# DCGM_FI_DEV_POWER_INFOROM_VER, label, Power management object inforom version
|
||||
# DCGM_FI_DEV_INFOROM_IMAGE_VER, label, Inforom image version
|
||||
# DCGM_FI_DEV_VBIOS_VERSION, label, VBIOS version of the device
|
||||
|
@@ -1,365 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 更新安装记录
|
||||
update_install_record() {
|
||||
local pid="$1"
|
||||
# 使用传入的安装目录参数,如果没有则使用默认值
|
||||
local install_base_dir="${2:-/opt/argus-metric/current}"
|
||||
local install_record="$install_base_dir/.install_record"
|
||||
|
||||
# 如果安装记录文件不存在,说明是首次安装,由主安装脚本统一创建
|
||||
if [[ ! -f "$install_record" ]]; then
|
||||
log_info "安装记录文件不存在,将由主安装脚本创建"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 如果文件存在,说明是重启场景,只更新 PID 字段
|
||||
if command -v jq &> /dev/null; then
|
||||
# 读取当前 PID
|
||||
local current_pid=$(jq -r '.components."dcgm-exporter".pid // ""' "$install_record" 2>/dev/null)
|
||||
|
||||
if [[ -z "$current_pid" ]]; then
|
||||
log_warning "无法读取当前 PID,跳过更新"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 使用 jq 只更新 pid 字段,保持字符串类型,保留其他字段
|
||||
jq --arg new_pid "$pid" '.components."dcgm-exporter".pid = $new_pid' "$install_record" > "$install_record.tmp" && mv "$install_record.tmp" "$install_record"
|
||||
log_info "PID 已更新: $current_pid -> $pid"
|
||||
else
|
||||
log_warning "jq 命令不可用,无法更新安装记录文件"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "DCGM Exporter 安装脚本"
|
||||
echo
|
||||
echo "用法: $0 [选项]"
|
||||
echo
|
||||
echo "选项:"
|
||||
echo " --help 显示此帮助信息"
|
||||
echo
|
||||
echo "示例:"
|
||||
echo " $0 # 安装 DCGM Exporter"
|
||||
echo
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
INSTALL_DIR=""
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# 如果参数不是以--开头,则认为是安装目录
|
||||
if [[ ! "$arg" =~ ^-- ]]; then
|
||||
INSTALL_DIR="$arg"
|
||||
else
|
||||
log_error "未知参数: $arg"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查是否为 root 用户
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要 root 权限运行"
|
||||
log_info "请使用: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查系统要求
|
||||
check_system() {
|
||||
log_info "检查系统要求..."
|
||||
|
||||
# 检查操作系统
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
log_error "无法检测操作系统版本"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source /etc/os-release
|
||||
log_info "检测到操作系统: $NAME $VERSION"
|
||||
|
||||
# 检查是否为 Ubuntu/Debian
|
||||
if [[ "$ID" != "ubuntu" && "$ID" != "debian" ]]; then
|
||||
log_warning "此脚本主要针对 Ubuntu/Debian 系统,其他系统可能需要调整"
|
||||
fi
|
||||
|
||||
# 检查 NVIDIA GPU
|
||||
if ! command -v nvidia-smi &> /dev/null; then
|
||||
log_warning "未检测到 nvidia-smi,请确保已安装 NVIDIA 驱动"
|
||||
else
|
||||
log_success "检测到 NVIDIA GPU"
|
||||
nvidia-smi --query-gpu=name --format=csv,noheader,nounits | head -1
|
||||
fi
|
||||
}
|
||||
|
||||
# 安装 DCGM 依赖
|
||||
install_dcgm_dependency() {
|
||||
log_info "安装 DCGM 依赖..."
|
||||
|
||||
local deb_file="bin/datacenter-gpu-manager_3.3.9_amd64.deb"
|
||||
|
||||
if [[ ! -f "$deb_file" ]]; then
|
||||
log_error "找不到 DCGM 依赖文件: $deb_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装 deb 包
|
||||
dpkg -i "$deb_file" || {
|
||||
log_warning "dpkg 安装失败,尝试使用 apt 修复依赖..."
|
||||
apt-get update
|
||||
apt-get install -f -y
|
||||
dpkg -i "$deb_file"
|
||||
}
|
||||
|
||||
log_success "DCGM 依赖安装完成"
|
||||
}
|
||||
|
||||
# 检查 DCGM 服务状态
|
||||
check_dcgm_service() {
|
||||
log_info "检查 DCGM 服务状态..."
|
||||
|
||||
# 检查 DCGM 服务是否在运行
|
||||
if systemctl is-active --quiet dcgm 2>/dev/null; then
|
||||
log_success "DCGM 服务已在运行"
|
||||
elif pgrep -f nv-hostengine > /dev/null; then
|
||||
log_success "nv-hostengine 进程已在运行"
|
||||
else
|
||||
log_warning "DCGM 服务未运行,需要手动启动"
|
||||
log_info "启动 DCGM 服务的方法:"
|
||||
log_info " 1. 使用 systemd: sudo systemctl start dcgm"
|
||||
log_info " 2. 手动启动: nohup nv-hostengine > /var/log/nv-hostengine.log 2>&1 &"
|
||||
fi
|
||||
|
||||
# 测试 DCGM 连接
|
||||
if systemctl is-active --quiet dcgm 2>/dev/null || pgrep -f nv-hostengine > /dev/null; then
|
||||
log_info "测试 DCGM 连接..."
|
||||
if dcgmi discovery -l > /dev/null 2>&1; then
|
||||
log_success "DCGM 连接测试成功"
|
||||
else
|
||||
log_warning "DCGM 连接测试失败,请检查服务状态"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止可能运行的服务
|
||||
stop_existing_service() {
|
||||
log_info "检查并停止可能运行的服务..."
|
||||
|
||||
local pid_file="/var/run/dcgm-exporter.pid"
|
||||
|
||||
# 检查并停止通过 PID 文件管理的服务
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "发现正在运行的 DCGM Exporter 服务 (PID: $pid),正在停止..."
|
||||
kill "$pid" > /dev/null 2>&1 || true
|
||||
sleep 2
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_warning "进程未响应,强制终止..."
|
||||
kill -9 "$pid" > /dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f "$pid_file"
|
||||
log_success "服务已停止"
|
||||
else
|
||||
log_warning "发现过期的 PID 文件,正在清理..."
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 查找并停止所有 dcgm-exporter 进程(排除脚本自身)
|
||||
local exporter_bin="/usr/local/bin/dcgm-exporter"
|
||||
local pids=$(pgrep -f "$exporter_bin")
|
||||
|
||||
if [[ -n "$pids" ]]; then
|
||||
log_info "发现其他 dcgm-exporter 进程,正在停止..."
|
||||
for pid in $pids; do
|
||||
if [[ "$pid" != "$$" ]]; then
|
||||
kill "$pid" > /dev/null 2>&1 || true
|
||||
sleep 1
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_warning "进程 $pid 未响应,强制终止..."
|
||||
kill -9 "$pid" > /dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
log_success "所有 dcgm-exporter 进程已停止"
|
||||
fi
|
||||
}
|
||||
|
||||
# 安装 DCGM Exporter 二进制文件
|
||||
install_dcgm_exporter() {
|
||||
log_info "安装 DCGM Exporter..."
|
||||
|
||||
local binary_file="bin/dcgm-exporter"
|
||||
local install_dir="/usr/local/bin"
|
||||
|
||||
if [[ ! -f "$binary_file" ]]; then
|
||||
log_error "找不到 DCGM Exporter 二进制文件: $binary_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 停止可能运行的服务
|
||||
stop_existing_service
|
||||
|
||||
# 复制二进制文件
|
||||
cp "$binary_file" "$install_dir/"
|
||||
chmod +x "$install_dir/dcgm-exporter"
|
||||
|
||||
log_success "DCGM Exporter 二进制文件安装完成"
|
||||
}
|
||||
|
||||
# 安装配置文件
|
||||
install_config() {
|
||||
log_info "安装配置文件..."
|
||||
|
||||
local config_dir="/etc/dcgm-exporter"
|
||||
local config_file="config/default-counters.csv"
|
||||
|
||||
# 创建配置目录
|
||||
mkdir -p "$config_dir"
|
||||
|
||||
if [[ -f "$config_file" ]]; then
|
||||
cp "$config_file" "$config_dir/"
|
||||
log_success "配置文件安装完成"
|
||||
else
|
||||
log_warning "未找到配置文件,使用默认配置"
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动 DCGM Exporter 服务
|
||||
start_dcgm_exporter() {
|
||||
log_info "启动 DCGM Exporter 服务..."
|
||||
|
||||
local binary_path="/usr/local/bin/dcgm-exporter"
|
||||
local log_file="/var/log/dcgm-exporter.log"
|
||||
local pid_file="/var/run/dcgm-exporter.pid"
|
||||
|
||||
# 检查服务是否已经在运行
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "DCGM Exporter 服务已在运行 (PID: $pid)"
|
||||
return 0
|
||||
else
|
||||
log_warning "发现过期的 PID 文件,正在清理..."
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查端口是否被占用
|
||||
if netstat -tuln 2>/dev/null | grep -q ":9400 "; then
|
||||
log_warning "端口 9400 已被占用,请检查是否有其他服务在运行"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
log_info "正在启动 DCGM Exporter..."
|
||||
nohup "$binary_path" --address=:9400 > "$log_file" 2>&1 &
|
||||
local pid=$!
|
||||
|
||||
# 保存 PID
|
||||
echo "$pid" > "$pid_file"
|
||||
|
||||
# 等待服务启动
|
||||
sleep 2
|
||||
|
||||
# 检查服务是否成功启动
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_success "DCGM Exporter 服务启动成功 (PID: $pid)"
|
||||
log_info "日志文件: $log_file"
|
||||
log_info "PID 文件: $pid_file"
|
||||
|
||||
# 更新安装记录
|
||||
update_install_record "$pid" "$INSTALL_DIR"
|
||||
else
|
||||
log_error "DCGM Exporter 服务启动失败"
|
||||
rm -f "$pid_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# 显示安装信息
|
||||
show_install_info() {
|
||||
log_success "DCGM Exporter 安装完成!"
|
||||
echo
|
||||
echo "安装信息:"
|
||||
echo " 二进制文件: /usr/local/bin/dcgm-exporter"
|
||||
echo " 配置文件: /etc/dcgm-exporter/default-counters.csv"
|
||||
echo " 默认端口: 9400"
|
||||
echo
|
||||
echo "使用方法:"
|
||||
echo " 1. 启动 DCGM 服务:"
|
||||
echo " sudo systemctl start dcgm"
|
||||
echo " 或: nohup nv-hostengine > /var/log/nv-hostengine.log 2>&1 &"
|
||||
echo " 2. 启动 DCGM Exporter:"
|
||||
echo " /usr/local/bin/dcgm-exporter --address=:9400"
|
||||
echo " 或: nohup /usr/local/bin/dcgm-exporter --address=:9400 &"
|
||||
echo
|
||||
echo "测试连接:"
|
||||
echo " curl http://localhost:9400/metrics"
|
||||
echo
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " DCGM Exporter 安装脚本 v1.0"
|
||||
echo "=========================================="
|
||||
echo
|
||||
|
||||
check_root
|
||||
check_system
|
||||
|
||||
log_info "开始安装 DCGM Exporter..."
|
||||
|
||||
install_dcgm_dependency
|
||||
check_dcgm_service
|
||||
install_dcgm_exporter
|
||||
install_config
|
||||
start_dcgm_exporter
|
||||
|
||||
show_install_info
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
# 获取当前目录
|
||||
CURRENT_DIR=$(pwd)
|
||||
PACKAGE_NAME="dcgm-exporter-$(date +%Y%m%d-%H%M%S)"
|
||||
PACKAGE_FILE="${PACKAGE_NAME}.tar.gz"
|
||||
|
||||
log_info "开始打包 DCGM Exporter 安装包..."
|
||||
|
||||
# 检查必要文件
|
||||
log_info "检查必要文件..."
|
||||
|
||||
required_files=(
|
||||
"install.sh"
|
||||
"uninstall.sh"
|
||||
"bin/dcgm-exporter"
|
||||
"bin/datacenter-gpu-manager_3.3.9_amd64.deb"
|
||||
"check_health.sh"
|
||||
)
|
||||
|
||||
missing_files=()
|
||||
for file in "${required_files[@]}"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
missing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_files[@]} -gt 0 ]]; then
|
||||
echo "缺少以下文件:"
|
||||
for file in "${missing_files[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "所有必要文件检查完成"
|
||||
|
||||
# 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
log_info "创建临时目录: $TEMP_DIR"
|
||||
|
||||
# 复制文件到临时目录
|
||||
cp -r . "$TEMP_DIR/$PACKAGE_NAME"
|
||||
|
||||
# 进入临时目录
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# 创建压缩包
|
||||
log_info "创建压缩包: $PACKAGE_FILE"
|
||||
tar -czf "$PACKAGE_FILE" "$PACKAGE_NAME"
|
||||
|
||||
# 移动压缩包到原目录
|
||||
mv "$PACKAGE_FILE" "$CURRENT_DIR/"
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# 返回原目录
|
||||
cd "$CURRENT_DIR"
|
||||
|
||||
# 显示结果
|
||||
log_success "打包完成!"
|
||||
echo
|
||||
echo "安装包文件: $PACKAGE_FILE"
|
||||
echo "文件大小: $(du -h "$PACKAGE_FILE" | cut -f1)"
|
||||
echo
|
||||
echo "使用方法:"
|
||||
echo "1. 将 $PACKAGE_FILE 传输到目标服务器"
|
||||
echo "2. 解压: tar -xzf $PACKAGE_FILE"
|
||||
echo "3. 进入目录: cd $PACKAGE_NAME"
|
||||
echo "4. 运行安装: sudo ./install.sh"
|
||||
echo
|
||||
echo "注意: 请确保 config/default-counters.csv 文件存在"
|
||||
@@ -1,216 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DCGM Exporter 卸载脚本
|
||||
# 版本: 1.0
|
||||
# 作者: AIOps Team
|
||||
# 日期: $(date +%Y-%m-%d)
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查是否为 root 用户
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要 root 权限运行"
|
||||
log_info "请使用: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止运行中的进程
|
||||
stop_processes() {
|
||||
log_info "停止 DCGM Exporter 进程..."
|
||||
|
||||
local pid_file="/var/run/dcgm-exporter.pid"
|
||||
local stopped=false
|
||||
|
||||
# 首先尝试通过 PID 文件停止服务
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "通过 PID 文件停止服务 (PID: $pid)..."
|
||||
kill "$pid"
|
||||
sleep 3
|
||||
|
||||
# 检查进程是否已停止
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_warning "进程未响应,强制终止..."
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
log_success "DCGM Exporter 进程已停止"
|
||||
stopped=true
|
||||
else
|
||||
log_warning "PID 文件存在但进程已不存在,清理 PID 文件"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 查找并杀死所有 dcgm-exporter 进程
|
||||
local pids=$(pgrep -f "dcgm-exporter" 2>/dev/null || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
log_info "发现 dcgm-exporter 进程,正在停止..."
|
||||
for pid in $pids; do
|
||||
log_info "停止进程 PID: $pid"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 2
|
||||
|
||||
# 检查是否还有进程在运行,如果有则强制终止
|
||||
local remaining_pids=$(pgrep -f "dcgm-exporter" 2>/dev/null || true)
|
||||
if [[ -n "$remaining_pids" ]]; then
|
||||
log_warning "进程未响应,强制终止..."
|
||||
for pid in $remaining_pids; do
|
||||
log_info "强制终止进程 PID: $pid"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# 最终检查
|
||||
if pgrep -f "dcgm-exporter" > /dev/null; then
|
||||
log_error "无法停止所有 dcgm-exporter 进程"
|
||||
else
|
||||
log_success "所有 DCGM Exporter 进程已停止"
|
||||
stopped=true
|
||||
fi
|
||||
else
|
||||
log_info "DCGM Exporter 进程未运行"
|
||||
fi
|
||||
|
||||
# 清理 PID 文件
|
||||
rm -f "$pid_file"
|
||||
|
||||
if [[ "$stopped" == "false" ]]; then
|
||||
log_warning "未发现需要停止的 DCGM Exporter 进程"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除二进制文件
|
||||
remove_binary() {
|
||||
log_info "删除 DCGM Exporter 二进制文件..."
|
||||
|
||||
local binary_file="/usr/local/bin/dcgm-exporter"
|
||||
|
||||
if [[ -f "$binary_file" ]]; then
|
||||
rm -f "$binary_file"
|
||||
log_success "二进制文件已删除"
|
||||
else
|
||||
log_info "二进制文件不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除配置文件
|
||||
remove_config() {
|
||||
log_info "删除配置文件..."
|
||||
|
||||
local config_dir="/etc/dcgm-exporter"
|
||||
|
||||
if [[ -d "$config_dir" ]]; then
|
||||
rm -rf "$config_dir"
|
||||
log_success "配置目录已删除"
|
||||
else
|
||||
log_info "配置目录不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 卸载 DCGM 依赖(可选)
|
||||
remove_dcgm_dependency() {
|
||||
log_info "检查 DCGM 依赖状态..."
|
||||
|
||||
# 检查是否安装了 DCGM 包
|
||||
if dpkg -l | grep -q datacenter-gpu-manager; then
|
||||
log_info "检测到 DCGM 依赖包已安装"
|
||||
log_warning "DCGM 是系统级依赖,可能被其他应用程序使用"
|
||||
log_info "为了系统稳定性,将保留 DCGM 依赖包"
|
||||
log_info "如需手动卸载,请运行: sudo apt-get remove --purge datacenter-gpu-manager"
|
||||
else
|
||||
log_info "DCGM 依赖包未安装"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理日志文件
|
||||
cleanup_logs() {
|
||||
log_info "清理日志文件..."
|
||||
|
||||
# 清理 journal 日志
|
||||
journalctl --vacuum-time=1s --quiet || true
|
||||
|
||||
# 删除可能的日志文件
|
||||
rm -f /var/log/nv-hostengine.log
|
||||
rm -f /var/log/dcgm-exporter.log
|
||||
|
||||
log_success "日志文件已清理"
|
||||
}
|
||||
|
||||
# 显示卸载信息
|
||||
show_uninstall_info() {
|
||||
log_success "DCGM Exporter 卸载完成!"
|
||||
echo
|
||||
echo "已删除的内容:"
|
||||
echo " - 二进制文件: /usr/local/bin/dcgm-exporter"
|
||||
echo " - 配置目录: /etc/dcgm-exporter"
|
||||
echo " - 相关日志文件"
|
||||
echo
|
||||
echo "注意:"
|
||||
echo " - DCGM 依赖包可能仍然存在"
|
||||
echo " - 如需完全清理,请手动检查并删除相关文件"
|
||||
echo
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " DCGM Exporter 卸载脚本 v1.0"
|
||||
echo "=========================================="
|
||||
echo
|
||||
|
||||
check_root
|
||||
|
||||
log_warning "此操作将完全卸载 DCGM Exporter"
|
||||
read -p "确认继续?(y/N): " confirm
|
||||
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
log_info "取消卸载操作"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_info "开始卸载 DCGM Exporter..."
|
||||
|
||||
stop_processes
|
||||
remove_binary
|
||||
remove_config
|
||||
cleanup_logs
|
||||
|
||||
# 询问是否卸载 DCGM 依赖
|
||||
remove_dcgm_dependency
|
||||
|
||||
show_uninstall_info
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,181 +0,0 @@
|
||||
# Fluent Bit 安装包
|
||||
|
||||
这是一个 Fluent Bit 的自动化安装包,提供了完整的安装、卸载和健康检查功能。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
fluent-bit-installer/
|
||||
├── install.sh # 安装脚本
|
||||
├── uninstall.sh # 卸载脚本
|
||||
├── package.sh # 打包脚本
|
||||
├── check_health.sh # 健康检查脚本
|
||||
├── bin/
|
||||
│ └── fluent-bit_3.1.9_amd64.deb # Fluent Bit 安装包
|
||||
└── config/
|
||||
├── fluent-bit.conf # 主配置文件
|
||||
├── inject_labels.lua # Lua 脚本
|
||||
├── parsers.conf # 解析器配置
|
||||
├── inputs.d/ # 输入配置目录
|
||||
│ ├── 10-train.conf
|
||||
│ └── 20-infer.conf
|
||||
└── outputs.d/ # 输出配置目录
|
||||
└── 10-es.conf
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **自动化安装**: 一键安装 Fluent Bit 及其依赖
|
||||
- **配置管理**: 自动部署预配置的配置文件
|
||||
- **服务管理**: 自动启动和停止 Fluent Bit 服务
|
||||
- **健康检查**: 提供 JSON 格式的健康状态检查
|
||||
- **完整卸载**: 彻底清理所有相关文件和配置
|
||||
- **用户管理**: 自动创建专用的 fluent-bit 用户
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 打包安装包
|
||||
|
||||
```bash
|
||||
./package.sh
|
||||
```
|
||||
|
||||
这将创建一个带时间戳的压缩包,例如:`fluent-bit-installer-20250924-160954.tar.gz`
|
||||
|
||||
### 2. 安装 Fluent Bit
|
||||
|
||||
```bash
|
||||
# 解压安装包
|
||||
tar -xzf fluent-bit-installer-*.tar.gz
|
||||
cd fluent-bit-installer-*
|
||||
|
||||
# 运行安装脚本(需要 root 权限)
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
### 3. 健康检查
|
||||
|
||||
```bash
|
||||
./check_health.sh
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```json
|
||||
{"name": "fluent-bit", "status": "health", "reason": "success"}
|
||||
```
|
||||
|
||||
### 4. 卸载 Fluent Bit
|
||||
|
||||
```bash
|
||||
sudo ./uninstall.sh
|
||||
```
|
||||
|
||||
## 安装后的文件位置
|
||||
|
||||
- **二进制文件**: `/opt/fluent-bit/bin/fluent-bit`
|
||||
- **配置文件**: `/etc/fluent-bit/`
|
||||
- **日志文件**: `/var/log/fluent-bit/`
|
||||
- **缓冲区目录**: `/var/lib/fluent-bit/buffers/`
|
||||
- **运行用户**: `fluent-bit`
|
||||
- **HTTP 端口**: `2020`
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 主配置文件
|
||||
|
||||
主配置文件位于 `/etc/fluent-bit/fluent-bit.conf`,包含以下主要部分:
|
||||
|
||||
- **SERVICE**: 服务配置,包括 HTTP 服务器设置
|
||||
- **INPUT**: 输入配置,通过 `inputs.d/` 目录管理
|
||||
- **FILTER**: 过滤器配置,包括解析器和标签注入
|
||||
- **OUTPUT**: 输出配置,通过 `outputs.d/` 目录管理
|
||||
|
||||
### 输入配置
|
||||
|
||||
- `10-train.conf`: 训练日志输入配置
|
||||
- `20-infer.conf`: 推理日志输入配置
|
||||
|
||||
### 输出配置
|
||||
|
||||
- `10-es.conf`: Elasticsearch 输出配置
|
||||
|
||||
## 服务管理
|
||||
|
||||
### 手动启动
|
||||
|
||||
```bash
|
||||
/opt/fluent-bit/bin/fluent-bit --config=/etc/fluent-bit/fluent-bit.conf
|
||||
```
|
||||
|
||||
### 后台启动
|
||||
|
||||
```bash
|
||||
nohup /opt/fluent-bit/bin/fluent-bit --config=/etc/fluent-bit/fluent-bit.conf &
|
||||
```
|
||||
|
||||
### 检查服务状态
|
||||
|
||||
```bash
|
||||
# 检查进程
|
||||
ps aux | grep fluent-bit
|
||||
|
||||
# 检查端口
|
||||
netstat -tuln | grep 2020
|
||||
|
||||
# 检查日志
|
||||
tail -f /var/log/fluent-bit/fluent-bit.log
|
||||
```
|
||||
|
||||
## API 接口
|
||||
|
||||
Fluent Bit 提供 HTTP API 用于监控和管理:
|
||||
|
||||
- **根路径**: `http://localhost:2020`
|
||||
- **状态接口**: `http://localhost:2020/api/v1/status`
|
||||
- **指标接口**: `http://localhost:2020/api/v1/metrics`
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **端口被占用**
|
||||
- 检查端口 2020 是否被其他服务占用
|
||||
- 修改配置文件中的端口设置
|
||||
|
||||
2. **权限问题**
|
||||
- 确保 fluent-bit 用户有足够的权限访问日志文件
|
||||
- 检查目录权限设置
|
||||
|
||||
3. **配置文件错误**
|
||||
- 检查配置文件语法
|
||||
- 查看日志文件中的错误信息
|
||||
|
||||
### 日志查看
|
||||
|
||||
```bash
|
||||
# 查看服务日志
|
||||
tail -f /var/log/fluent-bit/fluent-bit.log
|
||||
|
||||
# 查看系统日志
|
||||
journalctl -u fluent-bit -f
|
||||
```
|
||||
|
||||
## 系统要求
|
||||
|
||||
- **操作系统**: Ubuntu/Debian/CentOS/RHEL/Fedora
|
||||
- **架构**: x86_64/amd64
|
||||
- **权限**: root 权限(用于安装和卸载)
|
||||
- **依赖**: curl(用于健康检查)
|
||||
|
||||
## 版本信息
|
||||
|
||||
- **Fluent Bit 版本**: 3.1.9
|
||||
- **安装包版本**: 1.0
|
||||
- **支持架构**: amd64
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 安装前请确保系统已更新
|
||||
2. 卸载时会保留 fluent-bit 用户(系统用户,可能被其他服务使用)
|
||||
3. 配置文件包含环境变量,请根据实际环境调整
|
||||
4. 建议在生产环境使用前进行充分测试
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,69 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fluent Bit 健康检查脚本
|
||||
# 输出 JSON 格式结果
|
||||
|
||||
set -e
|
||||
|
||||
# 检查 Fluent Bit 健康状态
|
||||
check_health() {
|
||||
local name="fluent-bit"
|
||||
local status="unhealth"
|
||||
local reason=""
|
||||
local install_record="/opt/argus-metric/current/.install_record"
|
||||
|
||||
# 首先尝试通过安装记录文件检查进程
|
||||
if [[ -f "$install_record" ]]; then
|
||||
# 尝试使用jq解析JSON格式的安装记录文件
|
||||
local pid=""
|
||||
if command -v jq &> /dev/null; then
|
||||
pid=$(jq -r '.components."fluent-bit".pid // empty' "$install_record" 2>/dev/null || echo "")
|
||||
else
|
||||
# 如果没有jq,使用简单的文本解析方法
|
||||
pid=$(grep -A 10 '"fluent-bit"' "$install_record" | grep '"pid"' | cut -d'"' -f4 | head -1)
|
||||
fi
|
||||
|
||||
if [[ -n "$pid" && "$pid" =~ ^[0-9]+$ ]]; then
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
# 进程存在且运行正常
|
||||
status="health"
|
||||
reason="进程运行正常 (PID: $pid)"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 0
|
||||
else
|
||||
reason="安装记录中的 PID $pid 进程不存在"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
reason="安装记录文件中未找到有效的 fluent-bit PID"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# 如果安装记录文件不存在,尝试查找 fluent-bit 进程
|
||||
local pids=$(pgrep -f "fluent-bit" 2>/dev/null || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
# 取第一个找到的 PID
|
||||
local pid=$(echo "$pids" | head -1)
|
||||
status="health"
|
||||
reason="发现 fluent-bit 进程运行 (PID: $pid),但未找到安装记录"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 0
|
||||
else
|
||||
reason="未找到 fluent-bit 进程,且安装记录文件不存在"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
check_health
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,37 +0,0 @@
|
||||
[SERVICE]
|
||||
Daemon Off
|
||||
Parsers_File parsers.conf
|
||||
HTTP_Server On
|
||||
HTTP_Listen 0.0.0.0
|
||||
HTTP_Port 2020
|
||||
storage.path /buffers
|
||||
storage.sync normal
|
||||
storage.checksum on
|
||||
storage.backlog.mem_limit 128M
|
||||
# 备注:该镜像默认未开启 Hot Reload,修改配置后请重启容器。
|
||||
|
||||
@INCLUDE inputs.d/*.conf
|
||||
|
||||
[FILTER]
|
||||
Name parser
|
||||
Match app.*
|
||||
Key_Name log
|
||||
Parser timestamp_parser
|
||||
Reserve_Data On
|
||||
Preserve_Key On
|
||||
Unescape_Key On
|
||||
|
||||
[FILTER]
|
||||
Name record_modifier
|
||||
Match *
|
||||
Record cluster ${CLUSTER}
|
||||
Record rack ${RACK}
|
||||
Record host ${HOSTNAME}
|
||||
|
||||
[FILTER]
|
||||
Name lua
|
||||
Match app.*
|
||||
script inject_labels.lua
|
||||
call add_labels
|
||||
|
||||
@INCLUDE outputs.d/*.conf
|
||||
@@ -1,15 +0,0 @@
|
||||
function add_labels(tag, ts, record)
|
||||
record["job_id"] = os.getenv("FB_JOB_ID") or record["job_id"] or "unknown"
|
||||
record["user"] = os.getenv("FB_USER") or record["user"] or "unknown"
|
||||
record["model"] = os.getenv("FB_MODEL") or record["model"] or "unknown"
|
||||
record["gpu_id"] = os.getenv("FB_GPU_ID") or record["gpu_id"] or "na"
|
||||
local p = record["log_path"] or ""
|
||||
if string.find(p, "/logs/infer/") then
|
||||
record["role"] = "infer"
|
||||
elseif string.find(p, "/logs/train/") then
|
||||
record["role"] = "train"
|
||||
else
|
||||
record["role"] = record["role"] or "app"
|
||||
end
|
||||
return 1, ts, record
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
[INPUT]
|
||||
Name tail
|
||||
Path /logs/train/*.log
|
||||
Tag app.train
|
||||
Path_Key log_path
|
||||
Refresh_Interval 5
|
||||
DB /buffers/train.db
|
||||
Skip_Long_Lines On
|
||||
storage.type filesystem
|
||||
multiline.parser python,go,java
|
||||
@@ -1,10 +0,0 @@
|
||||
[INPUT]
|
||||
Name tail
|
||||
Path /logs/infer/*.log
|
||||
Tag app.infer
|
||||
Path_Key log_path
|
||||
Refresh_Interval 5
|
||||
DB /buffers/infer.db
|
||||
Skip_Long_Lines On
|
||||
storage.type filesystem
|
||||
multiline.parser python,go,java
|
||||
@@ -1,24 +0,0 @@
|
||||
# 重要:使用 Logstash_Format + Logstash_Prefix,生成 train-*/infer-* 索引
|
||||
[OUTPUT]
|
||||
Name es
|
||||
Match app.train
|
||||
Host ${ES_HOST:-localhost}
|
||||
Port ${ES_PORT:-9200}
|
||||
Logstash_Format On
|
||||
Logstash_Prefix train
|
||||
Replace_Dots On
|
||||
Generate_ID On
|
||||
Retry_Limit False
|
||||
Suppress_Type_Name On
|
||||
|
||||
[OUTPUT]
|
||||
Name es
|
||||
Match app.infer
|
||||
Host ${ES_HOST:-localhost}
|
||||
Port ${ES_PORT:-9200}
|
||||
Logstash_Format On
|
||||
Logstash_Prefix infer
|
||||
Replace_Dots On
|
||||
Generate_ID On
|
||||
Retry_Limit False
|
||||
Suppress_Type_Name On
|
||||
@@ -1,27 +0,0 @@
|
||||
[MULTILINE_PARSER]
|
||||
Name python
|
||||
Type regex
|
||||
Flush 2
|
||||
Rule "start_state" "/^\d{4}-\d{2}-\d{2}[\sT]/" "cont"
|
||||
Rule "cont" "/^\s+|^Traceback|^\tat\s+/" "cont"
|
||||
|
||||
[MULTILINE_PARSER]
|
||||
Name go
|
||||
Type regex
|
||||
Flush 2
|
||||
Rule "start_state" "/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/" "cont"
|
||||
Rule "cont" "/^\s+|^\t/" "cont"
|
||||
|
||||
[MULTILINE_PARSER]
|
||||
Name java
|
||||
Type regex
|
||||
Flush 2
|
||||
Rule "start_state" "/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/" "cont"
|
||||
Rule "cont" "/^\s+at\s+|^\t.../" "cont"
|
||||
|
||||
[PARSER]
|
||||
Name timestamp_parser
|
||||
Format regex
|
||||
Regex ^(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+(?<level>\w+)\s+(?<message>.*)$
|
||||
Time_Key timestamp
|
||||
Time_Format %Y-%m-%d %H:%M:%S
|
||||
@@ -1,291 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_info "Starting Fluent Bit installation..."
|
||||
|
||||
# 解析命令行参数
|
||||
INSTALL_DIR="${1:-/opt/argus-metric/current}"
|
||||
|
||||
# 更新安装记录
|
||||
update_install_record() {
|
||||
local pid="$1"
|
||||
# 使用传入的安装目录参数,如果没有则使用默认值
|
||||
local install_base_dir="${2:-/opt/argus-metric/current}"
|
||||
local install_record="$install_base_dir/.install_record"
|
||||
|
||||
# 如果安装记录文件不存在,说明是首次安装,由主安装脚本统一创建
|
||||
if [[ ! -f "$install_record" ]]; then
|
||||
log_info "安装记录文件不存在,将由主安装脚本创建"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 如果文件存在,说明是重启场景,只更新 PID 字段
|
||||
if command -v jq &> /dev/null; then
|
||||
# 读取当前 PID
|
||||
local current_pid=$(jq -r '.components."fluent-bit".pid // ""' "$install_record" 2>/dev/null)
|
||||
|
||||
if [[ -z "$current_pid" ]]; then
|
||||
log_warning "无法读取当前 PID,跳过更新"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 使用 jq 只更新 pid 字段,保持字符串类型,保留其他字段
|
||||
jq --arg new_pid "$pid" '.components."fluent-bit".pid = $new_pid' "$install_record" > "$install_record.tmp" && mv "$install_record.tmp" "$install_record"
|
||||
log_info "PID updated: $current_pid -> $pid"
|
||||
else
|
||||
log_warning "jq 命令不可用,无法更新安装记录文件"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查是否为 root 用户
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "This script requires root privileges"
|
||||
log_info "Please use: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 停止可能运行的服务
|
||||
log_info "Stopping existing fluent-bit processes..."
|
||||
|
||||
# 只匹配进程名为 fluent-bit 的进程
|
||||
pids=$(pgrep -x fluent-bit 2>/dev/null || true)
|
||||
|
||||
if [[ -n "$pids" ]]; then
|
||||
for pid in $pids; do
|
||||
log_info "Stopping process PID: $pid"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 2
|
||||
|
||||
# 检查是否还有残留进程
|
||||
remaining_pids=$(pgrep -x fluent-bit 2>/dev/null || true)
|
||||
if [[ -n "$remaining_pids" ]]; then
|
||||
log_warning "Force killing unresponsive processes..."
|
||||
for pid in $remaining_pids; do
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# 安装 Fluent Bit 依赖库 libpq5(离线模式)
|
||||
log_info "Checking Fluent Bit dependency: libpq5 ..."
|
||||
if ! ldconfig -p | grep -q libpq.so.5; then
|
||||
if ls bin/libpq5_*.deb >/dev/null 2>&1; then
|
||||
log_info "Installing local dependency package: libpq5"
|
||||
DEBIAN_FRONTEND=noninteractive dpkg -i bin/libpq5_*.deb >/dev/null 2>&1 || {
|
||||
log_error "Failed to install libpq5 from bin/, please check package validity"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
log_error "Missing dependency: libpq5 (libpq.so.5). Please put bin/libpq5_*.deb in the bin/ directory."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_info "libpq.so.5 already present on system"
|
||||
fi
|
||||
|
||||
# 安装 Fluent Bit 依赖库 libyaml-0-2(离线模式)
|
||||
log_info "Checking Fluent Bit dependency: libyaml-0.so.2 ..."
|
||||
if ! ldconfig -p | grep -q libyaml-0.so.2; then
|
||||
if ls bin/libyaml-0-2_*.deb >/dev/null 2>&1; then
|
||||
log_info "Installing local dependency package: libyaml-0-2"
|
||||
DEBIAN_FRONTEND=noninteractive dpkg -i bin/libyaml-0-2_*.deb >/dev/null 2>&1 || {
|
||||
log_error "Failed to install libyaml-0-2 from bin/, please check package validity"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
log_error "Missing dependency: libyaml-0-2 (libyaml-0.so.2). Please put bin/libyaml-0-2_*.deb in the bin/ directory."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_info "libyaml-0.so.2 already present on system"
|
||||
fi
|
||||
|
||||
# 清理可能存在的旧 fluent-bit 安装(避免配置文件冲突)
|
||||
log_info "Cleaning up old fluent-bit installation if exists..."
|
||||
if dpkg -l | grep -q "^ii.*fluent-bit"; then
|
||||
log_info "Found existing fluent-bit package, removing..."
|
||||
dpkg --purge fluent-bit 2>/dev/null || true
|
||||
apt-get remove --purge -y fluent-bit 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 确保清理残留的配置文件
|
||||
if [[ -d "/etc/fluent-bit" ]]; then
|
||||
log_info "Removing old fluent-bit configuration directory..."
|
||||
rm -rf /etc/fluent-bit
|
||||
fi
|
||||
|
||||
# 安装 Fluent Bit 主包
|
||||
log_info "Installing Fluent Bit from deb package..."
|
||||
deb_file="bin/fluent-bit_3.1.9_amd64.deb"
|
||||
if [[ ! -f "$deb_file" ]]; then
|
||||
log_error "Fluent Bit package not found: $deb_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive dpkg -i "$deb_file" >/dev/null 2>&1 || true
|
||||
|
||||
# 验证 Fluent Bit 可以运行
|
||||
fb_version=$(/opt/fluent-bit/bin/fluent-bit --version 2>&1 | head -1)
|
||||
log_info "Fluent Bit version: $fb_version"
|
||||
|
||||
# 创建 fluent-bit 用户
|
||||
log_info "Creating fluent-bit user..."
|
||||
if ! id "fluent-bit" &>/dev/null; then
|
||||
useradd --no-create-home --shell /bin/false fluent-bit
|
||||
fi
|
||||
|
||||
# 创建配置目录
|
||||
log_info "Installing configuration files..."
|
||||
mkdir -p /etc/fluent-bit
|
||||
if [[ -d "config" ]]; then
|
||||
cp -r config/* /etc/fluent-bit/
|
||||
chown -R fluent-bit:fluent-bit /etc/fluent-bit
|
||||
fi
|
||||
|
||||
# 创建日志和缓冲区目录
|
||||
log_info "Creating log and buffer directories..."
|
||||
mkdir -p /logs/train /logs/infer /buffers
|
||||
chmod 755 /logs/train /logs/infer
|
||||
chmod 770 /buffers
|
||||
chown -R fluent-bit:fluent-bit /logs /buffers
|
||||
|
||||
# 启动 Fluent Bit
|
||||
log_info "Starting Fluent Bit with configuration from /etc/fluent-bit/"
|
||||
config_path="/etc/fluent-bit/fluent-bit.conf"
|
||||
|
||||
if [[ ! -f "$config_path" ]]; then
|
||||
log_error "Configuration file not found: $config_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 设置环境变量
|
||||
log_info "Setting environment variables..."
|
||||
|
||||
# 获取非 127.0.0.1 的 IP 地址作为 HOSTNAME
|
||||
if [[ -z "${HOSTNAME:-}" ]]; then
|
||||
# 获取 177.x.x.x 段的 IP 地址
|
||||
HOSTNAME=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K\S+' | grep '^177\.' | head -1)
|
||||
|
||||
# 如果没有找到 177.x.x.x 段的 IP,则获取第一个非 127.0.0.1 的 IP
|
||||
if [[ -z "$HOSTNAME" ]]; then
|
||||
HOSTNAME=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K\S+' | grep -v '^127\.' | head -1)
|
||||
fi
|
||||
|
||||
# 如果还是没有找到,使用 hostname 命令
|
||||
if [[ -z "$HOSTNAME" ]]; then
|
||||
HOSTNAME=$(hostname)
|
||||
fi
|
||||
fi
|
||||
export HOSTNAME
|
||||
|
||||
export CLUSTER="${CLUSTER:-local}"
|
||||
export RACK="${RACK:-dev}"
|
||||
export ES_HOST="${ES_HOST:-localhost}"
|
||||
export ES_PORT="${ES_PORT:-9200}"
|
||||
|
||||
log_info "Environment variables:"
|
||||
log_info " CLUSTER=$CLUSTER"
|
||||
log_info " RACK=$RACK"
|
||||
log_info " HOSTNAME=$HOSTNAME"
|
||||
log_info " ES_HOST=$ES_HOST"
|
||||
log_info " ES_PORT=$ES_PORT"
|
||||
|
||||
# 检查 fluent-bit 二进制文件
|
||||
log_info "[DEBUG] Checking fluent-bit binary..."
|
||||
if [[ ! -f "/opt/fluent-bit/bin/fluent-bit" ]]; then
|
||||
log_error "fluent-bit binary not found at /opt/fluent-bit/bin/fluent-bit"
|
||||
exit 1
|
||||
fi
|
||||
log_info "[DEBUG] fluent-bit binary exists and is executable: $(ls -lh /opt/fluent-bit/bin/fluent-bit)"
|
||||
|
||||
# 检查配置文件
|
||||
log_info "[DEBUG] Checking configuration file: $config_path"
|
||||
if [[ ! -f "$config_path" ]]; then
|
||||
log_error "Configuration file not found: $config_path"
|
||||
exit 1
|
||||
fi
|
||||
log_info "[DEBUG] Configuration file exists: $(ls -lh $config_path)"
|
||||
|
||||
# 显示完整的启动命令
|
||||
log_info "[DEBUG] Full command to execute:"
|
||||
log_info "[DEBUG] su -s /bin/bash fluent-bit -c 'env CLUSTER=\"$CLUSTER\" RACK=\"$RACK\" HOSTNAME=\"$HOSTNAME\" ES_HOST=\"$ES_HOST\" ES_PORT=\"$ES_PORT\" /opt/fluent-bit/bin/fluent-bit --config=\"$config_path\"'"
|
||||
|
||||
# 清空或创建日志文件
|
||||
log_info "[DEBUG] Preparing log file: /var/log/fluent-bit.log"
|
||||
: > /var/log/fluent-bit.log
|
||||
chmod 666 /var/log/fluent-bit.log
|
||||
|
||||
log_info "Command: /opt/fluent-bit/bin/fluent-bit --config=$config_path"
|
||||
log_info "[DEBUG] Starting fluent-bit process as fluent-bit user (using su)..."
|
||||
nohup su -s /bin/bash fluent-bit -c "env CLUSTER='$CLUSTER' RACK='$RACK' HOSTNAME='$HOSTNAME' ES_HOST='$ES_HOST' ES_PORT='$ES_PORT' /opt/fluent-bit/bin/fluent-bit --config='$config_path' >> /var/log/fluent-bit.log 2>&1" &
|
||||
|
||||
bg_pid=$!
|
||||
log_info "[DEBUG] Background process started with PID: $bg_pid"
|
||||
|
||||
# 等待服务启动
|
||||
log_info "[DEBUG] Waiting 3 seconds for service to start..."
|
||||
sleep 3
|
||||
|
||||
# 查找实际的 fluent-bit 进程 PID
|
||||
log_info "[DEBUG] Searching for fluent-bit process..."
|
||||
log_info "[DEBUG] Running: pgrep -u fluent-bit -x fluent-bit"
|
||||
actual_pid=$(pgrep -u fluent-bit -x fluent-bit | head -1)
|
||||
|
||||
# 显示所有 fluent-bit 相关进程
|
||||
log_info "[DEBUG] All fluent-bit related processes:"
|
||||
ps aux | grep fluent-bit | grep -v grep || log_warning "No fluent-bit processes found in ps output"
|
||||
|
||||
if [[ -n "$actual_pid" ]]; then
|
||||
log_success "Fluent Bit started successfully (PID: $actual_pid)"
|
||||
log_info "[DEBUG] Process details: $(ps -p $actual_pid -o pid,user,cmd --no-headers)"
|
||||
|
||||
# 更新安装记录
|
||||
update_install_record "$actual_pid" "$INSTALL_DIR"
|
||||
else
|
||||
log_error "Fluent Bit failed to start - no fluent-bit process found"
|
||||
log_info "[DEBUG] Checking if background process $bg_pid still exists..."
|
||||
if ps -p $bg_pid > /dev/null 2>&1; then
|
||||
log_warning "Background shell process $bg_pid still exists"
|
||||
else
|
||||
log_warning "Background shell process $bg_pid has exited"
|
||||
fi
|
||||
|
||||
log_info "[DEBUG] Last 20 lines of /var/log/fluent-bit.log:"
|
||||
if [[ -f "/var/log/fluent-bit.log" ]]; then
|
||||
tail -20 /var/log/fluent-bit.log | while IFS= read -r line; do
|
||||
log_info "[LOG] $line"
|
||||
done
|
||||
else
|
||||
log_error "Log file /var/log/fluent-bit.log does not exist"
|
||||
fi
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Fluent Bit installation completed!"
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
# 获取当前目录
|
||||
CURRENT_DIR=$(pwd)
|
||||
PACKAGE_NAME="fluent-bit-$(date +%Y%m%d-%H%M%S)"
|
||||
PACKAGE_FILE="${PACKAGE_NAME}.tar.gz"
|
||||
|
||||
log_info "开始打包 Fluent Bit 安装包..."
|
||||
|
||||
# 检查必要文件
|
||||
log_info "检查必要文件..."
|
||||
|
||||
required_files=(
|
||||
"install.sh"
|
||||
"uninstall.sh"
|
||||
"bin/fluent-bit_3.1.9_amd64.deb"
|
||||
"check_health.sh"
|
||||
)
|
||||
|
||||
missing_files=()
|
||||
for file in "${required_files[@]}"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
missing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_files[@]} -gt 0 ]]; then
|
||||
echo "缺少以下文件:"
|
||||
for file in "${missing_files[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "所有必要文件检查完成"
|
||||
|
||||
# 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
log_info "创建临时目录: $TEMP_DIR"
|
||||
|
||||
# 复制文件到临时目录
|
||||
cp -r . "$TEMP_DIR/$PACKAGE_NAME"
|
||||
|
||||
# 进入临时目录
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# 创建压缩包
|
||||
log_info "创建压缩包: $PACKAGE_FILE"
|
||||
tar -czf "$PACKAGE_FILE" "$PACKAGE_NAME"
|
||||
|
||||
# 移动压缩包到原目录
|
||||
mv "$PACKAGE_FILE" "$CURRENT_DIR/"
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# 返回原目录
|
||||
cd "$CURRENT_DIR"
|
||||
|
||||
# 显示结果
|
||||
log_success "打包完成!"
|
||||
echo
|
||||
echo "安装包文件: $PACKAGE_FILE"
|
||||
echo "文件大小: $(du -h "$PACKAGE_FILE" | cut -f1)"
|
||||
echo
|
||||
echo "使用方法:"
|
||||
echo "1. 将 $PACKAGE_FILE 传输到目标服务器"
|
||||
echo "2. 解压: tar -xzf $PACKAGE_FILE"
|
||||
echo "3. 进入目录: cd $PACKAGE_NAME"
|
||||
echo "4. 运行安装: sudo ./install.sh"
|
||||
echo
|
||||
echo "注意: 请确保所有必要文件都存在"
|
||||
@@ -1,169 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[INFO] Starting Fluent Bit uninstallation..."
|
||||
|
||||
# 检查是否为 root 用户
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "[ERROR] This script requires root privileges"
|
||||
echo "[INFO] Please use: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[WARNING] This operation will completely uninstall Fluent Bit"
|
||||
read -p "Confirm to continue? (y/N): " confirm
|
||||
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
echo "[INFO] Uninstallation cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 停止运行中的进程
|
||||
echo "[INFO] Stopping Fluent Bit processes..."
|
||||
install_record="/opt/argus-metric/current/.install_record"
|
||||
stopped=false
|
||||
|
||||
# 首先尝试通过安装记录文件停止服务
|
||||
if [[ -f "$install_record" ]]; then
|
||||
# 尝试使用jq解析JSON格式的安装记录文件
|
||||
pid=""
|
||||
if command -v jq &> /dev/null; then
|
||||
pid=$(jq -r '.components."fluent-bit".pid // empty' "$install_record" 2>/dev/null || echo "")
|
||||
else
|
||||
# 如果没有jq,使用简单的文本解析方法
|
||||
pid=$(grep -A 10 '"fluent-bit"' "$install_record" | grep '"pid"' | cut -d'"' -f4 | head -1)
|
||||
fi
|
||||
|
||||
if [[ -n "$pid" && "$pid" =~ ^[0-9]+$ ]]; then
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo "[INFO] Stopping service via installation record (PID: $pid)..."
|
||||
kill "$pid"
|
||||
sleep 3
|
||||
|
||||
# 检查进程是否已停止
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo "[WARNING] Process unresponsive, force killing..."
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
echo "[SUCCESS] Fluent Bit process stopped"
|
||||
stopped=true
|
||||
else
|
||||
echo "[WARNING] PID in installation record no longer exists"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 查找并杀死所有 fluent-bit 进程
|
||||
pids=$(pgrep -f "fluent-bit" 2>/dev/null || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
echo "[INFO] Found fluent-bit processes, stopping..."
|
||||
for pid in $pids; do
|
||||
echo "[INFO] Stopping process PID: $pid"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 2
|
||||
|
||||
# 检查是否还有进程在运行,如果有则强制终止
|
||||
remaining_pids=$(pgrep -f "fluent-bit" 2>/dev/null || true)
|
||||
if [[ -n "$remaining_pids" ]]; then
|
||||
echo "[WARNING] Processes unresponsive, force killing..."
|
||||
for pid in $remaining_pids; do
|
||||
echo "[INFO] Force killing process PID: $pid"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# 最终检查
|
||||
if pgrep -f "fluent-bit" > /dev/null; then
|
||||
echo "[ERROR] Unable to stop all fluent-bit processes"
|
||||
else
|
||||
echo "[SUCCESS] All Fluent Bit processes stopped"
|
||||
stopped=true
|
||||
fi
|
||||
else
|
||||
echo "[INFO] No Fluent Bit processes running"
|
||||
fi
|
||||
|
||||
if [[ "$stopped" == "false" ]]; then
|
||||
echo "[WARNING] No Fluent Bit processes found to stop"
|
||||
fi
|
||||
|
||||
# 卸载 Fluent Bit 包
|
||||
echo "[INFO] Uninstalling Fluent Bit package..."
|
||||
if dpkg -l | grep -q "fluent-bit"; then
|
||||
echo "[INFO] Found fluent-bit package installed via dpkg, uninstalling..."
|
||||
dpkg --remove --force-remove-reinstreq fluent-bit || true
|
||||
echo "[SUCCESS] Fluent Bit package uninstalled"
|
||||
else
|
||||
echo "[INFO] No fluent-bit package found via package manager"
|
||||
fi
|
||||
|
||||
# 删除二进制文件
|
||||
echo "[INFO] Removing Fluent Bit binary files..."
|
||||
binary_dir="/opt/fluent-bit"
|
||||
if [[ -d "$binary_dir" ]]; then
|
||||
rm -rf "$binary_dir"
|
||||
echo "[SUCCESS] Binary directory removed: $binary_dir"
|
||||
else
|
||||
echo "[INFO] Binary directory does not exist"
|
||||
fi
|
||||
|
||||
# 删除配置文件
|
||||
echo "[INFO] Removing configuration files..."
|
||||
config_dir="/etc/fluent-bit"
|
||||
if [[ -d "$config_dir" ]]; then
|
||||
rm -rf "$config_dir"
|
||||
echo "[SUCCESS] Configuration directory removed"
|
||||
else
|
||||
echo "[INFO] Configuration directory does not exist"
|
||||
fi
|
||||
|
||||
# 删除数据目录
|
||||
echo "[INFO] Removing data directories..."
|
||||
data_dirs=("/logs" "/buffers")
|
||||
deleted=false
|
||||
for data_dir in "${data_dirs[@]}"; do
|
||||
if [[ -d "$data_dir" ]]; then
|
||||
rm -rf "$data_dir"
|
||||
echo "[SUCCESS] Data directory removed: $data_dir"
|
||||
deleted=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$deleted" == "false" ]]; then
|
||||
echo "[INFO] No data directories found"
|
||||
fi
|
||||
|
||||
# 清理安装记录
|
||||
echo "[INFO] Cleaning up installation record..."
|
||||
if [[ -f "$install_record" ]]; then
|
||||
# 从安装记录中移除 fluent-bit 条目
|
||||
sed -i '/^fluent-bit:/d' "$install_record"
|
||||
echo "[SUCCESS] Installation record cleaned"
|
||||
else
|
||||
echo "[INFO] Installation record file does not exist"
|
||||
fi
|
||||
|
||||
# 检查用户状态
|
||||
echo "[INFO] Checking fluent-bit user status..."
|
||||
if id "fluent-bit" &>/dev/null; then
|
||||
echo "[INFO] fluent-bit user exists"
|
||||
echo "[WARNING] fluent-bit is a system user, may be used by other services"
|
||||
echo "[INFO] fluent-bit user will be preserved for system stability"
|
||||
echo "[INFO] To manually remove, run: sudo userdel fluent-bit"
|
||||
else
|
||||
echo "[INFO] fluent-bit user does not exist"
|
||||
fi
|
||||
|
||||
echo "[SUCCESS] Fluent Bit uninstallation completed!"
|
||||
echo
|
||||
echo "Removed content:"
|
||||
echo " - Binary directory: /opt/fluent-bit"
|
||||
echo " - Configuration directory: /etc/fluent-bit"
|
||||
echo " - Application log directory: /logs"
|
||||
echo " - Buffer directory: /buffers"
|
||||
echo
|
||||
echo "Note:"
|
||||
echo " - fluent-bit user preserved (system user, may be used by other services)"
|
||||
echo " - For complete cleanup, manually check and remove related files"
|
||||
Binary file not shown.
@@ -1,55 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Node Exporter 健康检查脚本
|
||||
# 输出 JSON 格式结果
|
||||
|
||||
set -e
|
||||
|
||||
# 检查 Node Exporter 健康状态
|
||||
check_health() {
|
||||
local url="http://localhost:9100"
|
||||
local metrics_url="$url/metrics"
|
||||
local name="node-exporter"
|
||||
local status="unhealth"
|
||||
local reason=""
|
||||
|
||||
# 检查 curl 是否可用
|
||||
if ! command -v curl &> /dev/null; then
|
||||
reason="curl 命令不可用,无法进行健康检查"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试根路径连接
|
||||
local http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$http_code" == "200" ]]; then
|
||||
# 测试 metrics 端点
|
||||
local metrics_code=$(curl -s -o /dev/null -w "%{http_code}" "$metrics_url" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$metrics_code" == "200" ]]; then
|
||||
status="health"
|
||||
reason="success"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 0
|
||||
else
|
||||
reason="Metrics 端点异常 (HTTP $metrics_code)"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
reason="HTTP 服务异常 (HTTP $http_code),请检查 Node Exporter 是否正在运行在端口 9100"
|
||||
echo "{\"name\": \"$name\", \"status\": \"$status\", \"reason\": \"$reason\"}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
check_health
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -1,343 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 更新安装记录
|
||||
update_install_record() {
|
||||
local pid="$1"
|
||||
# 使用传入的安装目录参数,如果没有则使用默认值
|
||||
local install_base_dir="${2:-/opt/argus-metric/current}"
|
||||
local install_record="$install_base_dir/.install_record"
|
||||
|
||||
# 如果安装记录文件不存在,说明是首次安装,由主安装脚本统一创建
|
||||
if [[ ! -f "$install_record" ]]; then
|
||||
log_info "安装记录文件不存在,将由主安装脚本创建"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 如果文件存在,说明是重启场景,只更新 PID 字段
|
||||
if command -v jq &> /dev/null; then
|
||||
# 读取当前 PID
|
||||
local current_pid=$(jq -r '.components."node-exporter".pid // ""' "$install_record" 2>/dev/null)
|
||||
|
||||
if [[ -z "$current_pid" ]]; then
|
||||
log_warning "无法读取当前 PID,跳过更新"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 使用 jq 只更新 pid 字段,保持字符串类型,保留其他字段
|
||||
jq --arg new_pid "$pid" '.components."node-exporter".pid = $new_pid' "$install_record" > "$install_record.tmp" && mv "$install_record.tmp" "$install_record"
|
||||
log_info "PID 已更新: $current_pid -> $pid"
|
||||
else
|
||||
log_warning "jq 命令不可用,无法更新安装记录文件"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "Node Exporter 安装脚本"
|
||||
echo
|
||||
echo "用法: $0 [选项]"
|
||||
echo
|
||||
echo "选项:"
|
||||
echo " --help 显示此帮助信息"
|
||||
echo
|
||||
echo "示例:"
|
||||
echo " $0 # 安装 Node Exporter"
|
||||
echo
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
INSTALL_DIR=""
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# 如果参数不是以--开头,则认为是安装目录
|
||||
if [[ ! "$arg" =~ ^-- ]]; then
|
||||
INSTALL_DIR="$arg"
|
||||
else
|
||||
log_error "未知参数: $arg"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查是否为 root 用户
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要 root 权限运行"
|
||||
log_info "请使用: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查系统要求
|
||||
check_system() {
|
||||
log_info "检查系统要求..."
|
||||
|
||||
# 检查操作系统
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
log_error "无法检测操作系统版本"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source /etc/os-release
|
||||
log_info "检测到操作系统: $NAME $VERSION"
|
||||
|
||||
# 检查是否为 Linux 系统
|
||||
if [[ "$ID" != "ubuntu" && "$ID" != "debian" && "$ID" != "centos" && "$ID" != "rhel" && "$ID" != "fedora" ]]; then
|
||||
log_warning "此脚本主要针对常见 Linux 发行版,其他系统可能需要调整"
|
||||
fi
|
||||
|
||||
# 检查系统架构
|
||||
local arch=$(uname -m)
|
||||
log_info "系统架构: $arch"
|
||||
|
||||
if [[ "$arch" != "x86_64" && "$arch" != "amd64" ]]; then
|
||||
log_warning "当前架构为 $arch,node_exporter 主要支持 x86_64/amd64"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_existing_service() {
|
||||
log_info "检查并停止可能运行的 Node Exporter 服务..."
|
||||
|
||||
# 当前脚本 PID,防止误杀
|
||||
SELF_PID=$$
|
||||
|
||||
# 1. 停止 systemd 服务(如果存在)
|
||||
if systemctl list-units --full -all | grep -q "node_exporter.service"; then
|
||||
log_info "检测到 systemd 服务 node_exporter,正在停止..."
|
||||
systemctl stop node_exporter || true
|
||||
systemctl disable node_exporter || true
|
||||
fi
|
||||
|
||||
# 2. 清理可能存在的 PID 文件
|
||||
for pid_file in /var/run/node-exporter.pid /var/run/node_exporter.pid /tmp/node_exporter.pid; do
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "发现 Node Exporter (PID: $pid),正在停止..."
|
||||
kill "$pid"
|
||||
sleep 2
|
||||
kill -0 "$pid" 2>/dev/null && kill -9 "$pid"
|
||||
fi
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. 用 pgrep 查找进程,排除当前脚本
|
||||
local pids=$(pgrep -f "node_exporter|node-exporter|/usr/local/bin/node-exporter" | grep -vw "$SELF_PID" || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
log_info "发现 Node Exporter 进程 (PID: $pids),正在停止..."
|
||||
for pid in $pids; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 4. 兜底:检查是否有进程占用 9100 端口
|
||||
local listen_pids=$(lsof -ti:9100 2>/dev/null || true)
|
||||
if [[ -n "$listen_pids" ]]; then
|
||||
log_warning "发现占用 9100 端口的进程 (PID: $listen_pids),强制终止..."
|
||||
for pid in $listen_pids; do
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# 5. 最终验证
|
||||
if netstat -tuln 2>/dev/null | grep -q ":9100 "; then
|
||||
log_error "端口 9100 仍被占用,请手动检查"
|
||||
return 1
|
||||
else
|
||||
log_success "旧的 Node Exporter 已完全停止"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# 安装 Node Exporter 二进制文件
|
||||
install_node_exporter() {
|
||||
log_info "安装 Node Exporter..."
|
||||
|
||||
local binary_file="bin/node_exporter"
|
||||
local install_dir="/usr/local/bin"
|
||||
|
||||
if [[ ! -f "$binary_file" ]]; then
|
||||
log_error "找不到 Node Exporter 二进制文件: $binary_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 停止可能运行的服务
|
||||
stop_existing_service
|
||||
|
||||
# 复制二进制文件并重命名为统一格式
|
||||
cp "$binary_file" "$install_dir/node-exporter"
|
||||
chmod +x "$install_dir/node-exporter"
|
||||
|
||||
log_success "Node Exporter 二进制文件安装完成"
|
||||
}
|
||||
|
||||
# 创建用户和组
|
||||
create_user() {
|
||||
log_info "创建 node_exporter 用户..."
|
||||
|
||||
# 检查用户是否已存在
|
||||
if id "node_exporter" &>/dev/null; then
|
||||
log_info "用户 node_exporter 已存在"
|
||||
else
|
||||
useradd --no-create-home --shell /bin/false node_exporter
|
||||
log_success "用户 node_exporter 创建完成"
|
||||
fi
|
||||
}
|
||||
|
||||
# 安装配置文件
|
||||
install_config() {
|
||||
log_info "安装配置文件..."
|
||||
|
||||
local config_dir="/etc/node_exporter"
|
||||
|
||||
# 创建配置目录
|
||||
mkdir -p "$config_dir"
|
||||
|
||||
# 创建文本文件收集器目录
|
||||
mkdir -p "/var/lib/node_exporter/textfile_collector"
|
||||
chown node_exporter:node_exporter "/var/lib/node_exporter/textfile_collector"
|
||||
}
|
||||
|
||||
# 启动 Node Exporter 服务
|
||||
start_node_exporter() {
|
||||
log_info "启动 Node Exporter 服务..."
|
||||
|
||||
local binary_path="/usr/local/bin/node-exporter"
|
||||
local log_file="/var/log/node-exporter.log"
|
||||
local pid_file="/var/run/node-exporter.pid"
|
||||
|
||||
# 检查服务是否已经在运行
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "Node Exporter 服务已在运行 (PID: $pid)"
|
||||
return 0
|
||||
else
|
||||
log_warning "发现过期的 PID 文件,正在清理..."
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查端口是否被占用
|
||||
if netstat -tuln 2>/dev/null | grep -q ":9100 "; then
|
||||
log_warning "端口 9100 已被占用,请检查是否有其他服务在运行"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
log_info "正在启动 Node Exporter..."
|
||||
nohup "$binary_path" --web.listen-address=:9100 > "$log_file" 2>&1 &
|
||||
local pid=$!
|
||||
|
||||
# 保存 PID
|
||||
echo "$pid" > "$pid_file"
|
||||
|
||||
# 等待服务启动
|
||||
sleep 2
|
||||
|
||||
# 检查服务是否成功启动
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_success "Node Exporter 服务启动成功 (PID: $pid)"
|
||||
log_info "日志文件: $log_file"
|
||||
log_info "PID 文件: $pid_file"
|
||||
|
||||
# 更新安装记录
|
||||
update_install_record "$pid" "$INSTALL_DIR"
|
||||
else
|
||||
log_error "Node Exporter 服务启动失败"
|
||||
rm -f "$pid_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# 显示安装信息
|
||||
show_install_info() {
|
||||
log_success "Node Exporter 安装完成!"
|
||||
echo
|
||||
echo "安装信息:"
|
||||
echo " 二进制文件: /usr/local/bin/node-exporter"
|
||||
echo " 运行用户: node_exporter"
|
||||
echo " 配置目录: /etc/node_exporter/"
|
||||
echo " 默认端口: 9100"
|
||||
echo
|
||||
echo "使用方法:"
|
||||
echo " 手动启动: /usr/local/bin/node-exporter --web.listen-address=:9100"
|
||||
echo " 后台启动: nohup /usr/local/bin/node-exporter --web.listen-address=:9100 &"
|
||||
echo
|
||||
echo "测试连接:"
|
||||
echo " curl http://localhost:9100/metrics"
|
||||
echo " curl http://localhost:9100"
|
||||
echo
|
||||
echo "Prometheus 配置示例:"
|
||||
echo " - job_name: 'node_exporter'"
|
||||
echo " static_configs:"
|
||||
echo " - targets: ['localhost:9100']"
|
||||
echo
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " Node Exporter 安装脚本 v1.0"
|
||||
echo "=========================================="
|
||||
echo
|
||||
|
||||
check_root
|
||||
check_system
|
||||
|
||||
log_info "开始安装 Node Exporter..."
|
||||
|
||||
install_node_exporter
|
||||
create_user
|
||||
install_config
|
||||
start_node_exporter
|
||||
|
||||
show_install_info
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
# 获取当前目录
|
||||
CURRENT_DIR=$(pwd)
|
||||
PACKAGE_NAME="node-exporter-$(date +%Y%m%d-%H%M%S)"
|
||||
PACKAGE_FILE="${PACKAGE_NAME}.tar.gz"
|
||||
|
||||
log_info "开始打包 Node Exporter 安装包..."
|
||||
|
||||
# 检查必要文件
|
||||
log_info "检查必要文件..."
|
||||
|
||||
required_files=(
|
||||
"install.sh"
|
||||
"uninstall.sh"
|
||||
"bin/node_exporter"
|
||||
"check_health.sh"
|
||||
)
|
||||
|
||||
missing_files=()
|
||||
for file in "${required_files[@]}"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
missing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_files[@]} -gt 0 ]]; then
|
||||
echo "缺少以下文件:"
|
||||
for file in "${missing_files[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "所有必要文件检查完成"
|
||||
|
||||
# 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
log_info "创建临时目录: $TEMP_DIR"
|
||||
|
||||
# 复制文件到临时目录
|
||||
cp -r . "$TEMP_DIR/$PACKAGE_NAME"
|
||||
|
||||
# 进入临时目录
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# 创建压缩包
|
||||
log_info "创建压缩包: $PACKAGE_FILE"
|
||||
tar -czf "$PACKAGE_FILE" "$PACKAGE_NAME"
|
||||
|
||||
# 移动压缩包到原目录
|
||||
mv "$PACKAGE_FILE" "$CURRENT_DIR/"
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# 返回原目录
|
||||
cd "$CURRENT_DIR"
|
||||
|
||||
# 显示结果
|
||||
log_success "打包完成!"
|
||||
echo
|
||||
echo "安装包文件: $PACKAGE_FILE"
|
||||
echo "文件大小: $(du -h "$PACKAGE_FILE" | cut -f1)"
|
||||
echo
|
||||
echo "使用方法:"
|
||||
echo "1. 将 $PACKAGE_FILE 传输到目标服务器"
|
||||
echo "2. 解压: tar -xzf $PACKAGE_FILE"
|
||||
echo "3. 进入目录: cd $PACKAGE_NAME"
|
||||
echo "4. 运行安装: sudo ./install.sh"
|
||||
echo
|
||||
echo "注意: 请确保所有必要文件都存在"
|
||||
@@ -1,239 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Node Exporter 卸载脚本
|
||||
# 版本: 1.0
|
||||
# 作者: AIOps Team
|
||||
# 日期: $(date +%Y-%m-%d)
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查是否为 root 用户
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要 root 权限运行"
|
||||
log_info "请使用: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止运行中的进程
|
||||
stop_processes() {
|
||||
log_info "停止 Node Exporter 进程..."
|
||||
|
||||
local pid_file="/var/run/node-exporter.pid"
|
||||
local stopped=false
|
||||
|
||||
# 首先尝试通过 PID 文件停止服务
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "通过 PID 文件停止服务 (PID: $pid)..."
|
||||
kill "$pid"
|
||||
sleep 3
|
||||
|
||||
# 检查进程是否已停止
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
log_warning "进程未响应,强制终止..."
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
log_success "Node Exporter 进程已停止"
|
||||
stopped=true
|
||||
else
|
||||
log_warning "PID 文件存在但进程已不存在,清理 PID 文件"
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 查找并杀死所有 node_exporter 和 node-exporter 进程
|
||||
local pids=$(pgrep -f "node_exporter\|node-exporter" 2>/dev/null || true)
|
||||
if [[ -n "$pids" ]]; then
|
||||
log_info "发现 node_exporter 或 node-exporter 进程,正在停止..."
|
||||
for pid in $pids; do
|
||||
log_info "停止进程 PID: $pid"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 2
|
||||
|
||||
# 检查是否还有进程在运行,如果有则强制终止
|
||||
local remaining_pids=$(pgrep -f "node_exporter\|node-exporter" 2>/dev/null || true)
|
||||
if [[ -n "$remaining_pids" ]]; then
|
||||
log_warning "进程未响应,强制终止..."
|
||||
for pid in $remaining_pids; do
|
||||
log_info "强制终止进程 PID: $pid"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# 最终检查
|
||||
if pgrep -f "node_exporter\|node-exporter" > /dev/null; then
|
||||
log_error "无法停止所有 node_exporter 进程"
|
||||
else
|
||||
log_success "所有 Node Exporter 进程已停止"
|
||||
stopped=true
|
||||
fi
|
||||
else
|
||||
log_info "Node Exporter 进程未运行"
|
||||
fi
|
||||
|
||||
# 清理 PID 文件
|
||||
rm -f "$pid_file"
|
||||
|
||||
if [[ "$stopped" == "false" ]]; then
|
||||
log_warning "未发现需要停止的 Node Exporter 进程"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除二进制文件
|
||||
remove_binary() {
|
||||
log_info "删除 Node Exporter 二进制文件..."
|
||||
|
||||
local binary_files=(
|
||||
"/usr/local/bin/node-exporter"
|
||||
"/usr/local/bin/node_exporter"
|
||||
)
|
||||
|
||||
local deleted=false
|
||||
for binary_file in "${binary_files[@]}"; do
|
||||
if [[ -f "$binary_file" ]]; then
|
||||
rm -f "$binary_file"
|
||||
log_success "二进制文件已删除: $binary_file"
|
||||
deleted=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$deleted" == "false" ]]; then
|
||||
log_info "二进制文件不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除配置文件
|
||||
remove_config() {
|
||||
log_info "删除配置文件..."
|
||||
|
||||
local config_dir="/etc/node_exporter"
|
||||
|
||||
if [[ -d "$config_dir" ]]; then
|
||||
rm -rf "$config_dir"
|
||||
log_success "配置目录已删除"
|
||||
else
|
||||
log_info "配置目录不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除数据目录
|
||||
remove_data_dir() {
|
||||
log_info "删除数据目录..."
|
||||
|
||||
local data_dir="/var/lib/node_exporter"
|
||||
|
||||
if [[ -d "$data_dir" ]]; then
|
||||
rm -rf "$data_dir"
|
||||
log_success "数据目录已删除"
|
||||
else
|
||||
log_info "数据目录不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查用户状态(可选)
|
||||
check_user_status() {
|
||||
log_info "检查 node_exporter 用户状态..."
|
||||
|
||||
if id "node_exporter" &>/dev/null; then
|
||||
log_info "检测到 node_exporter 用户存在"
|
||||
log_warning "node_exporter 是系统用户,可能被其他服务使用"
|
||||
log_info "为了系统稳定性,将保留 node_exporter 用户"
|
||||
log_info "如需手动删除,请运行: sudo userdel node_exporter"
|
||||
else
|
||||
log_info "node_exporter 用户不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理日志文件
|
||||
cleanup_logs() {
|
||||
log_info "清理日志文件..."
|
||||
|
||||
# 清理 journal 日志
|
||||
journalctl --vacuum-time=1s --quiet || true
|
||||
|
||||
# 删除安装脚本创建的日志文件
|
||||
rm -f /var/log/node-exporter.log
|
||||
|
||||
log_success "日志文件已清理"
|
||||
}
|
||||
|
||||
# 显示卸载信息
|
||||
show_uninstall_info() {
|
||||
log_success "Node Exporter 卸载完成!"
|
||||
echo
|
||||
echo "已删除的内容:"
|
||||
echo " - 二进制文件: /usr/local/bin/node-exporter"
|
||||
echo " - 配置目录: /etc/node_exporter"
|
||||
echo " - 数据目录: /var/lib/node_exporter"
|
||||
echo " - 相关日志文件"
|
||||
echo
|
||||
echo "注意:"
|
||||
echo " - node_exporter 用户已保留(系统用户,可能被其他服务使用)"
|
||||
echo " - 如需完全清理,请手动检查并删除相关文件"
|
||||
echo
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " Node Exporter 卸载脚本 v1.0"
|
||||
echo "=========================================="
|
||||
echo
|
||||
|
||||
check_root
|
||||
|
||||
log_warning "此操作将完全卸载 Node Exporter"
|
||||
read -p "确认继续?(y/N): " confirm
|
||||
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
log_info "取消卸载操作"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_info "开始卸载 Node Exporter..."
|
||||
|
||||
stop_processes
|
||||
remove_binary
|
||||
remove_config
|
||||
remove_data_dir
|
||||
cleanup_logs
|
||||
|
||||
# 检查用户状态
|
||||
check_user_status
|
||||
|
||||
show_uninstall_info
|
||||
}
|
||||
|
||||
# 脚本入口
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user