洞察掌握android电视app开发中的安全与合规策略,提升企业运营效率
1004
2022-09-20
Zabbix自定义参数监控和awk命令
awk 命令
awk是一种处理文本文件的语言,是一个强大的文本分析公具。awk处理文本和数据的方式:逐行读入文本,寻找匹配特定模式的行,然后进行操作。
输出文件匹配行的特定字段
功能很强大,所以有很多用处。这里我主要关注下面这样的场景:逐行读入文本,按规则匹配特定的行,以空格为默认分隔符将每行切片,输出其中特定的某个切片(切开的部分可以进行各种分析处理,这里就是要输出其中以段):
$ cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 $ awk '/local/ {print $1}' /etc/hosts 127.0.0.1 ::1 $
这种方法很适合用来做zabbix的自定义key的监控。比如从free命令中,提取出内存的使用量:
$ free total used free shared buff/cache available Mem: 1855432 320688 1238808 10612 295936 1495432 Swap: 2093052 0 2093052 $ free | awk '/^Mem:/ {print $3}' 320688 $
grep命令 同样的效果,也可以通过grep命令来把需要的行过滤出来,然后还得借助cut命令来进行列切割。 但是使用awk的话就一步搞定了。
内置变量
内置变量先列出来,后面会用到其中一些。awk内置变量:
ARGC: 命令行参数个数 ARGV: 命令行参数排列 ENVIRON: 支持队列中系统环境变量的使用 FILENAME: awk浏览的文件名 FNR: 浏览文件的记录数 FS: 设置输入域分隔符,等价于命令行 -F选项 IGNORECASE: 如果把这个变量设为1,则正则表达式忽略大小写 NF: 浏览记录的域的个数 NR: 已读的记录数 OFS: 输出域分隔符 ORS: 输出记录分隔符 RS: 控制记录分隔符 $0: 变量是指整条记录 $1: 表示当前行的第一个域,$2表示当前行的第二个域,......以此类推 $NF: 既然 NF是列的总数,那么$NF就是最后一列的值 $NF-1: 这个是倒数第二列,其他以此类推
上面这些变量,有些是直接来使用的。比如$1,$NF,后面的例子中会用到,也比较好理解。 还有些是用来改变awk行为的,需要对变量进行设置,这个需要会为变量赋值,有多种方式可以实现。比如FS,是用来指定分隔符的,默认的分隔符是空白符,但是可以指定。这就需要自己定义FS的值。不过分隔符还提供了一个 -F 选项来定义。所以也可以在命令行选项中设置。但是其他一些变量需要指定,但又没有提供别的方法的话,就只能用过为变量赋值来实现了。分隔符和为变量赋值的方式在后面会展开,为变量赋值参考自定义变量的内容。
分隔符
默认awk是以空白符来做分隔的。使用 -F 选项可以自定义分隔符:
$ grep -e "^root" /etc/passwd root:x:0:0:root:/root:/bin/bash $ awk -F: '/^root/ {print $1,$NF}' /etc/passwd root /bin/bash $
这里将分隔符指定为冒号。
多分隔符 默认的也是多分隔符的情况,空格、制表符等都会被识别。自己要指定多个分隔符,则是用中括号把需要识别的分隔符都括起来:
$ echo "a-b_c=d-E_F=G" | awk -F[-_=] '{print $1,$2,$3,$4,$5,$6,$7}' a b c d E F G $
过滤连续的分隔符 -F 选项也是支持正则表达式的,中括号就是正则表达式字符集合的意思。但是如果这时遇到连续的分隔符,就会有问题。下面使用逗号和空格作为分隔符,并且每次都连续出现:
$ echo "a,,b c" | awk -F'[ ,]' '{print $1"-"$2"-"$3}' a--b $
正则表达式中匹配一次或多次,使用加号后,就可以了:
$ echo "a,,b c" | awk -F'[ ,]+' '{print $1"-"$2"-"$3}' a-b-c $
特殊字符分隔符 特殊字符应该就是这些: $、^、*、(、)、[、]、?、.、|单独作为分隔符并没有问题:
$ echo '1a$1b$1c' | awk -F'$' '{print $1"-"$2"-"$3}' 1a-1b-1c $
如果指定多个字符作为一个整体作为一个分隔符,就会有问题,需要转义。比如这里要将 $1 作为分隔符:
$ echo '1a$1b$1c' | awk -F'$1' '{print $1"-"$2"-"$3}' 1a$1b$1c-- $ echo '1a$1b$1c' | awk -F'\\$1' '{print $1"-"$2"-"$3}' 1a-b-c $
再来个多个特殊字符组合的:
$ echo 'a$|b$|c' | awk -F'\\$\\|' '{print $1"-"$2"-"$3}' a-b-c $
默认分隔符 默认就是空白符作为分隔符,并且能够识别连续的空白符。默认分隔符就是下面的这个正则表达式:
FS="[[:space:]+]"
看上面的内置变量,FS和-F选项是等价的。
格式化输出printf
除了用print,还可以用printf做格式化输出。这里就给出一个例子,关于printf格式化输出,需要的话再去参考下C语言的printf的功能把。
一般都用print输出:
$ awk -F: '{print "filename:" FILENAME ",linenumber:" NR ",columns:" NF ",linecontent:"$0}' /etc/passwd filename:/etc/passwd,linenumber:1,columns:7,linecontent:root:x:0:0:root:/root:/bin/bash filename:/etc/passwd,linenumber:2,columns:7,linecontent:bin:x:1:1:bin:/bin:/sbin/nologin filename:/etc/passwd,linenumber:3,columns:7,linecontent:daemon:x:2:2:daemon:/sbin:/sbin/nologin
对比下用printf格式化输出后的效果:
$ awk -F: '{printf ("filename:%10s, linenumber:%3s,column:%3s,content:%3f\n",FILENAME,NR,NF,$0)}' /etc/passwd filename:/etc/passwd, linenumber: 1,column: 7,content:0.000000 filename:/etc/passwd, linenumber: 2,column: 7,content:0.000000 filename:/etc/passwd, linenumber: 3,column: 7,content:0.000000
BEGIN 和 END 模块
通常,对于每个输入行,awk 都会执行一次脚本代码块。 有时,需要在 awk 开始处理输入文件中的文本之前执行初始化代码。这就需要定义一个 BEGIN 块。另外,还有一个 END 块,用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
在BEGIN块中定义内置变量 这里在BEGIN块中定义了两个内置变量:
$ echo "a,,b c" | awk 'BEGIN{FS="[ ,]+";OFS="-"}{print $1,$2,$3}' a-b-c $
FS是分隔符,OFS是输出字段分隔符。在之前的例子中,不用BEGIN块也是能实现这个效果的。 这里修改的是一个内置变量,但是方法是针对变量的,包括自定义变量。具体参考下一章“awk自定义变量”。
这里主要挑BEGIN块举例用法。END块可以实现计算统计输出的功能,暂时用不上,略过。
awk自定义变量
除了内置变量,也可以定义自定义变量并使用。这部分内容对于灵活的配置非常有用,而且如果自己写,也会遇到一些坑。
定义在后面
这里变量的赋值在发生在BEGIN块执行之后的直接写在后面:
$ echo | awk '{print key1,key2}' key1=v1 key2=V2 v1 V2 $
这种用法在BEGIN块中是识别不了变量的。BEGIN块的执行在这些变量定义之前。不过还有其他的方法可以用。另外,这里使用管道作为标准输入。如果是从文件输入的话,文件路径在写最后。
在BEGIN块中定义
这里变量额赋值是在BEGIN块执行的时候在BEGIN块中可以对内置变量赋值,同样的也可以为自定义变量赋值
$ echo | awk 'BEGIN{key1="v1";key2="value2";OFS="_"}{print key1,key2}' v1_value2 $
使用 -v 选项
这里变量的赋值是在BEGIN块执行之前这个方法在发生在BEGIN块执行之前的:
$ echo | awk -v key1=V1 -v key2=value2 '{print key1,key2}' V1 value2 $
如果是多个变量,则使用 -v 多次。
如果把上面两个方法合起来:
$ echo | awk -v key1=v1 -v key2=v2 'BEGIN{print "BEGIN: "key1,key2}{print "ACTION: "key1,key2}' key1=VALUE1 key2=VALUE2 BEGIN: v1 v2 ACTION: VALUE1 VALUE2 $
先是 -v 进行赋值,然后BEGIN块执行。之后是最后的变量赋值,如果有同名的就替换值,之后再逐行执行。打印出来的就是之后改变的值。
打印环境变量
最好的方法在后一小节。这里的方法也是可行的,但是可读性不好。写这段是为了理解一下命令参数解析的过程,以及一些特殊情况的处理。要直接打印环境变量是这样的:
$ echo | awk '{print "'"$PATH"'"}' /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin $
这里的显示不明显,两边都是一对双引号套一个单引号,看图
解释说明 先用一个简单点的环境变量来举例:
$ echo $USER root $
这个没有什么空格换特殊字符,这样可以去掉最里面的一对双引号:
$ echo | awk '{print "'$USER'"}' root $
这里成对出现了2对单引号,所以就被分成了这样两个部分:awk '{print 和 '"}'。awk对2个单引号内的命令起作用。剩下的就是 $USER 了,这个最早就被 shell 给处理替换了。 在变量本身被shell处理完之后,如果有空格之类的,有会被认为不是一个部分。这里就再用双引号把环境变量的值包起来,将值作为整体的一个域。
我的理解 最外层的引号是用来界定字符边界的,但是只要是连续的就被系统认为是一串(一个域)。可以用多对引号把多个字符串引起来,但是每对引号之间不要出现分隔符。这样,最后解析交给命令处理的还是一个整体的字符串(一个域)。 下面是用echo命令的演示:
$ echo 'abc''def' abcdef $ echo 'abc'$USER'def' abcrootdef $ echo 'abc '$USER' def' abc root def $
加上for循环再演示一次:
$ HELLO='Hello World !' $ for i in $HELLO; do echo $i;done Hello World ! $ for i in 'BEFOR'$HELLO'AFTER'; do echo $i;done BEFORHello World !AFTER $ for i in 'BEFOR'"$HELLO"'AFTER'; do echo $i;done BEFORHello World !AFTER $
最外层的引号仅仅是界定边界的,用多对引号但是所有内容都相连,也被认为是一个域。
虽然有多对引号,但是所有内容都是相连的,没有分隔符,最后交给命令处理的还是一个域。这样做的好处就是,用了单引号,但是把需要shell解析的部分放到了单引号的外面,这样shell还是可以正常解析。为了保证环境变量解析完之后依然是一个域,需要用双引号引起来。再来就是awk中接着print的双引号了。awk中的引号不是界定边界的而是区分是变量还是字符串的。没有双引号的话表示这个内容是变量,用双引号引起来表示里面的内容是字符串,直接打印。
其他写法 下面两种写法也能实现同样的效果,帮助理解吧:
$ echo | awk "{print \"$PATH\"}" /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin $ echo } awk \{print\""$PATH"\"\} } awk {print"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin"} $
awk命令还是尽量用单引号引起来,防止shell对其中内容进行解释。就是第一种办法就最好的。
引用命令行定义的变量
最开始的3种方法,有2种是在引号外完成变量定义的,这样就不会对shell进行干扰:
$ echo | awk '{print path}' path="$PATH" /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin $ echo | awk -v path="$PATH" '{print path}' /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin $
先是用命令行的方式把环境变量赋值给自定义变量,这个操作在引号外。然后再引号里面直接用自定义变量就好了。 如果是在BEGIN块中要这么做,就参考上以小节的做法。
awk 进阶
从free命令同获取当前内存使用数值:
$ free | awk '/^Mem:/ {print $3}' 335840 $
这里用的是正则匹配。不过awk还有其他的一些语法,可以做到更加精确的匹配,
条件限制
限制第一个字段值来匹配:
$ free | awk '$1 == "Mem:" {print $3}' 335744 $
限制要第几行的数据:
$ free | awk 'NR == 2 {print $3}' 335796 $
条件语句
awk 也提供了 if, else, while 等这些条件语句,不过似乎用不了那么深,举一个if的例子。同样是限制第几行,这里通过if语句来判断:
$ free | awk '{if(NR == 2) print $3}' 335740 $
正则匹配
~是匹配正则表达式的运算符。另外,~!是不匹配正则表达式的运算符。匹配第一个字段:
$ free | awk '$1 ~ /Mem/ {print $3}' 335844 $
关于正则还有一个内置变量是 IGNORECASE,如果设置为1,可以忽略大小写:
$ free | awk '$1 ~ "mem" {print $3}' IGNORECASE=1 335708 $
为变量赋值的方法之前讲过了,有好几种方式。
打印九九乘法表
这个很高端的样子,就贴在最后了:
$ seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}' 1x1=1 1x2=2 2x2=4 1x3=3 2x3=6 3x3=9 1x4=4 2x4=8 3x4=12 4x4=16 1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81 $
Zabbix 自定义参数(监控项)
用户自定义参数可以通过Zabbix agent执行非Zabbix原生的agent监控项。只要你有办法能通过命令获取到要监控的指标。
配置文件
可以直接在配置文件 zabbix_agentd.conf 中定义 UserParameter。
### Option: HostnameItem # Item used for generating Hostname if it is undefined. Ignored if Hostname is defined. # Does not support UserParameters or aliases. # # Mandatory: no # Default: # HostnameItem=system.hostname
Include配置虽然直接写在这下面就可以了,不过配置文件还有一个Include的配置:
### Option: Include # You may include individual files or all files in a directory in the configuration file. # Installing Zabbix will create include directory in /usr/local/etc, unless modified during the compile time. # # Mandatory: no # Default: # Include= Include=/etc/zabbix/zabbix_agentd.d/*.conf # Include=/usr/local/etc/zabbix_agentd.userparams.conf # Include=/usr/local/etc/zabbix_agentd.conf.d/ # Include=/usr/local/etc/zabbix_agentd.conf.d/*.conf
建议把这些配置分下类,创建独立的 zabbix_agentd.d/*.conf 文件,方便管理。
语法
自定义参数的语法如下:
UserParameter=
key,就是监控项用的key。必须全局唯一。命名要求:只能使用字母、数字、下划线、中横杠、点号。即 0-9a-zA-Z_-. 这些字符。
比如下面的文件中定义了3个通过free命令获取值的监控项:
$ cat /etc/zabbix/zabbix_agentd.d/os.conf UserParameter=os.memory.total, free -m | awk '$1=="Mem:" {print $2}' UserParameter=os.memory.used, free -m | awk '$1=="Mem:" {print $3}' UserParameter=os.memory.free, free -m | awk '$1=="Mem:" {print $4}'
具体一步步如何实现的,参考下一小节。
调试步骤
自定义参数,一步步实现的操作过程。
第一步:写一个命令或脚本能够成功的在命令行中把值打印出来:
$ free -m | awk '$1=="Mem:" {print $3}' 569 $
由于zabbix是使用zabbix账号执行的,有些命令有可能zabbix无权限。所以可以加上sudo指定zabbix用户执行再验证一下:
$ sudo -u zabbix free -m | awk '$1=="Mem:" {print $3}' 571 $
第二步:添加到配置文件中
UserParameter=os.memory.used, free -m | awk '$1=="Mem:" {print $3}'
第三步:测试key使用 zabbix_agentd 并且用 -t 选项指定key来进行测试:
$ zabbix_agentd -t os.memory.used os.memory.used [t|571] $
测试成功说明写的没问题
第四步:重启agent要重启agent才能使新的配置文件生效:
$ systemctl restart zabbix-agent $
之后就可以去Web添加监控项了。
带参数的用法
可以为key设置参数,这样一个设置可以应对多个监控项。语法如下:
UserParameter=key[*],command
这里的星号表示可以带任意数量的参数,并且似乎也只有这一种用法,没有指定参数数量的写法。
在command中使用参数在command中使用位置引用$1......$9,来引用key中相应的参数。另外$0表示命令本身。
关于$符号由于$1.....$9有了特殊的意义,在awk中的$1也会被zabbix先替换掉。这时应该使用$$1。zabbix仅仅只替换位置参数,对于单独的$符号或者其他组合(比如$NF),zabbix不会处理。zabbix仅仅只在使用了key[*],这样指定了key是带参数的时候才会进行位置参数替换的处理。所以之前的示例使用$1没有问题。
修改为带参数的key现在把之前的示例改成一种更灵活的设置方式:
UserParameter=os.free[*], free -m | awk '$$1~NAME {print $$(COLUMN+1)}' IGNORECASE=1 NAME="$1" COLUMN=$2
测试效果如下:
$ zabbix_agentd -t os.free[mem,2] os.free[mem,2] [t|570] $
示例
一些自定义参数的示例:
UserParameter=Nginx.active[*], /usr/bin/curl -s "| awk '/^Active/ {print $NF}' UserParameter=Nginx.reading[*], /usr/bin/curl -s "| grep 'Reading' | cut -d" " -f2 UserParameter=Nginx.writing[*], /usr/bin/curl -s "| grep 'Writing' | cut -d" " -f4 UserParameter=Nginx.waiting[*], /usr/bin/curl -s "| grep 'Waiting' | cut -d" " -f6 UserParameter=Nginx.accepted[*], /usr/bin/curl -s "| awk '/^([ \t]+[0-9]+){3}/ {print $$1}' UserParameter=Nginx.handled[*], /usr/bin/curl -s "| awk '/^([ \t]+[0-9]+){3}/ {print $$2}' UserParameter=Nginx.requests[*], /usr/bin/curl -s "| awk '/^([ \t]+[0-9]+){3}/ {print $$3}' UserParameter=os.free[*], free | awk '$$1~NAME {print $$(COLUMN+1)}' IGNORECASE=1 NAME="$1" COLUMN=$2 UserParameter=Mysql.dml[*] -h$1 -u$2 -p$3 -e 'SHOW GLOBAL STATUS' | awk '/^Com_$4\>/ {print $$2}'
正则表达式 词尾锚定在调试mysql的时候,遇到一些问题。正则表达式匹配不够精确,有多个值:
$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_select/ {print $0}' Com_select 67679 $ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_update/ {print $0}' Com_update 1098 Com_update_multi 0 $ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete/ {print $0}' Com_delete 678 Com_delete_multi 0 $ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_insert/ {print $0}' Com_insert 38494 Com_insert_select 0 $
这里是加了词尾锚定:
$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete\>/ {print $0}' Com_delete 708 $
词尾锚定是 \>,顺便词首就是 \<。
其实也没那么复杂,还有很多办法:
$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete[" "\t]/ {print $0}' Com_delete 712 $ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete[^_]/ {print $0}' Com_delete 715 $ mysql -e 'SHOW GLOBAL STATUS' | awk '$1 == "Com_delete" {print $0}' Com_delete 717 $
system.run
这个是zabbix内置key,也能够实现同样的功能,那么到底用哪个好?
内置key system.run这是一个内置key:
system.run[command,
在主机上指定的命令的执行。返回命令执行结果的文本值。如果指定NOWAIT的模式,这将返回执行命令的结果1。
command: 命令 mode: wait (默认值, 执行超时时间), nowait (不等待) 最大可用返回512KB数据,包含空白数据。命令输出数据必须是文本
默认agent不支持,安全隐患还是很大的。需要agent端开启RemoteCommand,允许远程执行命令。
优劣比较通过这个也能实现自定义监控功能,而且不用去agent上定义UserParameter。直接在web就能完成全部操作。这个可能是好处。用UserParameter,如果agent多,需要每一台agent上都去设置UserParameter,这就很烦。不过还有自动化运维工具可以解决批量更新、操作文件的问题。
不推荐使用 一方面是安全隐患,所以这个功能默认是关闭的。如果开启,使用Web服务就能通过这个key让agent执行任意命令了。虽然这里还会受到agent端zabbix账号的命令权限的限制。但是很多情况下可能都会为了方便,直接sudo就有了高权限。另一方面是丑。命令全写在key的参数里可能会很长。使用自定义参数,可以自定义key的值,让key值简短一些,并且表达式看起来也更漂亮。
一个求助 system.run 使用问题的帖子,在问题解决后解答者给的最后的回复:
Cool, one more hint: UserParameteres, this could shorten your keys and make the expressions look prettier
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~