- Linux自动化运维
- 一、文本三剑客
- 二、Shell脚本编程
- 三、Zabbix监控
- 四、Prometheus监控
- 五、Memcached
- 六、Kickstart
- 七、Rsync
- 八、Ansible
Linux自动化运维
一、文本三剑客
1、通配符
Linux 中的通配符是一种特殊的符号,用于匹配文件名或目录名。这些通配符可以帮助用户更快地输入命令和文件路径。或者可以理解为用户文件名和路径的匹配。由shell进行解析,一般情况下与某些命令配合使用,如:find,cp,mv,ls,rm等。
1.1、Shell中常见的通配符如下
*: 匹配0或多个字符
?: 匹配任意一个字符
[....]: 匹配方括号中任意单个字符
[!....]: 匹配除了方括号中指定字符之外的字符
范围 [a-z]: 匹配指定范围内的任意一个字符
大括号 {}: 用于匹配由逗号分隔的单个字符串
1.2、Shell元字符
在 Shell 脚本中,除了通配符,还有一些特殊的元字符需要了解。这些元字符有特殊的含义,在编写 Shell 脚本时需要特别注意。以下是 Shell 中常见的一些元字符及其用法
IFS:<tab>/<space>/<enter>
=:设定变量
$:取变量值
>/< :重定向
|:管道
&:后台执行命令
():在子shell中执行命令/运算或命令替换
{}:用于扩展变量、生成序列等。
例如 echo {A..Z} 会输出 A 到 Z 的大写字母序列。
;:命令结束后,忽略其返回值,继续执行下一个命令
&&:命令结束后,若为true,继续执行下一个命令
||:命令结束后,若为false,继续执行下一个命令
!:非
#:注释
\:转义符
Shell中的转义符:
1. 反斜杠 \: 是最常用的转义符,用于转义其后的一个字符
例如 echo "This is a backslash: \\" 会输出 This is a backslash: \
2. 单引号'...': 可以将整个字符串括起来,使其中的特殊字符失去特殊含义,硬转义
3. 双引号"...": 内部的大部分特殊字符仍然保留特殊含义,软转义
示例:
echo 'Hello, $USER' 输出---> Hello, $USER
echo "Hello, $USER" 输出---> Hello, root
2、find文件查找
实时查找工具,通过遍历指定路径下的文件系统完成文件查找
工作特点:
- 查找速度略慢
- 精确查找
- 实时查找
- 可以满足多种条件匹配
find [选项] [路径] [查找条件 + 处理动作]
查找路径:指定具体目录路径,默认是当前文件夹
查找条件:指定的查找标准(文件名/大小/类型/权限等),默认是找出所有文件
处理动作:对符合条件的文件做什么操作,默认输出屏幕
2.1、查找条件
根据文件名查找:
‐name "filename" 支持global
‐iname "filename" 忽略大小写
‐regex "PATTERN" 以Pattern匹配整个文件路径字符串,而不仅仅是文件名称
根据属主和属组查找:
‐user USERNAME:查找属主为指定用户的文件
‐group GROUPNAME:查找属组为指定属组的文件
‐uid UserID:查找属主为指定的ID号的文件
‐gid GroupID:查找属组为指定的GID号的文件
‐nouser:查找没有属主的文件
‐nogroup:查找没有属组的文件
根据文件类型查找:
‐type Type:
f/d/l/s/b/c/p
根据文件大小来查找:
‐size [+|‐]N[bcwkMG]
根据时间戳:
天:
‐atime [+|‐]N
‐mtime
‐ctime
分钟:
‐amin N
‐cmin N
‐mmin N
根据权限查找:
‐perm [+|‐]MODE
MODE:精确权限匹配
/MODE:任何一类(u,g,o)对象的权限中只要能一位匹配即可
‐MODE:每一类对象都必须同时拥有为其指定的权限标准
组合条件:
与:‐a 多个条件and并列
或:‐o 多个条件or并列
非:‐not 条件取反
2.1.1、示例:
- 根据文件名查找
[root@localhost ~]# find /etc -name "ifcfg-ens33"
[root@localhost ~]# find /etc -iname "ifcfg-ens33" # 忽略大小写
[root@localhost ~]# find /etc -iname "ifcfg*"
- 按文件大小
[root@localhost ~]# find /etc -size +5M # 大于5M
[root@localhost ~]# find /etc -size 5M # 等于5M
[root@localhost ~]# find /etc -size -5M # 小于5M
[root@localhost ~]# find /etc -size +5M -ls # 找到的处理动作-ls
- 指定查找的目录深度
[root@localhost ~]# find / -maxdepth 3 -a -name "ifcfg-ens33" # 最大查找深度
# -a是同时满足,-o是或
[root@localhost ~]# find / -mindepth 3 -a -name "ifcfg-ens33" # 最小查找深度
- 按时间找
[root@localhost ~]# find /etc -mtime +5 # 修改时间超过5天
[root@localhost ~]# find /etc -mtime 5 # 修改时间等于5天
[root@localhost ~]# find /etc -mtime -5 # 修改时间5天以内
- 按照文件属主、属组找,文件的属主和属组,会在下一篇详细讲解。
[root@localhost ~]# find /home -user xwz # 属主是xwz的文件
[root@localhost ~]# find /home -group xwz
[root@localhost ~]# find /home -user xwz -group xwz
[root@localhost ~]# find /home -user xwz -a -group root
[root@localhost ~]# find /home -user xwz -o -group root
[root@localhost ~]# find /home -nouser # 没有属主的文件
[root@localhost ~]# find /home -nogroup # 没有属组的文件
- 按文件类型
[root@localhost ~]# find /dev -type d
- 按文件权限
[root@localhost ~]# find / -perm 644 -ls
[root@localhost ~]# find / -perm -644 -ls # 权限小于644的
[root@localhost ~]# find / -perm 4000 -ls
[root@localhost ~]# find / -perm -4000 -ls
- 按正则表达式
[root@localhost ~]# find /etc -regex '.*ifcfg-ens[0-9][0-9]'
# .* 任意多个字符
# [0-9] 任意一个数字
2.2、处理动作
‐print:默认的处理动作,显示至屏幕
‐ls:类型于对查找到的文件执行“ls ‐l”命令
‐delete:删除查找到的文件
‐fls /path/to/somefile:查找到的所有文件的长格式信息保存至指定文件中
‐ok COMMAND {}\:对查找到的每个文件执行由COMMAND指定的命令
并且对于每个文件执行命令之前,都会交换式要求用户确认
‐exec COMMAND {} \:对查找到的每个文件执行由COMMAND指定的命令
{}:用于引用查找到的文件名称自身
[root@server1 ~]# find /etc/init.d/ -perm -111 -exec cp -r {} dir1/ \;
3、正则表达式
正则表达式是一种强大的文本匹配和处理工具。它允许你定义复杂的匹配模式,在文本中查找、替换和操作数据。正则表达式被广泛应用于各种文本处理工具和命令中,如 sed、awk、grep 等.....
1. 字符匹配
- `.`:匹配任意单个字符
- `[]`:匹配指定范围内任意单个字符 `[a-z]` `[0-9]`
- `[^]`:匹配指定范围外任意单个字符 `[^a-z]` `[^0-9]`
- `[:alnum:]`:字母与数字字符
- `[:alpha:]`:字母
- `[:ascii:]`:ASCII 字符
- `[:blank:]`:空格或制表符
- `[:cntrl:]`:ASCII 控制字符
- `[:digit:]`:数字
- `[:graph:]`:非控制、非空格字符
- `[:lower:]`:小写字母
- `[:print:]`:可打印字符
- `[:punct:]`:标点符号字符
- `[:space:]`:空白字符,包括垂直制表符
- `[:upper:]`:大写字母
- `[:xdigit:]`:十六进制数字
2. 匹配次数
- `*`:匹配前面的字符任意次数
- `.*`:匹配任意长度的字符
- `\?`:匹配其前面字符 0 或 1 次,即前面的可有可无 `'a\?b'`
- `\+`:匹配其前面的字符至少 1 次 `'a\+b'`
- `\{m\}`:匹配前面的字符 m 次
- `\{m,n\}`:匹配前面的字符至少 m 次,至多 n 次
- `\{0,n\}`:匹配前面的字符至多 n 次
- `\{m,\}`:匹配前面的字符至少 m 次
3. 位置锚定
- `^`:行首锚定,用于模式的最左侧
- `$`:行末锚定,用于模式的最右侧
- `^PATTERN$`:用于模式匹配整行
- `^$`:空行
- `\<` 或 `\b`:词首锚定,用于单词模式的左侧
- `\>` 或 `\b`:词尾锚定,用于单词模式的右侧
- `\<PATTERN\>`:匹配整个单词 `'\<hello\>'`
4. 分组
- `()`: 用于分组,可以对一组字符应用量词等操作
- 分组括号中的模式匹配到的内容会被正则表达式引擎记录于内部的变量中
- `\1`、`\2` 等: 用于引用前面匹配的分组内容
- `\1`:从左侧起,第一个左括号以及与之匹配右括号之间的模式所匹配到的字符;
4、文本三剑客之grep
grep作用:过滤文本内容
选项 | 描述 |
---|---|
-E :--extended--regexp | 模式是扩展正则表达式(ERE) |
-i :--ignore--case | 忽略大小写 |
-n: --line--number | 打印行号 |
-o:--only--matching | 只打印匹配的内容 |
-c:--count | 只打印每个文件匹配的行数 |
-B:--before--context=NUM | 打印匹配的前几行 |
-A:--after--context=NUM | 打印匹配的后几行 |
-C:--context=NUM | 打印匹配的前后几行 |
--color[=WHEN] | 匹配的字体颜色,别名已定义了 |
-v:--invert--match | 打印不匹配的行 |
-e | 多点操作eg:grep -e "^s" -e "s$" |
4.1、案例
文本内容:
[root@localhost ~]# python -c "import this" > file
[root@localhost ~]# cat file
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
案例1:过滤出所有包含a的行,无论大小写
[root@localhost ~]# grep -i "a" file
[root@localhost ~]# grep "a" file
结果.....
案例2:过滤出所有包含a的行,无论大小写,并且显示该行所在的行号
[root@localhost ~]# grep -in "a" file
3:Beautiful is better than ugly.
4:Explicit is better than implicit.
5:Simple is better than complex.
6:Complex is better than complicated.
7:Flat is better than nested.
8:Sparse is better than dense.
9:Readability counts.
10:Special cases aren't special enough to break the rules.
11:Although practicality beats purity.
12:Errors should never pass silently.
14:In the face of ambiguity, refuse the temptation to guess.
15:There should be one-- and preferably only one --obvious way to do it.
16:Although that way may not be obvious at first unless you're Dutch.
17:Now is better than never.
18:Although never is often better than *right* now.
19:If the implementation is hard to explain, it's a bad idea.
20:If the implementation is easy to explain, it may be a good idea.
21:Namespaces are one honking great idea -- let's do more of those!
案例3:仅仅打印出所有匹配的字符或者字符串
[root@localhost ~]# grep -o "a" file
结果....
案例4:统计匹配到的字符或字符串总共的行数
[root@localhost ~]# grep -c "a" file
18
案例5:打印所匹配到的字符串的前几行
[root@localhost ~]# grep -B 2 "Simple" file
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
案例6:打印所匹配到的字符串的后几行
[root@localhost ~]# grep -A 3 "Simple" file
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
案例7:打印所匹配到的字符串的前后几行
[root@localhost ~]# grep -C 1 "Simple" file
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
案例8:取反,过滤出不包含Simple的行
[root@localhost ~]# grep -vn "Simple" file
1:The Zen of Python, by Tim Peters
2:
3:Beautiful is better than ugly.
4:Explicit is better than implicit.
6:Complex is better than complicated.
7:Flat is better than nested.
8:Sparse is better than dense.
9:Readability counts.
10:Special cases aren't special enough to break the rules.
11:Although practicality beats purity.
12:Errors should never pass silently.
13:Unless explicitly silenced.
14:In the face of ambiguity, refuse the temptation to guess.
15:There should be one-- and preferably only one --obvious way to do it.
16:Although that way may not be obvious at first unless you're Dutch.
17:Now is better than never.
18:Although never is often better than *right* now.
19:If the implementation is hard to explain, it's a bad idea.
20:If the implementation is easy to explain, it may be a good idea.
21:Namespaces are one honking great idea -- let's do more of those!
4.2、正则表达式(基于grep)
- 功能就是用来检索、替换那些符合某个模式(规则)的文本,正则表达式在每种语言中都会有;
- 正则表达式就是为了处理大量的文本或字符串而定义的一套规则和方法
- 通过定义的这些特殊符号的辅助,系统管理员就可以快速过滤,替换或输出需要的字符串
- Linux正则表达式一般以行为单位处理
4.2.1、基础正则表达式
符号 | 描述 |
---|---|
. | 匹配任意单个字符(必须存在) |
^ | 匹配以某个字符开头的行 |
$ | 配以什么字符结尾的行 |
* | 匹配前面的一个字符出现0次或者多次;eg:a*b |
.* | 表示任意长度的任意字符 |
[] | 表示匹配括号内的一个字符 |
[^] | 匹配[^字符]之外的任意一个字符 |
[] | 匹配非[^字符]内字符开头的行 |
< | 锚定 单词首部;eg:\<root |
> | 锚定 单词尾部:eg:root> |
{m,n} | 表示匹配前面的字符出现至少m次,至多n次 |
() | 表示对某个单词进行分组;\1表示第一个分组进行调用 |
4.2.2、扩展正则
- egrep ...
- grep -E ...
- 扩展正则支持所有基础正则;并有补充
- 扩展正则中{}和[]不用转义可以直接使用;
符号 | 描述 | |
---|---|---|
+ | 表示前面的字符至少出现1次的情况 | |
\ | 表示“或” | |
? | 表示前面的字符至多出现1次的情况 |
最常用:查看配置文件时去除所有的注释和空行
[root@localhost ~]# grep -Ev "^#|^$" /etc/ssh/sshd_config
获取IP地址:
[root@localhost ~]# ifconfig ens33 | grep inet | grep -E '\.' | grep -oE '([[:digit:]]{1,3}\.){3}[[:digit:]]{1,3}' | head -n 1
192.168.88.10
5、文本三剑客之sed
(文本处理工具)
sed 是一个强大的文本处理工具,它可以用于对文本进行搜索、替换、删除、插入等操作。
sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
5.1、语法
sed的命令格式: sed [option] 'sed command' filename
sed的脚本格式:sed [option] ‐f 'sed script' filename
常用选项:
‐n :只打印模式匹配的行
‐e :直接在命令行模式上进行sed动作编辑,此为默认选项
‐f :将sed的动作写在一个文件内,用–f filename 执行filename内的sed动作
‐r :支持扩展表达式
‐i :直接修改文件内容
查询文本的方式
使用行号和行号范围
x:行号
x,y:从x行到y行
x,y!:x行到y行之外
/pattern:查询包含模式的行
/pattern/, /pattern/:查询包含两个模式的行
/pattern/,x:x行内查询包含模式的行
x,/pattern/:x行后查询匹配模式的行
5.2、动作说明
常用选项:
p:打印匹配的行(‐n)
=:显示文件行号
a\:指定行号后添加新文本
i\:指定行号前添加新文本
d:删除定位行
c\:用新文本替换定位文本
w filename:写文本到一个文件
r filename:从另一个文件读文本
s///:替换
替换标记:
g:行内全局替换
p:显示替换成功的行
w:将替换成功的结果保存至指定文件
q:第一个模式匹配后立即退出
{}:在定位行执行的命令组,用逗号分隔
g:将模式2粘贴到/pattern n/
5.3、案例
在testfile文件的第四行后添加一行,并将结果输出到标准输出
[root@localhost ~]# vim testfile
[root@localhost ~]# sed -e 4a\newline testfile
line one
line two
line three
line four
newline
line five
5.3.1、以行为单位的新增/删除
将 /etc/passwd 的内容列出并且列印行号,同时,请将第 2~5 行删除
[root@localhost ~]# nl /etc/passwd | sed '2,5d'
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
19 chrony:x:998:996::/var/lib/chrony:/sbin/nologin
只删除第2行
[root@localhost ~]# nl /etc/passwd | sed '2d'
1 root:x:0:0:root:/root:/bin/bash
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
19 chrony:x:998:996::/var/lib/chrony:/sbin/nologin
删除第3行到最后一行的内容
[root@localhost ~]# nl /etc/passwd | sed '3,$d'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
在第2行后面新增helloworld字符
[root@localhost ~]# nl /etc/passwd | sed '2a\hello world'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
hello world
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
19 chrony:x:998:996::/var/lib/chrony:/sbin/nologin
加到第2行的前面
[root@localhost ~]# nl /etc/passwd | sed '2i\hello world'
1 root:x:0:0:root:/root:/bin/bash
hello world
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
19 chrony:x:998:996::/var/lib/chrony:/sbin/nologin
添加多行内容
[root@localhost ~]# nl /etc/passwd | sed "2a\hello world\nnihao"
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
hello world
nihao
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
19 chrony:x:998:996::/var/lib/chrony:/sbin/nologin
5.3.2、以行为单位的替换与显示
将第2-5行的内容取代为”No 2-5 number“
[root@localhost ~]# nl /etc/passwd | sed '2,5c\No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
19 chrony:x:998:996::/var/lib/chrony:/sbin/nologin
修改SElinux的模式
[root@localhost ~]# nl /etc/selinux/config | sed '7c\SELINUX=permissive'
1 # This file controls the state of SELinux on the system.
2 # SELINUX= can take one of these three values:
3 # enforcing - SELinux security policy is enforced.
4 # permissive - SELinux prints warnings instead of enforcing.
5 # disabled - No SELinux policy is loaded.
SELINUX=permissive
7 # SELINUXTYPE= can take one of three values:
8 # targeted - Targeted processes are protected,
9 # minimum - Modification of targeted policy. Only selected processes are protected.
10 # mls - Multi Level Security protection.
11 SELINUXTYPE=targeted
仅列出 /etc/passwd 文件内的第 5-7 行
[root@localhost ~]# nl /etc/passwd | sed -n '5,7p'
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
5.3.3、数据的搜寻与显示
搜索 /etc/passwd有root关键字的行
[root@localhost ~]# nl /etc/passwd | sed -n '/root/p'
1 root:x:0:0:root:/root:/bin/bash
10 operator:x:11:0:operator:/root:/sbin/nologin
5.3.4、数据的搜寻并删除
删除/etc/passwd所有包含root的行,其他行输出
[root@localhost ~]# nl /etc/passwd | sed '/root/d'
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8 halt:x:7:0:halt:/sbin:/sbin/halt
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
14 systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
15 dbus:x:81:81:System message bus:/:/sbin/nologin
16 polkitd:x:999:998:User for polkitd:/:/sbin/nologin
17 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
19 chrony:x:998:996::/var/lib/chrony:/sbin/nologin
5.3.5、数据的搜寻并执行命令
搜索/etc/passwd,找到root对应的行,执行后面花括号中的一组命令,每个命令之间用分号分隔,这里把bash替换为blueshell,再输出这行
[root@localhost ~]# nl /etc/passwd | sed -n '/root/{s/bash/blueshell/p;q}'
1 root:x:0:0:root:/root:/bin/blueshell
5.3.6、数据的搜寻并替换
除了整行的处理模式之外, sed 还可以用行为单位进行部分数据的搜寻并取代
sed 's/要被取代的字串/新的字串/g'
案例:替换IP信息
先使用ifconfig命令查看当前的IP信息
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.88.10 netmask 255.255.255.0 broadcast 192.168.88.255
inet6 fe80::aee0:741:927e:335b prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:ae:c9:0f txqueuelen 1000 (Ethernet)
RX packets 4160 bytes 727537 (710.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2660 bytes 362687 (354.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
取出IP地址所在的行
[root@localhost ~]# ifconfig | sed -n '/netmask/p'
inet 192.168.88.10 netmask 255.255.255.0 broadcast 192.168.88.255
inet 127.0.0.1 netmask 255.0.0.0
取出IP地址前后不需要的内容
[root@localhost ~]# ifconfig | sed -n '/netmask/p' | sed 's/^.*inet//g'
192.168.88.10 netmask 255.255.255.0 broadcast 192.168.88.255
127.0.0.1 netmask 255.0.0.0
[root@localhost ~]# ifconfig | sed -n '/netmask/p' | sed 's/^.*inet//g' | sed 's/netmask.*$//g'
192.168.88.10
127.0.0.1
取出第一个IP
[root@localhost ~]# ifconfig | sed -n '/netmask/p' | sed 's/^.*inet//g' | sed 's/netmask.*$//g'|sed -n '1p'
192.168.88.10
最终获得当前主机的IP地址
其他方式:
[root@localhost ~]# ip a|sed -n '/inet /p'| sed 's/^.*inet //g'|sed 's/\/.*$//g'| sed -n '2p'
192.168.88.10
5.3.7、多点编辑
一条sed命令,删除/etc/passwd第三行到末尾的数据,并把bash替换为blueshell
[root@localhost ~]# nl /etc/passwd | sed -e '3,$d' -e 's/bash/blueshell/'
1 root:x:0:0:root:/root:/bin/blueshell
2 bin:x:1:1:bin:/bin:/sbin/nologin
-e 表示多点编辑,第一个编辑命令删除/etc/passwd第三行到末尾的数据,第二条命令搜索bash替换为blueshell
5.3.8、直接修改文件内容(危险动作)
在sed的选项中加入-i
,就是直接修改文件内容,实时生效,所以必须慎重使用。最好是先不加-i
参数在测试环境中测试好了以后,在去修改文件本身
[root@localhost ~]# cat testfile
line one
line two
line three
line four
line five
[root@localhost ~]# sed -i 's/line/hello/g' testfile
[root@localhost ~]# cat testfile
hello one
hello two
hello three
hello four
hello five
6、文本三剑客之awk
(文本分析工具)
awk 是一种编程语言,它可以进行更加复杂的数据处理和分析。
6.1、使用方法
awk '{pattern + action}' {filenames}
其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号({})不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。 pattern就是要表示的正则表达式,用斜杠括起来。
awk语言的最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,awk抽取信息后,才能进行其他文本操作。完整的awk脚本通常用来格式化文本文件中的信息。
通常,awk是以文件的一行为处理单位的。awk每接收文件的一行,然后执行相应的命令,来处理文本。
关注点:1、分隔符;2、具体的分析(定制化输出、数学与逻辑运算)
6.2、awk工作原理
执行 awk 时,它依次对/etc/passwd 中的每一行执行 print 命令
[root@localhost ~]# awk -F: '{print $0}' /etc/passwd
[root@localhost ~]# awk -F: '{print "用户名:" $1}' /etc/passwd
用户名:root
用户名:bin
用户名:daemon
用户名:adm
用户名:lp
用户名:sync
用户名:shutdown
用户名:halt
用户名:mail
用户名:operator
用户名:games
用户名:ftp
用户名:nobody
用户名:systemd-network
用户名:dbus
用户名:polkitd
用户名:sshd
用户名:postfix
用户名:chrony
[root@localhost ~]# awk -F":" '{print $1}' /etc/passwd
[root@localhost ~]# awk -F":" '{print $1 $3}' /etc/passwd
[root@localhost ~]# awk -F":" '{print $1 " " $3}' /etc/passwd
[root@localhost ~]# awk -F":" '{print "username:"$1 "\t tuid:" $3}' /etc/passwd
-F参数:指定分隔符,可指定一个或多个
print 后面做字符串的拼接
6.3、案例
6.3.1、查看文件内容
只查看test.txt文件(100行)内第20到第30行的内容(企业面试)
[root@localhost ~]# seq 100 > test.txt
[root@localhost ~]# awk '{if(NR>=20 && NR<=30) print $1}' test.txt
# NR为内置变量,表示行号,从1开始
20
21
22
23
24
25
26
27
28
29
30
6.3.2、过滤指定字符
已知文本内容如下:
[root@localhost ~]# cat testfile
I am nls, my qq is 12345678
从该文本中过滤出姓名和qq号,要求最后输出结果为:Name: QQ
[root@localhost ~]# awk -F '[ ,]' '{print "Name: " $3, "\nQQ: " $8}' testfile
Name: nls
QQ: 12345678
6.4、BEGIN 和 END 模块
- BEGIN 模块
BEGIN
模块是在 awk 开始处理输入数据之前执行的。- 它通常用于初始化一些变量或者打印一些提示信息。
-
比如在处理文件之前,先打印一行 "Processing the file..."。
-
END 模块
END
模块是在 awk 处理完所有输入数据之后执行的。- 它通常用于输出一些最终的统计信息或者结果。
- 比如在处理完文件后,打印出总共处理了多少行数据。
6.4.1、案例一:统计当前系统中存在的账户数量
[root@localhost ~]# awk 'BEGIN {count=0;print "[start] user count is: "count}{count++;print $0} END{print "[end] user count is: " count}' /etc/passwd
[start] user count is: 0
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
[end] user count is: 19
在BEGIN中先定义一个初始的变量count=0,并且打印一段话,然后第二个{}中是具体执行的语句。最后在END中定义结束的操作,打印count的值.....
awk 'BEGIN {一开始执行的内容,只执行一遍}{反复执行的内容} END{最后执行的内容,仅一遍}' /etc/passwd
6.4.2、实例二:统计某个文件夹下的文件占用的字节数
[root@localhost ~]# ll | awk 'BEGIN {size=0} {size=size+$5} END{print "size is ",size}'
size is 2226
[root@localhost ~]# ll | awk 'BEGIN {size=0} {size=size+$5} END{print "size is ",size/1024/1024,"M"}'
size is 0.00212288 M
6.5、awk运算符
运算符 | 描述 |
---|---|
赋值运算符 | |
= += -= = /= %= ^= *= | 赋值语句 |
逻辑运算符 | |
¦¦ | 逻辑或 |
&& | 逻辑与 |
正则运算符 | |
~ !~ | 匹配正则表达式和不匹配正则表达式 |
关系运算符 | |
< <= > >= != == | 关系运算符 |
算数运算符 | |
+ - | 加,减 |
* / & | 乘,除与求余 |
+ - ! | 一元加,减和逻辑非 |
^ *** | 求幂 |
++ -- | 增加或减少,作为前缀或后缀 |
其他运算符 | |
$ | 字段引用 |
空格 | 字符串链接符 |
?: | 三目运算符 |
ln | 数组中是否存在某键值 |
6.5.1、案例
awk 赋值运算符:a+=5;等价于: a=a+5;其他同类
[root@localhost ~]# awk 'BEGIN{a=5;a+=5;print a}'
10
awk逻辑运算符:判断表达式 a>2&&b>1为真还是为假,后面的表达式同理
[root@localhost ~]# awk 'BEGIN{a=1;b=2;print (a>2 && b>1,a=1 || b>1)}'
0 1
awk正则运算符:
[root@localhost ~]# awk 'BEGIN{a="100testaa";if(a~/100/) {print "OK"}else {print "NO"}}'
OK
关系运算符:
如: > < 可以作为字符串比较,也可以用作数值比较,关键看操作数如果是字符串就会转换为字符串比较。两个都为数字 才转为数值比较。字符串比较:按照ascii码顺序比较。
# 如果是字符的话,就会按照ASCII码的顺序进行比较
[root@localhost ~]# awk 'BEGIN{a=11;if(a>=9){print"OK"}}'
OK
[root@localhost ~]# awk 'BEGIN{a;if(a>=b){print"OK"}}'
OK
算术运算符:
说明:所有用作算术运算符进行操作,操作数自动转为数值,所有非数值都变为0
[root@localhost ~]# awk 'BEGIN{a="b";print a++,++a}'
0 2
[root@localhost ~]# awk 'BEGIN{a="20b4";print a++,++a}'
20 22
这里的a++ , ++a与其他语言一样:a++是先赋值加++;++a是先++再赋值
三目运算符?
[root@localhost ~]# awk 'BEGIN{a="b";print a=="b"?"ok":"err"}'
ok
[root@localhost ~]# awk 'BEGIN{a="b";print a=="c"?"ok":"err"}'
err
6.6、常用 awk 内置变量
变量名 | 属性 |
---|---|
$0 | 当前记录 |
$1~$n | 当前记录的第n个字段 |
FS | 输入字段分割符 默认是空格 |
RS | 输入记录分割符 默认为换行符 |
NF | 当前记录中的字段个数,就是有多少列 |
NR | 已经读出的记录数,就是行号,从1开始 |
OFS | 输出字段分割符 默认也是空格 |
ORS | 输出的记录分割符 默认为换行符 |
注:内置变量很多,参阅相关资料
字段分隔符 FS FS="\t+" 一个或多个 Tab 分隔
[root@localhost ~]# cat testfile
aa bb cc
[root@localhost ~]# awk 'BEGIN{FS="\t+"}{print $1,$2,$3}' testfile
aa bb cc
FS="[[:space:]+]" 一个或多个空白空格,默认的,匹配到不符合的就停止
[root@localhost ~]# cat testfile
aa bb cc
[root@localhost ~]# awk 'BEGIN{FS="[[:space:]+]"}{print $1,$2,$3}' testfile
aa bb
[root@localhost ~]# awk -F [[:space:]+] '{print $1,$2}' testfile
aa bb
FS="[" "]+" 以一个或多个空格分隔
[root@localhost ~]# cat testfile
aa bb cc
[root@localhost ~]# awk -F [" "]+ '{print $1,$2,$3}' testfile
aa bb cc
字段数量 NF:显示满足用:分割,并且有8个字段的
[root@localhost ~]# cat testfile
bin:x:1:1:bin:/bin:/sbin/nologin:888
bin:x:1:1:bin:/bin:/sbin/nologin
[root@localhost ~]# awk -F ":" 'NF==8{print $0}' testfile
bin:x:1:1:bin:/bin:/sbin/nologin:888
记录数量 NR (行号)
[root@localhost ~]# ifconfig ens33 | awk -F [" "]+ 'NR==2{print $3}'
192.168.88.10
RS 记录分隔符变量
# 写法一(命令行)
[root@localhost ~]# awk 'BEGIN{FS=":";RS="\n"}{ print $1","$2","$3}' testfile
bin,x,1
bin,x,1
# 写法二(awk脚本)
[root@localhost ~]# cat awk.txt
#!/bin/awk
BEGIN {
FS=":"
RS="\n"
}
{
print $1","$2","$3
}
[root@localhost ~]# awk -f awk.txt testfile
bin,x,1
bin,x,1
OFS:输出字段分隔符
[root@localhost ~]# awk 'BEGIN{FS=":";OFS="#"}{print $1,$2,$3}' testfile
bin#x#1
bin#x#1
ORS:输出记录分隔符
[root@localhost ~]# awk 'BEGIN{FS=":";ORS="\n\n"}{print $1,$2,$3}' testfile
bin x 1
bin x 1
6.7、awk正则
元字符 | 功能 | 示例 | 解释 |
---|---|---|---|
^ | 首航定位符 | /^root/ | 匹配所有以root开头的行 |
$ | 行尾定位符 | /root$/ | 匹配所有以root结尾的行 |
. | 匹配任意单个字符 | /r..t/ | 匹配字母r,然后两个任意字符,再以t结尾的行 |
* | 匹配0个或多个前导字符(包括回车) | /a*ool/ | 匹配0个或多个a之后紧跟着ool的行,比如ool,aaaaool等 |
+ | 匹配1个或多个前导字符 | /a+b/ | ab, aaab |
? | 匹配0个或1个前导字符 | /a?b/ | b,ab |
[] | 匹配指定字符组内的任意一个字符 | /^[abc]/ | 匹配以a或b或c开头的行 |
[^] | 匹配不在指定字符组内任意一个字符 | /\^[\^abc]/ | 匹配不以字母a或b或c开头的行 |
() | 子表达式组合 | /(rool)+/ | 表示一个或多个rool组合,当有一些字符需要组合时,使用括号括起来 |
¦ | 或者的意思 | /(root)\¦B/ | 匹配root或者B的行 |
\ | 转义字符 | /a\/\// | 匹配a// |
~,!~ | 匹配,不匹配的条件语句 | $1~/root/ | 匹配第一个字段包含字符root的所有记录 |
x{m}x{m,}x{m,n} | x重复m次x重复至少m次x重复至少m次,但是不超过n次 | /(root){3}//(root){3,}//(root){3,6}/ |
awk使用正则表达式
规则表达式
awk '/REG/{action} ' file
,其中/REG/为正则表达式,可以将满足条件的记录送入到:action 进行处理
[root@localhost ~]# awk '/root/{print$0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@localhost ~]# awk -F ":" '$5~/root/{print$0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# ifconfig ens33 | awk 'BEGIN{FS="[[:space:]:]+"} NR==2{print$3}'
192.168.88.10
布尔表达式
awk '布尔表达式{action}' file
仅当对前面的布尔表达式求值为真时, awk 才执行代码块
[root@localhost ~]# awk -F: '$1=="root"{print$0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# awk -F: '($1=="root")&&($5=="root"){print$0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
6.8、awk的if、循环、和数组
6.8.1、if 条件判断
awk 提供了非常好的类似于 C 语言的 if 语句
{
if ($1=="foo"){
if ($2=="foo"){
print"uno"
}else{
print"one"
}
}elseif($1=="bar"){
print "two"
}else{
print"three"
}
}
还可以转换为:
{
if ( $0 !~ /matchme/ ) {
print $1 $3 $4
}
}
6.8.2、while 循环
awk 的 while 循环结构,它等同于相应的 C 语言 while 循环。 awk 还有"do...while"循环,它在代码块结尾处对条件求值,而不像标准 while 循环那样在开始处求值。
它类似于其它语言中的"repeat...until"循环。以下是一个示例:
do...while 示例:
{
count=1 do {
print "I get printed at least once no matter what"
} while ( count !=1 )
}
与一般的 while 循环不同,由于在代码块之后对条件求值, "do...while"循环永远都至少执行一次。换句话说,当第一次遇到普通 while 循环时,如果条件为假,将永远不执行该循环
6.8.3、for 循环
awk 允许创建 for 循环,它就象 while 循环,也等同于 C 语言的 for 循环:
for ( initial assignment; comparison; increment ) {
code block
}
以下是一个简短示例:
[root@localhost ~]# cat awk.txt
#!/bin/awk
BEGIN{for ( x=1; x<=4; x++ ) {
print "iteration", x
}}
[root@localhost ~]# awk -f awk.txt
iteration 1
iteration 2
iteration 3
iteration 4
break 和 continue
如同 C 语言一样, awk 提供了 break 和 continue 语句。使用这些语句可以更好地控制 awk 的循环结构。
break语句:
[root@localhost ~]# cat awk.txt
#!/bin/awk
BEGIN{
x=1
while(1) {
print "iteration",x
if ( x==10 ){
break
}
x++
}
}
[root@localhost ~]# awk -f awk.txt
iteration 1
iteration 2
iteration 3
iteration 4
iteration 5
iteration 6
iteration 7
iteration 8
iteration 9
iteration 10
continue 语句:
[root@localhost ~]# cat awk.txt
#!/bin/awk
BEGIN{
x=1
while (1) {
if ( x==4 ) {
x++
continue
}
print "iteration", x
if ( x>20 ) {
break
}
x++
}
}
[root@localhost ~]# awk -f awk.txt
iteration 1
iteration 2
iteration 3
iteration 5
iteration 6
iteration 7
iteration 8
iteration 9
iteration 10
iteration 11
iteration 12
iteration 13
iteration 14
iteration 15
iteration 16
iteration 17
iteration 18
iteration 19
iteration 20
iteration 21
6.8.4、数组
AWK 中的数组都是关联数组,数字索引也会转变为字符串索引
在awk中,数组叫关联数组,与我们在其它编程语言中的数组有很大的区别。关联数组,简单来说,类似于python语言中的dict、java语言中的map,其下标不再局限于数值型,而可以是字符串,即下标为key,value=array[key]。既然为key,那其下标也不再是有序的啦。
#!/bin/awk
BEGIN{
cities[1]="beijing"
cities[2]="shanghai"
cities["three"]="guangzhou"
for( c in cities) {
print cities[c]
}
print cities[1]
print cities["1"]
print cities["three"]
}
awk -f awk.txt <filename>
用 awk 中查看服务器连接状态并汇总
[root@localhost ~]# netstat -an|awk '/^tcp/{++s[$NF]}END{for(a in s)print a,s[a]}'
LISTEN 4
ESTABLISHED 2
6.9、常用字符串函数
字符串函数的应用:
在 info 中查找满足正则表达式, /[0-9]+/ 用”!”替换,并且替换后的值,赋值给 info
[root@localhost ~]# awk 'BEGIN{info="this is a test2010test!";gsub(/[0-9]+/,"!",info);print info}'
this is a test!test!
如果查找到数字则匹配成功返回 ok,否则失败,返回未找到
[root@localhost ~]# awk 'BEGIN{info="this is a test2010test!";print index(info,"test")?"ok":"no found";}'
ok
从第 4 个 字符开始,截取 10 个长度字符串
[root@localhost ~]# awk 'BEGIN{info="this is a test2010test!";print substr(info,4,10);}'
s is a tes
分割 info,动态创建数组 tA,awk for …in 循环,是一个无序的循环。 并不是从数组下标1…n 开始
[root@localhost ~]# awk 'BEGIN{info="this is a test";split(info,tA," ");print length(tA);for(k in tA){print k,tA[k];}}'
4
4 test
1 this
2 is
3 a
二、Shell脚本编程
1、Shell脚本编程
- Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
- Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。(翻译官,帮你翻译命令给内核执行)
- Linux 的 Shell 种类众多,常见的有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
- 程序编程风格
- 过程式:以指令为中心,数据服务于命令
- 对象式:以数据为中心,命令服务于数据
- shell是一种过程式编程
- 过程式编程
- 顺序执行
- 循环执行
- 选择执行
- 编程语言分类
- 编译型语言
- 解释型语言(shell是一种解释型语言)
- 运行脚本
- 给予执行权限,通过具体的文件路径指定文件执行
- 直接运行解释器,将脚本作为解释器程序的参数运行
- bash退出状态码
- 范围是0-255
- 脚本中一旦遇到exit命令,脚本会立即终止,终止退出状态取决于exit命令后面的数字
- 如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态
2、变量
2.1、变量命名
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。
2.2、声明变量
访问变量的语法形式为:${var}
和 $var
。
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。
#!/bin/bash
word="hello"
echo ${word}
# Output: hello
2.3、只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
#!/bin/bash
rword="hello"
echo ${rword}
readonly rword
# rword="bye" # 如果放开注释,执行时会报错
2.4、删除变量
dword="hello" # 声明变量
echo ${dword} # 输出变量值
# Output: hello
unset dword # 删除变量
echo ${dword}
# Output: (空)
2.5、变量类型
- 局部变量 - 局部变量是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
- 环境变量 - 环境变量是对当前 shell 会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是
export
关键字,shell 脚本也可以定义环境变量。
常见的环境变量:
变量 | 描述 |
---|---|
$HOME |
当前用户的用户目录 |
$PATH |
用分号分隔的目录列表,shell 会到这些目录中查找命令 |
$PWD |
当前工作目录 |
$RANDOM |
0 到 32767 之间的整数 |
$UID |
数值类型,当前用户的用户 ID |
$PS1 |
主要系统输入提示符 |
$PS2 |
次要系统输入提示符 |
-
本地变量 - 生效范围仅为当前shell进程;(其他shell,当前的子sehll进程均无效)
-
变量赋值:name = “value”
-
位置变量 - shell 脚本中用来引用命令行参数的特殊变量。当你运行一个 shell 脚本时,可以在命令行上传递参数,这些参数可以在脚本中使用位置变量引用。
位置变量包括以下几种:
$0
: 表示脚本本身的名称。$1
,$2
,$3
, ...,$n
: 分别表示第1个、第2个、第3个...第n个参数。$#
: 表示传递给脚本的参数个数。$*
: 表示所有参数,将所有参数当作一个整体。$@
: 表示所有参数,但是每个参数都是独立的。
[root@localhost ~]# cat hello.sh
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Total arguments: $#"
echo "All arguments: $*"
echo "All arguments (separately): $@"
[root@localhost ~]# ./hello.sh world 2023
案例:统计给出指定文件的行数
[root@localhost ~]# cat hello.sh
#!/bin/bash
linecount="$(wc -l /etc/passwd | awk -F" " '{print $1}')"
echo "This file have ${linecount} lines"
[root@localhost ~]# bash hello.sh
This file have 21 lines
3、字符串
shell 字符串可以用单引号 ' '
,也可以用双引号 " "
,也可以不用引号。
- 单引号的特点
- 单引号里不识别变量
- 单引号里不能出现单独的单引号(使用转义符也不行),但可成对出现,作为字符串拼接使用。
- 双引号的特点
- 双引号里识别变量
- 双引号里可以出现转义字符
综上,推荐使用双引号。
3.1、字符串的拼接
# 使用单引号拼接
name1='white'
str1='hello, '${name1}''
str2='hello, ${name1}'
echo ${str1}_${str2}
# Output:
# hello, white_hello, ${name1}
# 使用双引号拼接
name2="black"
str3="hello, "${name2}""
str4="hello, ${name2}"
echo ${str3}_${str4}
# Output:
# hello, black_hello, black
3.2、获取字符串的长度
text="12345"
echo ${#text}
# Output:
# 5
3.3、截取子字符串
${variable:start:length}
text="12345"
echo ${text:2:2}
# Output:
# 34
4、数组
bash 只支持一维数组。
数组下标从 0 开始,下标可以是整数或算术表达式,其值应大于或等于 0。
4.1、创建/访问数组
array_name=(value1 value2 value3 ...)
array_name=([0]=value1 [1]=value2 ...)
# 案例一
[root@localhost ~]# cat a.sh
#!/bin/bash
# 创建数组
fruits=("apple" "banana" "orange")
# 访问元素
echo "First fruit: ${fruits[0]}"
echo "All fruits: ${fruits[@]}"
[root@localhost ~]# bash a.sh
First fruit: apple
All fruits: apple banana orange
# 案例二
[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo ${nums[1]}
[root@localhost ~]# bash a.sh
18
访问数组中所有的元素:
[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo ${nums[*]}
echo ${nums[@]}
[root@localhost ~]# bash a.sh
nls 18 teacher
nls 18 teacher
4.2、获取数组的长度
[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo "数组元素个数为: ${#nums[*]}"
[root@localhost ~]# bash a.sh
数组元素个数为: 3
4.3、删除元素
用unset
命令来从数组中删除一个元素:
[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo "数组元素个数为: ${#nums[*]}"
unset nums[0]
echo "数组元素个数为: ${#nums[*]}"
[root@localhost ~]# bash a.sh
数组元素个数为: 3
数组元素个数为: 2
5、运算符
5.1、算数运算符
下表列出了常用的算术运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | expr $x + $y 结果为 30。 |
- | 减法 | expr $x - $y 结果为 -10。 |
* | 乘法 | expr $x * $y 结果为 200。 |
/ | 除法 | expr $y / $x 结果为 2。 |
% | 取余 | expr $y % $x 结果为 0。 |
= | 赋值 | x=$y 将把变量 y 的值赋给 x。 |
== | 相等。用于比较两个数字,相同则返回 true。 | [ $x == $y ] 返回 false。 |
!= | 不相等。用于比较两个数字,不相同则返回 true。 | [ $x != $y ] 返回 true。 |
注意:条件表达式要放在方括号之间,并且要有空格,例如: [$x==$y]
是错误的,必须写成 [ $x == $y ]
示例:
- expr本身是一个命令,可以直接进行运算
x=10
y=20
echo "x=${x}, y=${y}"
val=`expr ${x} + ${y}`
echo "${x} + ${y} = $val"
val=`expr ${x} - ${y}`
echo "${x} - ${y} = $val"
val=`expr ${x} \* ${y}`
echo "${x} * ${y} = $val"
val=`expr ${y} / ${x}`
echo "${y} / ${x} = $val"
val=`expr ${y} % ${x}`
echo "${y} % ${x} = $val"
if [[ ${x} == ${y} ]]
then
echo "${x} = ${y}"
fi
if [[ ${x} != ${y} ]]
then
echo "${x} != ${y}"
fi
# Execute: ./operator-demo.sh
# Output:
# x=10, y=20
# 10 + 20 = 30
# 10 - 20 = -10
# 10 * 20 = 200
# 20 / 10 = 2
# 20 % 10 = 0
# 10 != 20
5.1.1、案例一:计算ID之和
计算/etc/passwd文件中第10个用户和第15个用户的ID之和
[root@localhost ~]# cat id.sh
#!/bin/bash
# userid1=$(cat /etc/passwd | sed -n '10p'| awk -F: '{print $3}')
# userid2=$(cat /etc/passwd | sed -n '15p'| awk -F: '{print $3}')
userid1=$(awk -F: '{if (NR==10) print $3}' /etc/passwd)
userid2=$(awk -F: '{if (NR==15) print $3}' /etc/passwd)
userid_sum=$[$userid1 + $userid2]
echo $userid_sum
# Execute:
[root@localhost ~]# bash id.sh
92
5.1.2、案例二:统计文件数量
统计/etc/,/var/,/usr/目录下有多少目录和文件
[root@localhost ~]# cat file.sh
#!/bin/bash
sum_etc=$(find /etc | wc -l)
sum_var=$(find /var | wc -l)
sum_usr=$(find /usr | wc -l)
sum=$[$sum_etc + $sum_var + $sum_usr]
echo $sum
# Execute:
[root@localhost ~]# bash file.sh
35686
5.2、关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 |
---|---|---|
-eq |
检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] 返回 false。 |
-ne |
检测两个数是否相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
-gt |
检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] 返回 false。 |
-lt |
检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] 返回 true。 |
-ge |
检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ $a -ge $b ] 返回 false。 |
-le |
检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] 返回 true。 |
示例:
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} -eq ${y} ]]; then
echo "${x} -eq ${y} : x 等于 y"
else
echo "${x} -eq ${y}: x 不等于 y"
fi
if [[ ${x} -ne ${y} ]]; then
echo "${x} -ne ${y}: x 不等于 y"
else
echo "${x} -ne ${y}: x 等于 y"
fi
if [[ ${x} -gt ${y} ]]; then
echo "${x} -gt ${y}: x 大于 y"
else
echo "${x} -gt ${y}: x 不大于 y"
fi
if [[ ${x} -lt ${y} ]]; then
echo "${x} -lt ${y}: x 小于 y"
else
echo "${x} -lt ${y}: x 不小于 y"
fi
if [[ ${x} -ge ${y} ]]; then
echo "${x} -ge ${y}: x 大于或等于 y"
else
echo "${x} -ge ${y}: x 小于 y"
fi
if [[ ${x} -le ${y} ]]; then
echo "${x} -le ${y}: x 小于或等于 y"
else
echo "${x} -le ${y}: x 大于 y"
fi
# Execute: ./operator-demo2.sh
# Output:
# x=10, y=20
# 10 -eq 20: x 不等于 y
# 10 -ne 20: x 不等于 y
# 10 -gt 20: x 不大于 y
# 10 -lt 20: x 小于 y
# 10 -ge 20: x 小于 y
# 10 -le 20: x 小于或等于 y
5.2.1、案例:猜数字小游戏
[root@localhost ~]# vim guess.sh
#!/bin/bash
num2=66
while true
do
read -p "请输入你要猜的数字:" num1
if [ $num1 -gt $num2 ];then
echo "你猜大了"
elif [ $num1 -lt $num2 ];then
echo "你猜小了"
else
echo "你猜对了"
break
fi
done
# Execute:
[root@localhost ~]# bash guess.sh
请输入你要猜的数字:60
你猜小了
请输入你要猜的数字:66
你猜对了
5.3、字符串运算符
下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":
运算符 | 说明 | 举例 |
---|---|---|
= |
检测两个字符串是否相等,相等返回 true。 | [ $a = $b ] 返回 false。 |
!= |
检测两个字符串是否相等,不相等返回 true。 | [ $a != $b ] 返回 true。 |
-z |
检测字符串长度是否为 0,为 0 返回 true。 | [ -z $a ] 返回 false。 |
-n |
检测字符串长度是否为 0,不为 0 返回 true。 | [ -n $a ] 返回 true。 |
str |
检测字符串是否为空,不为空返回 true。 | [ $a ] 返回 true。 |
示例:
x="abc"
y="xyz"
echo "x=${x}, y=${y}"
if [[ ${x} = ${y} ]]; then
echo "${x} = ${y} : x 等于 y"
else
echo "${x} = ${y}: x 不等于 y"
fi
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等于 y"
else
echo "${x} != ${y}: x 等于 y"
fi
if [[ -z ${x} ]]; then
echo "-z ${x} : 字符串长度为 0"
else
echo "-z ${x} : 字符串长度不为 0"
fi
if [[ -n "${x}" ]]; then
echo "-n ${x} : 字符串长度不为 0"
else
echo "-n ${x} : 字符串长度为 0"
fi
if [[ ${x} ]]; then
echo "${x} : 字符串不为空"
else
echo "${x} : 字符串为空"
fi
# Execute: ./operator-demo5.sh
# Output:
# x=abc, y=xyz
# abc = xyz: x 不等于 y
# abc != xyz : x 不等于 y
# -z abc : 字符串长度不为 0
# -n abc : 字符串长度不为 0
# abc : 字符串不为空
5.4、逻辑运算符
以下介绍 Shell 的逻辑运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 | ||
---|---|---|---|---|
&& |
逻辑的 AND | [[ ${x} -lt 100 && ${y} -gt 100 ]] 返回 false |
||
| | |
逻辑的 OR | [[ ${x} -lt 100 && ${y} -gt 100 ]] 返回 true |
示例:
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} -lt 100 && ${y} -gt 100 ]]
then
echo "${x} -lt 100 && ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 && ${y} -gt 100 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]
then
echo "${x} -lt 100 || ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 || ${y} -gt 100 返回 false"
fi
# Execute: ./operator-demo4.sh
# Output:
# x=10, y=20
# 10 -lt 100 && 20 -gt 100 返回 false
# 10 -lt 100 || 20 -gt 100 返回 true
5.5、布尔运算符
下表列出了常用的布尔运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 |
---|---|---|
! |
非运算,表达式为 true 则返回 false,否则返回 true。 | [ ! false ] 返回 true。 |
-o |
或运算,有一个表达式为 true 则返回 true。 | [ $a -lt 20 -o $b -gt 100 ] 返回 true。 |
-a |
与运算,两个表达式都为 true 才返回 true。 | [ $a -lt 20 -a $b -gt 100 ] 返回 false。 |
示例:
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等于 y"
else
echo "${x} != ${y}: x 等于 y"
fi
if [[ ${x} -lt 100 && ${y} -gt 15 ]]; then
echo "${x} 小于 100 且 ${y} 大于 15 : 返回 true"
else
echo "${x} 小于 100 且 ${y} 大于 15 : 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]; then
echo "${x} 小于 100 或 ${y} 大于 100 : 返回 true"
else
echo "${x} 小于 100 或 ${y} 大于 100 : 返回 false"
fi
if [[ ${x} -lt 5 || ${y} -gt 100 ]]; then
echo "${x} 小于 5 或 ${y} 大于 100 : 返回 true"
else
echo "${x} 小于 5 或 ${y} 大于 100 : 返回 false"
fi
# Execute: ./operator-demo3.sh
# Output:
# x=10, y=20
# 10 != 20 : x 不等于 y
# 10 小于 100 且 20 大于 15 : 返回 true
# 10 小于 100 或 20 大于 100 : 返回 true
# 10 小于 5 或 20 大于 100 : 返回 false
5.6、文件测试运算符
文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:
操作符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -c $file ] 返回 false。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。 |
-p file | 检测文件是否是有名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。 |
-r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。 |
-w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。 |
-x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。 |
-s file | 检测文件是否为空(文件大小是否大于 0),不为空返回 true。 | [ -s $file ] 返回 true。 |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。 |
⌨️ 『示例源码』 operator-demo6.sh
file="/etc/hosts"
if [[ -r ${file} ]]; then
echo "${file} 文件可读"
else
echo "${file} 文件不可读"
fi
if [[ -w ${file} ]]; then
echo "${file} 文件可写"
else
echo "${file} 文件不可写"
fi
if [[ -x ${file} ]]; then
echo "${file} 文件可执行"
else
echo "${file} 文件不可执行"
fi
if [[ -f ${file} ]]; then
echo "${file} 文件为普通文件"
else
echo "${file} 文件为特殊文件"
fi
if [[ -d ${file} ]]; then
echo "${file} 文件是个目录"
else
echo "${file} 文件不是个目录"
fi
if [[ -s ${file} ]]; then
echo "${file} 文件不为空"
else
echo "${file} 文件为空"
fi
if [[ -e ${file} ]]; then
echo "${file} 文件存在"
else
echo "${file} 文件不存在"
fi
# Execute: ./operator-demo6.sh
# Output:(根据文件的实际情况,输出结果可能不同)
# /etc/hosts 文件可读
# /etc/hosts 文件可写
# /etc/hosts 文件不可执行
# /etc/hosts 文件为普通文件
# /etc/hosts 文件不是个目录
# /etc/hosts 文件不为空
# /etc/hosts 文件存在
6、用户交互read
6.1、常用选项
选项 | 描述 |
---|---|
-p |
在读取输入之前显示提示信息 |
-n |
限制输入的字符数 |
-s |
隐藏用户输入 |
-a |
将输入存储到数组变量中 |
-d |
指定用于终止输入的分隔符 |
-t |
设置超时时间(以秒为单位) |
-e |
允许使用 Readline 编辑键 |
-i |
设置默认值 |
示例:
#!/bin/bash
read -p "input you name:" name
echo $name
# Output:
nls
6.2、案例:计算器
#!/bin/bash
echo "Enter the first number:"
read num1
echo "Enter the second number:"
read num2
echo "The sum is: $((num1 + num2))"
echo "The difference is: $((num1 - num2))"
echo "The product is: $((num1 * num2))"
echo "The quotient is: $((num1 / num2))"
# Output:
[root@localhost ~]# bash read.sh
Enter the first number:
10
Enter the second number:
10
The sum is: 20
The difference is: 0
The product is: 100
The quotient is: 1
7、控制语句
7.1、条件语句
跟其它程序设计语言一样,Bash 中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在[[ ]]
里的表达式。
由[[ ]]
(sh
中是[ ]
)包起来的表达式被称作 检测命令 或 基元。这些表达式帮助我们检测一个条件的结果
if
语句
if
在使用上跟其它语言相同。如果中括号里的表达式为真,那么then
和fi
之间的代码会被执行。fi
标志着条件代码块的结束。
# 写成一行
if [[ 1 -eq 1 ]]; then echo "1 -eq 1 result is: true"; fi
# Output: 1 -eq 1 result is: true
# 写成多行
if [[ "abc" -eq "abc" ]]
then
echo ""abc" -eq "abc" result is: true"
fi
# Output: abc -eq abc result is: true
if else
语句
同样,我们可以使用if..else
语句,例如:
if [[ 2 -ne 1 ]]; then
echo "true"
else
echo "false"
fi
# Output: true
if elif else
语句
有些时候,if..else
不能满足我们的要求。别忘了if..elif..else
,使用起来也很方便。
x=10
y=20
if [[ ${x} > ${y} ]]; then
echo "${x} > ${y}"
elif [[ ${x} < ${y} ]]; then
echo "${x} < ${y}"
else
echo "${x} = ${y}"
fi
# Output: 10 < 20
7.2、循环语句
循环其实不足为奇。跟其它程序设计语言一样,bash 中的循环也是只要控制条件为真就一直迭代执行的代码块。Bash 中有四种循环:for
,while
,until
和select
。
7.2.1、for循环
for
与 C 语言中非常像。看起来是这样:
for arg in elem1 elem2 ... elemN
do
### 语句
done
在每次循环的过程中,arg
依次被赋值为从elem1
到elemN
。这些值还可以是通配符或者大括号扩展。
当然,我们还可以把for
循环写在一行,但这要求do
之前要有一个分号,就像下面这样:
for i in {1..5}; do echo $i; done
还有,如果你觉得for..in..do
对你来说有点奇怪,那么你也可以像 C 语言那样使用for
,比如:
for (( i = 0; i < 10; i++ )); do
echo $i
done
当我们想对一个目录下的所有文件做同样的操作时,for
就很方便了。举个例子,如果我们想把所有的.bash
文件移动到script
文件夹中,并给它们可执行权限,我们的脚本可以这样写:
DIR=/home/zp
for FILE in ${DIR}/*.sh; do
mv "$FILE" "${DIR}/scripts"
done
# 将 /home/zp 目录下所有 sh 文件拷贝到 /home/zp/scripts
案例一:创建用户
创建用户user1‐user10家目录,并且在user1‐10家目录下创建1.txt‐10.txt
[root@localhost ~]# cat adduser.sh
#!/bin/bash
for i in {1..10}
do
mkdir /home/user$i
for j in $(seq 10)
do
touch /home/user$i/$j.txt
done
done
# Output:
[root@localhost ~]# bash adduser.sh
[root@localhost ~]# ls /home/
user01 user10 user3 user5 user7 user9
user1 user2 user4 user6 user8
[root@localhost ~]# ls /home/user1
10.txt 2.txt 4.txt 6.txt 8.txt
1.txt 3.txt 5.txt 7.txt 9.txt
案例二:检查磁盘占用
列出/var/目录下各个子目录占用磁盘大小
[root@localhost ~]# cat size.sh
#!/bin/bash
for i in `ls /var/`
do
path="/var/$i"
if [ -d $path ];then
du -sh $path
fi
done
# Output:
[root@localhost ~]# bash size.sh
0 /var/adm
654M /var/cache
0 /var/crash
8.0K /var/db
0 /var/empty
0 /var/games
0 /var/gopher
0 /var/kerberos
54M /var/lib
0 /var/local
0 /var/lock
3.2M /var/log
0 /var/mail
0 /var/nis
0 /var/opt
0 /var/preserve
0 /var/run
16K /var/spool
0 /var/tmp
0 /var/www
0 /var/yp
案例三:测试连通性
批量测试地址是否在线
[root@localhost ~]# cat ping.sh
#!/bin/bash
for i in {1..10}
do
ping -c 2 192.168.88.$i &> /dev/null
if [ $? -eq 0 ];then
echo 192.168.88.$i >> /root/host.txt
fi
done
# Output:
[root@localhost ~]# cat host.txt
192.168.88.1
192.168.88.2
192.168.88.10
7.2.2、while循环
while
循环检测一个条件,只要这个条件为 真,就执行一段命令。被检测的条件跟if..then
中使用的基元并无二异。因此一个while
循环看起来会是这样:
while 循环条件
do
### 语句
done
案例一:数字累加
计算1+2+..10的总和
[root@localhost ~]# cat sum.sh
#!/bin/bash
i=1
sum=0
while [ $i -lt 10 ]
do
let sum+=$i
let i++
done
echo $sum
# Output:
[root@localhost ~]# bash sum.sh
45
案例二:猜数字小游戏
加上随机数
[root@localhost ~]# cat guess.sh
#!/bin/bash
num2=$((RANDOM%100+1))
while true
do
read -p "请输入你要猜的数字:" num1
if [ $num1 -gt $num2 ];then
echo "你猜大了"
elif [ $num1 -lt $num2 ];then
echo "你猜小了"
else
echo "你猜对了"
break
fi
done
# Output:
[root@localhost ~]# bash guess.sh
请输入你要猜的数字:50
你猜小了
请输入你要猜的数字:70
你猜小了
请输入你要猜的数字:90
你猜大了
请输入你要猜的数字:80
你猜大了
7.2.3、until循环
until
循环跟while
循环正好相反。它跟while
一样也需要检测一个测试条件,但不同的是,只要该条件为 假 就一直执行循环:
until 条件测试
do
##循环体
done
示例:
[root@localhost ~]# cat until.sh
x=0
until [ ${x} -ge 5 ]; do
echo ${x}
x=`expr ${x} + 1`
done
# Output
[root@localhost ~]# bash until.sh
0
1
2
3
4
7.2.4、退出循环
break` 和 `continue
如果想提前结束一个循环或跳过某次循环执行,可以使用 shell 的break
和continue
语句来实现。它们可以在任何循环中使用。
break
语句用来提前结束当前循环。
continue
语句用来跳过某次迭代。
示例:
# 查找 10 以内第一个能整除 2 和 3 的正整数
i=1
while [[ ${i} -lt 10 ]]; do
if [[ $((i % 3)) -eq 0 ]] && [[ $((i % 2)) -eq 0 ]]; then
echo ${i}
break;
fi
i=`expr ${i} + 1`
done
# Output: 6
示例:
# 打印10以内的奇数
for (( i = 0; i < 10; i ++ )); do
if [[ $((i % 2)) -eq 0 ]]; then
continue;
fi
echo ${i}
done
# Output:
# 1
# 3
# 5
# 7
# 9
8、函数
8.1、函数定义
bash 函数定义语法如下:
[ function ] funname [()] {
action;
[return int;]
}
function FUNNAME(){
函数体
返回值
}
FUNNME #调用函数
💡 说明:
- 函数定义时,
function
关键字可有可无。- 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值。
- 函数返回值在调用该函数后通过
$?
来获得。- 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。
示例:
[root@localhost ~]# cat func.sh
#!/bin/bash
func(){
echo "这是我的第一个函数"
}
echo "------函数执行之前-------"
func
echo "------函数执行之前-------"
# Output:
[root@localhost ~]# bash func.sh
------函数执行之前-------
这是我的第一个函数
------函数执行之前-------
8.2、返回值
示例:
func(){
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
func
echo "输入的两个数字之和为 $? !"
#可以使用$?来获取返回值
8.3、函数参数
位置参数是在调用一个函数并传给它参数时创建的变量
变量 | 描述 | |
---|---|---|
$0 |
脚本名称 | |
$1 … $9 |
第 1 个到第 9 个参数列表 | |
${10} … ${N} |
第 10 个到 N 个参数列表 | |
$* or $@ |
除了$0 外的所有位置参数 |
|
$# |
不包括$0 在内的位置参数的个数 |
|
$FUNCNAME |
函数名称(仅在函数内部有值) |
示例:
#!/bin/bash
x=0
if [[ -n $1 ]]; then
echo "第一个参数为:$1"
x=$1
else
echo "第一个参数为空"
fi
y=0
if [[ -n $2 ]]; then
echo "第二个参数为:$2"
y=$2
else
echo "第二个参数为空"
fi
paramsFunction(){
echo "函数第一个入参:$1"
echo "函数第二个入参:$2"
}
paramsFunction ${x} ${y}
执行结果:
[root@localhost ~]# vim func1.sh
[root@localhost ~]# bash func1.sh
第一个参数为空
第二个参数为空
函数第一个入参:0
函数第二个入参:0
[root@localhost ~]# bash func1.sh 10 20
第一个参数为:10
第二个参数为:20
函数第一个入参:10
函数第二个入参:20
8.4、函数处理参数
另外,还有几个特殊字符用来处理参数:
参数处理 | 说明 |
---|---|
$# |
返回参数个数 |
$* |
返回所有参数 |
`$ | 参数处理 |
-------- | ------------------------------------------------ |
| $!
| 后台运行的最后一个进程的 ID 号 | | $@
| 返回所有参数 | | $-
| 返回 Shell 使用的当前选项,与 set 命令功能相同。 | | $?
| 函数返回值 |
runner() {
return 0
}
name=zp
paramsFunction(){
echo "函数第一个入参:$1"
echo "函数第二个入参:$2"
echo "传递到脚本的参数个数:$#"
echo "所有参数:"
printf "+ %s\n" "$*"
echo "脚本运行的当前进程 ID 号:$$"
echo "后台运行的最后一个进程的 ID 号:$!"
echo "所有参数:"
printf "+ %s\n" "$@"
echo "Shell 使用的当前选项:$-"
runner
echo "runner 函数的返回值:$?"
}
paramsFunction 1 "abc" "hello, \"zp\""
# Output:
# 函数第一个入参:1
# 函数第二个入参:abc
# 传递到脚本的参数个数:3
# 所有参数:
# + 1 abc hello, "zp"
# 脚本运行的当前进程 ID 号:26400
# 后台运行的最后一个进程的 ID 号:
# 所有参数:
# + 1
# + abc
# + hello, "zp"
# Shell 使用的当前选项:hB
# runner 函数的返回值:0
9、实际案例
9.1、案例一:开机显示系统信息脚本
[root@localhost ~]# cat os.sh
#!/bin/bash
yum install -y net-tools &> /dev/null
wangka=`ip a | grep ens | head -1 | cut -d: -f2`
System=$(hostnamectl | grep System | awk '{print $3,$4,$5}')
Kernel=$(hostnamectl | grep Kernel | awk -F: '{print $2}')
Virtualization=$(hostnamectl | grep Virtualization| awk '{print $2}')
Statichostname=$(hostnamectl | grep Static|awk -F: '{print $2}')
Ens32=$(ifconfig $wangka | awk 'NR==2 {print $2}')
Lo=$(ifconfig lo0 | awk 'NR==2 {print $2}')
NetworkIp=$(curl -s icanhazip.com)
echo "当前系统版本是:$System"
echo "当前系统内核是:$Kernel"
echo "当前虚拟平台是:$Virtualization"
echo "当前主机名是:$Statichostname"
echo "当前网卡$wangka的地址是:$Ens32"
echo "当前lo0接口的地址是:$Lo"
echo "当前公网地址是:$NetworkIp"
# Output:
[root@localhost ~]# bash os.sh
当前系统版本是:CentOS Linux 7
当前系统内核是: Linux 3.10.0-957.el7.x86_64
当前虚拟平台是:vmware
当前主机名是: localhost
当前网卡 ens33的地址是:192.168.88.10
当前lo0接口的地址是:127.0.0.1
当前公网地址是:153.101.189.87
9.2、案例二:监控httpd进程
需求:
1.每隔10s监控httpd的进程数,若进程数大于等于500,则自动重启Apache服务,并检测服务是否重启成功
2.若未成功则需要再次启动,若重启5次依旧没有成功,则向管理员发送告警邮件(使用echo输出已发送即可),并退出检测
3.如果启动成功,则等待1分钟后再次检测httpd进程数,若进程数正常,则恢复正常检测(10s一次),否则放弃重启并向管理员发送告警邮件,并退出检测
[root@localhost ~]# cat httpd.sh
#!/bin/bash
function check_httpd_process_number() {
process_num=`ps -ef | grep httpd| wc -l`
if [ $process_num -gt 50 ];then
systemctl restart httpd &> /dev/null
# 重启五次httpd确保服务启动
systemctl status httpd &> /dev/null
if [ $? -ne 0 ];then
num_restart_httpd=0
while true;do
let num_restart_httpd++
systemctl restart httpd &> /dev/null
systemctl status httpd &> /dev/null
[ $? -eq 0 ] && break
[ $num_restart_httpd -eq 6 ] && break
done
fi
# 判断重启服务的结果
systemctl status httpd &> /dev/null
[ $? -ne 0 ] && echo "apache未正常重启,已发送邮件给管理员" && return 1
sleep 60
return 0
# 再次判断进程是否正常
process_num=`ps -ef | grep httpd| wc -l`
if [ $process_num -gt 50 ] ;then
echo "apache经过重启进程数依然大于50"
return 1
else
return 0
fi
else
echo "进程数小于50"
sleep 3
return 0
fi
}
# 每十秒钟执行一次函数,检查进程是否正常
while true;do
check_httpd_process_number
[ $? -eq 1 ] && exit
done
# Output:
[root@localhost ~]# bash http.sh
进程数小于50
进程数小于50
进程数小于50
进程数小于50
# 复制窗口进行压力测试
[root@localhost ~]# for i in {1..10}; do ab -c $((10000/$i)) -n 2000 http://127.0.0.1/ & done
9.3、案例三:统计文件
统计两个目录下的相同文件,以及不同文件
#!/bin/bash
# server1的文件在/test/目录中,server2的文件在/root/demo中,通过md5值来判断文件一致性,最终输出相同文件以及各自的不同文件
#定义两个数组的索引
point1=0
point2=0
echo "/test/的文件:"
# 将server1上的文件的散列值记录到数组当中
for i in `ls /root/demo`;do
md5=`md5sum /root/demo/$i | awk '{print $1}'`
arrar1[$point1]=$md5:$i
echo ${arrar1[$point1]}
let point1++
done
echo "/root/demo的文件:"
# 将server2上的文件的散列值记录到数组当中
for i in `ls /test`;do
md5=`md5sum /test/$i | awk '{print $1}'`
arrar2[$point2]=$md5:$i
echo ${arrar2[$point2]}
let point2++
done
# 找出相同文件以及server1上的独立文件,server1的每个文件都和server2上进行比较
echo "-------------------------------"
for i in ${arrar1[@]};do
for j in ${arrar2[@]};do
temp_flag=0 #定义一个标志位,表示没找到相同的文件
server1_md5=`echo $i | awk -F: '{print $1}'`
server2_md5=`echo $j | awk -F: '{print $1}'`
server1_filename=`echo $i | awk -F: '{print $2}'`
server2_filename=`echo $j | awk -F: '{print $2}'`
if [ $server1_md5 == $server2_md5 ];then
echo -e "两边共同文件\t\t\t$server1_filename"
temp_flag=1 #找到了相同的文件
break
fi
done
if [ $temp_flag -eq 0 ];then
echo -e "server1不同文件\t\t\t$i"
fi
done
# 找出server2上的独立文件
for i in ${arrar2[@]};do
for j in ${arrar1[@]};do
temp_flag=0
server1_md5=`echo $i | awk -F: '{print $1}'`
server2_md5=`echo $j | awk -F: '{print $1}'`
server1_filename=`echo $i | awk -F: '{print $2}'`
server2_filename=`echo $j | awk -F: '{print $2}'`
if [ $server1_md5 == $server2_md5 ];then
temp_flag=1
break
fi
done
if [ $temp_flag -eq 0 ];then
echo -e "server2不同文件\t\t\t$i"
fi
done
三、Zabbix监控
1、Zabbix监控简介
1.1、zabbix优点
- 开源,无软件成本投入
- Server 对设备性能要求低
- 支持设备多,自带多种监控模板
- 支持分布式集中管理,有自动发现功能,可以实现自动化监控
- 开放式接口,扩展性强,插件编写容易
- 当监控的 item 比较多服务器队列比较大时可以采用主动状态,被监控客户端主动 从server 端去下载需要监控的 item 然后取数据上传到 server 端。 这种方式对服务器的负载比较小。
- Api 的支持,方便与其他系统结合
1.2、zabbix缺点
- 需在被监控主机上安装 agent,所有数据都存在数据库里, 产生的数据据很大,瓶颈主要在数据库。
- 项目批量修改不方便
- 社区虽然成熟,但是中文资料相对较少,服务支持有限;
- 入门容易,能实现基础的监控,但是深层次需求需要非常熟悉Zabbix并进行大量的二次定制开发难度较大
- 系统级别报警设置相对比较多,如果不筛选的话报警邮件会很多;并且自定义的项目报警需要自己设置,过程比较繁琐;
- 缺少数据汇总功能,如无法查看一组服务器平均值,需进行二次开发;
1.3、zabbix组件结构
- Zabbix_Server:整个监控体系中最核心的组件,它负责接收客户端发送的报告信息,所有配置、统计数据及操作 数据都由它组织。
- 数据库存储:所有配置信息和Zabbix收集到的数据都被存储在数据库中。
- Web界面:为了从任何地方和任何平台都可以轻松的访问Zabbix, 我们提供基于Web的Zabbix界面。该界面是 Zabbix Server的一部分,通常跟Zabbix Server运行在同一台物理机器上(!如果使用SQLite,Zabbix Web界面必 须要跟Zabbix Server运行在同一台物理机器上。)
- Zabbix_Proxy(可选):用于监控节点非常多的分布式环境中,它可以代理zabbix-server的功能,减轻zabbixserver的压力。
- Zabbix_Agent:zabbix-agent为客户端软件,用于采集各监控项目的数据,并把采集的数据传输给zabbixproxy或zabbix-server。
1.4、zabbix监控方式
- 被动模式:
- 被动检测:相对于agent而言;agent, server向agent请求获取配置的各监控项相关的数据,agent接收请求、获取数据并响应给server;
- 主动模式
- 主动检测:相对于agent而言;agent(active),agent向server请求与自己相关监控项配置,主动地将server配置的监控项相关的数据发送给server;
- 主动监控能极大节约监控server 的资源。
1.5、Zabbix架构
1.6、Zabbix常用术语
- 主机:一台你想监控的网络设备,用IP或域名表示
- 主机组:主机的逻辑组;它包含主机和模板。一个主机组里的主机和模板之间并没有任何直接的关联。通常在给不同用户组的主机分配权限时候使用主机组。
- 监控项:你想要接收的主机的特定数据,一个度量数据。
- 触发器:一个被用于定义问题阈值和“评估”监控项接收到的数据的逻辑表达式 当接收到的数据高于阈值时,触发器从“OK”变成“Problem”状态。当接收到的数据低于阈值时,触发器保留/返回一个“OK”的状态。
2、Zabbix-server部署
2.1、安装Zabbix 服务端
准备zabbix的yum源,这里通过安装zabbix-release软件包来自动获取zabbix的源。然后安装服务端相关组件
官网地址:https://www.zabbix.com/
[root@server1 ~]# rpm -Uvh https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-2.el7.noarch.rpm
[root@server1 ~]# yum install zabbix-server-mysql zabbix-web-mysql zabbix-agent -y
关闭防火墙和Selinux
[root@server1 ~]# setenforce 0
[root@server1 ~]# systemctl stop firewalld
2.2、准备数据库环境
安装数据库,这里还是选择mariadb即可
[root@server1 ~]# yum install -y mariadb-server
# 启动mariadb数据库
[root@server1 ~]# systemctl enable --now mariadb
# 初始化数据库并设置密码
[root@server1 ~]# mysqladmin -uroot password '123456'
# 测试数据库连接是否正常
[root@server1 ~]# mysql -uroot -p123456 -e "show databases;"
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
+--------------------+
创建zabbix所需数据库以及zabbix账户
# 创建zabbix库
MariaDB [(none)]> create database zabbix char set utf8 collate utf8_bin;
Query OK, 1 row affected (0.00 sec)
# 创建zabbix账户
MariaDB [(none)]> grant all privileges on zabbix.* to zabbix@localhost identified by '123456';
Query OK, 0 rows affected (0.00 sec)
# 刷新账户信息
MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.00 sec)
2.3、修改服务端相关配置
导入初始架构数据
[root@server1 ~]# zcat /usr/share/doc/zabbix-server-mysql*/create.sql.gz | mysql -uzabbix -D zabbix -p123456
# zabbix安装包中有一个SQL文件,这是zabbix架构的初始数据。
[root@server1 ~]# mysql -uzabbix -p123456 -D zabbix -e "show tables"
+----------------------------+
| Tables_in_zabbix |
+----------------------------+
| acknowledges |
| actions |
| alerts |
| application_discovery |
| application_prototype |
| application_template |
| applications |
| auditlog |
| auditlog_details |
| autoreg_host |
......
......
......
配置zabbix连接数据库
[root@server1 ~]# vim /etc/zabbix/zabbix_server.conf
DBPassword=123456
编辑zabbix网站中php的相关配置
[root@server1 ~]# vim /etc/httpd/conf.d/zabbix.conf
php_value max_execution_time 300
php_value memory_limit 128M
php_value post_max_size 16M
php_value upload_max_filesize 2M
php_value max_input_time 300
php_value always_populate_raw_post_data ‐1
php_value date.timezone Asia/Shanghai
# 配置解释
设置 PHP 脚本的最大执行时间为 300 秒(5分钟)
设置 PHP 脚本的最大内存使用限制为 128 MB
设置 POST 请求的最大数据大小为 16 MB
设置上传文件的最大大小为 2 MB
设置 PHP 脚本接受输入的最大时间为 300 秒(5分钟)
设置 PHP 总是填充 $HTTP_RAW_POST_DATA 变量
设置 PHP 的时区为 Asia/Shanghai(中国时区)
启动所有服务
# 安装zabbix的时候,会附带按照httpd来提供web服务,因为zabbix默认情况下是有一个web网站的
[root@server1 ~]# systemctl restart zabbix-server httpd mariadb zabbix-agent
2.4、服务端初始化
完成上述配置后,可以在浏览器中输入http://IP/zabbix
打开zabbix的web界面开始初始化
状态检测
确保所有的php检测都是ok的状态
连接数据库
数据库的信息按照图上的填入,其中密码是我们刚刚设置的zabbix用户的密码,端口号3306
zabbix信息
这里保持默认,name可以空着不填
完成安装
看到如下界面,说明zabbix初始化成功
登录zabbix的web网站
默认的username:Admin password:zabbix
修改中文显示
2.5、客户端配置
在server2上安装zabbix-agent客户端
[root@server2 ~]# rpm -Uvh https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-2.el7.noarch.rpm
[root@server2 ~]# yum install zabbix-agent -y
修改客户端配置文件
[root@server2 ~]# vim /etc/zabbix/zabbix_agentd.conf
# 主要修改以下三个参数
Server=192.168.175.10
ServerActive=192.168.175.10
Hostname=server1
启动zabbix-agent
[root@server2 ~]# setenforce 0
[root@server2 ~]# systemctl stop firewalld
[root@server2 ~]# systemctl start zabbix-agent.service
3、快速使用
3.1、主机监控
先简单的监控一个主机的状态和信息
添加一个主机
添加模板
zabbix内置了很多模板,比如监控Linux主机的模板,监控httpd的模板,监控MySQL的模板等等。我们可以直接使用这些模板,当然也可以自定义。一个模板中包含很多的应用集,每个应用集中又包含很多具体的监控项
创建应用集
模板中内置的很多的监控项,但是如果没有我们想要的监控项的话,我们可以手动添加。先创建一个应用集,然后再应用集中创建监控项
创建监控项
创建好了test应用集以后,我们再向该应用集中添加具体的监控项。
3.2、自定义配置监控项
3.2.1、Nginx进程监控
在server2上安装nginx服务,然后编写监控项配置文件,来监控nginx的进程数量
安装nginx
[root@server2 ~]# yum install -y epel-release
[root@server2 ~]# yum install -y nginx
[root@server2 ~]# systemctl enable --now nginx
配置监控项文件
[root@server2 ~]# vim /etc/zabbix/zabbix_agentd.d/userparameter_nginx.conf
UserParameter=nginx_process_num,ps aux | grep -c nginx
# 重启zabbix-agent
[root@server2 ~]# systemctl restart zabbix-agent
服务端验证
可以在服务端上安装一个zabbix工具zabbix-get
,可以通过该工具在命令行中验证监控项的是否生效
[root@server1 ~]# yum install -y zabbix-get
[root@server1 ~]# zabbix_get -s 192.168.88.20 -k nginx_process_num
7
添加监控项
在web网站上使用刚刚编写的监控项
查看刚刚添加的监控项键值的状态
查看具体的检测到的数据,在菜单栏中点击监测->最新数据,然后过滤刚刚的监控项
添加触发器
给该监控项添加触发器,让他能够出发警告通知
创建一个新的触发器
可以看到我们刚刚添加的触发器
手动宕机告警测试
在server2上手动关闭nginx后,在仪表盘中查看告警信息
[root@server2 ~]# systemctl stop nginx
查看仪表盘
可以看到这里有一个告警信息,说明我们刚刚创建的触发器生效了。
这样的话,运维工程师就可以直接在这个网站上看到当前服务器组或者集群中各个机器和服务的状态。 不需要在一个一个登录上去查看了。
但是只是这样还是不够只能,稍后我们将继续配置触发告警以后,通过邮件或者叮叮企业微信等平台向工程师发送告警信息。
3.3、常见服务的监控项
3.3.1、Redis自定义监控项
vim /usr/local/zabbix/etc/zabbix_agentd.conf.d/redis.conf
UserParameter=Redis.Status,/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 6379 ping |grep -c PONG
UserParameter=Redis_conn[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "connected_clients" | awk -F':' '{print $2}'
UserParameter=Redis_rss_mem[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "used_memory_rss" | awk -F':' '{print $2}'
UserParameter=Redis_lua_mem[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "used_memory_lua" | awk -F':' '{print $2}'
UserParameter=Redis_cpu_sys[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "used_cpu_sys" | awk -F':' '{print $2}'
UserParameter=Redis_cpu_user[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "used_cpu_user" | awk -F':' '{print $2}'
UserParameter=Redis_cpu_sys_cline[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "used_cpu_sys_children" | awk -F':' '{print $2}'
UserParameter=Redis_cpu_user_cline[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "used_cpu_user_children" | awk -F':' '{print $2}'
UserParameter=Redis_keys_num[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep -w "$$1" | grep -w "keys" | grep db$3 | awk -F'=' '{print $2}' | awk -F',' '{print $1}'
UserParameter=Redis_loading[*],/usr/local/redis/bin/redis-cli -h $1 -p $2 info | grep loading | awk -F':' '{print $$2}'
Redis.Status --检测Redis运行状态, 返回整数
Redis_conn --检测Redis成功连接数,返回整数
Redis_rss_mem --检测Redis系统分配内存,返回整数
Redis_lua_mem --检测Redis引擎消耗内存,返回整数
Redis_cpu_sys --检测Redis主程序核心CPU消耗率,返回整数
Redis_cpu_user --检测Redis主程序用户CPU消耗率,返回整数
Redis_cpu_sys_cline --检测Redis后台核心CPU消耗率,返回整数
Redis_cpu_user_cline --检测Redis后台用户CPU消耗率,返回整数
Redis_keys_num --检测库键值数,返回整数
Redis_loding --检测Redis持久化文件状态,返回整数
3.3.2、Nginx自定义监控项
vim /etc/nginx/conf.d/default.conf
location /nginx-status
{
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
vim /usr/local/zabbix/etc/zabbix_agentd.conf.d/nginx.conf
UserParameter=Nginx.active,/usr/bin/curl -s "http://127.0.0.1:80/nginx-status" | awk '/Active/ {print $NF}'
UserParameter=Nginx.read,/usr/bin/curl -s "http://127.0.0.1:80/nginx-status" | grep 'Reading' | cut -d" " -f2
UserParameter=Nginx.wrie,/usr/bin/curl -s "http://127.0.0.1:80/nginx-status" | grep 'Writing' | cut -d" " -f4
UserParameter=Nginx.wait,/usr/bin/curl -s "http://127.0.0.1:80/nginx-status" | grep 'Waiting' | cut -d" " -f6
UserParameter=Nginx.accepted,/usr/bin/curl -s "http://127.0.0.1:80/nginx-status" | awk '/^[ \t]+[0-9]+[ \t]+[0-9]+[ \t]+[0-9]+/ {print $1}'
UserParameter=Nginx.handled,/usr/bin/curl -s "http://127.0.0.1:80/nginx-status" | awk '/^[ \t]+[0-9]+[ \t]+[0-9]+[ \t]+[0-9]+/ {print $2}'
UserParameter=Nginx.requests,/usr/bin/curl -s "http://127.0.0.1:80/nginx-status" | awk '/^[ \t]+[0-9]+[ \t]+[0-9]+[ \t]+[0-9]+/ {print $3}'
3.3.3、TCP协议自定义监控项
vim /usr/local/zabbix/share/zabbix/alertscripts/tcp_connection.sh
#!/bin/bash
function ESTAB {
/usr/sbin/ss -ant |awk '{++s[$1]} END {for(k in s) print k,s[k]}' | grep 'ESTAB' | awk '{print $2}'
}
function TIMEWAIT {
/usr/sbin/ss -ant | awk '{++s[$1]} END {for(k in s) print k,s[k]}' | grep 'TIME-WAIT' | awk '{print $2}'
}
function LISTEN {
/usr/sbin/ss -ant | awk '{++s[$1]} END {for(k in s) print k,s[k]}' | grep 'LISTEN' | awk '{print $2}'
}
$1
vim /usr/local/zabbix/etc/zabbix_agentd.conf.d/cattcp.conf
UserParameter=tcp[*],/usr/local/zabbix/share/zabbix/alertscripts/tcp_connection.sh $1
tcp[TIMEWAIT] --检测TCP的驻留数,返回整数
tcp[ESTAB] --检测tcp的连接数、返回整数
tcp[LISTEN] --检测TCP的监听数,返回整数
3.3.4、系统监控自带监控项
agent.ping 检测客户端可达性、返回nothing表示不可达。1表示可达
system.cpu.load --检测cpu负载。返回浮点数
system.cpu.util -- 检测cpu使用率。返回浮点数
vfs.dev.read -- 检测硬盘读取数据,返回是sps.ops.bps浮点类型,需要定义1024倍
vfs.dev.write -- 检测硬盘写入数据。返回是sps.ops.bps浮点类型,需要定义1024倍
net.if.out[br0] --检测网卡流速、流出方向,时间间隔为60S
net-if-in[br0] --检测网卡流速,流入方向(单位:字节) 时间间隔60S
proc.num[] 目前系统中的进程总数,时间间隔60s
proc.num[,,run] 目前正在运行的进程总数,时间间隔60S
###处理器信息
通过zabbix_get 获取负载值
合理的控制用户态、系统态、IO等待时间剋保证进程高效率的运行
系统态运行时间较高说明进程进行系统调用的次数比较多,一般的程序如果系统态运行时间占用过高就需要优化程序,减少系统调用
io等待时间过高则表明硬盘的io性能差,如果是读写文件比较频繁、读写效率要求比较高,可以考虑更换硬盘,或者使用多磁盘做raid的方案
system.cpu.swtiches --cpu的进程上下文切换,单位sps,表示每秒采样次数,api中参数history需指定为3
system.cpu.intr --cpu中断数量、api中参数history需指定为3
system.cpu.load[percpu,avg1] --cpu每分钟的负载值,按照核数做平均值(Processor load (1 min average per core)),api中参数history需指定为0
system.cpu.load[percpu,avg5] --cpu每5分钟的负载值,按照核数做平均值(Processor load (5 min average per core)),api中参数history需指定为0
system.cpu.load[percpu,avg15] --cpu每5分钟的负载值,按照核数做平均值(Processor load (15 min average per core)),api中参数history需指定为0
3.3.5、自定义系统监控项
###内存相关
vim /usr/local/zabbix/etc/zabbix_agentd.conf.d/catcarm.conf
UserParameter=ram.info[*],/bin/cat /proc/meminfo |awk '/^$1:{print $2}'
ram.info[Cached] --检测内存的缓存使用量、返回整数,需要定义1024倍
ram.info[MemFree] --检测内存的空余量,返回整数,需要定义1024倍
ram.info[Buffers] --检测内存的使用量,返回整数,需要定义1024倍
####TCP相关的自定义项
vim /usr/local/zabbix/share/zabbix/alertscripts/tcp_connection.sh
#!/bin/bash
function ESTAB {
/usr/sbin/ss -ant |awk '{++s[$1]} END {for(k in s) print k,s[k]}' | grep 'ESTAB' | awk '{print $2}'
}
function TIMEWAIT {
/usr/sbin/ss -ant | awk '{++s[$1]} END {for(k in s) print k,s[k]}' | grep 'TIME-WAIT' | awk '{print $2}'
}
function LISTEN {
/usr/sbin/ss -ant | awk '{++s[$1]} END {for(k in s) print k,s[k]}' | grep 'LISTEN' | awk '{print $2}'
}
$1
vim /usr/local/zabbix/etc/zabbix_agentd.conf.d/cattcp.conf
UserParameter=tcp[*],/usr/local/zabbix/share/zabbix/alertscripts/tcp_connection.sh $1
tcp[TIMEWAIT] --检测TCP的驻留数,返回整数
tcp[ESTAB] --检测tcp的连接数、返回整数
tcp[LISTEN] --检测TCP的监听数,返回整数
4、Zabbix告警配置
4.1、Zabbix通过邮件告警
4.1.1、配置告警设置
配置E-mail参数
在上面菜单栏中选择管理->报警媒介类型
按照图中配置参数,最后面的密码是在QQ邮箱中申请的授权码
教程如下:https://www.hzhcontrols.com/new-2123428.html
修改Admin用户的报警媒介
配置告警动作
这里定义如果触发告警,应该怎么处理。以及如果发送邮件,邮件的内容是什么样的....
当然也可以自定义告警信息
4.1.2、邮件告警测试
手动停止server2上面的nginx服务以后,查看邮件
可以看到,成功通过邮件发送告警信息
当问题恢复以后,也会发送邮件给我们
4.2、Zabbix通过钉钉告警
4.2.1、创建钉钉群聊
先下载电脑版本的钉钉,然后创建一个群聊(创建群聊需要三个好友起步,大家可以相互之间添加好友)并且创建群聊
4.2.2、添加群机器人
添加自定义机器人,并且配置关键词
保存好Webhook,并且设置安全关键词为:告警
4.2.3、创建python脚本
由于zabbix无法直接向钉钉发送告警信息,所以这里需要借助python脚本来实现,让zabbix调用python脚本来向机器人接口发送信息
安装requests模块
[root@server1 ~]# yum install -y python-requests
python脚本如下:
[root@server1 alertscripts]# cd /usr/lib/zabbix/alertscripts
[root@server1 alertscripts]# vim zabbix_send_ding.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author: xxxxxxxx
import requests
import json
import sys
import os
headers = {'Content-Type': 'application/json;charset=utf-8'}
api_url = "https://oapi.dingtalk.com/robot/send?access_token=d68555399dc737c450c50ddac16ab6c529f3a99dc68bdf88ac60e88f1b4ef340"
def msg(text):
json_text= {
"msgtype": "text",
"at": {
"atMobiles": [
"18888888888"
],
"isAtAll": True
},
"text": {
"content": text
}
}
print requests.post(api_url,json.dumps(json_text),headers=headers).content
if __name__ == '__main__':
# 这里用发送关键字用来触发告警
text = "告警"
# 使用sys.argv来接收一个参数,该参数是zabbix发送的消息
text = sys.argv[1]
msg(text)
测试脚本
# 如果手动测试脚本的话,需要把text = sys.argv[1]先注释了
[root@server1 alertscripts]# chmod a+x zabbix_send_ding.py
[root@server1 alertscripts]# ./zabbix_send_ding.py
{"errcode":0,"errmsg":"ok"}
4.2.4、配置zabbix钉钉告警
添加告警媒介
因为我们使用的python脚本只接收一个参数(内容),所以只需要添加一个参数{ALERT.MESSAGE}即可
添加处理动作
恢复操作
告警消息内容如下:
【zabbix告警】
告警问题:{EVENT.NAME}
告警时间:{EVENT.DATE}-{EVENT.TIME}
告警主机:{HOST.NAME}
告 警 IP:{HOST.IP}
监控项目:{ITEM.NAME}
故障等级:{EVENT.SEVERITY}
【zabbix告警恢复】
恢复时间:{EVENT.DATE}-{EVENT.TIME}
告警名称:{EVENT.NAME}
告警主机:{HOST.NAME}
告 警 IP:{HOST.IP}
告警等级:{EVENT.SEVERITY}
【zabbix告警更新】
{USER.FULLNAME} {EVENT.UPDATE.ACTION} problem at {EVENT.UPDATE.DATE} {EVENT.UPDATE.TIME}.
{EVENT.UPDATE.MESSAGE}
绑定用户,填写钉钉的手机号即可
4.2.5、测试钉钉告警
四、Prometheus监控
1、Prometheus简介
1.1、什么是Prometheus?
Prometheus是由前 Google 工程师从 2012 年开始在 Soundcloud以开源软件的形式进行研发的系统监控和告警工具包,自此以后,许多公司和组织都采用了 Prometheus 作为监控告警工具。Prometheus 的开发者和用户社区非常活跃,它现在是一个独立的开源项目,可以独立于任何公司进行维护。为了证明这一点,Prometheus 于 2016 年 5 月加入CNCF基金会,成为继 Kubernetes 之后的第二个 CNCF 托管项目。
1.2、Prometheus的优势
Prometheus 的主要优势有:
- 由指标名称和和键/值对标签标识的时间序列数据组成的多维数据模型
- 强大的查询语言 PromQL
- 不依赖分布式存储;单个服务节点具有自治能力。
- 时间序列数据是服务端通过 HTTP 协议主动拉取获得的。
- 也可以通过中间网关来推送时间序列数据
- 可以通过静态配置文件或服务发现来获取监控目标。
- 支持多种类型的图表和仪表盘。
1.3、Prometheus的组件、架构
Prometheus 的整体架构以及生态系统组件如下图所示:
Prometheus Server 直接从监控目标中或者间接通过推送网关来拉取监控指标,它在本地存储所有抓取到的样本数据,并对此数据执行一系列规则,以汇总和记录现有数据的新时间序列或生成告警。可以通过 Grafana或者其他工具来实现监控数据的可视化。
- Prometheus server是Prometheus架构中的核心组件,基于go语言编写而成,无第三方依赖关系,可以独立部署在物理服务器上、云主机、Docker容器内。主要用于收集每个目标数据,并存储为时间序列数据,对外可提供数据查询支持和告警规则配置管理。
- Prometheus服务器可以对监控目标进行静态配置管理或者动态配置管理,它将监控采集到的数据按照时间序列存储在本地磁盘的时序数据库中(当然也支持远程存储),自身对外提供了自定义的PromQL语言,可以对数据进行查询和分析
- Client Library是用于检测应用程序代码的客户端库。在监控服务之前,需要向客户端库代码添加检测实现Prometheus中metric的类型。
- Exporter(数据采集)用于输出被监控组件信息的HTTP接口统称为Exporter(导出器)。目前互联网公司常用的组件大部分都有Expoter供直接使用,比如Nginx、MySQL、linux系统信息等。
- Pushgateway是指用于支持短期临时或批量计划任务工作的汇聚节点。主要用于短期的job,此类存在的job时间较短,可能在Prometheus来pull之前就自动消失了。所以针对这类job,设计成可以直接向Pushgateway推送metric,这样Prometheus服务器端便可以定时去Pushgateway拉去metric
- Pushgateway是prometheus的一个组件,prometheus server默认是通过exporter主动获取数据(默认采取pull拉取数据),pushgateway则是通过被动方式推送数据到prometheus server,用户可以写一些自定义的监控脚本把需要监控的数据发送给pushgateway, 然后pushgateway再把数据发送给Prometheus server
- 总结就是pushgateway是prometheus的一个组件,是通过被动的方式将数据上传至prometheus。这个可以解决不在一个网段的问题
- Alertmanager主要用于处理Prometheus服务器端发送的alerts信息,对其去除重数据、分组并路由到正确的接收方式,发出告警,支持丰富的告警方式。
- Service Discovery:动态发现待监控的target,从而完成监控配置的重要组件,在容器环境中尤为重要,该组件目前由Prometheus Server内建支持
参考博客:https://blog.51cto.com/u_986899/5658872
1.4、Prometheus适用于什么场景
Prometheus适用于记录文本格式的时间序列,它既适用于以机器为中心的监控,也适用于高度动态的面向服务的监控,在微服务的世界中,它对多维数据收集和查询的支持有特殊优势。Prometheus是专为提高系统可靠性而设计的,它可以在断电期间快速诊断问题,每个Prometheus Server都是相互独立的,不依赖于网络存储或者其他远程服务。当基础架构出现问题时,你可以通过Prometheus快速定位故障点,而且不会消耗大量的基础架构资源。
1.5、Prometheus不适合什么场景
Prometheus非常重视可靠性,即使在出现故障的情况下,你也可以随时统计有关系统的可用系统信息。如果你需要百分之百的准确度,例如按请求数量计费,那么Prometheus可能不太适合你,因为它收集的数据可能不够详细完整精确。
2、相关概念
2.1、数据模型
Prometheus所有采集的监控数据均以指标的形式保存在内置的时间序列数据库当中(TSDB):属于同一指标名称、同一标签集合的、有时间戳标记的数据流。除了存储的时间序列,Prometheus还可以根据查询请求产生临时的、衍生的时间序列作为返回结果。
2.1.1、指标名称和标签
每一条时间序列由指标名称(Metric Name)以及一组标签(键值对)唯一标识。其中指标的名称(Metric Name)可以反映被监控样本的含义(例如,http*request_total可以看出来表示当前系统接收到的http请求总量),指标名称只能由ASCII字符、数字、下划线以及冒号组成,同时必须匹配正则表达式[a-zA-Z*:][a-zA-Z0-9_:]*
。
注意
冒号用来表示用户自定义的记录规则,不能在 exporter 中或监控对象直接暴露的指标中使用冒号来定义指标名称。
通过使用标签,Prometheus开启了强大的多维数据模型:对于相同的指标名称,通过不同标签列表的集合,会形成特定的度量维度实例(例如,所有包含度量名称为 /api/tracks
的 http 请求,打上 method=POST
的标签,就会形成具体的 http 请求)。查询语言在这些指标和标签列表的基础上进行过滤和聚合,改变任何度量指标上的任何标签值(包括添加或删除指标),都会创建新的时间序列。
标签的名称只能由ASCII字符、数字、以及下划线组成并满足正则表达式[a-zA-Z_][a-zA-Z0-9_]*
。其中以 __
作为前缀的标签,是系统保留的关键字,只能在系统内部使用。标签的值则可以包含任何 Unicode
编码的字符。
2.1.2、样本
在时间序列中的每一个点称为样本,样本由以下三部分组成:
- 指标(metric):指标名称和描述当前样本特征的labelset;
- 时间戳:一个精确到时间毫秒的时间戳
- 样本值:一个浮点型数据表示当前样本的值
2.1.3、表示方式
通过如下表示方式表示指定名称和指定标签集合的时间序列
<metric name>{<label name>=<label value>, ...}
例如,指标名称为 api_http_requests_total
,标签为 method="POST"
和 handler="/messages"
的时间序列可以表示为:
api_http_requests_total{method="POST", handler="/messages"}
2.2、指标类型
Prometheus的客户端库中提供了四种核心的指标类型。但这些类型只是在客户端库(客户端可以根据不同的数据类型调用不同的api接口)和在线协议中,实际在Prometheus Server中并不对指标类型进行区分,而是简单地把这些指标统一视为无类型的时间序列
2.2.1、Counter计数器
Counter类型代表一种样本数据单调递增的指标,即只增不减,除非监控系统发生了重置。例如,你可以使用counter类型的指标表示服务请求总数、已经完成的任务数、错误发生的次数等。
counter类型数据可以让用户方便的了解事件发生的速率的变化,在PromQL内置的相关操作函数可以提供相应的分析,比如HTTP应用请求量来进行说明:
//通过rate()函数获取HTTP请求量的增长率
rate(http_requests_total[5m])
//查询当前系统中,访问量前10的HTTP地址
topk(10, http_requests_total)
2.2.2、Gauge仪表盘
Gauge类型代表一种样本数据可以任意变化的指标,即可增可减。Gauge通常用于像温度或者内存使用率这种指标数据,也可以表示能随时请求增加会减少的总数,例如当前并发请求的数量。
对于Gauge类型的监控指标,通过PromQL内置的函数delta()可以获取样本在一段时间内的变化情况,例如,计算cpu温度在两小时内的差异:
delta(cpu_temp_celsius{host="zeus"}[2h])
还可以通过PromQL内置函数predict_linear()基于简单线性回归的方式,对样本数据的变化趋势做出预测。例如,基于两小时的样本数据,来预测主机可用磁盘空间在4个小时之后的剩余情况
predict_linear(node_filesystem_free{job="node"}[2h], 4 * 3600) < 0
2.2.3、Histogram直方图
在大多数情况下人们都倾向于使用某些量化指标的平均值,例如cpu的平均使用率、页面的平均响应时间。这种方式的问题很明显,以系统api调用的平均响应时间为例:如果大多数api请求都维持在100ms的响应时间范围内,而个别请求的响应时间需要5秒,那么就会导致某些web页面的响应落到中位数的情况,而这种现象被称为长尾问题。
为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在0-10ms之间的请求数有多少而10-20ms之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。Histogram和Summary都是为了能够解决这样问题的存在,通过Histogram和Summary类型的监控指标我们可以快速了解监控样本的分布情况。
Histogram在一段时间范围内对数据进行采样(通常是请求持续时间或响应大小等),并将其计入可配置的存储桶(bucket)中,后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图。
Histogram类型的样本会提供三种指标(假设指标名称为):
- 样本的值分布在 bucket 中的数量,命名为
<basename>_bucket{le="<上边界>"}
。解释的更通俗易懂一点,这个值表示指标值小于等于上边界的所有样本数量。
// 在总共2次请求当中。http 请求响应时间 <=0.005 秒 的请求次数为0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.005",} 0.0
// 在总共2次请求当中。http 请求响应时间 <=0.01 秒 的请求次数为0 io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.01",} 0.0
// 在总共2次请求当中。http 请求响应时间 <=0.025 秒 的请求次数为0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.025",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.05",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.075",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.1",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.25",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.5",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="0.75",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="1.0",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="2.5",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="5.0",} 0.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="7.5",} 2.0
// 在总共2次请求当中。http 请求响应时间 <=10 秒 的请求次数为 2
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="10.0",} 2.0
io_namespace_http_requests_latency_seconds_histogram_bucket{path="/",method="GET",code="200",le="+Inf",} 2.0
- 所有样本值的大小总和,命名为
<basename>_sum
。
// 实际含义: 发生的2次 http 请求总的响应时间为 13.107670803000001 秒
io_namespace_http_requests_latency_seconds_histogram_sum{path="/",method="GET",code="200",} 13.107670803000001
- 样本总数,命名为
<basename>_count
。值和<basename>_bucket{le="+Inf"}
相同。
// 实际含义: 当前一共发生了 2 次 http 请求
io_namespace_http_requests_latency_seconds_histogram_count{path="/",method="GET",code="200",} 2.0
bucket 可以理解为是对数据指标值域的一个划分,划分的依据应该基于数据值的分布。注意后面的采样点是包含前面的采样点的,假设 xxx_bucket{...,le="0.01"}
的值为 10,而 xxx_bucket{...,le="0.05"}
的值为 30,那么意味着这 30 个采样点中,有 10 个是小于 10 ms 的,其余 20 个采样点的响应时间是介于 10 ms 和 50 ms 之间的。
2.2.4、Summary摘要
Summary即概率图,类似于Histogram,常用于跟踪与时间相关的数据。典型的应用包括请求持续时间、响应大小等。Summary同样提供样本的count和sum功能;还提供quantiles功能,可以按百分比划分跟踪结果,例如,quantile取值0.95,表示取样本里的95%数据。Histogram需要通过_bucket计算quantile,而Summary直接存储了quantile的值。
2.3、Jobs和Instances
在Prometheus中,任何被采集的目标,即每一个暴露监控样本数据的HTTP服务都称为一个实例instance,通常对应于单个进程。而具有相同采集目的实例集合称为作业job。
3、Prometheus部署
3.1、二进制部署
下载prometheus的二进制包
官网地址:https://prometheus.io/download/
[root@server1 ~]# wget https://github.com/prometheus/prometheus/releases/download/v2.47.0/prometheus-2.47.0.linux-amd64.tar.gz
获取软件包的哈希值,与官网提供的软件包的哈希值进行对比,保证下载的Prometheus软件包的完整性
[root@server1 ~]# sha256sum prometheus-2.47.0.linux-amd64.tar.gz
277ad9f110ded8e326bc885848952941e839fa38dd3237e36415f0fa35a04424 prometheus-2.47.0.linux-amd64.tar.gz
解压软件包到指定目录
[root@server1 ~]# mkdir /data
[root@server1 ~]# tar -zxvf prometheus-2.47.0.linux-amd64.tar.gz -C /data/
[root@server1 ~]# cd /data/
[root@server1 data]# chown -R root:root /data/prometheus-2.47.0.linux-amd64
[root@server1 data]# ln -sv prometheus-2.47.0.linux-amd64 prometheus
"prometheus" -> "prometheus-2.47.0.linux-amd64"
3.1.1、前台启动
启动Prometheus,会输出如下信息,此时当终端关闭或者按下ctrl + c服务会自动关闭
[root@server1 data]# cd /data/prometheus
[root@server1 prometheus]# ./prometheus
level=info ts=2021-02-28T06:03:36.885Z caller=main.go:366 msg="No time or size retention was set so using the default time retention" duration=15d
level=info ts=2021-02-28T06:03:36.885Z caller=main.go:404 msg="Starting Prometheus" version="(version=2.47.0, branch=HEAD, revision=a6be548dbc17780d562a39c0e4bd0bd4c00ad6e2)"
level=info ts=2021-02-28T06:03:36.885Z caller=main.go:409 build_context="(go=go1.15.8, user=root@615f028225c9, date=20210217-14:17:24)"
level=info ts=2021-02-28T06:03:36.885Z caller=main.go:410 host_details="(Linux 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 server1 (none))"
level=info ts=2021-02-28T06:03:36.885Z caller=main.go:411 fd_limits="(soft=1024, hard=4096)"
level=info ts=2021-02-28T06:03:36.885Z caller=main.go:412 vm_limits="(soft=unlimited, hard=unlimited)"
level=info ts=2021-02-28T06:03:36.891Z caller=web.go:532 component=web msg="Start listening for connections" address=0.0.0.0:9090
level=info ts=2021-02-28T06:03:36.895Z caller=main.go:779 msg="Starting TSDB ..."
level=info ts=2021-02-28T06:03:36.897Z caller=tls_config.go:191 component=web msg="TLS is disabled." http2=false
level=info ts=2021-02-28T06:03:36.930Z caller=head.go:668 component=tsdb msg="Replaying on-disk memory mappable chunks if any"
level=info ts=2021-02-28T06:03:36.930Z caller=head.go:682 component=tsdb msg="On-disk memory mappable chunks replay completed" duration=5.69µs
level=info ts=2021-02-28T06:03:36.930Z caller=head.go:688 component=tsdb msg="Replaying WAL, this may take a while"
level=info ts=2021-02-28T06:03:36.933Z caller=head.go:740 component=tsdb msg="WAL segment loaded" segment=0 maxSegment=0
level=info ts=2021-02-28T06:03:36.933Z caller=head.go:745 component=tsdb msg="WAL replay completed" checkpoint_replay_duration=38.416µs wal_replay_duration=2.357106ms total_replay_duration=2.638813ms
level=info ts=2021-02-28T06:03:36.934Z caller=main.go:799 fs_type=XFS_SUPER_MAGIC
level=info ts=2021-02-28T06:03:36.934Z caller=main.go:802 msg="TSDB started"
level=info ts=2021-02-28T06:03:36.934Z caller=main.go:928 msg="Loading configuration file" filename=prometheus.yml
新开一个窗口,检查端口号以及关闭防火墙和selinux
[root@server1 prometheus]# systemctl stop firewalld
[root@server1 prometheus]# setenforce 0
[root@server1 prometheus~]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 128 :::9090 :::*
3.1.2、检查配置文件
在Prometheus日常维护中,一定会对配置文件prometheus.yml进行再编辑操作,通常对Prometheus服务进行重新启动操作即可完成对配置文件的加载。当然也可以通过动态的热加载来更新prometheus.yml中的配置信息 查看进程id,向进程发送SIHHUP信号
# kill -HUP pid
通过HTTP API发送post请求到/-/reload
# curl -X POST http://localhost:9090/-/reload
- 检查配置文件的语法正确性
[root@server1 ~]# cd /data/prometheus
[root@server1 prometheus]# ls
console_libraries consoles data LICENSE NOTICE prometheus prometheus.yml promtool
[root@server1 prometheus]# ./promtool check config prometheus.yml
Checking prometheus.yml
SUCCESS: 0 rule files found
3.1.3、创建自启动脚本
[root@server1 ~]# vim /usr/lib/systemd/system/prometheus.service
[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/introduction/overview/
After=network.target
[Service]
Type=simple
Restart=on-failure
ExecStart=/data/prometheus/prometheus \
--config.file=/data/prometheus/prometheus.yml \
--storage.tsdb.path=/data/prometheus/data \
--web.listen-address=:9090 \
--web.enable-lifecycle
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
systemd重载配置以及启动prometheus
[root@server1 ~]# systemctl daemon-reload
[root@server1 ~]# systemctl start prometheus
[root@server1 prometheus]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 128 :::9090 :::*
3.1.4、浏览器访问测试
4、Exporter
4.1、简介
在Prometheus的核心组件中,Exporter是重要的组成部分,在实际中监控样本数据的收集都是由Exporter完成的,Prometheus服务器只需要定时从这些Exporter提供的HTTP服务获取数据即可。官方提供了多种常用的Exporter,比如用于对数据库监控的mysqld_exporter和redis_exporter等。
Exporter本质上是将收集的数据转化为对应的文本格式,并提供HTTP接口,供Prometheus定期采集数据。
4.2、Exporter类型
- 直接采集型
- 这类Exporter直接内置了响应的应用程序,用于向Prometheus直接提供target数据支持。这样设计的好处是,可以更好地监控各自系统内部的运行状态,同时也适合更多自定义监控指标的项目实施。
- 间接采集型
- 原始监控目标并不直接支持Prometheus,需要我们使用Prometheus提供的客户端库编写该监控目标的监控采集数据,用户可以将该程序独立运行,取获取指定的各类监控数据值。例如,由于Linux操作系统自身并不能直接支持Prometheus,用户无法从操作系统层面上直接提供对Prometheus的支持,因此单独提供Node Exporter,还有数据库或网站HTTP应用类等Exporter。
4.3、文本数据格式
在Prometheus的监控环境中,所有返回监控样本数据的Exporter程序,均需要遵守Prometheus规范,即基于文本的数据格式,其特点是具有更好的跨平台和可读性。
- 可以使用浏览器,或者通过curl工具来获得采集数据
# 以HELP开头的行,表示metric的帮助与说明解释,可以包含当前监控指标名称和对应的说明信息
# 以TYPE开始的行,表示定义metric的类型
# 以非#开头的行即监控样本的数据,包括metric_name{label_name1,label_name2...} value [timestamp可选项]
[root@server1 ~]# curl 192.168.175.10:9090/metrics
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 9.4601e-05
go_gc_duration_seconds{quantile="0.25"} 0.000141153
go_gc_duration_seconds{quantile="0.5"} 0.000416738
go_gc_duration_seconds{quantile="0.75"} 0.001050261
go_gc_duration_seconds{quantile="1"} 0.008308442
go_gc_duration_seconds_sum 0.014675204
go_gc_duration_seconds_count 13
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 32
# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.15.8"} 1
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 1.6849432e+07
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total counter
go_memstats_alloc_bytes_total 6.016028e+07
# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
# TYPE go_memstats_buck_hash_sys_bytes gauge
go_memstats_buck_hash_sys_bytes 1.461736e+06
# HELP go_memstats_frees_total Total number of frees.
# TYPE go_memstats_frees_total counter
go_memstats_frees_total 219435
# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started.
# TYPE go_memstats_gc_cpu_fraction gauge
go_memstats_gc_cpu_fraction 3.012333999853177e-05
# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 5.719272e+06
4.4、Linux主机监控
Prometheus社区很活跃,提供了非常多类型的Exporter。可以在官网中找到自己想要的Exporter并进行下载https://prometheus.io/download/
由于Linux操作系统自身并不支持Prometheus,所以Prometheus官方提供了go语言编写的Node Exporter来实现对Linux操作系统主机的监控数据采集。它提供了系统内部几乎所有的标准指标,如cpu、内存、磁盘空间、磁盘I/O、系统负载和网络带宽。另外它还提供了由内核公开的大量额外监控指标,从负载平均到主板温度等。
4.4.1、安装Exporter
下载node exporter的二进制包并解压
[root@server2 ~]# wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
[root@server2 ~]# mkdir /data
[root@server2 ~]# tar -zxvf node_exporter-1.8.2.linux-amd64.tar.gz -C /data/
node_exporter-1.8.2.linux-amd64/
node_exporter-1.8.2.linux-amd64/NOTICE
node_exporter-1.8.2.linux-amd64/node_exporter
node_exporter-1.8.2.linux-amd64/LICENSE
[root@server2 ~]# cd /data/
[root@server2 data]# chown -R root:root node_exporter-1.8.2.linux-amd64/
[root@server2 data]# ln -sv node_exporter-1.8.2.linux-amd64 node_exporter
"node_exporter" -> "node_exporter-1.8.2.linux-amd64"
启动node_exporter
[root@server2 node_exporter]# ./node_exporter
4.4.2、关联Prometheus server
当启动node_exporter开始工作时,node_exporter和Prometheus server还没有进行关联,二者各自独立没有关联。
可以在Prometheus server中,找到主机目录,找到主配置文件,使用其中的静态配置功能static_configs来采集node_exporter提供的数据
server主配置文件介绍:
[root@server1 prometheus]# cat prometheus.yml
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minut e.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval' .
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this conf ig.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ["localhost:9090"]
==================================================================================
# 配置文件解释
global:
scrape_interval:每次数据采集的时间间隔,默认为1分钟
scrape_timeout:采集请求超时时间,默认为10秒
evaluation_interval:执行rules的频率,默认为1分钟
scrape_configs:主要用于配置被采集数据节点操作,每一个采集配置主要由以下几个参数
job_name:全局唯一名称
scrape_interval:默认等于global内设置的参数,设置后可以覆盖global中的值
scrape_timeout:默认等于global内设置的参数
metrics_path:从targets获取meitric的HTTP资源路径,默认是/metrics
honor_labels:Prometheus如何处理标签之间的冲突。若设置为True,则通过保留变迁来解决冲突;若设置为false,则通过重命名;
scheme:用于请求的协议方式,默认是http
params:数据采集访问时HTTP URL设定的参数
relabel_configs:采集数据重置标签配置
metric_relabel_configs:重置标签配置
sample_limit:对每个被已知样本数量的每次采集进行限制,如果超过限制,该数据将被视为失败。默认值为0,表示无限制
- 直接编辑主配置文件,添加job与node_exporter关联
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ["localhost:9090"]
- job_name: "node_exporter"
static_configs:
- targets: ["192.168.88.20:9100"]
- 检查配置文件并且重启服务
[root@server1 prometheus]# ./promtool check config prometheus.yml
Checking prometheus.yml
SUCCESS: prometheus.yml is valid prometheus config file syntax
[root@server1 prometheus]# systemctl restart prometheus
[root@server1 prometheus]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 128 :::9090 :::*
4.4.3、查看Targets
重启服务即可成功关联
4.4.4、metricts数据采集
- cpu数据采集
对于cpu数据采集的主要监控指标是node_cpu_seconds_total
可以通过PromQL(后面会介绍这种查询语言)表达式进行查询,计算每核cpu每秒的空闲时间,然后对主机上的所有cpu求平均值
avg without(cpu,mode) (rate(node_cpu_seconds_total {mode="idle"} [1m]))
- 内存信息采集
[root@server2 node_exporter]# free -b
total used free shared buff/cache available
Mem: 1907970048 112123904 1564102656 10014720 231743488 1609289728
Swap: 2147479552 0 2147479552
总内存:
空闲内存:
- 磁盘信息采集
- 文件系统采集
- 网络采集
任意一个Exporter都会提供足够多的metric,我们在学习的时候也不需要关心具体有多少metric,每个metric具体意思(其实见名知义大概也可以猜到)
4.5、MySQL监控
4.5.1、部署MySQL环境
[root@server2 ~]# yum install -y mariadb-server
# 启动mariadb数据库
[root@server2 ~]# systemctl enable --now mariadb
# 初始化数据库并设置密码
[root@server2 ~]# mysqladmin -uroot password '123456'
# 测试数据库连接是否正常
[root@server2 ~]# mysql -uroot -p123456 -e "show databases;"
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
+--------------------+
# 创建exporter用户
ariaDB [(none)]> grant all privileges on *.* to mysqld_exporter@'%' identified by '123456';
Query OK, 0 rows affected (0.00 sec)
# 刷新账户信息
MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.00 sec)
4.5.2、安装Exporter
官网下载mysqld_exporter二进制包解压缩
[root@server2 ~]# wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.15.1/mysqld_exporter-0.15.1.linux-amd64.tar.gz
[root@server2 ~]# tar -zxvf mysqld_exporter-0.15.1.linux-amd64.tar.gz -C /data/
mysqld_exporter-0.15.1.linux-amd64/
mysqld_exporter-0.15.1.linux-amd64/LICENSE
mysqld_exporter-0.15.1.linux-amd64/mysqld_exporter
mysqld_exporter-0.15.1.linux-amd64/NOTICE
[root@server2 ~]# cd /data/
[root@server2 data]# chown -R root:root mysqld_exporter-0.15.1.linux-amd64/
[root@server2 data]# ln -sv mysqld_exporter-0.15.1.linux-amd64/ mysqld_exporter
"mysqld_exporter" -> "mysqld_exporter-0.15.1.linux-amd64/"
4.5.3、配置Exporter
[root@server2 mysqld_exporter]# pwd
/data/mysqld_exporter
# 创建一个配置文件.mysqld_exporter.cnf
[root@server2 mysqld_exporter]# vim .mysqld_exporter.cnf
[client]
user=mysqld_exporter
password=1
[root@server2 mysqld_exporter]# ./mysqld_exporter --config.my-cnf='.mysqld_exporter.cnf' &
[root@server2 mysqld_exporter]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 50 *:3306 *:*
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::9100 :::*
LISTEN 0 128 :::9104 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
4.5.4、关联Prometheus server
[root@server1 prometheus]# vim prometheus.yml
- job_name: 'mysqld_exporter'
scrape_interval: 10s
static_configs:
- targets: [192.168.88.20:9104]
[root@server1 prometheus]# ./promtool check config prometheus.yml
Checking prometheus.yml
SUCCESS: prometheus.yml is valid prometheus config file syntax
[root@server1 prometheus]# systemctl restart prometheus
4.5.5、metricts数据采集
MySQL数据库的性能状态监控内容非常多,但通常必不可少的内容包括查询吞吐量、查询执行性能、连接情况、缓冲池使用情况等。
- 查询吞吐量,MySQL客户端应用程序发送的所有查询语句,该计数器都是递增的
- 查询执行性能,每当查询时间超过预先设定的慢查询时间计数器都会递增
- 连接情况,查询MySQL最大连接数,防止连接数量过大导致服务器过载运行
- 查看当前连接数
- 缓存池使用情况
5、服务发现
Prometheus服务发现能够自动化检测分类,并且能够识别新目标和变更目标。也就是说,可以自动发现并监控目标或变更目标,动态进行数据采集和处理。
5.1、基于文件的服务发现
- 准备JSON格式的文件
[root@server1 ~]# cd /data/prometheus
[root@server1 prometheus]# mkdir targets
[root@server1 prometheus]# vim targets/dev_node.json
[{
"targets": [ "192.168.175.20:9100","192.168.175.20:9104" ],
"labels": {
"env": "dev_webgame"
}
}]
-------------------------------------------------------------------
或者这里是准备yaml文件,那么下面相应的配置文件需要与yaml匹配
vim targets/dev_node.yml
- targets:
- "192.168.175.20:9100"
- "192.168.175.20:9104"
- 修改配置文件
[root@server1 prometheus]# vim /data/prometheus/prometheus.yml
- job_name: 'node_service_discovery'
file_sd_configs:
- files:
- targets/*.yml
refresh_interval: 60m
- 重新启动服务
扩展:这是基于文件发现,还有基于consul基于dns的服务发现,这个自行扩展。
6、PromQL
Prometheus提供了一种功能强大的表达式语言PromQL(Prometheus Query Language)。Prometheus允许用户实时选择和汇聚时间序列数据,是Prometheus自己开发的数据查询语言,使用这个查询语言能够进行各种聚合、分析和计算,使管理员能够根据指标更好地了解系统性能。
6.1、时序数据库
首先Prometheus是一款时序数据库TSDB,它结合生态系统内的其他组件例如Pushgateway、Alertmanager等可构成一个完整的IT监控系统。
时序数据库的特点如下:
- 数据写入特点——写入平稳、持续、高并发高吞吐;写多读少,在写操作上数据上能达到95%以上;无更新时写入最近生成的数据
- 数据查询特点——按时间范围读取一段时间的数据;对最近生成的数据读取概率高,对历史数据查询概率低;按照数据点的不同密集度实现多精度查询
- 数据存储特点——数据存储量比较大;具有时效性,数据通常会有一个保存周期,多精度数据存储
对时序数据库的基本要求如下:
- 能够支持高并发、高吞吐的写入
- 交互及的聚合查询,能够达到低查询延迟
- 依据场景设计可以支持海量数据存储
- 在线应用服务场景中,需要高可用架构支持
- 针对写入和存储量的要求,应用环境底层需要分布式架构支持
6.2、时序数据
时间序列数据:按照时间顺序记录系统、设备状态变化的数据,每个数据称为一个样本
数据采集以特定的时间周期进行,随着时间的流逝,将这些样本数据记录下来,将生成一个离散的样本数据序列
把该序列称作为向量,而将多个序列放在同一个坐标系内(以时间为横轴,以序列为纵轴,将形成一个有数据点组成的矩阵)
- 即时向量:特定或全部的时间序列上的集合,具有相同时间戳的一组样本称之为即时向量
- 范围向量:特定或全部的时间序列上的集合,在指定的同一时间范围内的所有样本值称之为范围向量
6.3、时间序列选择器
6.3.1、即时向量选择器
即时向量选择器由两部分组成
- 指标名称:用于限定特定指标下的时间序列,即负责过滤指标,可选
- 匹配器:或称为标签选择器,用于过滤时间序列上的标签,定义在{}中
- 常见使用举例
- prometheus_http_requests_total,仅给定指标名称
- {job="node_exporter"},仅给定匹配器
- up{job="node_exporter"},指标名称和匹配器的组合
匹配器用于定义标签过滤的条件,目前支持如下四种
- =:相等匹配模式,用来指定返回的时间序列与指定的标签完全匹配
- !=:反向匹配模式,即不等于模式
- =~:正则表达式匹配模式
- !~:反向正则表达式
6.3.2、范围向量选择器
同即时向量选择器唯一不同的地方在于,范围向量选择器需要在表达式后紧跟一个方括号[]来表达需要在时序上返回的样本所处的时间范围
时间范围:以当前时间为基准点,指向过去一个特定的时间长度,例如[5m],便是指过去5分钟之内
可用的时间单位有:
- ms
- s
- m
- h
- d
- w
- y
必须使用整数时间,例如1h30m,但不允许使用1.5h
需要注意的是,范围向量选择器返回的是一定时间范围内的数据样本,虽然不同时间序列的数据抓点时间点相同,但特们的时间戳并不会严格对齐。多个target上的数据抓取需要分散在抓取时间点的周围,他们的时间戳并不会严格对齐,目的是为了均衡Prometheus的负载
因而,Prometheus在趋势上准确,但并非绝对精准
6.3.3、偏移量修改器
默认情况下,即时向量选择器和范围向量选择器都以当前时间为基准,而偏移量修改器能够修改该基准
例如:
up{job="node_exporter"}[5m]表示的是获取过去5分钟的即时样本
up{job="node_exporter"}[5m] offset 1d表示的是获取过去1天的即时样本
6.4、PromQL操作符
6.4.1、数学运算
PromQL支持的所有数学运算符如下所示:
+
(加法)-
(减法)*
(乘法)/
(除法)%
(求余)^
(幂运算)
6.4.2、布尔运算
目前,Prometheus支持以下布尔运算符如下:
==
(相等)!=
(不相等)>
(大于)<
(小于)>=
(大于等于)<=
(小于等于)
6.4.3、集合运算符
使用瞬时向量表达式能够获取到一个包含多个时间序列的集合,我们称为瞬时向量。 通过集合运算,可以在两个瞬时向量与瞬时向量之间进行相应的集合操作。目前,Prometheus支持以下集合运算符:
and
(并且,交集)or
(或者,并集)unless
(差集)
*vector1 and vector2* 会产生一个由vector1的元素组成的新的向量。该向量包含vector1中完全匹配vector2中的元素组成。
*vector1 or vector2* 会产生一个新的向量,该向量包含vector1中所有的样本数据,以及vector2中没有与vector1匹配到的样本数据。
*vector1 unless vector2* 会产生一个新的向量,新向量中的元素由vector1中没有与vector2匹配的元素组
6.4.4、操作符优先级
在PromQL操作符中优先级由高到低依次为:
^
*, /, %
+, -
==, !=, <=, <, >=, >
and, unless
or
6.5、PromQL聚合操作
Prometheus还提供了下列内置的聚合操作符,这些操作符作用域瞬时向量。可以将瞬时表达式返回的样本数据进行聚合,形成一个新的时间序列。
sum
(求和)min
(最小值)max
(最大值)avg
(平均值)stddev
(标准差)stdvar
(标准差异)count
(计数)count_values
(对value进行计数)bottomk
(后n条时序)topk
(前n条时序)quantile
(分布统计)
使用聚合操作的语法如下:
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
其中只有count_values
, quantile
, topk
, bottomk
支持参数(parameter)。
without用于从计算结果中移除列举的标签,而保留其它标签。by则正好相反,结果向量中只保留列出的标签,其余标签则移除。通过without和by可以按照样本的问题对数据进行聚合。
例如:
sum(node_memory_Active_bytes) without (instance)
等价于
sum(http_requests_total) by (code,handler,job,method)
如果只需要计算整个应用的HTTP请求总量,可以直接使用表达式:
sum(http_requests_total)
count_values用于时间序列中每一个样本值出现的次数。count_values会为每一个唯一的样本值输出一个时间序列,并且每一个时间序列包含一个额外的标签。
例如:
count_values("count", http_requests_total)
topk和bottomk则用于对样本值进行排序,返回当前样本值前n位,或者后n位的时间序列。
获取HTTP请求数前5位的时序样本数据,可以使用表达式:
topk(5, http_requests_total)
quantile用于计算当前样本数据值的分布情况quantile(φ, express)其中0 ≤ φ ≤ 1。
例如,当φ为0.5时,即表示找到当前样本数据中的中位数:
quantile(0.5, http_requests_total)
6.6、向量匹配
6.6.1、one-to-one
一对一向量匹配模式,它从运算符的两侧表达式中获取即时向量,依次比较并找到一对唯一条目进行匹配,如果两个条目具有完全相同的标签和对应的值,则他们匹配。
在操作符两边表达式标签不一致的情况下,可以使用on(label list)或者ignoring(label list)来修改便签的匹配行为。使用ignoreing可以在匹配时忽略某些便签。而on则用于将匹配行为限定在某些便签之内。
<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>
例如当存在样本:
method_code:http_errors:rate5m{method="get", code="500"} 24
method_code:http_errors:rate5m{method="get", code="404"} 30
method_code:http_errors:rate5m{method="put", code="501"} 3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21
method:http_requests:rate5m{method="get"} 600
method:http_requests:rate5m{method="del"} 34
method:http_requests:rate5m{method="post"} 120
使用PromQL表达式:
method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m
该表达式会返回在过去5分钟内,HTTP请求状态码为500的在所有请求中的比例。如果没有使用ignoring(code),操作符两边表达式返回的瞬时向量中将找不到任何一个标签完全相同的匹配项。
因此结果如下:
{method="get"} 0.04 // 24 / 600
{method="post"} 0.05 // 6 / 120
6.6.2、many-to-one和one-to-many
多对一和一对多的匹配模式,可以理解为向量元素中的一个样本数据匹配到了多个样本数据标签。在使用该匹配模式时,需要使用group_left或者group_right修饰符明确指定哪一个向量具有更高的基数,也就是说左或者右决定了哪边的向量具有较高的子集
<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>
多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况。因此需要使用ignoring和on修饰符来排除或者限定匹配的标签列表。
例如,使用表达式:
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m
该表达式中,左向量method_code:http_errors:rate5m
包含两个标签method和code。而右向量method:http_requests:rate5m
中只包含一个标签method,因此匹配时需要使用ignoring限定匹配的标签为code。 在限定匹配标签后,右向量中的元素可能匹配到多个左向量中的元素 因此该表达式的匹配模式为多对一,需要使用group修饰符group_left指定左向量具有更好的基数。
最终的运算结果如下:
{method="get", code="500"} 0.04 // 24 / 600
{method="get", code="404"} 0.05 // 30 / 600
{method="post", code="500"} 0.05 // 6 / 120
{method="post", code="404"} 0.175 // 21 / 120
6.7、内置函数
6.7.1、计算Counter指标增长率
我们知道Counter类型的监控指标其特点是只增不减,在没有发生重置(如服务器重启,应用重启)的情况下其样本值应该是不断增大的。为了能够更直观的表示样本数据的变化剧烈情况,需要计算样本的增长速率。
如下图所示,样本增长率反映出了样本变化的剧烈程度:
通过增长率表示样本的变化情况
increase(v range-vector)函数是PromQL中提供的众多内置函数之一。其中参数v是一个区间向量,increase函数获取区间向量中的第一个后最后一个样本并返回其增长量。因此,可以通过以下表达式Counter类型指标的增长率:
increase(node_cpu_seconds_total[2m] )/120
这里通过node_cpu[2m]获取时间序列最近两分钟的所有样本,increase计算出最近两分钟的增长量,最后除以时间120秒得到node_cpu样本在最近两分钟的平均增长率。并且这个值也近似于主机节点最近两分钟内的平均CPU使用率。
除了使用increase函数以外,PromQL中还直接内置了rate(v range-vector)函数,rate函数可以直接计算区间向量v在时间窗口内平均增长速率。因此,通过以下表达式可以得到与increase函数相同的结果:
rate(node_cpu_seconds_total[2m])
需要注意的是使用rate或者increase函数去计算样本的平均增长速率,容易陷入“长尾问题”当中,其无法反应在时间窗口内样本数据的突发变化。 例如,对于主机而言在2分钟的时间窗口内,可能在某一个由于访问量或者其它问题导致CPU占用100%的情况,但是通过计算在时间窗口内的平均增长率却无法反应出该问题。
为了解决该问题,PromQL提供了另外一个灵敏度更高的函数irate(v range-vector)。irate同样用于计算区间向量的计算率,但是其反应出的是瞬时增长率。irate函数是通过区间向量中最后两个样本数据来计算区间向量的增长速率。这种方式可以避免在时间窗口范围内的“长尾问题”,并且体现出更好的灵敏度,通过irate函数绘制的图标能够更好的反应样本数据的瞬时变化状态。
irate(node_cpu_seconds_total[2m])
irate函数相比于rate函数提供了更高的灵敏度,不过当需要分析长期趋势或者在告警规则中,irate的这种灵敏度反而容易造成干扰。因此在长期趋势分析或者告警中更推荐使用rate函数。
6.7.2、预测Gauge指标变化趋势
在一般情况下,系统管理员为了确保业务的持续可用运行,会针对服务器的资源设置相应的告警阈值。例如,当磁盘空间只剩512MB时向相关人员发送告警通知。 这种基于阈值的告警模式对于当资源用量是平滑增长的情况下是能够有效的工作的。 但是如果资源不是平滑变化的呢? 比如有些某些业务增长,存储空间的增长速率提升了高几倍。这时,如果基于原有阈值去触发告警,当系统管理员接收到告警以后可能还没来得及去处理问题,系统就已经不可用了。 因此阈值通常来说不是固定的,需要定期进行调整才能保证该告警阈值能够发挥去作用。 那么还有没有更好的方法吗?
PromQL中内置的predict_linear(v range-vector, t scalar) 函数可以帮助系统管理员更好的处理此类情况,predict_linear函数可以预测时间序列v在t秒后的值。它基于简单线性回归的方式,对时间窗口内的样本数据进行统计,从而可以对时间序列的变化趋势做出预测。例如,基于2小时的样本数据,来预测主机可用磁盘空间的是否在4个小时候被占满,可以使用如下表达式:
predict_linear(node_filesystem_free_bytes{job="node-exporter"}[2h], 4 * 3600) < 0
6.7.3、统计Histogram指标的分位数
在本章的第2小节中,我们介绍了Prometheus的四种监控指标类型,其中Histogram和Summary都可以同于统计和分析数据的分布情况。区别在于Summary是直接在客户端计算了数据分布的分位数情况。而Histogram的分位数计算需要通过histogram_quantile(φ float, b instant-vector)函数进行计算。其中φ(0<φ<1)表示需要计算的分位数,如果需要计算中位数φ取值为0.5,以此类推即可。
以指标http_request_duration_seconds_bucket为例:
# HELP http_request_duration_seconds request duration histogram
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.5"} 0
http_request_duration_seconds_bucket{le="1"} 1
http_request_duration_seconds_bucket{le="2"} 2
http_request_duration_seconds_bucket{le="3"} 3
http_request_duration_seconds_bucket{le="5"} 3
http_request_duration_seconds_bucket{le="+Inf"} 3
http_request_duration_seconds_sum 6
http_request_duration_seconds_count 3
当计算9分位数时,使用如下表达式:
histogram_quantile(0.5, http_request_duration_seconds_bucket)
通过对Histogram类型的监控指标,用户可以轻松获取样本数据的分布情况。同时分位数的计算,也可以非常方便的用于评判当前监控指标的服务水平。
获取分布直方图的中位数
需要注意的是通过histogram_quantile计算的分位数,并非为精确值,而是通过http_request_duration_seconds_bucket和http_request_duration_seconds_sum近似计算的结果。
7、Grafana数据展示
grafana是一款图形显示非常优秀的工具,支持图表模板导入,支持除Prometheus之外多种数据源(包括MySQL、zabbix、elasticsearch等等)
7.1、部署Grafana
下载软件包并且安装,官网地址:https://grafana.com/grafana/download
[root@server1 ~]# wget https://dl.grafana.com/oss/release/grafana-7.5.3-1.x86_64.rpm
[root@server1 ~]# yum install -y grafana-7.5.3-1.x86_64.rpm
启动Grafana
[root@server1 ~]# systemctl start grafana-server
# 检查3000端口是否监听
[root@server1 ~]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::22 :::*
LISTEN 0 128 :::3000 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 128 :::9090 :::*
7.2、访问Grafana
首次登陆用户名密码为admin admin
登录以后会提示修改密码
7.3、使用Grafana
- 添加数据源
- 连接Prometheus
- 添加数据展示(Dashboard)
- 添加成功以后,就可以在首页中看到我们的数据展示了
我们可以添加多个数据展示
7.4、使用第三方模板
自己创建图表展示比较麻烦,我们可以直接使用别人以及做好的模板
官方模板网站:https://grafana.com/grafana/dashboards/
可以在搜索框中搜索我们想要的模板
找到想要的模板以后,直接复制模板代码
12633
回到我们自己的Grafana中直接import导入进去
然后就可以看到我们导入的模板了,并且可以选择不同的Exporter
8、Alertmanager告警
8.1、概述
-
Prometheus对指标的收集、存储同告警能力分属于Prometheus Server和AlertManager两个独立的组件组成,前者仅仅负责基于告警规则生成告警通知,具体的告警操作则有后者完成;
-
AlertManager负责处理由客户端发来的告警通知
-
客户端通常是Prometheus Server,但也支持来自其他工具的告警
-
AlertManageer对告警通知进行分组、去重后根据路由规则将其路由到不同的receiver,如email、企业微信、钉钉等
-
告警逻辑
-
在AlertManager上定义receiver,他们能够基于某个媒介接收告警信息的特定用户;
- 在Alertmanager上定义路由规则(route),以便将收到的告警通知按需分别进行处理
- 在Prometheus上定义告警规则生成告警通知,发送给Alertmanager
8.2、Alertmanager机制
除了基本的告警通知能力外,Alertmanager还支持对告警进行去重分组抑制静默和路由等功能
8.2.1、分组机制
将相似告警合并为单个告警通知的机制,在系统因大面积故障而触发告警是,分组机制能避免用户被大量的告警噪声淹没,进而导致关键信息的隐没
8.2.2、抑制机制
系统中某个组件或服务故障而触发告警通知后,那些依赖于该组件或服务的其他组件或服务也会因此而触发告警,抑制是避免类似的级联告警的一种特性,从而让用户的经历集中于真正的故障所在
8.2.3、静默机制
在一个特定的时间窗口内,便接收到告警通知,Alertmanager也不会真正向用户发送告警行为;通常,在系统例行维护期间,需要激活告警系统的静默特性
8.3、部署Alertmanger
下载二进制包
[root@server1 ~]# wget https://github.com/prometheus/alertmanager/releases/download/v0.21.0/alertmanager-0.21.0.linux-amd64.tar.gz
解压到指定位置以及赋予权限
[root@server1 ~]# tar -zxvf alertmanager-0.21.0.linux-amd64.tar.gz -C /data/
alertmanager-0.21.0.linux-amd64/
alertmanager-0.21.0.linux-amd64/alertmanager
alertmanager-0.21.0.linux-amd64/amtool
alertmanager-0.21.0.linux-amd64/NOTICE
alertmanager-0.21.0.linux-amd64/LICENSE
alertmanager-0.21.0.linux-amd64/alertmanager.yml
[root@server1 ~]# cd /data/
[root@server1 data]# ln -sv alertmanager-0.21.0.linux-amd64 alertmanager
"alertmanager" -> "alertmanager-0.21.0.linux-amd64"
[root@server1 data]# chown -R root:root alertmanager-0.21.0.linux-amd64
编辑Alertmanger配置文件
默认的配置文件内容:
[root@server3 alertmanager]# cat alertmanager.yml
global: # 全局配置模块
resolve_timeout: 5m # 用于设置处理超时时间,默认是5分钟
route: # 路由配置模块
group_by: ['alertname'] # 告警分组
group_wait: 10s # 10s内收到的同组告警在同一条告警通知中发送出去
group_interval: 10s # 同组之间发送告警通知的时间间隔
repeat_interval: 1h # 相同告警信息发送重复告警的周期
receiver: 'web.hook' # 使用的接收器名称
receivers: # 接收器
- name: 'web.hook' # 接收器名称
webhook_configs: # 设置webhook地址
- url: 'http://127.0.0.1:5001/'
inhibit_rules: # 告警抑制功能模块
- source_match:
severity: 'critical' # 当存在源标签告警触发时抑制含有目标标签的告警
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance'] # 保证该配置下标签内容相同才会被抑制
自定义Alertmanager使用qq邮箱报警的配置文件:
[root@server1 alertmanager]# vim alertmanager.yml
global:
resolve_timeout: 5m
smtp_from: '2898485992@qq.com'
smtp_smarthost: 'smtp.qq.com:465'
smtp_auth_username: '2898485992@qq.com'
smtp_auth_password: 'cailsiuiswridcdc'
smtp_require_tls: false
smtp_hello: 'qq.com'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'email'
receivers:
- name: 'email'
email_configs:
- to: '2898485992@qq.com'
send_resolved: true
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
启动Alertmanager
[root@server1 alertmanager]# ./alertmanager &
关联Prometheus
[root@server1 v]# vim prometheus.yml
alerting:
alertmanagers:
- static_configs:
- targets:
- 192.168.88.10:9093
# - alertmanager:9093
.....
- job_name: "Alertmanager"
static_configs:
- targets: ["192.168.88.10:9093"]
添加告警规则并且在Prometheus配置文件中导入
[root@server1 prometheus]# mkdir -p /data/prometheus/rules/
[root@server1 prometheus]# vim prometheus.yml
rule_files:
- "/data/prometheus/rules/*.yml"
[root@server1 rules]# cd /data/prometheus/rules
[root@server1 rules]# vim rules.yml
groups:
- name: up
rules:
- alert: node
expr: up{job="node_exporter"} == 0
for: 1m
labels:
severity: critical
annotations:
description: "Node has been dwon for more than 1 minutes"
summary: "Node down"
[root@server1 prometheus]# ./promtool check rules rules/rules.yml
Checking rules/rules.yml
SUCCESS: 1 rules found
# 重启Prometheus
[root@server1 prometheus]# systemctl restart prometheus
在web中查看Rules
8.3.1、关闭node-exporer测试
- Interval没有满足触发条件,告警未激活状态
- pending,已满足触发条件,但未满足告警持续时间的状态,即未满足告警中for子句指定的持续时间
- firing,已满足触发条件且已经超过for子句中指定的持续时间时的状态
其他告警方式自行扩展,以及Grafana工具的更多用法
五、Memcached
1、Memcached介绍
1.1、什么是Memcached缓存数据库
Memcached是一个自由开源的,高性能,分布式内存对象缓存系统。
Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。
Memcached简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。
本质上,它是一个简洁的key-value存储系统。
一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
Memcached 官网:https://memcached.org
1.2、Memcached和Redis对比
我们都知道,把一些热数据存到缓存中可以极大的提高速度,那么问题来了,是用Redis好还是Memcached好呢,以下是它们两者之间一些简单的区别与比较:
- Redis不仅支持简单的k/v类型的数据,同时还支持list、set、zset(sorted set)、hash等丰富数据结构的存储,使得它拥有更广阔的应用场景。
- Redis最大的亮点是支持数据持久化,它在运行的时候可以将数据备份在磁盘中,断电或重启后,缓存数据可以再次加载到内存中,只要Redis配置的合理,基本上不会丢失数据。
- Redis支持主从模式的应用。
- Redis单个value的最大限制是1GB,而Memcached则只能保存1MB内的数据。
- Memcache在并发场景下,能用cas保证一致性,而Redis事务支持比较弱,只能保证事务中的每个操作连续执行。
- 性能方面,根据网友提供的测试,Redis在读操作和写操作上是略领先Memcached的。
- Memcached的内存管理不像Redis那么复杂,元数据metadata更小,相对来说额外开销就很少。Memcached唯一支持的数据类型是字符串string,非常适合缓存只读数据,因为字符串不需要额外的处理。
2、快速开始
2.1、Memcached部署
通过yum安装memcache软件包
# 先安装一些依赖包
[root@localhost ~]# yum install libevent libevent-devel -y
# 安装memcached
[root@localhost ~]# yum install -y memcached
命令行启动测试
[root@localhost ~]# memcached -d -m 1024 -u memcached -l 127.0.0.1 -p 11211 -c 1024 -P /tmp/memcached.pid
- memcached启动参数说明:
- -d是启动一个守护进程;
- -m是分配给Memcache使用的内存数量,单位是MB;
- -u是运行Memcache的用户;
- -l是监听的服务器IP地址,可以有多个地址;
- -p是设置Memcache监听的端口,最好是1024以上的端口;
- -c是最大运行的并发连接数,默认是1024;
- -P是设置保存Memcache的pid文件。
- 查看端口号是否启动成功
[root@localhost ~]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:11211 *:*
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::11211 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
memcached的端口号为:11211
关闭服务
[root@localhost ~]# pkill memcached
服务自启动(systemd)
[root@localhost ~]# systemctl start memcached
2.2、Memcached连接
对于Memcached的连接,这里要使用到一个协议:telnet(远程连接协议)
Telnet 是一种用于远程访问和控制计算机系统的网络协议。它允许用户从一台计算机连接到另一台计算机,并在远程系统上执行命令和操作。
Telnet 的主要特点如下:
- 远程访问:Telnet 协议可以让用户远程登录到其他计算机系统,就像直接在那台机器上工作一样。这对于管理远程服务器或设备非常有用。
- 文本界面:Telnet 使用纯文本界面进行交互,不需要图形界面。这使它适用于各种类型的终端设备和操作系统。
- 简单易用:Telnet 客户端通常内置在操作系统中,或者可以很容易地下载安装。建立连接只需输入远程主机的 IP 地址或主机名即可。
- 功能丰富:Telnet 协议支持各种命令和操作,如文件传输、远程执行命令等。用户可以在 Telnet 会话中执行系统管理任务。
- 不安全:Telnet 使用明文传输数据,包括用户名和密码,存在严重的安全隐患。因此在现代网络环境下,Telnet 逐步被更安全的 SSH 协议所取代。
Telnet和ssh对比:
特性 | Telnet | SSH |
---|---|---|
安全性 | 使用明文传输数据,非常不安全 | 使用强大的加密技术,提供高度安全性 |
加密 | 不提供任何加密功能 | 支持多种加密算法,如 AES、3DES 等 |
认证 | 仅依赖用户名和密码进行身份验证 | 支持多种认证方式,如用户名/密码、公钥/私钥等 |
端口 | 默认使用 23 号端口 | 默认使用 22 号端口 |
应用场景 | 用于简单的远程控制和诊断任务,但已很少使用 | 广泛用于企业和个人的远程管理和数据传输 |
[root@localhost ~]# yum install telnet -y
[root@localhost ~]# telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
# 连接以后并不会给出明确提示,我们可以直接输入命令进行测试
2.3、查看Memcached信息
Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。
[root@localhost ~]# telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 16509
STAT uptime 8793
STAT time 1723623164
STAT version 1.4.15
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 0.219841
STAT rusage_system 0.109920
STAT curr_connections 10
STAT total_connections 12
STAT connection_structures 11
STAT reserved_fds 20
STAT cmd_get 8
STAT cmd_set 2
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 1
STAT get_misses 7
STAT delete_misses 0
STAT delete_hits 2
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 222
STAT bytes_written 1197
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT bytes 0
STAT curr_items 0
STAT total_items 2
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 0
END
如果可以看到以上输出,说明安装并且连接成功,至于具体解释,后面再说....
3、slab存储机制
memcached接收来此客户端的存储请求,那么服务端是如何存储来自客户端的存储内容的呢,这里就涉及到了slabs存储机制,在此之前首先介绍memcached中关于内存管理的几个重要的概念
3.1、item数据存储节点
items:客户端传送的键-值包装成items结构体,其内存通过slab分配
源码说明:
typedef struct _stritem {
/* Protected by LRU locks */
//一个item的地址, 主要用于LRU链和freelist链
struct _stritem *next;
//下一个item的地址,主要用于LRU链和freelist链
struct _stritem *prev;
/* Rest are protected by an item lock */
//用于记录哈希表槽中下一个item节点的地址
struct _stritem *h_next; /* hash chain next */
//最近访问时间
rel_time_t time; /* least recent access */
//缓存过期时间
rel_time_t exptime; /* expire time */
int nbytes; /* size of data */
//当前item被引用的次数,用于判断item是否被其它的线程在操作中
//refcount == 1的情况下该节点才可以被删除
unsigned short refcount;
uint8_t nsuffix; /* length of flags-and-length string */
uint8_t it_flags; /* ITEM_* above */
//记录该item节点位于哪个slabclass_t中
uint8_t slabs_clsid;/* which slab class we're in */
uint8_t nkey; /* key length, w/terminating null and padding */
/* this odd type prevents type-punning issues when we do
* the little shuffle to save space when not using CAS. */
union {
uint64_t cas;
char end;
} data[];
/* if it_flags & ITEM_CAS we have 8 bytes CAS */
/* then null-terminated key */
/* then " flags length\r\n" (no terminating null) */
/* then data with terminating \r\n (no terminating null; it's binary!) */
} item;
3.2、slab与chunk
slab是一块内存空间,默认大小为1M,memcached会把一个slab分割成一个个chunk, 这些被切割的小的内存块,主要用来存储item
3.3、slabclass
每个item的大小都可能不一样,item存储于chunk,如果chunk大小不够,则不足以分配给item使用,如果chunk过大,则太过于浪费内存空间。因此memcached采取的做法是,将slab切割成不同大小的chunk,这样就满足了不同大小item的存储。被划分不同大小chunk的slab的内存在memcached就是用slabclass这个结构体来表现的
slabclass结构体源码:
typedef struct {
//chunk大小
unsigned int size; /* sizes of items */
//1M内存大小被分割为多少个chunck
unsigned int perslab; /* how many items per slab */
//空闲chunk链表
void *slots; /* list of item ptrs */
//空闲chunk的个数
unsigned int sl_curr; /* total free items in list */
//当前slabclass已经分配了所少个1M空间的slab
unsigned int slabs; /* how many slabs were allocated for this class */
//slab指针数组
void **slab_list; /* array of slab pointers */
//slab指针数组的大小
unsigned int list_size; /* size of prev array */
size_t requested; /* The number of requested bytes */
} slabclass_t;
- slabclass数组初始化的时候,每个slabclass_t都会分配一个1M大小的slab,slab会被切分为N个小的内存块,这个小的内存块的大小取决于slabclass_t结构上的size的大小
- 每个slabclass_t都只存储一定大小范围的数据,并且下一个slabclass切割的chunk块大于前一个slabclass切割的chunk块大小
- memcached中slabclass数组默认大小为64,slabclass切割块大小的增长因子默认是1.25 例如:slabclass[1]切割的chunk块大小为100字节,slabclass[2]为125,如果需要存储一个110字节的缓存,那么就需要到slabclass[2] 的空闲链表中获取一个空闲节点进行存储
3.4、item节点分配流程
- 根据大小,找到合适的slabclass
- slabclass空闲列表中是否有空闲的item节点,如果有直接分配item,用于存储内容
- 空闲列表没有空闲的item可以分配,会重新开辟一个slab(默认大小为1M)的内存块,然后切割slab并放入到空闲列表中,然后从空闲列表中获取节点
3.5、item节点的释放
释放一个item节点,并不会free内存空间,而是将item节点归还到slabclass的空闲列表中
4、Memcached常用操作
4.1、数据存储
4.1.1、set命令
语法:
set key flags exptime bytes [noreply]
value
相关参数说明:
- key:键值 key-value 结构中的 key,用于查找缓存值。
- flags:flags 是一个整型参数,用于存储关于键值对的额外信息。这些信息可以被客户端用来解释或处理存储的数据。通过 flags,客户端可以在存储数据时为该数据打上一些标记,以便后续处理时能够正确识别数据的类型或属性。
- exptime:在缓存中保存键值对的时间长度(以秒为单位,0 表示永远)
- bytes:在缓存中存储的字节数
- noreply(可选): 该参数告知服务器不需要返回数据
- value:存储的值(始终位于第二行)(可直接理解为key-value结构中的value)
示例:
set name 0 0 8
zhangsan
STORED
get name
VALUE name 0 8
zhangsan
END
4.1.2、add命令
Memcached add 命令用于将 value(数据值) 存储在不存在的 key(键) 中。
如果 add 的 key 已经存在,则不会更新数据(过期的 key 会更新),之前的值将仍然保持相同,并且您将获得响应 NOT_STORED
语法:
add key flags exptime bytes [noreply]
value
示例:
add name 0 0 10
zhangsan
NOT_STORED
get name
VALUE name 0 8
zhangsan
END
# 加入一个不存在的key就可以成功
add age 1 0 10
18
STORED
get age
VALUE age 1 10
18
END
4.1.3、replace命令
Memcached replace 命令用于替换已存在的 key(键) 的 value(数据值)。
如果 key 不存在,则替换失败,并且您将获得响应 NOT_STORED。
语法:
replace key flags exptime bytes [noreply]
value
示例:
replace name 0 900 8
lisilisi
replace gender 0 900 4
male
4.1.4、append命令
Memcached append 命令用于向已存在 key(键) 的 value(数据值) 后面追加数据 。
示例:
get key1
END
set key1 0 900 9
memcached
STORED
get key1
VALUE key1 0 9
memcached
END
append key1 0 900 5
redis
STORED
get key1
VALUE key1 0 14
memcachedredis
END
4.1.5、prepend命令
Memcached prepend 命令用于向已存在 key(键) 的 value(数据值) 前面追加数据 。
语法:
prepend key flags exptime bytes [noreply]
value
示例:
prepend key1 0 900 5
hello
STORED
get key1
VALUE key1 0 19
hellomemcachedredis
END
4.2、CAS命令
CAS (Check-And-Set) 是 Memcached 中一个非常有用的原子操作特性。它可以帮助我们解决多客户端并发更新同一数据的问题。
4.2.1、CAS 命令格式
CAS 命令的完整格式为:
复制
cas key flags exptime bytes casunique
其中:
key
: 缓存数据的键flags
: 缓存数据的标志位exptime
: 缓存数据的过期时间(单位为秒)bytes
: 缓存数据的长度casunique
: 一个唯一标识符,用于检查值是否被修改过
4.2.2、CAS 操作流程
CAS 操作的流程如下:
- 客户端先使用
get
命令获取某个 key 的值,并记录下返回的casunique
。 - 客户端准备更新这个值时,会使用
cas
命令,并附带之前获取的casunique
。 - Memcached 服务器收到
cas
命令后,会先检查当前值的casunique
是否与客户端传来的一致。 - 如果一致,说明这个值自从客户端获取后就没有被其他人修改过,服务器会接受这次更新。
- 如果不一致,说明这个值在客户端获取后已经被其他人修改过了,服务器会拒绝这次更新。
4.2.3、CAS 的应用场景
CAS 命令主要用于解决多客户端并发更新同一缓存数据的问题,避免出现"丢失更新"的情况。
例如,在一个电商网站上,多个用户可能同时操作同一个购物车。这时就可以使用 CAS 来确保只有最后一个更新成功的客户端的修改生效。
假设我们有一个电商网站,需要缓存用户的购物车信息。多个用户可能同时操作同一个购物车,此时就需要使用 CAS 来避免"丢失更新"的问题。
案例流程如下:
- 用户 A 访问网站,获取自己的购物车信息:
- 使用
get
命令从 Memcached 中获取购物车数据 - 同时记录下返回的
casunique
值 - 用户 A 添加一件商品到购物车:
- 使用
cas
命令更新购物车数据 - 同时带上之前获取的
casunique
值 - 与此同时,用户 B 也访问网站,获取自己的购物车信息:
- 同样使用
get
命令从 Memcached 中获取购物车数据 - 记录下返回的
casunique
值 - 用户 B 也想修改购物车中的商品:
- 使用
cas
命令尝试更新购物车数据 - 但此时 Memcached 检查发现
casunique
已经不一致了 - 因此拒绝了用户 B 的更新请求
- 最终只有用户 A 的更新生效,用户 B 的更新被拒绝。
示例:
set name 0 0 3
nls
STORED
get name
VALUE name 0 3
nls
END
gets name
VALUE name 0 3 12
nls
END
cas name 0 0 3 12
xls
STORED
get name
VALUE name 0 3
xls
END
cas name 0 0 2 12
cs
EXISTS
输出信息说明:
- STORED:保存成功后输出。
- ERROR:保存出错或语法错误。
- EXISTS:在最后一次取值后另外一个用户也在更新该数据。
- NOT_FOUND:Memcached 服务上不存在该键值。
4.3、数据查找
4.3.1、get命令
get 命令的基本语法格式如下:
get key
多个 key 使用空格隔开,如下:
get key1 key2 key3
参数说明如下:
- key:键值 key-value 结构中的 key,用于查找缓存值。
4.3.2、gets命令
Memcached gets 命令获取带有 CAS 令牌存 的 value(数据值) ,如果 key 不存在,则返回空。不带的也可以正常获取
语法
gets 命令的基本语法格式如下:
gets key
多个 key 使用空格隔开,如下:
gets key1 key2 key3
参数说明如下:
- key:键值 key-value 结构中的 key,用于查找缓存值。
4.3.3、delete命令
Memcached delete 命令用于删除已存在的 key(键)。
语法
delete 命令的基本语法格式如下:
delete key [noreply]
参数说明如下:
- key:键值 key-value 结构中的 key,用于查找缓存值。
- noreply(可选): 该参数告知服务器不需要返回数据
输出信息说明:
- DELETED:删除成功。
- ERROR:语法错误或删除失败。
- NOT_FOUND:key 不存在。
4.4、统计命令
4.4.1、stat命令
Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。
stats
stats
STAT pid 1162
STAT uptime 5022
STAT time 1415208270
STAT version 1.4.14
STAT libevent 2.0.19-stable
STAT pointer_size 64
STAT rusage_user 0.096006
STAT rusage_system 0.152009
STAT curr_connections 5
STAT total_connections 6
STAT connection_structures 6
STAT reserved_fds 20
STAT cmd_get 6
STAT cmd_set 4
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 4
STAT get_misses 2
STAT delete_misses 1
STAT delete_hits 1
STAT incr_misses 2
STAT incr_hits 1
STAT decr_misses 0
STAT decr_hits 1
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 262
STAT bytes_written 313
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT expired_unfetched 1
STAT evicted_unfetched 0
STAT bytes 142
STAT curr_items 2
STAT total_items 6
STAT evictions 0
STAT reclaimed 1
END
这里显示了很多状态信息,下边详细解释每个状态项:
- pid: memcache服务器进程ID
- uptime:服务器已运行秒数
- time:服务器当前Unix时间戳
- version:memcache版本
- pointer_size:操作系统指针大小
- rusage_user:进程累计用户时间
- rusage_system:进程累计系统时间
- curr_connections:当前连接数量
- total_connections:Memcached运行以来连接总数
- connection_structures:Memcached分配的连接结构数量
- cmd_get:get命令请求次数
- cmd_set:set命令请求次数
- cmd_flush:flush命令请求次数
- get_hits:get命令命中次数
- get_misses:get命令未命中次数
- delete_misses:delete命令未命中次数
- delete_hits:delete命令命中次数
- incr_misses:incr命令未命中次数
- incr_hits:incr命令命中次数
- decr_misses:decr命令未命中次数
- decr_hits:decr命令命中次数
- cas_misses:cas命令未命中次数
- cas_hits:cas命令命中次数
- cas_badval:使用擦拭次数
- auth_cmds:认证命令处理的次数
- auth_errors:认证失败数目
- bytes_read:读取总字节数
- bytes_written:发送总字节数
- limit_maxbytes:分配的内存总大小(字节)
- accepting_conns:服务器是否达到过最大连接(0/1)
- listen_disabled_num:失效的监听数
- threads:当前线程数
- conn_yields:连接操作主动放弃数目
- bytes:当前存储占用的字节数
- curr_items:当前存储的数据总数
- total_items:启动以来存储的数据总数
- evictions:LRU释放的对象数目
- reclaimed:已过期的数据条目来存储新数据的数目
4.4.2、stats items
Memcached stats items 命令用于显示各个 slab 中 item 的数目和存储时长(最后一次访问距离现在的秒数)。
语法
stats items
示例
stats items
STAT items:1:number 1
STAT items:1:age 7
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 0
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0
END
4.4.3、stats slab
Memcached stats slabs 命令用于显示各个slab的信息,包括chunk的大小、数目、使用情况等。
stats slabs
示例
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 1
STAT 1:free_chunks 10921
STAT 1:free_chunks_end 0
STAT 1:mem_requested 71
STAT 1:get_hits 0
STAT 1:cmd_set 1
STAT 1:delete_hits 0
STAT 1:incr_hits 0
STAT 1:decr_hits 0
STAT 1:cas_hits 0
STAT 1:cas_badval 0
STAT 1:touch_hits 0
STAT active_slabs 1
STAT total_malloced 1048512
END
4.4.4、stats sizes
Memcached stats sizes 命令用于显示所有item的大小和个数。
该信息返回两列,第一列是 item 的大小,第二列是 item 的个数。
语法:stats sizes 命令的基本语法格式如下:
stats sizes
实例:
stats sizes
STAT 96 1
END
4.4.5、flush_all命令
Memcached flush_all 命令用于清理缓存中的所有 key=>value(键=>值) 对。
该命令提供了一个可选参数 time,用于在制定的时间后执行清理缓存操作。
语法:
flush_all 命令的基本语法格式如下:
flush_all [time] [noreply]
实例
清理缓存:
set runoob 0 900 9
memcached
STORED
get runoob
VALUE runoob 0 9
memcached
END
flush_all
OK
get runoob
END
六、Kickstart
1、PXE无人值守安装系统
使用PXE+KickStart可以通过非交互模式完成无人值守安装操作系统。
PXE 客户端从DHCP服务器获取到PXE服务端的具体IP,然后再从PXE配置文件中获取vmlinuz、initrd.img、ks.cfg、系统镜像等文件所在的服务器和位置信息。
2、什么是PXE?
- PXE (Preboot Execution Environment) 是一种基于网络的操作系统无人值守安装技术。它允许客户端计算机通过网络引导并安装操作系统,而无需任何本地存储介质。
将 PXE 与 Kickstart 配合使用,可以实现以下自动化安装流程:
- 配置 PXE 服务器 在网络上建立一个 PXE 引导服务器,提供 DHCP 和 TFTP 服务,用于为客户端机器提供 PXE 引导环境。
- 准备 Kickstart 配置文件 编写好符合您需求的 Kickstart 配置文件,并将其置于 PXE 服务器的 HTTP 共享目录下。
- 客户端机器 PXE 引导 配置客户端机器的 BIOS 以支持 PXE 网络引导。在客户端启动时,它会通过 DHCP 获取 PXE 引导所需的信息,然后从 TFTP 服务器下载引导程序。
-
自动安装操作系统 引导程序会从 HTTP 服务器下载 Kickstart 配置文件,并根据文件中定义的规则自动安装操作系统。整个过程不需要任何人工干预。
-
Client/Server的工作模式
3、批量装机软件
-
Cobbler: Cobbler 是一款开源的网络安装管理系统,提供基于 PXE 的自动化安装和部署功能。它支持多种操作系统,包括 Red Hat、CentOS、Fedora、Debian、Ubuntu 等。Cobbler 使用简单的配置文件来定义系统安装过程,类似于 Kickstart。它可以与 Puppet、Ansible 等配置管理工具集成使用。
-
Preseed: Preseed 是 Debian/Ubuntu 系列操作系统的自动化安装工具,与 Kickstart 的功能类似。Preseed 使用一个应答文件来定义安装过程中的各种选项,如磁盘分区、软件包选择等。这个应答文件可以放在网络上供安装程序自动获取。
-
AutoYaST: AutoYaST 是 SUSE/openSUSE 系统的自动安装工具,同样使用一个配置文件来定义安装过程。与 Kickstart 相似,AutoYaST 配置文件可以指定分区信息、软件包选择、服务配置等。
-
Kickstart:
Kickstart 是 Red Hat Enterprise Linux (RHEL) 和 CentOS 等发行版中的一个功能,用于实现操作系统的自动化安装。它可以通过一个预先配置好的安装配置文件来完成整个安装过程,省去了手动安装的繁琐步骤。
Kickstart 的主要特点包括:
- 自动化安装 Kickstart 配置文件定义了操作系统的各种安装选项,如分区方案、软件包选择、网络设置等。安装过程可以完全自动化进行,无需人工干预。
- 可重复性 一旦 Kickstart 配置文件编写好,就可以在多台机器上重复执行安装,确保系统环境的一致性。这对于大规模部署非常有帮助。
- 灵活性 Kickstart 支持丰富的配置指令,可以满足各种复杂的安装需求。您可以根据实际情况自定义配置文件。
- 集中管理 Kickstart 配置文件可以集中存储在网络服务器上,在需要时由客户端机器自动下载并执行安装。这简化了配置文件的维护和更新。
使用 Kickstart 的典型流程如下:
- 编写 Kickstart 配置文件
- 将配置文件发布到网络服务器
- 在客户端机器上启动 PXE 网络安装
- 客户端自动读取配置文件并完成无人值守的安装过程
4、实验部署
- 关闭防火墙和selinux
[root@server1 ~]# systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Docs: man:firewalld(1)
[root@server1 ~]# getenforce
Permissive
- 配置DHCP Server:DHCP是一个局域网的网络协议,使用UDP协议工作,主要有两个用途,给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机中央管理的手段。
- 准备网卡,添加一张仅主机网卡
[root@server1 ~]# nmcli connection modify '有线连接 1' con-name ens37
[root@server1 ~]# nmcli connection modify ens37 ipv4.addresses 192.168.99.200/24 autoconnect yes ipv4.method manual
[root@server1 ~]# nmcli connection down ens37
[root@server1 ~]# nmcli connection up ens37
[root@server1 ~]# ip a | grep global
inet 192.168.92.200/24 brd 192.168.92.255 scope global ens37
- 配置dhcp服务
- 关闭仅主机网卡的自动dhcp
- 准备dhcp服务器
[root@server1 ~]# yum -y install dhcp -y
[root@server1 ~]# vim /etc/dhcp/dhcpd.conf
subnet 192.168.99.0 netmask 255.255.255.0 {
range 192.168.99.100 192.168.99.199;
option subnet-mask 255.255.255.0;
default-lease-time 21600;
max-lease-time 43200;
next-server 192.168.99.200;
filename "/pxelinux.0";
}
[root@server1 ~]# systemctl restart dhcpd
[root@server1 ~]# systemctl enable dhcpd
Created symlink from /etc/systemd/system/multi-user.target.wants/dhcpd.service to /usr/lib/systemd/system/dhcpd.service.
# 配置文件解释
range 192.168.92.100 192.168.92.199; # 可分配的起始IP-结束IP
option subnet-mask 255.255.255.0; # 设定netmask
default-lease-time 21600; # 设置默认的IP租用期限
max-lease-time 43200; # 设置最大的IP租用期限
next-server 192.168.92.200; # 告知客户端TFTP服务器的ip
filename "/pxelinux.0"; # 告知客户端从TFTP根目录下载pxelinux.0文件
# 验证端口号
[root@server1 ~]# ss -uanp | grep 67
UNCONN 0 0 *:67 *:* users:(("dhcpd",pid=4333,fd=7))
- 安装tftp
[root@server1 ~]# yum install tftp-server -y
[root@server1 ~]# systemctl start tftp
[root@server1 ~]# systemctl enable tftp
[root@server1 ~]# ss -uanp | grep 69
UNCONN 0 0 :::69 :::* users:(("systemd",pid=1,fd=25))
- pxe引导配置,syslinux是一个功能强大的引导加载程序,而且兼容各种介质。syslinux是一个小型的Linux操作系统,它的目的是简化首次安装或其他特殊用途的启动盘。首先需要将pxelinux.0配置文件复制到tftp目录下,再将光盘镜像中的一些文件复制到tftp的目录中。
[root@server1 ~]# yum install syslinux -y
[root@server1 ~]# cd /var/lib/tftpboot/
[root@server1 tftpboot]# cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/
[root@server1 tftpboot]# mkdir -p /media/cdrom
[root@server1 tftpboot]# mount /dev/cdrom /media/cdrom/
mount: /dev/sr0 写保护,将以只读方式挂载
[root@server1 tftpboot]# cp /media/cdrom/images/pxeboot/{vmlinuz,initrd.img} /var/lib/tftpboot/
[root@server1 tftpboot]# cp /media/cdrom/isolinux/{vesamenu.c32,boot.msg} /var/lib/tftpboot/
- 配置syslinux服务程序,这个文件是开机时的选项菜单
[root@server1 tftpboot]# mkdir -p /var/lib/tftpboot/pxelinux.cfg
[root@server1 tftpboot]# cp /media/cdrom/isolinux/isolinux.cfg pxelinux.cfg/default
[root@server1 tftpboot]# vim /var/lib/tftpboot/pxelinux.cfg/default
1 default linux
64 append initrd=initrd.img inst.stage2=ftp://192.168.99.200 ks=ftp://192.168.99.200/pub/ks.cfg quiet
- 配置vsftpd服务程序,光盘镜像是通过ftp协议传输的,因此要用到vsftpd服务程序
[root@server1 tftpboot]# yum install -y vsftpd
[root@server1 tftpboot]# systemctl restart vsftpd
[root@server1 tftpboot]# systemctl enable vsftpd
Created symlink from /etc/systemd/system/multi-user.target.wants/vsftpd.service to /usr/lib/systemd/system/vsftpd.service.
[root@server1 tftpboot]# cp -r /media/cdrom/* /var/ftp/
- 创建Kickstart应答文件,Kickstart应答文件中包含了系统安装过程中需要使用的选项和参数信息,系统可以自动调取这个应答文件的内容,从而彻底实现无人值守安装系统。
[root@server1 tftpboot]# cp ~/anaconda-ks.cfg /var/ftp/pub/ks.cfg
[root@server1 tftpboot]# chmod +r /var/ftp/pub/ks.cfg
[root@server1 tftpboot]# vim /var/ftp/pub/ks.cfg
5 url --url=ftp://192.168.99.200 # 删除原本的cdrom
30 clearpart --all --initlabel # 意思是清空所有磁盘内容并初始化磁盘
- 自动部署客户端主机
七、Rsync
1、什么是RSYNC
rsync是类unix下的一款数据镜像备份工具——remote sync。
Rsync 的基本特点如下:
- 可以镜像保存整个目录树和文件系统;
- 可以很容易做到保持原来文件的权限、时间、软硬链接等;
- 无须特殊权限即可安装;
- 优化的流程,文件传输效率高;
- 可以使用 rcp、ssh 等方式来传输文件,当然也可以通过直接的 socket 连接;
- 支持匿名传输;
- rsync的主要特点就是增量传输,只对变更的部分进行传送。
2、RSYNC原理
2.1、rsync原理
rsync
是linux
下同步文件的一个高效算法,用于同步更新两处计算机的文件和目录,并适当利用查找文件中的不同块以减少数据传输。rsync
的主要特点就是增量传输,只对变更的部分进行传送。
vim 1.txt
1 2 3 4
vim 2.txt
1 2 3 4
diff 1.txt 2.txt
2.1.1、增量同步算法
假如我们现在需要同步两个文件保持一致,并且只想传送不同的部分,那么我们就需要对两边的文件做diff
,但是这两个文件在两台不同的机器上,无法做diff
。如果我们做diff
,就要把一个文件传到另一台机器上做diff
,但这样一来,我们就传了整个文件,这与我们只想传输不同部分的初衷相背。于是我们就要想一个办法,让这两边的文件见不到面,但还能知道它们间有什么不同。这就是rsync
的算法。
2.2.2、rsync同步算法
我们将同步源文件名称为fileSrc
,同步目的文件叫fileDst
。
1.分块Checksum算法
找到文件不同的地方
首先,我们会把fileDst
的文件平均切分成若干个小块,比如每块512
个字节(最后一块会小于这个数),然后对每块计算两个checksum
:
- 一个叫
rolling checksum
,是弱checksum
,32
位的checksum
(相对粗略,但是快) - 另一个是强
checksum
,128
位的,以前用md4
,现在用md5 hash
算法。
为什么要这样?因为若干年前的硬件上跑md4
的算法太慢了,所以,我们需要一个快算法来鉴别文件块的不同,但是弱的adler32
算法碰撞概率太高了,所以我们还要引入强的checksum
算法以保证两文件块是相同的。也就是说,弱的checksum
是用来区别不同,而强的是用来确认相同。
2. 传输算法
同步目标端会把fileDst
的一个checksum
列表传给同步源,这个列表里包括了三个东西,rolling checksum(32bits),md5 checksume(128bits)
,文件块编号。
同步源机器拿到了这个列表后,会对fileSrc
做同样的checksum
,然后和fileDst
的checksum
做对比,这样就知道哪些文件块改变了。
但是,聪明的你一定会有以下两个疑问:
如果我fileSrc
这边在文件中间加了一个字符,这样后面的文件块都会位移一个字符,这样就完全和fileDst
这边的不一样了,但理论上来说,我应该只需要传一个字符就好了。这个怎么解决? 如果这个checksum
列表特别长,而我的两边的相同的文件块可能并不是一样的顺序,那就需要查找,线性的查找起来应该特别慢吧。这个怎么解决? 很好,让我们来看一下同步源端的算法。
3. checksum查找算法
同步源端拿到fileDst
的checksum
数组后,会把这个数据存到一个hash table
(特殊的数据结构体,可以快速检索)中,用rolling checksum
做hash
,以便获得O(1)
时间复杂度的查找性能。这个hash table
是16bits
的,所以,hash table
的尺寸是2的16次方
,对rolling checksum
的hash
会被散列到0 到 2^16 – 1
中的某个整数值。
4. 比对算法
- 取
fileSrc
的第一个文件块(我们假设的是512
个长度),也就是从fileSrc
的第1
个字节到第512
个字节,取出来后做rolling checksum
计算。计算好的值到hash
表中查。 - 如果查到了,说明发现在
fileDst
中有潜在相同的文件块,于是就再比较md5
的checksum
,因为rolling checksume
太弱了,可能发生碰撞。于是还要算md5
的128bits
的checksum
,这样一来,我们就有2^-(32+128) = 2^-160
的概率发生碰撞,这太小了可以忽略。如果rolling checksum
和md5 checksum
都相同,这说明在fileDst
中有相同的块,我们需要记下这一块在fileDst
下的文件编号。 - 如果
fileSrc
的rolling checksum
没有在hash table
中找到,那就不用算md5 checksum
了。表示这一块中有不同的信息。总之,只要rolling checksum
或md5 checksum
其中有一个在fileDst
的checksum hash
表中找不到匹配项,那么就会触发算法对fileSrc
的rolling
动作。于是,算法会住后step 1
个字节,取fileSrc
中字节2-513
的文件块要做checksum,go to (1.)
– 现在你明白什么叫rolling checksum
了吧。(主动往后一位) - 这样,我们就可以找出
fileSrc
相邻两次匹配中的那些文本字符,这些就是我们要往同步目标端传的文件内容了。
5. 传输
最终在同步源这端,我们的rsync
算法可能会得到这个样子的一个数据数组,图中,红色块表示在目标端已匹配上,不用传输(注:我专门在其中显示了两块chunk #5
,代表数据中有复制的地方,不用传输),而白色的地方就是需要传输的内容(注意:这些白色的块是不定长的),这样,同步源这端把这个数组(白色的就是实际内容,红色的就放一个标号)压缩传到目的端,在目的端的rsync
会根据这个表重新生成文件,这样,同步完成。
参考博客:
https://segmentfault.com/a/1190000018391604?utm_source=tag-newest
3、安装部署服务端
- 安装xineted服务
[root@server1 ~]# yum install rsync.x86_64 -y
[root@server1 ~]# yum install xinetd -y
- 修改xineted配置文件
[root@server1 ~]# vim /etc/xinetd.d/rsync
service rsync
{
disable = no
socket_type = stream
wait = no
user = root
server = /usr/bin/rsync
server_args = --daemon
port = 873
log_on_failure = USERID
}
- 修改rsync配置文件
[root@server1 ~]# vim /etc/rsyncd.conf
[test] // 模块名,主要是定义服务器哪个目录要被同步
path = /test // 指定文件目录所在位置,这是必须指定的
uid = root
gid = root
max connections = 2
timeout = 300
read only = false
auth users = root // 认证用户是 root,必须是服务器上真实存在的用户
secrets file = /etc/rsync.passwd // 密码存在哪个文件
strict modes = yes
use chroot = yes // 在传输文件之前,服务器守护程序在将chroot 到文件系统中的目录中
- 准备密码文件
[root@server1 ~]# vim /etc/rsync.passwd
root:123456
[root@server1 ~]# chmod 600 /etc/rsync.passwd
- 启动服务
[root@server1 ~]# systemctl start xinetd.service
[root@server1 ~]# ss -tanl | grep 873
LISTEN 0 64 :::873 :::*
- 准备文件
[root@server1 ~]# mkdir /test
[root@server1 ~]# touch /test/123
4、六种不同的工作模式
- 在server2上安装rsync
[root@server2 ~]# yum install rsync.x86_64 -y
rysnc工具的参数
rsync 相关参数
-v --verbose详细
-a --avchive归档模式,表示递归方式传输文件,并保持所有文件属性,等于-rlptgoD
-z 传递过程中使用zip压缩传递,速度更快
-p, --perms 保持文件权限
-P --partial 保留那些因故没有完全传输的文件,以便加快随后的再次传输
-r --recursiv递归目录
-e --rsh=COMMAND指定使用rsh、ssh方式进行数据同步
--progress 在传输时现实传输过程(显示备份过程)
-topg 保持文件原有属性,o=owner,t=time,p=perms(权限),g=group
-b --backup创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename
-u --update仅仅进行更新,也就是跳过已经存在的文件
-l--links保留软连接
--delete 删除那些DST中SRC没有的文件(就是在目的目录中只保留传输过去的文件,其它的都删除),保持和源文件相同
-q, --quiet 精简输出模式
-c, --checksum 打开校验开关,强制对文件传输进行校验
-R, --relative 使用相对路径信息
-b, --backup 创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用--suffix选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename)存放在在目录下。
-suffix=SUFFIX 定义备份文件前缀
-u, --update 仅仅进行更新,也就是跳过所有已经存在于DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件)
-l, --links 保留软链结
-L, --copy-links 想对待常规文件一样处理软链结
--copy-unsafe-links 仅仅拷贝指向SRC路径目录树以外的链结
--safe-links 忽略指向SRC路径目录树以外的链结
-H, --hard-links 保留硬链结
-o, --owner 保持文件属主信息
-g, --group 保持文件属组信息
-D, --devices 保持设备文件信息
-t, --times 保持文件时间信息
-S, --sparse 对稀疏文件进行特殊处理以节省DST的空间
-n, --dry-run现实哪些文件将被传输
-W, --whole-file 拷贝文件,不进行增量检测
-x, --one-file-system 不要跨越文件系统边界
-B, --block-size=SIZE 检验算法使用的块尺寸,默认是700字节
-e, --rsh=COMMAND 指定使用rsh、ssh方式进行数据同步
--rsync-path=PATH 指定远程服务器上的rsync命令所在路径信息
-C, --cvs-exclude 使用和CVS一样的方法自动忽略文件,用来排除那些不希望传输的文件
--existing 仅仅更新那些已经存在于DST的文件,而不备份那些新创建的文件
--delete-excluded 同样删除接收端那些被该选项指定排除的文件
--delete-after 传输结束以后再删除
--ignore-errors 及时出现IO错误也进行删除
--max-delete=NUM 最多删除NUM个文件
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输
--force 强制删除目录,即使不为空
--numeric-ids 不将数字的用户和组ID匹配为用户名和组名
--timeout=TIME IP超时时间,单位为秒
-I, --ignore-times 不跳过那些有同样的时间和长度的文件
--size-only 当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间
--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为0
-T --temp-dir=DIR 在DIR中创建临时文件
--compare-dest=DIR 同样比较DIR中的文件来决定是否需要备份
--exclude=PATTERN 指定排除不需要传输的文件模式
--include=PATTERN 指定不排除而需要传输的文件模式
--exclude-from=FILE 排除FILE中指定模式的文件。
如果排除单个文件或者目录,可以直接指定 --exclude-from=File_Name
如果是多个文件或目录,可以新建一个文件 exclude,里面写上要排除的文件名或目录名,可以使用正则,然后使用--exclude-from='/../exclude'指定
--include-from=FILE 不排除FILE指定模式匹配的文件。用法同上
--version 打印版本信息
--address 绑定到特定的地址
--config=FILE 指定其他的配置文件,不使用默认的rsyncd.conf文件
--port=PORT 指定其他的rsync服务端口
--blocking-io 对远程shell使用阻塞IO
-stats 给出某些文件的传输状态
--log-format=formAT 指定日志文件格式
--password-file=FILE 从FILE中得到密码
--bwlimit=KBPS 限制I/O带宽,KBytes per second
-h, --help 显示帮助信息
- 模式一,查看服务端有哪些可用数据源
[root@server2 ~]# rsync --list-only root@192.168.88.10::
test
- 模式二,本地文件拷贝到本地,当src和dest都不包含有冒号时就启动从本地进行拷贝
[root@server2 ~]# mkdir /backup
[root@server2 ~]# touch local.txt
[root@server2 ~]# rsync local.txt /backup/
[root@server2 ~]# ls /backup/
local.txt
- 模式三,本地文件拷贝到远程,当dest包含冒号时就启动拷贝到远程
[root@server2 ~]# rsync local.txt root@192.168.88.10:/test
The authenticity of host '192.168.175.10 (192.168.175.10)' can't be established.
ECDSA key fingerprint is SHA256:x573vWoEULGOYwloNT7s9EqxZa6lA1k5zZMFk7bU0xg.
ECDSA key fingerprint is MD5:60:21:e0:bf:3c:c0:d8:09:74:b8:23:26:55:4e:d1:0e.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.175.10' (ECDSA) to the list of known hosts.
root@192.168.175.10's password:
[root@server1 ~]# ll /test/local.txt
-rw-r--r-- 1 root root 0 3月 24 11:05 /test/local.txt
- 模式四,将远程文件拷贝到本地,当src包含冒号的时候就启动远程拷贝到本地
[root@server2 ~]# rsync root@192.168.88.10:/test/123 /backup/
root@192.168.175.10 password:
[root@server2 ~]# ll /backup/123
-rw-r--r-- 1 root root 0 3月 24 11:07 /backup/123
- 模式五,从远程服务器拷贝到本地,当src使用两个冒号的时候启用这种方式,冒号后面跟的是服务端设置的模块
[root@server2 ~]# rsync -r root@192.168.88.10::test /backup/
Password:
#这时的密码就不是主机root用户的密码而是rsync设置的密码了
[root@server2 ~]# ls /backup/
123 local.txt
- 模式六,从本地拷贝到远程服务器
[root@server2 ~]# touch 456
[root@server2 ~]# rsync -r 456 root@192.168.88.10::test
Password:
[root@server1 ~]# ls /test/
123 456 local.txt
5、案例,定时备份
客户端需求
1.客户端提前准备存放的备份的目录,目录规则如下:/backup/主机_ip_时间
2.客户端在本地打包备份(系统配置文件、应用配置等)例如/etc/passwd,拷贝至/backup/主机_ip_时间
3.客户端最后将备份的数据进行推送至备份服务器
4.客户端每天凌晨1点定时执行该脚本
5.客户端服务器本地保留最近7天的数据,避免浪费磁盘空间
服务端需求
1.服务端部署rsync,用于接收客户端推送过来的备份数据
⒉.服务端需要每天校验客户端推送过来的数据是否完整
3.服务端需要每天校验的结果通知给管理员
4.服务端仅保留6个月的备份数据,其余的全部删除
客户端准备
- 创建目录
[root@server2 ~]# mkdir /backup
- 安装expect工具
[root@server2 ~]# yum install expect* -y
- 准备expect脚本
[root@server2 ~]# vim rsync.exp
#!/usr/bin/expect
#set: 进行赋值
set mulu [lindex $argv 0]
#位置参数,0 表示第一个参数
set timeout 10
spawn rsync -avzr /backup/$mulu root@192.168.88.10::test
#spawn: 启动新的进程
expect Password
#expect: 从进程接收字符串
send "123456\n"
#send: 用于向进程发送字符串
expect eof
- 准备备份脚本
[root@server2 ~]# vim beifen.sh
#!/bin/bash
# 准备压缩文件的目录
mulu=`ip a | grep global|awk -F'[ /]+' '{print $3}'`_`date +%F`
echo $mulu
mkdir -pv /backup/$mulu &> /dev/null
# 打包待发送的数据
tar zcf /backup/$mulu/conf.tar.gz /etc/passwd /etc/vimrc &> /dev/null
# 为了后面模拟一个月数据的变化
touch /backup/$mulu
# 发送数据
#rsync -avzr /backup/$mulu root@192.168.175.10::test
expect rsync.exp $mulu
# 保留七天以内的数据
find /backup -mtime +7 -delete
[root@server2 ~]# chmod +x beifen.sh
- 计划任务
[root@server2 ~]# cat /etc/crontab
......
0 1 * * * root /root/beifen.sh
服务端准备
- 安装rsync
[root@server1 ~]# yum install rsync.x86_64 -y
- 修改配置文件
[root@server1 ~]# vim /etc/rsyncd.conf
[test]
path = /test
uid = root
gid = root
max connections = 2
timeout = 300
read only = false
auth users = root
secrets file = /etc/rsync.passwd
strict modes = yes
use chroot = yes
- 创建目录
[root@server1 ~]# mkdir /test -pv
- 准备密码文件
[root@server1 ~]# cat /etc/rsync.passwd
root:123456
[root@server1 ~]# chmod 600 /etc/rsync.passwd
- 启动rsync
[root@server1 ~]# systemctl start rsyncd.service
[root@server1 ~]# ss -tnl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 5 *:873 *:*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 5 :::873 :::*
- 验证服务端
[root@server2 ~]# rsync --list-only root@192.168.88.10::
test
- 模拟一个月的数据来验证结果
[root@server2 ~]# for i in {1..31};do date -s 2024/08/$i; /root/beifen.sh ; done
#修改时间,模拟一个月的时间
[root@server2 ~]# ll /backup
总用量 0
drwxr-xr-x 2 root root 25 8月 24 00:00 192.168.175.30_2022-08-24
drwxr-xr-x 2 root root 25 8月 25 00:00 192.168.175.30_2022-08-25
drwxr-xr-x 2 root root 25 8月 26 00:00 192.168.175.30_2022-08-26
drwxr-xr-x 2 root root 25 8月 27 00:00 192.168.175.30_2022-08-27
drwxr-xr-x 2 root root 25 8月 28 00:00 192.168.175.30_2022-08-28
drwxr-xr-x 2 root root 25 8月 29 00:00 192.168.175.30_2022-08-29
drwxr-xr-x 2 root root 25 8月 30 00:00 192.168.175.30_2022-08-30
drwxr-xr-x 2 root root 25 8月 31 00:00 192.168.175.30_2022-08-31
[root@server2 ~]# date
[root@server2 ~]# systemctl restart chronyd
#重置时间
[root@server2 ~]# date
[root@server1 ~]# ll /test
总用量 0
drwxr-xr-x 2 root root 25 8月 1 00:00 192.168.175.30_2022-08-01
drwxr-xr-x 2 root root 25 8月 2 00:00 192.168.175.30_2022-08-02
drwxr-xr-x 2 root root 25 8月 3 00:00 192.168.175.30_2022-08-03
drwxr-xr-x 2 root root 25 8月 4 00:00 192.168.175.30_2022-08-04
drwxr-xr-x 2 root root 25 8月 5 00:00 192.168.175.30_2022-08-05
drwxr-xr-x 2 root root 25 8月 7 00:00 192.168.175.30_2022-08-07
drwxr-xr-x 2 root root 25 8月 8 00:00 192.168.175.30_2022-08-08
drwxr-xr-x 2 root root 25 8月 9 00:00 192.168.175.30_2022-08-09
drwxr-xr-x 2 root root 25 8月 10 00:00 192.168.175.30_2022-08-10
drwxr-xr-x 2 root root 25 8月 11 00:00 192.168.175.30_2022-08-11
drwxr-xr-x 2 root root 25 8月 12 00:00 192.168.175.30_2022-08-12
drwxr-xr-x 2 root root 25 8月 20 2022 192.168.175.30_2022-08-20
drwxr-xr-x 2 root root 25 8月 21 2022 192.168.175.30_2022-08-21
drwxr-xr-x 2 root root 25 8月 22 2022 192.168.175.30_2022-08-22
drwxr-xr-x 2 root root 25 8月 23 2022 192.168.175.30_2022-08-23
drwxr-xr-x 2 root root 25 8月 24 2022 192.168.175.30_2022-08-24
drwxr-xr-x 2 root root 25 8月 25 2022 192.168.175.30_2022-08-25
drwxr-xr-x 2 root root 25 8月 27 2022 192.168.175.30_2022-08-27
drwxr-xr-x 2 root root 25 8月 28 2022 192.168.175.30_2022-08-28
drwxr-xr-x 2 root root 25 8月 29 2022 192.168.175.30_2022-08-29
drwxr-xr-x 2 root root 25 8月 30 2022 192.168.175.30_2022-08-30
drwxr-xr-x 2 root root 25 8月 31 2022 192.168.175.30_2022-08-31
八、Ansible
1、ansible自动化运维
1.1、ansible介绍
ansible 是一个开源的自动化运维工具,主要用于系统配置管理、应用部署、任务编排等场景。它使用 YAML 语法编写配置文件,语法简单易懂,学习曲线平缓。ansible 的任务是幂等的,意味着多次执行结果是一致的,不会产生意外结果,非常适合于持续部署和集成。
ansible 支持众多常见操作系统和中间件,具有良好的扩展性。同时它还支持自定义模块,可以满足各种复杂的自动化需求。另一个特点是 ansible 不需要在远程主机上安装任何代理,只需要有 SSH 访问权限即可,并且不需要中央控制节点,使用 SSH 协议直接连接远程主机,部署和维护相对简单。ansible 使用 SSH 进行远程连接和命令执行,保证了数据传输的安全性。
ansible由python开发,集合了众多自动化运维工具的优点,实现了批量系统部署、批量程序部署,批量运行命令等功能。ansible是基于模块工作的,本身没有批量部署的能力,真正具有批量部署能力的是ansible运行的模块,ansible只是提供一个框架。
1.2、核心组件
ansible:ansible核心程序。 HostInventory:记录由ansible管理的主机信息,包括端口、密码、ip等。 Playbooks:“剧本”YAML格式文件,多个任务定义在一个文件中,定义主机需要调用哪些模块来完成的功能。 CoreModules:核心模块,主要操作是通过调用核心模块来完成管理任务。 CustomModules:自定义模块,完成核心模块无法完成的功能,支持多种语言。 ConnectionPlugins:连接插件,ansible和Host通信使用
1.3、任务执行方式
- ad-HOC 点对点模式 使用单个模块,支持批量执行单条命令。ad-hoc 命令是一种可以快速输入的命令 例如对大量服务器执行shell 或者执行某个linux命令。
- playbook 模式(剧本模式) 通过编写yaml格式文件组合多个task任务,实现一个想要达到的功能,相对于多个点对点模式组合操作配置,playbook这个功能非常强大
1.4、特点
- 不需要在被监控端上安装任何服务程序
- 无服务器端,使用时直接运行命令即可
- 基于模块工作,可以使用任意语言开发
- 使用yaml语言编写playbook
- 基于ssh工作
- 可实现多级指挥
- 具有幂等性,一种操作重复多次执行结果相同
1.5、执行过程
- 加载自己的配置文件,默认为/etc/ansible/ansible.cfg
- 加载自己对应的模块文件
- 通过ansible将模块或命令生成对应的临时py文件,并将该文件传输至远程服务器
- 对应执行用户的家目录的.ansible/tmp/xx.py文件
- 给文件+x执行
- 执行并将返回结果,删除临时py文件,然后退出
2、ansible部署
- 安装ansible
[root@localhost ~]# yum install -y epel-release
[root@localhost ~]# yum install -y ansible
说明:ansible只是一个工具,不需要启动,安装好以后,直接使用即可。并且只有服务端需要安装,客户端不需要安装....
2.1、参数说明
`Inventory 文件参数:
-i 或 --inventory: 指定 Inventory 文件的路径
-l 或 --limit: 限制操作的主机范围
-g 或 --groups: 指定要操作的主机组
`剧本(Playbook)参数:
-p 或 --playbook-dir: 指定 Playbook 所在目录
-e 或 --extra-vars: 传递额外的变量
`任务(Task)参数:
-m 或 --module-name: 指定要使用的模块名称
-a 或 --args: 传递模块的参数
`连接参数:
-c 或 --connection: 指定连接类型,如 ssh、local 等
-u 或 --user: 指定远程连接的用户
`输出参数:
-v 或 --verbose: 增加输出信息的详细程度
--check: 进行一次"试运行",不会实际改变任何状态
--diff: 显示配置文件的改动情况
`其他参数:
-f 或 --forks: 指定并行执行的进程数
-t 或 --tags: 只执行带有指定 tag 的任务
--list-hosts: 列出受管主机
--list-tasks: 列出所有任务
2.2、快速开始
实验环境:四台Linux虚拟机,HOSTNAME分别为:ansible、server1、server2、server3
其中,ansible做为服务端,其他server均作为客户端
免密登录
# 在ansible上修改hosts文件,方便使用主机名管理主机
[root@ansible ~]# vim /etc/hosts
.......
192.168.88.10 server1
192.168.88.20 server2
192.168.88.30 server3
# 生成密钥对
[root@ansible ~]# ssh-keygen -P "" -t rsa
.....
# 将公钥发送给需要被管理端,以实现免密登录
[root@ansible ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@server1
[root@ansible ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@server2
[root@ansible ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@server3
2.2.1、常用工具
- ansible:临时命令执行工具,常用于执行临时命令
- ansible-doc:常用于模块功能的查询
- ansible-playbook:用于执行剧本
2.2.2、主要配置文件
- /etc/ansible/ansible.cfg:主配置文件
- /etc/ansible/hosts:主机清单文件
- /etc/ansible/roles:角色目录
2.2.3、配置主机清单
编辑/etc/ansible/hosts
文件,再最后面添加上被管理端
[root@ansible ~]# vim /etc/ansible/hosts
......
......
......
## [dbservers]
##
## db01.intranet.mydomain.net
## db02.intranet.mydomain.net
## 10.25.1.56
## 10.25.1.57
# Here's another example of host ranges, this time there are no
# leading 0s:
## db-[99:101]-node.example.com
# 定义自己的主机组
[all_servers]
server1
server2
server3
[node1]
server1
[node2]
server2
[node3]
server3
- 主配置文件
ansible.cfg
,默认可以不用改
[root@localhost ~]# vim /etc/ansible/ansible.cfg
[defaults]
# some basic default values...
#inventory = /etc/ansible/hosts # 定义主机清单文件
#library = /usr/share/my_modules/ # 库文件的存放位置
#module_utils = /usr/share/my_module_utils/
#remote_tmp = ~/.ansible/tmp # 生成的临时py文件在远程主机的目录
#local_tmp = ~/.ansible/tmp # 生成的临时py文件在本地主机的目录
#plugin_filters_cfg = /etc/ansible/plugin_filters.yml
#forks = 5 # 默认的并发数
#poll_interval = 15 # 默认的线程池
#sudo_user = root # 默认的sudo用户
#ask_sudo_pass = True
#ask_pass = True
#transport = smart
#remote_port = 22
#module_lang = C
#module_set_locale = False
transport = smart
在ansible配置中,transport = smart 是指定 ansible 用于远程连接的传输机制。smart 是 ansible 的默认传输选项,它会尝试根据环境自动选择最佳的传输机制。
当 smart 被设置时,ansible 会按照以下顺序尝试不同的传输机制:
如果已经建立了 SSH 连接(例如,通过 SSH Agent 或者在 ansible.cfg 中配置了 SSH 连接参数),则使用 SSH 传输。
如果未建立 SSH 连接,并且目标主机是本地主机,则使用本地传输(即直接在本地执行命令)。
如果未建立 SSH 连接,并且目标主机是远程主机,则使用 Paramiko 传输(基于 Python 的 SSH2 实现)。
通过使用 smart 选项,ansible 可以自动选择合适的传输机制,以确保在不同的环境中都能正常工作。如果您希望显式地指定传输机制,可以将 transport 设置为 ssh、local 或 paramiko,以强制使用相应的传输方式。
2.2.4、执行状态
ansible的执行状态
- 绿色:执行成功并且不需要做改变的操作
- 黄色:执行成功并且对目标主机做变更
- 红色:执行失败
- 粉色:警告信息
- 蓝色:显示ansible命令执行的过程
2.3、常用模块
2.3.1、ping模块
测试与主机的连通性
示例:
[root@ansible ~]# ansible -m ping all_servers
server1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
server3 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
server2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
# 返回说明:
"SUCCESS" 表示 ansible 成功执行了任务,没有遇到错误。
"ansible_facts" 是一个包含 ansible 任务执行期间收集到的事实(facts)的字典。
"discovered_interpreter_python" 是一个收集到的事实,它指示目标主机上的 Python 解释器的路径为 /usr/bin/python。这对于后续的 ansible 任务可能需要使用 Python 的情况很有用。
"changed" 表示 ansible 是否对目标主机进行了更改。在这种情况下,值为 false 表示没有进行任何更改。
"ping" 是一个简单的回应,用于测试与目标主机的连通性。如果值为 "pong",表示与目标主机的连接正常。
2.3.2、Group模块
用户创建和修改用户组
示例:对node1主机组的成员创建一个IT组,组ID为111
[root@ansible ~]# ansible-doc -s group
action: group
gid # 设置组的GID号
name= # 管理组的名称
state # 指定组状态,默认为创建,设置值为absent为删除
system # 设置值为yes,表示为创建系统组
[root@ansible ~]# ansible -m group -a "name=IT gid=111 system=yes" node1
server1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 111,
"name": "IT",
"state": "present",
"system": true
}
2.3.3、User模块
用于对用户的创建,修改和删除等操作
# 查看某个模块的具体用法
[root@ansible ~]# ansible-doc -l|wc -l
3387 #共有3387个模块
[root@ansible ~]# ansible‐doc ‐s user
comment # 用户的描述信息
createhom # 是否创建家目录
force # 在使用`state=absent'是, 行为与`userdel ‐‐force'一致.
group # 指定基本组
groups # 指定附加组,如果指定为('groups=')表示删除所有组
home # 指定用户家目录
name # 指定用户名
password # 指定用户密码
remove # 在使用 `state=absent'时, 行为是与 `userdel ‐‐remove'一致.
shell # 指定默认shell
state #设置帐号状态,不指定为创建,指定值为absent表示删除
system # 当创建一个用户,设置这个用户是系统用户。这个设置不能更改现有用户
uid #指定用户的uid
update_password # 更新用户密码
expires #指明密码的过期时间
......
示例:在主机组node1上创建一个系统用户张三,家目录为/home/zhangsan,uid为111,附加组为IT,以及给一个注释
[root@ansible ~]# ansible -m user -a "system=yes name=zhangsan home=/home/zhangsan uid=111 groups=IT comment='hello zhangsan'" node1
server1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"comment": "hello zhangsan",
"create_home": true,
"group": 995,
"groups": "IT",
"home": "/home/zhangsan",
"name": "zhangsan",
"shell": "/bin/bash",
"state": "present",
"system": true,
"uid": 111
}
# 删除用户及家目录
[root@ansible ~]# ansible -m user -a "name=zhangsan state=absent remove=yes" node1
# 添加系统用户,指定uid、家目录、主组及注释、密码
[root@ansible ~]# ansible -m user -a "system=yes name=zhangsan home=/home/zhangsan uid=111 group=root comment='hello zhangsan' password='123456' shell=/bin/cbash " node1
2.3.4、Command模块
command模块是ansible默认使用的模块。不支持管道,变量及重定向等
示例:
[root@ansible ~]# ansible-doc ‐s command
......
......
[root@ansible ~]# ansible -a "touch /root/ansible.txt" all_servers
[WARNING]: Consider using the file module with state=touch rather than running
'touch'. If you need to use command because file is insufficient you can add
'warn: false' to this command task or set 'command_warnings=False' in
ansible.cfg to get rid of this message.
server2 | CHANGED | rc=0 >>
server1 | CHANGED | rc=0 >>
server3 | CHANGED | rc=0 >>
[root@ansible ~]# ansible -a "find / -name ifcfg-ens33" all_servers
server1 | CHANGED | rc=0 >>
/etc/sysconfig/network-scripts/ifcfg-ens33
server2 | CHANGED | rc=0 >>
/etc/sysconfig/network-scripts/ifcfg-ens33
server3 | CHANGED | rc=0 >>
/etc/sysconfig/network-scripts/ifcfg-ens33
2.3.5、Shell模块
在远程主机上执行bash命令
相对于command而言,支持性更好一点,但是对于某些复杂的命令,也可能会执行失败
解决方法:可以把命令卸载脚本中,使用script模块执行脚本到远程主机
[root@ansible ~]# ansible -m shell -a "hostname" all_servers
server1 | CHANGED | rc=0 >>
server1
server2 | CHANGED | rc=0 >>
server2
server3 | CHANGED | rc=0 >>
server3
2.3.6、Script模块
可以发送shell脚本到远程主机上并执行
示例:
[root@ansible ~]# vim test.sh
#!/bin/bash
for i in `seq 5`
do
touch /root/test_${i}.txt
done
# script模块
[root@ansible ~]# ansible -m script -a "/root/test.sh" node2
server2 | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to server2 closed.\r\n",
"stderr_lines": [
"Shared connection to server2 closed."
],
"stdout": "",
"stdout_lines": []
}
# server2验证
[root@server2 ~]# ls
anaconda-ks.cfg test_1.txt test_3.txt test_5.txt
ansible.txt test_2.txt test_4.txt
# 参数说明
chdir参数: 此参数的作用就是指定一个远程主机中的目录,在执行对应的脚本之前,会先进入到 chdir 参数指定的目录中。
creates参数: 使用此参数指定一个远程主机中的文件,当指定的文件存在时,就不执行对应脚本
removes参数: 使用此参数指定一个远程主机中的文件,当指定的文件不存在时,就不执行对应脚本
2.3.7、Copy模块
用于向复制文件到主机组中
参数解释:
[root@ansible ~]# ansible-doc -s copy
backup:在覆盖之前,将源文件备份,备份文件包含时间信息。
content:用于替代“src”,可以直接设定指定文件的值
dest:必选项。要将源文件复制到的远程主机的绝对路径
directory_mode:递归设定目录的权限,默认为系统默认权限
force:强制覆盖目的文件内容,默认为yes
others:所有的file模块里的选项都可以在这里使用
src:被复制到远程主机的本地文件,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制
ansible -m copy -a "src=/本地文件 dest=/远程文件" nodes
示例:
- 在创建文件时修改文件的属主和属组信息
[root@ansible ~]# ansible -m copy -a "src=/root/test.sh dest=/root/test1 owner=zhangsan group=ansibles" node1
- 在传输文件时修改文件的权限信息,并且备份远程主机文件
[root@ansible ~]# ansible -m copy -a "src=/root/test.sh dest=/root/test2 backup=yes mode=777" node1
- 创建一个文件并直接编辑文件
[root@ansible ~]# ansible -m copy -a "content='hello ansibles\n' dest=/root/test3" node1
2.3.8、File模块
用于对文件进行相关操作
参数解释:
[root@ansible ~]# ansible‐doc ‐s file
‐ name: Sets attributes of files
force:需要在两种情况下强制创建软链接,一种是源文件不存在,但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no
group:定义文件/目录的属组
mode:定义文件/目录的权限
owner:定义文件/目录的属主
path:必选项,定义文件/目录的路径
recurse:递归设置文件的属性,只对目录有效
src:被链接的源文件路径,只应用于state=link的情况
dest:被链接到的路径,只应用于state=link的情况
state:
absent: 删除文件
directory:如果目录不存在,就创建目录
file:验证文件是否存在,即使文件不存在,也不会被创建
link:创建软链接
hard:创建硬链接
touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其后修改时间
示例:
- 创建目录
[root@ansible ~]# ansible -m file -a "name=test1 owner=root group=root mode=644 state=directory " node1
- 创建文件
[root@ansible ~]# ansible -m file -a "path=/root/test2 owner=root group=root mode=644 state=touch" node1
- 删除文件/目录
[root@ansible ~]# ansible -m file -a "path=/root/test2 state=absent" node1
- 创建软链接文件
[root@ansible ~]# ansible -m file -a "src=/root/test1 dest=/root/test2 state=link" node1
- 创建硬链接文件
[root@ansible ~]# ansible -m file -a "src=/root/test.txt dest=/root/test2 state=hard" node2
2.3.9、Yum模块
用于远程操作主机下载软件包
参数说明:
[root@ansible ~]# ansible‐doc ‐s yum
conf_file #设定远程yum安装时所依赖的配置文件。如配置文件没有在默认的位置。
disable_gpg_check #是否禁止GPG checking,只用于`present' or `latest'。
disablerepo #临时禁止使用yum库。 只用于安装或更新时。
enablerepo #临时使用的yum库。只用于安装或更新时。
name= #所安装的包的名称
state #present安装, latest安装最新的, absent 卸载软件。
update_cache #强制更新yum的缓存
示例:
[root@ansible ~]# ansible -m yum -a "name=httpd state=latest" node3
server3 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"changes": {
"installed": [
"httpd"
],
"updated": []
},
"msg": "",
"rc": 0,
2.3.10、Service模块
用于远程管理主机上的service服务类
参数说明:
[root@ansible ~]# ansible-doc -s service
> SERVICE (/usr/lib/python2.7/site‐packages/ansible/modules/system/service.py)
Controls services on remote hosts. Supported init systems include BSD init, OpenRC, SysV, Solaris
SMF, systemd, upstart. For Windows targets, use the [win_service] module instead.
* note: This module has a corresponding action plugin.
......
......
arguments #命令行提供额外的参数
enabled #设置开机启动,可以设置为yes或者no。
name= #服务名称
runlevel #开机启动的级别,一般不用指定。
sleep #在重启服务的过程中,是否等待。如在服务关闭以后等待2秒再启动。
state #started启动服务, stopped停止服务, restarted重启服务, reloaded重载配置
示例:
[root@ansible ~]# ansible -m service -a "name=httpd state=started" node3
server3 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "httpd",
"state": "started"
.....
.....
2.3.11、Selinux模块
用于管理远程主机的selinux设置
参考说明:
[root@node1 ~]# ansible-doc -s selinux
# selinux模块针对selinux的修改操作是针对配置文件进行修改的
‐ name: Change policy and state of SELinux
configfile:
描述: SELinux 配置文件的路径,如果不是标准路径。
参数类型: 字符串
policy:
描述: 要使用的 SELinux 策略的名称。
参数类型: 字符串
state:
描述: (必需) SELinux 的模式。
参数类型: 字符串
可选值:
enforcing: 强制 SELinux 策略生效。
permissive: 以警告模式运行 SELinux,不会阻止任何操作。
disabled: 完全禁用 SELinux。
示例:
[root@ansible ~]# ansible -m selinux -a "state=enforcing policy=targeted" node1
[WARNING]: Reboot is required to set SELinux state to 'enforcing'
server1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"configfile": "/etc/selinux/config",
"msg": "Config SELinux state changed from 'disabled' to 'enforcing'",
"policy": "targeted",
"reboot_required": true,
"state": "enforcing"
}
3、Playbook(剧本)
3.1、介绍
Ansible playbook是一种可执行的YAML文件,用于描述如何部署和配置一个系统或应用程序。一个playbook由一个或多个play组成,每个play都针对特定的主机或主机组执行一系列任务。
一个playbook的基本结构如下:
- hosts: all
vars:
package_name: nginx
config_file: /etc/nginx/nginx.conf
tasks:
- name: Install Nginx
yum:
name: "{{ package_name }}"
state: present
- name: Copy Nginx configuration
copy:
src: nginx.conf
dest: "{{ config_file }}"
notify:
- restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
在上面的例子中,我们定义了以下几个主要字段:
hosts
: 指定要运行任务的主机或主机组。vars
: 定义要在playbook中使用的变量。tasks
: 定义要执行的任务列表。每个任务都有一个名称和一个模块。handlers
: 定义当某些任务触发时需要执行的处理程序,比如重启服务。
执行playbook剧本:
ansible-playbook xxxxx.yaml
即可
3.2、快速开始
3.2.1、案例:服务安装
安装nginx并且修改配置文件
- 先编写一个nginx的子配置文件
[root@ansible ~]# mkdir -p playbook/conf
[root@ansible ~]# cd playbook/conf
[root@ansible conf]# cat site.conf
server {
listen 666;
server_name localhost;
location / {
root /data;
index index.html
}
}
- 编写playbook,要求是为目标机器安装nginx并且拷贝配置文件到该机器上,然后启动nginx。
[root@ansible playbook]# vim nginx.yaml
- name: install nginx web server
hosts: node1
remote_user: root
tasks:
- name: Install epel-release
yum:
name: epel-release
state: latest
- name: Install Nginx
yum:
name: nginx
state: latest
- name: Copy conf to nginx.conf.d
copy:
src: /root/playbook/conf/site.conf
dest: /etc/nginx/conf.d/site.conf
- name: Create "data" directory
file:
name: /data
state: directory
- name: Start Nginx service
service:
name: nginx
state: started
- name: create web index file
shell: echo "Install Nginx use Ansible...." > /data/index.html
3.3、高级配置
3.3.1、fact(事实变量)
Ansible 内置了大量的事实(fact)变量,可以在 Playbook 中使用。这些事实变量可以帮助我们更好地了解目标主机的环境和配置信息,从而编写更加智能和动态的自动化脚本。
常用的内置事实变量包括:
- 操作系统信息:
ansible_distribution
: 操作系统发行版名称,如 "CentOS"、"Ubuntu"ansible_distribution_version
: 操作系统版本号ansible_os_family
: 操作系统家族,如 "RedHat"、"Debian"ansible_kernel
: 内核版本- 硬件信息:
ansible_processor
: CPU 型号ansible_processor_vcpus
: 虚拟 CPU 核数ansible_memtotal_mb
: 内存总量(MB)ansible_architecture
: CPU 架构,如 "x86_64"- 网络信息:
ansible_default_ipv4
: 默认 IPv4 地址和网关ansible_all_ipv4_addresses
: 所有 IPv4 地址ansible_interfaces
: 所有网络接口名称ansible_hostname
: 主机名- 其他信息:
ansible_user_id
: 当前执行 Ansible 的用户 IDansible_date_time
: 主机当前日期和时间ansible_env
: 主机环境变量ansible_play_hosts
: 当前 play 中涉及的所有主机
这些事实变量可以帮助我们编写出更加智能和定制化的 Playbook。比如,我们可以根据操作系统的不同,执行不同的软件包安装任务;根据 CPU 架构,选择合适的软件包版本;根据内存大小,调整应用程序的配置等。
3.3.2、循环迭代
在playbook中,可以使用循环进行数据的迭代。这样一个模块就可以执行多次任务,因为往往我们部署一个服务的时候,都需要安装多个软件包的。
示例:使用yum循环安装软件包
- name: Install packages
yum:
name: "{{ item }}"
state: present
loop:
- nginx
- mysql
- php
或者:
- name: Install packages
yum:
name: "{{ item }}"
state: present
with_items:
- httpd
- mysql
- php
这样就可以实现一个yum安装多个软件包了,避免了playbook过于臃肿。
RHCE真题讲解(2023-10)
创建一个名为 /home/student/ansible/packages.yml的 playbook:
- 将 php 和 mariadb 软件包安装到 dev、test 和 prod 主机组中的主机上
- 将 Development Tools 软件包组安装到 dev 主机组中的主机上
- 将 dev 主机组中主机上的所有软件包更新为最新版本
[root@ansible ~]# vim playbook/packages.yml
- name: install pkgs
hosts: dev,test,prod
tasks:
- name: install mariadb php
yum:
name: "{{ item }}"
state: present
loop:
- php
- mariadb
- name: install group pkgs
hosts: dev
tasks:
- name: install Development Tools
yum:
name: "@Development Tools"
state: present
- name: update pkgs
hosts: dev
tasks:
- name: update pkgs
yum:
name: "*"
state: latest
案例:循环创建用户
循环创建用户,用户信息如下 名称、组、家目录、shell、描述信息 zhangsan xsb /home/xsb/zhangsan /bin/bash 销售 lisi xsb /home/xsb/lisi /bin/bash 销售 wangwu jsb /home/jsb/wangwu /bin/sh java工程师 maliu jsb /home/jsb/maliu /bin/sh linux工程师 zhaoqi cwb /home/cwb/zhaoqi /bin/sh 会计
循环创建出以上用户并指定用户信息:
[root@ansible ~]# vim playbook/user.yml
- name: Manage user
hosts: node1
remote_user: root
tasks:
- name: Ensure groups xsb, jsb, cwb exist
group:
name: "{{ item.group }}"
with_items:
- { group: xsb }
- { group: jsb }
- { group: cwb }
- name: Create users zhangsan, lisi, wangwu, maliu, zhaoqi
user:
name: "{{ item.name }}"
group: "{{ item.group }}"
shell: "{{ item.shell }}"
comment: "{{ item.comment }}"
home: "{{ item.home }}"
with_items:
- { name: 'zhangsan', group: 'xsb', home: '/home/xsb/zhangsan', shell: '/bin/bash', comment: '销售' }
- { name: 'lisi', group: 'xsb', home: '/home/xsb/lisi', shell: '/bin/bash', comment: '销售' }
- { name: 'wangwu', group: 'jsb', home: '/home/jsb/wangwu', shell: '/bin/sh', comment: 'java工程师' }
- { name: 'maliu', group: 'jsb', home: '/home/jsb/maliu', shell: '/bin/sh', comment: 'linux工程师' }
- { name: 'zhaoqi', group: 'cwb', home: '/home/cwb/zhaoqi', shell: '/bin/sh', comment: '会计' }
3.3.3、条件判断
在 Ansible Playbook 中,我们可以使用条件判断语句来根据不同的条件执行不同的任务。
when 语句:
when
语句是最常用的条件判断语句。它可以根据变量的值、事实(facts)或者 Jinja2 表达式来决定是否执行某个任务。
- name: Install packages on CentOS 7
yum:
name:
- httpd
- mariadb-server
state: present
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version|int == 7
- name: Install packages on CentOS 8
yum:
name:
- nginx
- mysql-server
state: present
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version|int == 8
在这个例子中:
- 第一个任务会在 CentOS 7 系统上安装 httpd 和 mariadb-server 软件包。
when
语句确保了只有在ansible_distribution
等于 'CentOS' 且ansible_distribution_major_version
等于 7 时,这个任务才会执行。 - 第二个任务会在 CentOS 8 系统上安装 nginx 和 mysql-server 软件包。同样的,
when
语句确保了只有在ansible_distribution
等于 'CentOS' 且ansible_distribution_major_version
等于 8 时,这个任务才会执行。
RHCE真题讲解(2023-10-)
考试原题(第八题):
创建一个名为/home/student/ansible/parted.yml 的playbook,它将在dev主机组上运行下列任务
- 如果磁盘/dev/vdd存在,则创建1500m分区
- 如果无法创建请求的分区大小,应显示错误消息 Could not create partition of that size,并且应改为使用大小 800m。
- 如果磁盘/dev/vdd不存在 ,应显示错误消息 disk /dev/vdd does not exist。
- 如果磁盘/dev/vdb存在,则创建1500m分区
- 如果无法创建请求的分区大小,应显示错误消息 Could not create partition of that size,并且应改为使用大小 800m。
- 最后分区都要格式化为ext4文件系统,并挂载在/mnt/fs01上
简化题目:在所有机器上创建sdb1分区,大小为1Gib,前提是sdb存在,如果不存在,请提示.......
[root@ansible playbook]# vim disk.yaml
- name: Create sdb1 partition
hosts: all_servers
tasks:
- name: Check if sdb block device exists
stat:
path: /dev/sdb
register: sdb_stat
- name: Create 1GB partition on sdb
parted:
device: /dev/sdb
number: 1
state: present
part_end: 1GB
when: sdb_stat.stat.exists
- name: sdb block device not exists
debug:
msg: "sdb block device does not exist, cannot create partition."
when: not sdb_stat.stat.exists
# Output:
TASK [Create 1GB partition on sdb] *********************************************
skipping: [server1]
skipping: [server3]
changed: [server2]
TASK [sdb block device not exists] *********************************************
ok: [server1] => {
"msg": "sdb block device does not exist, cannot create partition."
}
skipping: [server2]
ok: [server3] => {
"msg": "sdb block device does not exist, cannot create partition."
}
验证:
[root@server2 ~]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 20G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 19G 0 part
├─centos-root 253:0 0 17G 0 lvm /
└─centos-swap 253:1 0 2G 0 lvm [SWAP]
sdb 8:16 0 2G 0 disk
└─sdb1 8:17 0 953M 0 part
sr0 11:0 1 918M 0 rom
3.4、Jinjia2模板
Jinja2是一个功能强大的Python模板引擎,它被广泛应用于Ansible的playbook中。Jinja2模板语法提供了丰富的功能,使得在playbook中插入动态内容变得更加容易和灵活。
简单来讲,就是将原本静态的playbook转变为动态的。
3.4.1、RHCE真题讲解(2023-10)
原题(第九题):生成主机文件
- 编写模板文件/home/student/ansible/hosts.j2 ,针对每个清单主机包含一行内容,其格式与 /etc/hosts 相同。
- 创建名为 /home/student/ansible/hosts.yml 的playbook,它将使用此模板在 dev 主机组中的主 机上生成文件 /etc/myhosts。
- 该 playbook 运行后,dev 主机组中主机上的文件/etc/myhosts 应针对每个受管主机包含一行内 容。
题目变更如下:
我们使用jinjia2和ansible内置变量动态的生成hosts文件,并且发送给远程主机
- 编写模板文件
[root@ansible playbook]# vim hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups.all_servers %}
{{hostvars[host].ansible_ens33.ipv4.address}} {{hostvars[host].ansible_hostname}}
{% endfor %}
- 编写hosts.yaml剧本来渲染并且发送该模板文件到目标主机,使之替换原本的hosts
[root@ansible playbook]# vim hosts.yaml
- name: Config hosts file
hosts: all_servers
remote_user: root
tasks:
- name: copy hosts.j2 to group servers
template:
src: hosts.j2
dest: /etc/hosts
# 执行该playbook
[root@ansible playbook]# ansible-playbook hosts.yaml
- 检查并验证:
[root@server1 ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.88.10 server1
192.168.88.20 server2
192.168.88.30 server3
3.4.2、案例:生成nginx配置文件
我们可以在playbook中自定义变量,然后更具自定义的变量使用jinjia2模板渲染nginx的配置文件
- 先编写nginx.yaml剧本,在里面定义变量
[root@ansible ~]# mkdir ansible
[root@ansible ~]# cd ansible
[root@ansible ansible]# vim nginx.yaml
- name: nginx conf
hosts: node1
remote_user: root
vars:
nginx_vhosts:
- web1:
listen: 8080
root: "/var/www/nginx/web1/"
- web2:
listen: 8080
server_name: "web2.baidu.com"
root: "/var/www/nginx/web2/"
- web3:
listen: 8080
server_name: "web3.baidu.com"
root: "/var/www/nginx/web3/"
tasks:
- name: mkdir /data
file:
name: /data
state: directory
- name: template config
template:
src: /root/ansible/site.conf.j2
dest: /data/nginx.conf
- 编写nginx子配置文件site.conf,子配置文件中使用jinjia2语法进行渲染
[root@ansible ansible]# vim site.conf.j2
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.listen }}
{% if vhost.server_name is defined %}
server_name {{ vhost.server_name }}
{% endif %}
root {{ vhost.root }}
}
{% endfor %}
- server1上验证配置文件是否渲染成功
[root@server1 ~]# cat /data/nginx.conf
server {
listen 8080
root /var/www/nginx/web1/
}
server {
listen 8080
server_name web2.baidu.com
root /var/www/nginx/web2/
}
server {
listen 8080
server_name web3.baidu.com
root /var/www/nginx/web3/
}
4、Role(角色)
Ansible 中的 Role 是一种组织和重用代码的强大方式。角色可以帮助你将相关的任务、变量、文件等集中管理,使得代码更加模块化和可重用。
如果将所有的play都写在一个playbook中,很容易导致这个playbook文件变得臃肿庞大,且不易读。因此,可以将多个不同任务分别写在不同的playbook中,然后使用include将其包含进去即可。而role则是整合playbook的方式。无论是include还是role,其目的都是分割大playbook以及复用某些细化的play甚至是task。
4.1、目录结构
在角色中,将task,templates,handlers,files等内容都分开存放,然后再playbook中直接调用角色即可.....
[root@ansible roles]# tree apache/
apache/
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
defaults/main.yml
: 定义角色的默认变量handlers/main.yml
: 定义角色的处理程序meta/main.yml
: 定义角色的元数据,如依赖关系、作者信息等tasks/main.yml
: 定义角色的主要任务templates/
: 存放角色使用的模板文件tests/
: 存放角色的测试相关文件vars/main.yml
: 定义角色的变量
4.2、初始化自定义Role
可以使用ansible-galaxy工具通过init选项初始化一个角色
[root@ansible roles]# ansible-galaxy init apache
- Role apache was created successfully
[root@ansible role]# ls apache/
defaults files handlers meta README.md tasks templates tests vars
4.3、案例:使用Role安装httpd
- 初始化httpd角色
[root@ansible roles]# ansible-galaxy init httpd
- Role httpd was created successfully
[root@ansible role]# tree httpd/
httpd/
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
8 directories, 8 files
- 编写角色中的task
[root@ansible httpd]# vim tasks/main.yml
# tasks file for httpd
- name: Install httpd
yum:
name: httpd
state: present
- name: copy site2.conf to apache web server
copy:
src: site.conf
dest: /etc/httpd/conf.d/site2.conf
- name: create directory1 for apache web server
file:
name: /data/site1/
state: directory
- name: create directory2 for apache web server
file:
name: /data/site2/
state: directory
- name: Start httpd
service:
name: httpd
state: started
- name: Write index file
shell: echo "site1" > /data/site1/index.html && echo "site2" > /data/site2/index.html
- 编写角色中的file
[root@ansible httpd]# vim files/site.conf
Listen 8080
Listen 9090
<Directory "/data/">
Require all granted
</Directory>
<VirtualHost *:8080>
DocumentRoot "/data/site1/"
</VirtualHost>
<VirtualHost *:9090>
DocumentRoot "/data/site2/"
</VirtualHost>
- 编写一个playbook剧本调用该角色
[root@ansible roles]# vim httpd.yaml
- name: Install httpd web server
hosts: node1
roles:
- httpd
- 验证
[root@server1 ~]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::8080 :::*
LISTEN 0 128 :::80 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 128 :::9090 :::*
4.4、RHCE真题讲解(2023-10)
题目(第六题):
根据下列要求,在 /home/student/ansible/roles中创建名为 apache 的角色:
- httpd软件包已安装,设为在系统启动时启用
- 防火墙已启用并正在运行,并使用允许访问 Web 服务器的规则
- 模板文件 index.html.j2 已存在,用于创建具有以下输出的文件 /var/www/html/index.html: Welcome to HOSTNAME on IPADDRESS 其中,HOSTNAME 是受管节点的完全限定域名,IPADDRESS 则是受管节点的 IP 地址。
- 按照下方所述,创建一个使用此角色的 playbook /home/student/ansible/newrole.yml: 该 playbook 在 webservers 主机组中的主机上运行
[student@workstation ansible# cd roles/
[student@workstation roles]# ansible-galaxy init apache
[student@workstation roles]# vim apache/tasks/main.yml
---
# tasks file for apache
- name: install http
yum:
name: httpd
state: present
- name: config system service
service:
name: "{{ item }}"
state: started
enabled: yes
loop:
- httpd
- firewalld
- name: firewalld service
firewalld:
zone: public
service: http
permanent: yes
immediate: yes
state: enabled
- name: user templates
template:
src: index.html.j2
dest: /var/www/html/index.html
[student@workstation roles]# vim apache/templates/index.html.j2
Welcome to {{ ansible_fqdn }} on {{ ansible_default_ipv4.address }}
[student@workstation roles]# cd ..
[student@workstation ansible]# vim newrole.yml
- name: use apache role
hosts: webservers
roles:
- apache
# 运行脚本
[student@workstation ansible]# ansible-playbook newrole.yml
# 访问测试
[student@workstation ansible]# curl serverc
Welcome to serverc.lab.example.com on 172.25.250.12
[student@workstation ansible]# curl serverd
Welcome to serverd.lab.example.com on 172.25.250.13
评论区