后记
本文的主要逻辑代码已提交PR并合并到官方代码中,不过获取变量改为FALCON_ENDPOINT。因此我们在线上环境中,仍然自行维护了下文中的补丁,以适应自身的业务需求。
背景
open-falcon 监控系统里面有一个重要的概念为 endpoint,可以理解为监控项目在主机标识。
默认将操作系统的主机名上报为 endpoint ,同时可以修改 agent 的 cfg.json 的 hostname 属性来自定义主机标识。
服务器规模大的时候,主机名命名趋向与业务无关、IP地址无关,常见以各种随机算法命名,也就是从主机名中很难看出来这台服务器是什么业务,也不一定能主机名中看出其IP地址的特征。
自定义主机名
我们的做法是,主机名随机命名,监控上报时以自定义数据来代替主机名,自定义数据中,有业务类型、业务属性、IP 地址等信息,仅仅通过自定义主机名即可判断告警的影响范围。
自定义属性举例: basic_nginx_1.2.3.4, cache_redis_web_5.6.7.8, java_pay_11.22.33.44.
我们将自定义主机名命名为 ENDPOINT 环境变量。
自定义主机名的实现
在每个主机增加 /etc/endpoint.env 文件,示例内容为:
ENDPOINT=basic_nginx_1.2.3.4
创建 /etc/profile.d/91-env.sh 文件,解析 /etc/endpoint.env 文件,输出环境变量 ENDPOINT,并覆盖 shell 的 PS1,文件内容为:
#!/bin/bash
# /etc/profile.d/91-env.sh
export ENDPOINT=$(test -f /etc/endpoint.env && cat /etc/endpoint.env | cut -d'=' -f 2 || hostname -s)
export PS1='[\u@${ENDPOINT} \W]\$'
同时,监控系统 zabbix、open-falcon 上报的主机名也不是默认的操作系统的主机名,而是自定义的ENDPOINT 环境变量的值 。
1.0 时代:修改 open-falcon 客户端配置
在初始化每台主机时,自动将自定义主机名填入到 falcon-agent 的配置文件 cfg.json 中的 hostname 属性。 这样操作以后,每台主机的 falcon-agent 的 cfg.json 配置文件的 hostname 属性都不一样,其他配置均相同。
PS: zabbix 修改 /etc/zabbix/zabbix_agentd.conf 的 Hostname 的属性。
思考改进
以上修改配置文件的方式,将每个 agent 的配置文件变成了“有状态”的方式,始终觉得不够优雅,增加了维护成本。
改进思路:
- 修改启动脚本,启动时获取环境变量 ENDPOINT,通过 jq 命令行修改 cfg.json 。
- 修改 agent 代码,获取环境变量 ENDPOINT 来代替获取系统主机名。
分析 falcon-agent 关于主机名获取的逻辑代码,修改代码相对容易,后期维护成本也比较低,我们采用了第二种方式。(在此YY一下,如果这种方式的处理能被官方接受,代码合并到官方就更没有维护成本了。)
处理代码
主机名获取的优先级为:
- cfg.json 中的 hostname 属性
- 环境变量 ENDPOINT
- 文件 /etc/endpoint.env 中的 ENDPOINT 的值 (作为取不到环境变量的 fallback 方法)
- 操作系统的主机名
diff --git a/modules/agent/g/cfg.go b/modules/agent/g/cfg.go
index c3e0181..6e4765a 100644
--- a/modules/agent/g/cfg.go
+++ b/modules/agent/g/cfg.go
@@ -2,8 +2,10 @@ package g
import (
"encoding/json"
+ "io/ioutil"
"log"
"os"
+ "strings"
"sync"
"github.com/toolkits/file"
@@ -72,6 +74,29 @@ func Hostname() (string, error) {
return hostname, nil
}
+ if os.Getenv("ENDPOINT") != "" {
+ hostname = os.Getenv("ENDPOINT")
+ return hostname, nil
+ }
+
+ // parse /etc/endpoint.env ( ENDPOINT=xxx_xxx_1.2.3.4 )
+ filePath := "/etc/endpoint.env"
+ if _, err := os.Stat(filePath); err == nil {
+ data, _ := ioutil.ReadFile(filePath)
+ str := string(data)
+ if !strings.ContainsAny(str, "ENDPOINT") {
+ log.Panic("ERROR: /etc/endpoint.env missing ENDPOINT")
+ }
+ if !strings.ContainsAny(str, "=") {
+ log.Panic("ERROR: /etc/endpoint.env missing =")
+ }
+ str = strings.Trim((strings.SplitAfter(str, "ENDPOINT"))[1], " ")
+ hostname = strings.Trim((strings.SplitAfter(str, "="))[1], " ")
+ if len(hostname) > 1 {
+ return hostname, nil
+ }
+ }
+
if os.Getenv("FALCON_ENDPOINT") != "" {
hostname = os.Getenv("FALCON_ENDPOINT")
return hostname, nil
falcon-agent 的 Systemd 启动脚本
启动脚本会将 ENDPOINT 环境变量传递给 falcon-agent 程序
/etc/systemd/system/falcon-agent.service
[Unit]
Description=The falcon agent
After=network.target
[Service]
LimitNOFILE=1048576
Restart=always
RestartSec=30
Type=simple
EnvironmentFile=-/etc/endpoint.env
Workdir=/app/monitor/falcon
ExecStartPre=/app/monitor/falcon/falcon-agent -c /app/monitor/falcon/cfg.json -check
ExecStart=/app/monitor/falcon/falcon-agent -c /app/monitor/falcon/cfg.json
TimeoutStopSec=5
PrivateTmp=true
User=nobody
[Install]
WantedBy=multi-user.target
说明: master 分支的代码已经修复了 agent check 启动失败的问题。
falcon-agent 的 init 启动脚本
启动脚本会将 ENDPOINT 环境变量传递给 falcon-agent 程序
#!/bin/bash
# chkconfig: 2345 90 10
# description: falcon-agent
export PATH=/usr/java/latest/bin:/sbin:/bin:/usr/sbin:/usr/bin
WORKDIR="/app/monitor/falcon"
EXEC_CMD='/app/monitor/falcon/control'
if [[ ! -f ${EXEC_CMD} ]];then
echo "$EXEC_CMD not found"
exit 1
fi
RETVAL=$?
start_pre(){
source /etc/profile
test -f /etc/endpoint.env && export $( cat /etc/endpoint.env )
cd $WORKDIR
}
case "$1" in
start|stop|restart|status)
start_pre
su - nobody -s /bin/bash -c "$EXEC_CMD $1"
;;
*)
echo $"Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit $RETVAL
愉快的玩耍
修改后的 falcon-agent 程序和配置文件,在我们几百台服务器上都是一样,通过灰度上线部分节点运行无异常后,已通过自动化发布程序全部更新上线。