通过OpenTelemetry eBPF Instrumentation(OBI)提供的无侵入监控能力上报链路数据至ARMS后,ARMS即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OBI为应用进行监控并上报数据。
前提条件
如果您在使用OpenTelemetry eBPF过程中有任何问题,欢迎通过钉钉答疑群(群号:159215000379)与我们联系。
已准备一台满足以下条件的 Linux 服务器(云服务器 ECS 或本地环境均可)。
要求
支持范围
内核版本
Linux 5.8 及以上。RHEL 系发行版(带有 eBPF 回移补丁)最低支持 Linux 4.18。
BTF
内核必须暴露 BTF(BPF Type Format)信息。
CPU 架构
amd64(x86_64)、arm64(aarch64)。
权限
Root 权限。
说明阿里云 Alibaba Cloud Linux 3 和 Alibaba Cloud Linux 4 默认满足以上要求。
如果需要确认服务器是否满足条件,运行以下命令逐项检查:
# 查看内核版本(需 >= 5.8,RHEL 系 >= 4.18) uname -r # 查看 CPU 架构(需为 amd64 或 arm64) uname -m # 检查 BTF 是否可用(文件存在即支持) ls /sys/kernel/btf/vmlinux # 检查是否有 root 权限 whoami
背景信息
OBI(OpenTelemetry eBPF Instrumentation)是一款基于 eBPF 技术的 Kubernetes 可观测性自动插桩组件。它以 DaemonSet 方式部署在集群节点上,无需修改应用代码或注入 Sidecar,即可自动采集应用的 Metrics 和 Traces 数据。OBI 支持 HTTP、gRPC、SQL、Redis、Kafka 等主流协议的自动探测,并针对 GenAI 场景(OpenAI、Anthropic、通义千问、MCP 、Embedding、Rerank等)提供了请求/响应体的深度提取能力。采集的遥测数据通过 OTLP 协议导出,可无缝对接 ARMS 等可观测性平台,帮助用户实现零侵入、低开销的全链路可观测。
应用发现匹配方式
OBI 通过 discovery.instrument 配置段发现和匹配要监控的进程,支持以下匹配方式(所有条件为 AND 逻辑,全部满足才会匹配):
YAML 字段 | 类型 | 说明 | 示例 |
| 端口号/范围 | 进程监听的端口号,支持逗号分隔和范围 |
|
| Glob 通配符 | 匹配进程可执行文件完整路径 |
|
| Glob 通配符 | 匹配进程命令行参数(不含可执行文件名) |
|
| Glob 通配符 | 按编程语言自动匹配(go/java/python/ruby/nodejs/rust/dotnet/php/cpp) |
|
| PID 列表 | 直接指定进程 PID |
|
| Glob 通配符 | 匹配 OCI 容器名称(适用于 Docker 部署) |
|
| 布尔值 | 仅匹配容器内进程 |
|
| 字符串 | 在ARMS控制台显示的应用名称 |
|
| 字符串 | 服务命名空间 |
|
| 信号列表 | 控制导出的信号类型(metrics/traces/logs),空数组关闭全部 |
|
| 采样配置 | 按比例采样 Traces,减少数据量 |
|
常见场景配置示例
场景1:按端口号匹配 — 最简单,适用于任何类型的 Web 应用
discovery:
instrument:
- name: my-web-app
open_ports: 8080场景2:Java 应用(exe_path + cmd_args) — 通过 java 进程 + jar 包名定位
discovery:
instrument:
- name: my-java-app
exe_path: "*/java"
cmd_args: "*petclinic*"场景3:按编程语言自动发现 — 自动检测指定语言的所有进程
discovery:
instrument:
- name: go-services
languages: go
- name: python-services
languages: "{python,nodejs}"场景4:按可执行文件路径匹配 — 适用于自编译的二进制
discovery:
instrument:
- name: my-binary
exe_path: "*/my-server"场景5:Docker 容器名匹配 — 适用于 ECS 上跑 Docker 的场景
discovery:
instrument:
- name: my-container-app
container_name: "my-container-*"场景6:多端口范围 — 监控一批端口范围内的服务
discovery:
instrument:
- name: microservices
open_ports: 8080-8090,9090场景7:指定 PID 临时调试 — 直接指定进程 PID 进行监控
discovery:
instrument:
- name: debug-target
target_pids: [12345]场景8:仅容器进程 + 采样 — 只监控容器内进程且对 Traces 采样 50%
discovery:
instrument:
- name: container-apps
containers_only: true
open_ports: 8080
sampler:
name: traceidratio
arg: "0.5"场景9:多应用 + 排除规则 — 监控多个应用,排除特定进程
discovery:
instrument:
- name: api-gateway
open_ports: 80,443
- name: backend-java
exe_path: "*/java"
cmd_args: "*backend*"
- name: worker
exe_path: "*/python*"
cmd_args: "*celery*"
exclude_instrument:
- exe_path: "{obi,prometheus,otelcol*}"场景10:控制导出信号 — 只导出 Metrics 不导出 Traces
discovery:
instrument:
- name: high-qps-app
open_ports: 8080
exports: [metrics]操作步骤
操作流程总览:安装部署(获取接入点信息 → 下载 OBI → 编写配置文件 → 部署服务) → 验证监控数据
安装部署
提供一键脚本部署(推荐)和手动分步部署方式。
方式一:一键脚本部署(推荐)
已将所有步骤合并为一个 obi-setup.sh 脚本,支持交互式和非交互式两种模式。
#!/bin/bash
# ============================================================================
# OBI (OpenTelemetry eBPF Instrumentation) 一键部署脚本
# 合并了环境检查、下载、配置生成、服务安装的完整流程
#
# 用法:
# sudo bash obi-setup.sh install — 交互式安装(二进制 + systemd)
# sudo bash obi-setup.sh docker — Docker 方式部署
# sudo bash obi-setup.sh check — 仅做环境检查
# sudo bash obi-setup.sh uninstall — 卸载 OBI 服务
# sudo bash obi-setup.sh status — 查看运行状态与日志
#
# 非交互式部署(环境变量):
# export OTLP_ENDPOINT="http://xxx:4318"
# export OTLP_HEADERS="Authentication=xxx"
# export APP_NAME="my-app"
# export APP_MATCH_TYPE="port" # port|cmd|exe|lang|container|pid
# export APP_MATCH_VALUE="8080"
# sudo -E bash obi-setup.sh install
# ============================================================================
set -euo pipefail
# ========================= 全局变量 =========================
VERSION="0.9.5"
SERVICE_NAME="obi"
INSTALL_DIR="/opt/obi"
CONFIG_FILE="${INSTALL_DIR}/config.yaml"
OBI_BINARY="${INSTALL_DIR}/obi"
DOCKER_IMAGE="registry.cn-hangzhou.aliyuncs.com/private-mesh/obi:${VERSION}"
OSS_BASE="https://arms-apm-cn-hangzhou-pre.oss-cn-hangzhou.aliyuncs.com/obi-agent/${VERSION}"
# 颜色
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
# 应用列表(数组)
declare -a APP_ENTRIES=()
# 排除规则
EXCLUDE_PATTERN=""
# ========================= 工具函数 =========================
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
fail() { echo -e "${RED}[FAIL]${NC} $*"; }
die() { fail "$*"; exit 1; }
prompt_input() {
local var_name="$1" prompt_msg="$2" default_val="${3:-}"
local current_val="${!var_name:-}"
if [[ -n "$current_val" ]]; then
info "使用环境变量 ${var_name}=${current_val}"
return
fi
if [[ -n "$default_val" ]]; then
read -rp "$(echo -e "${BOLD}${prompt_msg} [${default_val}]: ${NC}")" input
eval "$var_name=\"${input:-$default_val}\""
else
while true; do
read -rp "$(echo -e "${BOLD}${prompt_msg}: ${NC}")" input
if [[ -n "$input" ]]; then
eval "$var_name=\"$input\""
return
fi
warn "此项为必填"
done
fi
}
prompt_yesno() {
local prompt_msg="$1" default="${2:-n}"
local yn
read -rp "$(echo -e "${BOLD}${prompt_msg} [y/N]: ${NC}")" yn
case "${yn:-$default}" in
[Yy]*) return 0 ;;
*) return 1 ;;
esac
}
# ========================= 环境检查 =========================
check_root() {
if [[ "$EUID" -ne 0 ]]; then
die "请使用 root 权限运行: sudo bash $0 $*"
fi
ok "Root 权限"
}
check_env() {
echo ""
echo -e "${BOLD}========== 环境检查 ==========${NC}"
local kernel_ver major minor
kernel_ver=$(uname -r)
major=$(echo "$kernel_ver" | cut -d. -f1)
minor=$(echo "$kernel_ver" | cut -d. -f2)
local is_rhel=false
if [[ -f /etc/redhat-release ]] || grep -qi 'rhel\|centos\|rocky\|alma\|anolis\|alinux' /etc/os-release 2>/dev/null; then
is_rhel=true
fi
if [[ "$major" -gt 5 ]] || { [[ "$major" -eq 5 ]] && [[ "$minor" -ge 8 ]]; }; then
ok "内核版本: ${kernel_ver} (>= 5.8)"
elif $is_rhel && { [[ "$major" -gt 4 ]] || { [[ "$major" -eq 4 ]] && [[ "$minor" -ge 18 ]]; }; }; then
ok "内核版本: ${kernel_ver} (RHEL 系, >= 4.18)"
else
fail "内核版本: ${kernel_ver} (最低要求 5.8, RHEL 系 4.18)"
return 1
fi
local arch
arch=$(uname -m)
case "$arch" in
x86_64|amd64) ok "CPU 架构: ${arch} (amd64)" ;;
aarch64|arm64) ok "CPU 架构: ${arch} (arm64)" ;;
*) fail "CPU 架构: ${arch} (不支持)"; return 1 ;;
esac
if [[ -f /sys/kernel/btf/vmlinux ]]; then
ok "BTF: 可用"
else
fail "BTF: /sys/kernel/btf/vmlinux 不存在"
return 1
fi
echo -e "${GREEN}========== 环境检查通过 ==========${NC}"
echo ""
return 0
}
detect_arch() {
local arch
arch=$(uname -m)
case "$arch" in
x86_64|amd64) echo "amd64" ;;
aarch64|arm64) echo "arm64" ;;
*) die "不支持的 CPU 架构: ${arch}" ;;
esac
}
# ========================= 单个应用配置收集 =========================
collect_one_app() {
local app_name match_type
local entry=""
prompt_input app_name "应用名称 (ARMS 控制台显示名)" "my-app"
echo ""
echo -e " ${BOLD}选择应用匹配方式:${NC}"
echo -e " 1) 端口号匹配 — open_ports, 如 8080 或 80,443 或 8000-8999"
echo -e " 2) 命令行匹配 — exe_path + cmd_args, 适合 java -jar 等场景"
echo -e " 3) 可执行文件路径匹配 — exe_path, 适合自编译二进制"
echo -e " 4) 编程语言自动发现 — languages, 自动检测 go/java/python/nodejs 等"
echo -e " 5) Docker 容器名匹配 — container_name, 适合 ECS 上跑 Docker"
echo -e " 6) PID 指定 — target_pids, 临时调试用"
read -rp "$(echo -e "${BOLD}选择 [1-6, 默认1]: ${NC}")" match_choice
entry=" - name: ${app_name}"
case "${match_choice:-1}" in
1)
local ports
prompt_input ports "监听端口号 (如 8080, 80,443, 8000-8999)" "8080"
entry="${entry}
open_ports: ${ports}"
;;
2)
local exe_path cmd_args
prompt_input exe_path "可执行文件路径 Glob (如 */java)" "*/java"
prompt_input cmd_args "命令行参数 Glob (如 *petclinic*)"
entry="${entry}
exe_path: \"${exe_path}\"
cmd_args: \"${cmd_args}\""
;;
3)
local exe_path
prompt_input exe_path "可执行文件路径 Glob (如 */my-server, */nginx)"
entry="${entry}
exe_path: \"${exe_path}\""
;;
4)
local lang
echo -e " 支持的语言: go, java, python, ruby, nodejs, rust, dotnet, php, cpp"
echo -e " 多语言用花括号: {go,rust} 或 {python,nodejs}"
prompt_input lang "编程语言" "java"
entry="${entry}
languages: \"${lang}\""
;;
5)
local cname
prompt_input cname "Docker 容器名 Glob (如 my-container-*)"
entry="${entry}
container_name: \"${cname}\""
;;
6)
local pids
prompt_input pids "PID 列表, 逗号分隔 (如 12345,67890)"
local pid_array
pid_array=$(echo "$pids" | tr ',' '\n' | sed 's/^[[:space:]]*//' | awk '{printf " - %s\n", $0}')
entry="${entry}
target_pids:
${pid_array}"
;;
*)
local ports
prompt_input ports "监听端口号" "8080"
entry="${entry}
open_ports: ${ports}"
;;
esac
# 可选:仅容器进程
if [[ "${match_choice:-1}" != "5" ]]; then
if prompt_yesno "是否仅匹配容器内进程 (containers_only)?"; then
entry="${entry}
containers_only: true"
fi
fi
# 可选:采样
if prompt_yesno "是否配置 Traces 采样 (减少数据量)?"; then
echo -e " 采样器: always_on, always_off, traceidratio, parentbased_traceidratio"
local sampler_name sampler_arg
prompt_input sampler_name "采样器名称" "traceidratio"
prompt_input sampler_arg "采样参数 (如 0.5 表示采样50%)" "1.0"
entry="${entry}
sampler:
name: ${sampler_name}
arg: \"${sampler_arg}\""
fi
APP_ENTRIES+=("$entry")
ok "已添加应用: ${app_name}"
}
# ========================= 收集配置(完整) =========================
collect_config() {
echo -e "${BOLD}========== 配置信息 ==========${NC}"
echo ""
info "请从 ARMS 接入中心获取以下信息:"
info " ARMS 控制台 -> 接入中心 -> OpenTelemetry 卡片 -> 点击获取 LicenseKey"
echo ""
prompt_input OTLP_ENDPOINT "OTEL_EXPORTER_OTLP_ENDPOINT (如 http://xxx:4318)"
prompt_input OTLP_HEADERS "OTEL_EXPORTER_OTLP_HEADERS (如 Authentication=xxx)"
echo ""
echo -e "${BOLD}========== 应用配置 ==========${NC}"
# 非交互模式:从环境变量构建单个 app
if [[ -n "${APP_MATCH_TYPE:-}" ]]; then
local entry=" - name: ${APP_NAME:-my-app}"
case "$APP_MATCH_TYPE" in
port)
entry="${entry}
open_ports: ${APP_MATCH_VALUE:-8080}" ;;
cmd)
entry="${entry}
exe_path: \"${APP_EXE_PATH:-*/java}\"
cmd_args: \"${APP_CMD_ARGS}\"" ;;
exe)
entry="${entry}
exe_path: \"${APP_MATCH_VALUE}\"" ;;
lang)
entry="${entry}
languages: \"${APP_MATCH_VALUE}\"" ;;
container)
entry="${entry}
container_name: \"${APP_MATCH_VALUE}\"" ;;
pid)
local pid_array
pid_array=$(echo "${APP_MATCH_VALUE}" | tr ',' '\n' | sed 's/^[[:space:]]*//' | awk '{printf " - %s\n", $0}')
entry="${entry}
target_pids:
${pid_array}" ;;
*)
die "未知的 APP_MATCH_TYPE: ${APP_MATCH_TYPE} (支持: port|cmd|exe|lang|container|pid)" ;;
esac
APP_ENTRIES+=("$entry")
ok "已添加应用: ${APP_NAME:-my-app} (${APP_MATCH_TYPE})"
return
fi
# 交互模式:循环添加应用
while true; do
echo ""
collect_one_app
echo ""
if ! prompt_yesno "是否继续添加另一个应用?"; then
break
fi
done
# 可选:排除规则
echo ""
if prompt_yesno "是否配置排除规则 (exclude_instrument)?"; then
echo -e " 输入要排除的可执行文件路径 Glob"
echo -e " 示例: {obi,prometheus,otelcol*,node_exporter}"
local exclude_val
prompt_input exclude_val "排除路径 Glob"
EXCLUDE_PATTERN="$exclude_val"
ok "已添加排除规则: ${exclude_val}"
fi
}
# ========================= 生成配置文件 =========================
generate_config() {
local endpoint_metrics="${OTLP_ENDPOINT}/v1/metrics"
# 拼接 instrument 段
local instrument_yaml=""
for entry in "${APP_ENTRIES[@]}"; do
instrument_yaml="${instrument_yaml}
${entry}"
done
# 拼接 exclude 段
local exclude_yaml=""
if [[ -n "$EXCLUDE_PATTERN" ]]; then
exclude_yaml="
exclude_instrument:
- exe_path: \"${EXCLUDE_PATTERN}\""
fi
cat > "${CONFIG_FILE}" <<YAML
# OBI 配置文件 - 由 obi-setup.sh 自动生成
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
log_level: WARN
# ========== Metrics 导出 ==========
otel_metrics_export:
endpoint: ${endpoint_metrics}
metrics:
features:
- application
- network
- stats_tcp_rtt
routes:
unmatched: heuristic
# ========== eBPF 探针配置 ==========
ebpf:
context_propagation: all
track_request_headers: true
payload_extraction:
http:
genai:
openai:
enabled: true
anthropic:
enabled: true
jsonrpc:
enabled: true
mcp:
enabled: true
qwen:
enabled: true
rerank:
enabled: true
embedding:
enabled: true
buffer_sizes:
http: 8192
# ========== Traces 导出 ==========
otel_traces_export:
endpoint: ${OTLP_ENDPOINT}
instrumentations:
- http
- grpc
- sql
- redis
- kafka
- mqtt
- mongo
- couchbase
- memcached
- genai
javaagent:
enabled: false
# ========== 应用发现规则 ==========
discovery:
instrument:${instrument_yaml}${exclude_yaml}
YAML
ok "配置文件已生成: ${CONFIG_FILE}"
}
# ========================= 下载 OBI =========================
download_obi() {
local arch
arch=$(detect_arch)
local url="${OSS_BASE}/obi-linux-${arch}"
info "下载 OBI v${VERSION} (${arch})..."
mkdir -p "${INSTALL_DIR}"
if command -v wget &>/dev/null; then
wget -q --show-progress "${url}" -O "${OBI_BINARY}"
elif command -v curl &>/dev/null; then
curl -fL --progress-bar "${url}" -o "${OBI_BINARY}"
else
die "wget 和 curl 均不可用"
fi
chmod +x "${OBI_BINARY}"
ok "OBI 已下载至 ${OBI_BINARY}"
}
# ========================= Systemd 服务 =========================
install_systemd() {
info "创建 systemd 服务..."
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
[Unit]
Description=OBI eBPF Instrumentation Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}
Environment="OTEL_EBPF_CONFIG_PATH=${CONFIG_FILE}"
Environment="OTEL_EXPORTER_OTLP_HEADERS=${OTLP_HEADERS}"
ExecStart=${OBI_BINARY}
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=${SERVICE_NAME}
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}" --quiet
ok "Systemd 服务已创建并设为开机自启"
}
start_service() {
info "启动 OBI 服务..."
systemctl start "${SERVICE_NAME}"
sleep 2
if systemctl is-active --quiet "${SERVICE_NAME}"; then
ok "OBI 服务启动成功!"
else
fail "OBI 服务启动失败, 错误日志如下:"
journalctl -u "${SERVICE_NAME}" -n 30 --no-pager
exit 1
fi
}
# ========================= Docker 部署 =========================
deploy_docker() {
if ! command -v docker &>/dev/null; then
die "Docker 未安装"
fi
collect_config
mkdir -p "${INSTALL_DIR}"
generate_config
info "拉取 Docker 镜像: ${DOCKER_IMAGE}"
docker pull "${DOCKER_IMAGE}"
if docker ps -a --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
warn "移除已有容器..."
docker rm -f "${SERVICE_NAME}" >/dev/null 2>&1
fi
info "启动 OBI 容器..."
docker run -d \
--name "${SERVICE_NAME}" \
--pid=host \
--privileged \
--restart=always \
-v "${CONFIG_FILE}:/config/config.yaml:ro" \
-e "OTEL_EXPORTER_OTLP_HEADERS=${OTLP_HEADERS}" \
-e "OTEL_EXPORTER_OTLP_ENDPOINT=${OTLP_ENDPOINT}" \
"${DOCKER_IMAGE}" \
--config=/config/config.yaml
sleep 2
if docker ps --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
ok "OBI 容器启动成功!"
else
fail "容器启动失败:"
docker logs "${SERVICE_NAME}" --tail 30
exit 1
fi
echo ""
echo -e "${BOLD}Docker 管理命令:${NC}"
echo " 查看日志: docker logs -f ${SERVICE_NAME}"
echo " 停止容器: docker stop ${SERVICE_NAME}"
echo " 删除容器: docker rm -f ${SERVICE_NAME}"
echo ""
}
# ========================= 卸载 =========================
uninstall() {
echo -e "${BOLD}========== 卸载 OBI ==========${NC}"
if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then
systemctl stop "${SERVICE_NAME}"
fi
if [[ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]]; then
systemctl disable "${SERVICE_NAME}" --quiet 2>/dev/null || true
rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
systemctl daemon-reload
ok "Systemd 服务已移除"
fi
if command -v docker &>/dev/null && docker ps -a --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
docker rm -f "${SERVICE_NAME}" >/dev/null 2>&1
ok "Docker 容器已移除"
fi
if [[ -d "${INSTALL_DIR}" ]]; then
rm -rf "${INSTALL_DIR}"
ok "安装目录已清理"
fi
ok "OBI 已完全卸载"
}
# ========================= 状态查看 =========================
show_status() {
echo -e "${BOLD}========== OBI 运行状态 ==========${NC}"
if [[ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]]; then
echo -e "\n${CYAN}[Systemd 服务]${NC}"
systemctl status "${SERVICE_NAME}" --no-pager 2>/dev/null || true
echo -e "\n${CYAN}[最近日志]${NC}"
journalctl -u "${SERVICE_NAME}" -n 20 --no-pager 2>/dev/null || true
fi
if command -v docker &>/dev/null && docker ps -a --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
echo -e "\n${CYAN}[Docker 容器]${NC}"
docker ps -f "name=${SERVICE_NAME}"
echo -e "\n${CYAN}[最近日志]${NC}"
docker logs "${SERVICE_NAME}" --tail 20 2>&1 || true
fi
if [[ -f "${CONFIG_FILE}" ]]; then
echo -e "\n${CYAN}[配置文件: ${CONFIG_FILE}]${NC}"
cat "${CONFIG_FILE}"
fi
}
# ========================= 二进制安装主流程 =========================
install_binary() {
check_env || die "环境检查未通过"
collect_config
download_obi
generate_config
install_systemd
start_service
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} OBI 部署完成!${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo -e "${BOLD}安装目录:${NC} ${INSTALL_DIR}"
echo -e "${BOLD}配置文件:${NC} ${CONFIG_FILE}"
echo ""
echo -e "常用命令:"
echo " 查看状态: sudo systemctl status ${SERVICE_NAME}"
echo " 查看日志: sudo journalctl -u ${SERVICE_NAME} -f"
echo " 重启服务: sudo systemctl restart ${SERVICE_NAME}"
echo " 卸载: sudo bash $0 uninstall"
echo ""
}
# ========================= 入口 =========================
print_usage() {
echo ""
echo -e "${BOLD}OBI 一键部署脚本 v${VERSION}${NC}"
echo ""
echo "用法: sudo bash $0 <命令>"
echo ""
echo "命令:"
echo " install 二进制方式安装 (systemd 管理)"
echo " docker Docker 方式部署"
echo " check 仅执行环境检查"
echo " status 查看运行状态与日志"
echo " uninstall 卸载 OBI"
echo ""
echo "应用匹配方式 (APP_MATCH_TYPE):"
echo " port 端口号匹配 (open_ports)"
echo " cmd 命令行匹配 (exe_path + cmd_args)"
echo " exe 可执行文件路径匹配 (exe_path)"
echo " lang 编程语言自动发现 (languages)"
echo " container Docker 容器名匹配 (container_name)"
echo " pid PID 指定 (target_pids)"
echo ""
}
main() {
local action="${1:-}"
case "$action" in
install) check_root "$@"; install_binary ;;
docker) check_root "$@"; check_env || die "环境检查未通过"; deploy_docker ;;
check) check_root "$@"; check_env ;;
uninstall) check_root "$@"; uninstall ;;
status) check_root "$@"; show_status ;;
*) print_usage; exit 1 ;;
esac
}
main "$@"
1. 获取接入点信息
登录 ARMS 控制台,在左侧导航栏单击「接入中心」,在服务端应用区域单击 OpenTelemetry 卡片,获取以下两个值:
OTEL_EXPORTER_OTLP_ENDPOINT(如http://xxx:4318\)OTEL_EXPORTER_OTLP_HEADERS(如Authentication=xxx)
2. 下载并执行脚本
chmod +x obi-setup.sh3. 交互式安装(二进制 + systemd)
sudo bash obi-setup.sh install脚本会依次完成:环境检查 → 收集 endpoint/headers → 选择应用匹配方式(支持 6 种场景) → 支持添加多个应用 → 可选配置排除规则/采样/导出控制 → 自动检测 CPU 架构并下载 OBI → 生成 config.yaml → 创建 systemd 服务并启动。
4. Docker 方式部署
sudo bash obi-setup.sh docker5. 非交互式部署(批量场景)
通过环境变量传参跳过交互。支持以下匹配类型:
# 按端口匹配
export OTLP_ENDPOINT="http://xxx:4318"
export OTLP_HEADERS="Authentication=xxx"
export APP_NAME="my-app"
export APP_MATCH_TYPE="port"
export APP_MATCH_VALUE="8080"
sudo -E bash obi-setup.sh install
# Java 应用 (exe_path + cmd_args)
export APP_MATCH_TYPE="cmd"
export APP_EXE_PATH="*/java"
export APP_CMD_ARGS="*petclinic*"
# 按编程语言
export APP_MATCH_TYPE="lang"
export APP_MATCH_VALUE="java"
# 按可执行文件路径
export APP_MATCH_TYPE="exe"
export APP_MATCH_VALUE="*/my-server"
# 按 Docker 容器名
export APP_MATCH_TYPE="container"
export APP_MATCH_VALUE="my-container-*"
# 按 PID
export APP_MATCH_TYPE="pid"
export APP_MATCH_VALUE="12345,67890"
6. 其他命令
sudo bash obi-setup.sh check # 仅执行环境检查
sudo bash obi-setup.sh status # 查看运行状态与日志
sudo bash obi-setup.sh uninstall # 卸载 OBI方式二:手动分步部署
如果你需要更精细地控制每一步,可以按以下步骤手动操作。
步骤一:获取接入点信息
同上,从 ARMS 控制台 获取 OTEL_EXPORTER_OTLP_ENDPOINT 和 OTEL_EXPORTER_OTLP_HEADERS。
步骤二:下载 OBI
# amd64 架构
wget https://arms-apm-cn-hangzhou-pre.oss-cn-hangzhou.aliyuncs.com/obi-agent/0.9.5/obi-linux-amd64 -O obi
# arm64 架构
wget https://arms-apm-cn-hangzhou-pre.oss-cn-hangzhou.aliyuncs.com/obi-agent/0.9.5/obi-linux-arm64 -O obi
chmod +x obi步骤三:编写配置文件
创建 config.yaml,将 <OTLP_ENDPOINT> 替换为实际值,并根据应用情况修改 discovery.instrument 部分(参见上方「应用发现匹配方式」章节选择合适的配置):
log_level: WARN
otel_metrics_export:
endpoint: <OTLP_ENDPOINT>/v1/metrics
metrics:
features:
- application
- network
- stats_tcp_rtt
routes:
unmatched: heuristic
ebpf:
context_propagation: all
track_request_headers: true
payload_extraction:
http:
genai:
openai:
enabled: true
anthropic:
enabled: true
jsonrpc:
enabled: true
mcp:
enabled: true
qwen:
enabled: true
rerank:
enabled: true
embedding:
enabled: true
buffer_sizes:
http: 8192
otel_traces_export:
endpoint: <OTLP_ENDPOINT>
instrumentations:
- http
- grpc
- sql
- redis
- kafka
- mqtt
- mongo
- couchbase
- memcached
- genai
javaagent:
enabled: false
discovery:
instrument:
# 根据实际场景选择匹配方式,参见上方「应用发现匹配方式」
- name: my-app
open_ports: 8080步骤四:部署服务
二进制方式(systemd 管理):
创建 systemd 服务文件 /etc/systemd/system/obi.service:
[Unit]
Description=OBI eBPF Instrumentation Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/path/to/obi-dir
Environment="OTEL_EBPF_CONFIG_PATH=config.yaml"
Environment="OTEL_EXPORTER_OTLP_HEADERS=<替换为实际值>"
ExecStart=/path/to/obi-dir/obi
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=obi
[Install]
WantedBy=multi-user.target启动服务:
sudo systemctl daemon-reload
sudo systemctl enable obi
sudo systemctl start obi
sudo systemctl status obi
Docker 方式:
docker pull registry.cn-hangzhou.aliyuncs.com/private-mesh/obi:0.9.5
docker run -d \
--name obi \
--pid=host \
--privileged \
--restart=always \
-v $(pwd)/config.yaml:/config/config.yaml:ro \
-e OTEL_EXPORTER_OTLP_HEADERS="<替换为实际值>" \
-e OTEL_EXPORTER_OTLP_ENDPOINT="<替换为实际值>" \
registry.cn-hangzhou.aliyuncs.com/private-mesh/obi:0.9.5 \
--config=/config/config.yaml查看监控数据
登录 ARMS 控制台,选择目标工作空间,在左侧导航栏选择「所有功能 → 应用监控」,在应用列表中找到你配置的应用名称,点击即可查看拓扑、链路、异常等监控详情。
常用运维命令速查
操作 | systemd 方式 | Docker 方式 |
查看状态 |
|
|
查看日志 |
|
|
重启 |
|
|
停止 |
|
|
卸载 |
|
|
附录:一键部署脚本(obi-setup.sh)
将以下内容保存为 obi-setup.sh,赋予执行权限后使用。
#!/bin/bash
# ============================================================================
# OBI (OpenTelemetry eBPF Instrumentation) 一键部署脚本
# 合并了环境检查、下载、配置生成、服务安装的完整流程
#
# 用法:
# sudo bash obi-setup.sh install — 交互式安装(二进制 + systemd)
# sudo bash obi-setup.sh docker — Docker 方式部署
# sudo bash obi-setup.sh check — 仅做环境检查
# sudo bash obi-setup.sh uninstall — 卸载 OBI 服务
# sudo bash obi-setup.sh status — 查看运行状态与日志
#
# 非交互式部署(环境变量):
# export OTLP_ENDPOINT="http://xxx:4318"
# export OTLP_HEADERS="Authentication=xxx"
# export APP_NAME="my-app"
# export APP_MATCH_TYPE="port" # port|cmd|exe|lang|container|pid
# export APP_MATCH_VALUE="8080"
# sudo -E bash obi-setup.sh install
# ============================================================================
set -euo pipefail
# ========================= 全局变量 =========================
VERSION="0.9.5"
SERVICE_NAME="obi"
INSTALL_DIR="/opt/obi"
CONFIG_FILE="${INSTALL_DIR}/config.yaml"
OBI_BINARY="${INSTALL_DIR}/obi"
DOCKER_IMAGE="registry.cn-hangzhou.aliyuncs.com/private-mesh/obi:${VERSION}"
OSS_BASE="https://arms-apm-cn-hangzhou-pre.oss-cn-hangzhou.aliyuncs.com/obi-agent/${VERSION}"
# 颜色
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
# 应用列表(数组)
declare -a APP_ENTRIES=()
# 排除规则
EXCLUDE_PATTERN=""
# ========================= 工具函数 =========================
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
fail() { echo -e "${RED}[FAIL]${NC} $*"; }
die() { fail "$*"; exit 1; }
prompt_input() {
local var_name="$1" prompt_msg="$2" default_val="${3:-}"
local current_val="${!var_name:-}"
if [[ -n "$current_val" ]]; then
info "使用环境变量 ${var_name}=${current_val}"
return
fi
if [[ -n "$default_val" ]]; then
read -rp "$(echo -e "${BOLD}${prompt_msg} [${default_val}]: ${NC}")" input
eval "$var_name=\"${input:-$default_val}\""
else
while true; do
read -rp "$(echo -e "${BOLD}${prompt_msg}: ${NC}")" input
if [[ -n "$input" ]]; then
eval "$var_name=\"$input\""
return
fi
warn "此项为必填"
done
fi
}
prompt_yesno() {
local prompt_msg="$1" default="${2:-n}"
local yn
read -rp "$(echo -e "${BOLD}${prompt_msg} [y/N]: ${NC}")" yn
case "${yn:-$default}" in
[Yy]*) return 0 ;;
*) return 1 ;;
esac
}
# ========================= 环境检查 =========================
check_root() {
if [[ "$EUID" -ne 0 ]]; then
die "请使用 root 权限运行: sudo bash $0 $*"
fi
ok "Root 权限"
}
check_env() {
echo ""
echo -e "${BOLD}========== 环境检查 ==========${NC}"
local kernel_ver major minor
kernel_ver=$(uname -r)
major=$(echo "$kernel_ver" | cut -d. -f1)
minor=$(echo "$kernel_ver" | cut -d. -f2)
local is_rhel=false
if [[ -f /etc/redhat-release ]] || grep -qi 'rhel\|centos\|rocky\|alma\|anolis\|alinux' /etc/os-release 2>/dev/null; then
is_rhel=true
fi
if [[ "$major" -gt 5 ]] || { [[ "$major" -eq 5 ]] && [[ "$minor" -ge 8 ]]; }; then
ok "内核版本: ${kernel_ver} (>= 5.8)"
elif $is_rhel && { [[ "$major" -gt 4 ]] || { [[ "$major" -eq 4 ]] && [[ "$minor" -ge 18 ]]; }; }; then
ok "内核版本: ${kernel_ver} (RHEL 系, >= 4.18)"
else
fail "内核版本: ${kernel_ver} (最低要求 5.8, RHEL 系 4.18)"
return 1
fi
local arch
arch=$(uname -m)
case "$arch" in
x86_64|amd64) ok "CPU 架构: ${arch} (amd64)" ;;
aarch64|arm64) ok "CPU 架构: ${arch} (arm64)" ;;
*) fail "CPU 架构: ${arch} (不支持)"; return 1 ;;
esac
if [[ -f /sys/kernel/btf/vmlinux ]]; then
ok "BTF: 可用"
else
fail "BTF: /sys/kernel/btf/vmlinux 不存在"
return 1
fi
echo -e "${GREEN}========== 环境检查通过 ==========${NC}"
echo ""
return 0
}
detect_arch() {
local arch
arch=$(uname -m)
case "$arch" in
x86_64|amd64) echo "amd64" ;;
aarch64|arm64) echo "arm64" ;;
*) die "不支持的 CPU 架构: ${arch}" ;;
esac
}
# ========================= 单个应用配置收集 =========================
# 生成单个 instrument entry 的 YAML 文本(每行缩进 4 空格 + 属性缩进 6 空格)
collect_one_app() {
local app_name match_type
local entry=""
prompt_input app_name "应用名称 (ARMS 控制台显示名)" "my-app"
echo ""
echo -e " ${BOLD}选择应用匹配方式:${NC}"
echo -e " 1) 端口号匹配 — open_ports, 如 8080 或 80,443 或 8000-8999"
echo -e " 2) 命令行匹配 — exe_path + cmd_args, 适合 java -jar 等场景"
echo -e " 3) 可执行文件路径匹配 — exe_path, 适合自编译二进制"
echo -e " 4) 编程语言自动发现 — languages, 自动检测 go/java/python/nodejs 等"
echo -e " 5) Docker 容器名匹配 — container_name, 适合 ECS 上跑 Docker"
echo -e " 6) PID 指定 — target_pids, 临时调试用"
read -rp "$(echo -e "${BOLD}选择 [1-6, 默认1]: ${NC}")" match_choice
entry=" - name: ${app_name}"
case "${match_choice:-1}" in
1)
local ports
prompt_input ports "监听端口号 (如 8080, 80,443, 8000-8999)" "8080"
entry="${entry}
open_ports: ${ports}"
;;
2)
local exe_path cmd_args
prompt_input exe_path "可执行文件路径 Glob (如 */java)" "*/java"
prompt_input cmd_args "命令行参数 Glob (如 *petclinic*)"
entry="${entry}
exe_path: \"${exe_path}\"
cmd_args: \"${cmd_args}\""
;;
3)
local exe_path
prompt_input exe_path "可执行文件路径 Glob (如 */my-server, */nginx)"
entry="${entry}
exe_path: \"${exe_path}\""
;;
4)
local lang
echo -e " 支持的语言: go, java, python, ruby, nodejs, rust, dotnet, php, cpp"
echo -e " 多语言用花括号: {go,rust} 或 {python,nodejs}"
prompt_input lang "编程语言" "java"
entry="${entry}
languages: \"${lang}\""
;;
5)
local cname
prompt_input cname "Docker 容器名 Glob (如 my-container-*)"
entry="${entry}
container_name: \"${cname}\""
;;
6)
local pids
prompt_input pids "PID 列表, 逗号分隔 (如 12345,67890)"
# 转成 YAML 数组
local pid_array
pid_array=$(echo "$pids" | tr ',' '\n' | sed 's/^[[:space:]]*//' | awk '{printf " - %s\n", $0}')
entry="${entry}
target_pids:
${pid_array}"
;;
*)
local ports
prompt_input ports "监听端口号" "8080"
entry="${entry}
open_ports: ${ports}"
;;
esac
# 可选:仅容器进程
if [[ "${match_choice:-1}" != "5" ]]; then
if prompt_yesno "是否仅匹配容器内进程 (containers_only)?"; then
entry="${entry}
containers_only: true"
fi
fi
# 可选:采样
if prompt_yesno "是否配置 Traces 采样 (减少数据量)?"; then
echo -e " 采样器: always_on, always_off, traceidratio, parentbased_traceidratio"
local sampler_name sampler_arg
prompt_input sampler_name "采样器名称" "traceidratio"
prompt_input sampler_arg "采样参数 (如 0.5 表示采样50%)" "1.0"
entry="${entry}
sampler:
name: ${sampler_name}
arg: \"${sampler_arg}\""
fi
APP_ENTRIES+=("$entry")
ok "已添加应用: ${app_name}"
}
# ========================= 收集配置(完整) =========================
collect_config() {
echo -e "${BOLD}========== 配置信息 ==========${NC}"
echo ""
info "请从 ARMS 接入中心获取以下信息:"
info " ARMS 控制台 -> 接入中心 -> OpenTelemetry 卡片 -> 点击获取 LicenseKey"
echo ""
prompt_input OTLP_ENDPOINT "OTEL_EXPORTER_OTLP_ENDPOINT (如 http://xxx:4318)"
prompt_input OTLP_HEADERS "OTEL_EXPORTER_OTLP_HEADERS (如 Authentication=xxx)"
echo ""
echo -e "${BOLD}========== 应用配置 ==========${NC}"
# 非交互模式:从环境变量构建单个 app
if [[ -n "${APP_MATCH_TYPE:-}" ]]; then
local entry=" - name: ${APP_NAME:-my-app}"
case "$APP_MATCH_TYPE" in
port)
entry="${entry}
open_ports: ${APP_MATCH_VALUE:-8080}" ;;
cmd)
entry="${entry}
exe_path: \"${APP_EXE_PATH:-*/java}\"
cmd_args: \"${APP_CMD_ARGS}\"" ;;
exe)
entry="${entry}
exe_path: \"${APP_MATCH_VALUE}\"" ;;
lang)
entry="${entry}
languages: \"${APP_MATCH_VALUE}\"" ;;
container)
entry="${entry}
container_name: \"${APP_MATCH_VALUE}\"" ;;
pid)
local pid_array
pid_array=$(echo "${APP_MATCH_VALUE}" | tr ',' '\n' | sed 's/^[[:space:]]*//' | awk '{printf " - %s\n", $0}')
entry="${entry}
target_pids:
${pid_array}" ;;
*)
die "未知的 APP_MATCH_TYPE: ${APP_MATCH_TYPE} (支持: port|cmd|exe|lang|container|pid)" ;;
esac
APP_ENTRIES+=("$entry")
ok "已添加应用: ${APP_NAME:-my-app} (${APP_MATCH_TYPE})"
return
fi
# 交互模式:循环添加应用
while true; do
echo ""
collect_one_app
echo ""
if ! prompt_yesno "是否继续添加另一个应用?"; then
break
fi
done
# 可选:排除规则
echo ""
if prompt_yesno "是否配置排除规则 (exclude_instrument)?"; then
echo -e " 输入要排除的可执行文件路径 Glob"
echo -e " 示例: {obi,prometheus,otelcol*,node_exporter}"
local exclude_val
prompt_input exclude_val "排除路径 Glob"
EXCLUDE_PATTERN="$exclude_val"
ok "已添加排除规则: ${exclude_val}"
fi
}
# ========================= 生成配置文件 =========================
generate_config() {
local endpoint_metrics="${OTLP_ENDPOINT}/v1/metrics"
# 拼接 instrument 段
local instrument_yaml=""
for entry in "${APP_ENTRIES[@]}"; do
instrument_yaml="${instrument_yaml}
${entry}"
done
# 拼接 exclude 段
local exclude_yaml=""
if [[ -n "$EXCLUDE_PATTERN" ]]; then
exclude_yaml="
exclude_instrument:
- exe_path: \"${EXCLUDE_PATTERN}\""
fi
cat > "${CONFIG_FILE}" <<YAML
# OBI 配置文件 - 由 obi-setup.sh 自动生成
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
log_level: WARN
# ========== Metrics 导出 ==========
otel_metrics_export:
endpoint: ${endpoint_metrics}
metrics:
features:
- application
- network
- stats_tcp_rtt
routes:
unmatched: heuristic
# ========== eBPF 探针配置 ==========
ebpf:
context_propagation: all
track_request_headers: true
payload_extraction:
http:
genai:
openai:
enabled: true
anthropic:
enabled: true
jsonrpc:
enabled: true
mcp:
enabled: true
qwen:
enabled: true
rerank:
enabled: true
embedding:
enabled: true
buffer_sizes:
http: 8192
# ========== Traces 导出 ==========
otel_traces_export:
endpoint: ${OTLP_ENDPOINT}
instrumentations:
- http
- grpc
- sql
- redis
- kafka
- mqtt
- mongo
- couchbase
- memcached
- genai
javaagent:
enabled: false
# ========== 应用发现规则 ==========
discovery:
instrument:${instrument_yaml}${exclude_yaml}
YAML
ok "配置文件已生成: ${CONFIG_FILE}"
}
# ========================= 下载 OBI =========================
download_obi() {
local arch
arch=$(detect_arch)
local url="${OSS_BASE}/obi-linux-${arch}"
info "下载 OBI v${VERSION} (${arch})..."
mkdir -p "${INSTALL_DIR}"
if command -v wget &>/dev/null; then
wget -q --show-progress "${url}" -O "${OBI_BINARY}"
elif command -v curl &>/dev/null; then
curl -fL --progress-bar "${url}" -o "${OBI_BINARY}"
else
die "wget 和 curl 均不可用"
fi
chmod +x "${OBI_BINARY}"
ok "OBI 已下载至 ${OBI_BINARY}"
}
# ========================= Systemd 服务 =========================
install_systemd() {
info "创建 systemd 服务..."
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
[Unit]
Description=OBI eBPF Instrumentation Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}
Environment="OTEL_EBPF_CONFIG_PATH=${CONFIG_FILE}"
Environment="OTEL_EXPORTER_OTLP_HEADERS=${OTLP_HEADERS}"
ExecStart=${OBI_BINARY}
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=${SERVICE_NAME}
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}" --quiet
ok "Systemd 服务已创建并设为开机自启"
}
start_service() {
info "启动 OBI 服务..."
systemctl start "${SERVICE_NAME}"
sleep 2
if systemctl is-active --quiet "${SERVICE_NAME}"; then
ok "OBI 服务启动成功!"
else
fail "OBI 服务启动失败, 错误日志如下:"
journalctl -u "${SERVICE_NAME}" -n 30 --no-pager
exit 1
fi
}
# ========================= Docker 部署 =========================
deploy_docker() {
if ! command -v docker &>/dev/null; then
die "Docker 未安装"
fi
collect_config
mkdir -p "${INSTALL_DIR}"
generate_config
info "拉取 Docker 镜像: ${DOCKER_IMAGE}"
docker pull "${DOCKER_IMAGE}"
if docker ps -a --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
warn "移除已有容器..."
docker rm -f "${SERVICE_NAME}" >/dev/null 2>&1
fi
info "启动 OBI 容器..."
docker run -d \
--name "${SERVICE_NAME}" \
--pid=host \
--privileged \
--restart=always \
-v "${CONFIG_FILE}:/config/config.yaml:ro" \
-e "OTEL_EXPORTER_OTLP_HEADERS=${OTLP_HEADERS}" \
-e "OTEL_EXPORTER_OTLP_ENDPOINT=${OTLP_ENDPOINT}" \
"${DOCKER_IMAGE}" \
--config=/config/config.yaml
sleep 2
if docker ps --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
ok "OBI 容器启动成功!"
else
fail "容器启动失败:"
docker logs "${SERVICE_NAME}" --tail 30
exit 1
fi
echo ""
echo -e "${BOLD}Docker 管理命令:${NC}"
echo " 查看日志: docker logs -f ${SERVICE_NAME}"
echo " 停止容器: docker stop ${SERVICE_NAME}"
echo " 删除容器: docker rm -f ${SERVICE_NAME}"
echo ""
}
# ========================= 卸载 =========================
uninstall() {
echo -e "${BOLD}========== 卸载 OBI ==========${NC}"
if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then
systemctl stop "${SERVICE_NAME}"
fi
if [[ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]]; then
systemctl disable "${SERVICE_NAME}" --quiet 2>/dev/null || true
rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
systemctl daemon-reload
ok "Systemd 服务已移除"
fi
if command -v docker &>/dev/null && docker ps -a --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
docker rm -f "${SERVICE_NAME}" >/dev/null 2>&1
ok "Docker 容器已移除"
fi
if [[ -d "${INSTALL_DIR}" ]]; then
rm -rf "${INSTALL_DIR}"
ok "安装目录已清理"
fi
ok "OBI 已完全卸载"
}
# ========================= 状态查看 =========================
show_status() {
echo -e "${BOLD}========== OBI 运行状态 ==========${NC}"
if [[ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]]; then
echo -e "\n${CYAN}[Systemd 服务]${NC}"
systemctl status "${SERVICE_NAME}" --no-pager 2>/dev/null || true
echo -e "\n${CYAN}[最近日志]${NC}"
journalctl -u "${SERVICE_NAME}" -n 20 --no-pager 2>/dev/null || true
fi
if command -v docker &>/dev/null && docker ps -a --format '{{.Names}}' | grep -q "^${SERVICE_NAME}$"; then
echo -e "\n${CYAN}[Docker 容器]${NC}"
docker ps -f "name=${SERVICE_NAME}"
echo -e "\n${CYAN}[最近日志]${NC}"
docker logs "${SERVICE_NAME}" --tail 20 2>&1 || true
fi
if [[ -f "${CONFIG_FILE}" ]]; then
echo -e "\n${CYAN}[配置文件: ${CONFIG_FILE}]${NC}"
cat "${CONFIG_FILE}"
fi
}
# ========================= 二进制安装主流程 =========================
install_binary() {
check_env || die "环境检查未通过"
collect_config
download_obi
generate_config
install_systemd
start_service
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} OBI 部署完成!${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo -e "${BOLD}安装目录:${NC} ${INSTALL_DIR}"
echo -e "${BOLD}配置文件:${NC} ${CONFIG_FILE}"
echo ""
echo -e "常用命令:"
echo " 查看状态: sudo systemctl status ${SERVICE_NAME}"
echo " 查看日志: sudo journalctl -u ${SERVICE_NAME} -f"
echo " 重启服务: sudo systemctl restart ${SERVICE_NAME}"
echo " 卸载: sudo bash $0 uninstall"
echo ""
}
# ========================= 入口 =========================
print_usage() {
echo ""
echo -e "${BOLD}OBI 一键部署脚本 v${VERSION}${NC}"
echo ""
echo "用法: sudo bash $0 <命令>"
echo ""
echo "命令:"
echo " install 二进制方式安装 (systemd 管理)"
echo " docker Docker 方式部署"
echo " check 仅执行环境检查"
echo " status 查看运行状态与日志"
echo " uninstall 卸载 OBI"
echo ""
echo "应用匹配方式 (APP_MATCH_TYPE):"
echo " port 端口号匹配 (open_ports)"
echo " cmd 命令行匹配 (exe_path + cmd_args)"
echo " exe 可执行文件路径匹配 (exe_path)"
echo " lang 编程语言自动发现 (languages)"
echo " container Docker 容器名匹配 (container_name)"
echo " pid PID 指定 (target_pids)"
echo ""
}
main() {
local action="${1:-}"
case "$action" in
install) check_root "$@"; install_binary ;;
docker) check_root "$@"; check_env || die "环境检查未通过"; deploy_docker ;;
check) check_root "$@"; check_env ;;
uninstall) check_root "$@"; uninstall ;;
status) check_root "$@"; show_status ;;
*) print_usage; exit 1 ;;
esac
}
main "$@"