picaj的分享

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)
4.2.1、基础正则表达式
符号 描述
. 匹配任意单个字符(必须存在)
^ 匹配以某个字符开头的行
$ 配以什么字符结尾的行
* 匹配前面的一个字符出现0次或者多次;eg:a*b
.* 表示任意长度的任意字符
[] 表示匹配括号内的一个字符
[^] 匹配[^字符]之外的任意一个字符
[] 匹配非[^字符]内字符开头的行
< 锚定 单词首部;eg:\<root
> 锚定 单词尾部:eg:root>
{m,n} 表示匹配前面的字符出现至少m次,至多n次
() 表示对某个单词进行分组;\1表示第一个分组进行调用
4.2.2、扩展正则
符号 描述
+ 表示前面的字符至少出现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

image-353

[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 模块
  1. BEGIN 模块
  2. BEGIN 模块是在 awk 开始处理输入数据之前执行的。
  3. 它通常用于初始化一些变量或者打印一些提示信息。
  4. 比如在处理文件之前,先打印一行 "Processing the file..."。

  5. END 模块

  6. END 模块是在 awk 处理完所有输入数据之后执行的。
  7. 它通常用于输出一些最终的统计信息或者结果。
  8. 比如在处理完文件后,打印出总共处理了多少行数据。
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、常用字符串函数

image-354

image-355

字符串函数的应用:

在 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脚本编程

image-356

image-357

2、变量

2.1、变量命名
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、变量类型

常见的环境变量:

变量 描述
$HOME 当前用户的用户目录
$PATH 用分号分隔的目录列表,shell 会到这些目录中查找命令
$PWD 当前工作目录
$RANDOM 0 到 32767 之间的整数
$UID 数值类型,当前用户的用户 ID
$PS1 主要系统输入提示符
$PS2 次要系统输入提示符

位置变量包括以下几种:

  1. $0: 表示脚本本身的名称。
  2. $1, $2, $3, ..., $n: 分别表示第1个、第2个、第3个...第n个参数。
  3. $#: 表示传递给脚本的参数个数。
  4. $*: 表示所有参数,将所有参数当作一个整体。
  5. $@: 表示所有参数,但是每个参数都是独立的。
[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 ]

示例:

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在使用上跟其它语言相同。如果中括号里的表达式为真,那么thenfi之间的代码会被执行。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 中有四种循环:forwhileuntilselect

7.2.1、for循环

for与 C 语言中非常像。看起来是这样:

for arg in elem1 elem2 ... elemN
do
  ### 语句
done

在每次循环的过程中,arg依次被赋值为从elem1elemN。这些值还可以是通配符或者大括号扩展

当然,我们还可以把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 的breakcontinue语句来实现。它们可以在任何循环中使用。

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 #调用函数

💡 说明:

  1. 函数定义时,function 关键字可有可无。
  2. 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值。
  3. 函数返回值在调用该函数后通过 $? 来获得。
  4. 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 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优点
1.2、zabbix缺点
1.3、zabbix组件结构
1.4、zabbix监控方式
1.5、Zabbix架构

image-358

1.6、Zabbix常用术语

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界面开始初始化

image-359

状态检测

确保所有的php检测都是ok的状态

image-360

连接数据库

数据库的信息按照图上的填入,其中密码是我们刚刚设置的zabbix用户的密码,端口号3306

image-361

zabbix信息

这里保持默认,name可以空着不填

image-362

完成安装

看到如下界面,说明zabbix初始化成功

image-363

登录zabbix的web网站

默认的username:Admin password:zabbix

image-364

修改中文显示

image-365

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、主机监控

先简单的监控一个主机的状态和信息

添加一个主机

image-366

image-367

添加模板

zabbix内置了很多模板,比如监控Linux主机的模板,监控httpd的模板,监控MySQL的模板等等。我们可以直接使用这些模板,当然也可以自定义。一个模板中包含很多的应用集,每个应用集中又包含很多具体的监控项

image-368

创建应用集

模板中内置的很多的监控项,但是如果没有我们想要的监控项的话,我们可以手动添加。先创建一个应用集,然后再应用集中创建监控项

image-369

image-370

创建监控项

创建好了test应用集以后,我们再向该应用集中添加具体的监控项。

image-371

image-372

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网站上使用刚刚编写的监控项

image-373

查看刚刚添加的监控项键值的状态

image-374

查看具体的检测到的数据,在菜单栏中点击监测->最新数据,然后过滤刚刚的监控项

image-375

image-376

添加触发器

给该监控项添加触发器,让他能够出发警告通知

image-377

创建一个新的触发器

image-378

可以看到我们刚刚添加的触发器

image-379

手动宕机告警测试

在server2上手动关闭nginx后,在仪表盘中查看告警信息

[root@server2 ~]# systemctl stop nginx

查看仪表盘

image-380

可以看到这里有一个告警信息,说明我们刚刚创建的触发器生效了。

这样的话,运维工程师就可以直接在这个网站上看到当前服务器组或者集群中各个机器和服务的状态。 不需要在一个一个登录上去查看了。

但是只是这样还是不够只能,稍后我们将继续配置触发告警以后,通过邮件或者叮叮企业微信等平台向工程师发送告警信息。

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参数

在上面菜单栏中选择管理->报警媒介类型

image-381

按照图中配置参数,最后面的密码是在QQ邮箱中申请的授权码

教程如下:https://www.hzhcontrols.com/new-2123428.html

image-382

修改Admin用户的报警媒介

image-383

配置告警动作

这里定义如果触发告警,应该怎么处理。以及如果发送邮件,邮件的内容是什么样的....

image-384

当然也可以自定义告警信息

image-385

4.1.2、邮件告警测试

手动停止server2上面的nginx服务以后,查看邮件

image-386

可以看到,成功通过邮件发送告警信息

当问题恢复以后,也会发送邮件给我们

image-387

4.2、Zabbix通过钉钉告警
4.2.1、创建钉钉群聊

先下载电脑版本的钉钉,然后创建一个群聊(创建群聊需要三个好友起步,大家可以相互之间添加好友)并且创建群聊

4.2.2、添加群机器人

添加自定义机器人,并且配置关键词

image-388

保存好Webhook,并且设置安全关键词为:告警

image-389

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}即可

image-390

添加处理动作

image-391

image-392

恢复操作

image-393

告警消息内容如下:

【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}

绑定用户,填写钉钉的手机号即可

image-394

4.2.5、测试钉钉告警

image-395

四、Prometheus监控

1、Prometheus简介

1.1、什么是Prometheus?

Prometheus是由前 Google 工程师从 2012 年开始在 Soundcloud以开源软件的形式进行研发的系统监控和告警工具包,自此以后,许多公司和组织都采用了 Prometheus 作为监控告警工具。Prometheus 的开发者和用户社区非常活跃,它现在是一个独立的开源项目,可以独立于任何公司进行维护。为了证明这一点,Prometheus 于 2016 年 5 月加入CNCF基金会,成为继 Kubernetes 之后的第二个 CNCF 托管项目。

1.2、Prometheus的优势

Prometheus 的主要优势有:

1.3、Prometheus的组件、架构

Prometheus 的整体架构以及生态系统组件如下图所示:

image-396

Prometheus Server 直接从监控目标中或者间接通过推送网关来拉取监控指标,它在本地存储所有抓取到的样本数据,并对此数据执行一系列规则,以汇总和记录现有数据的新时间序列或生成告警。可以通过 Grafana或者其他工具来实现监控数据的可视化。

参考博客: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、样本

在时间序列中的每一个点称为样本,样本由以下三部分组成:

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通常用于像温度或者内存使用率这种指标数据,也可以表示能随时请求增加会减少的总数,例如当前并发请求的数量。

image-397

对于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类型的监控指标我们可以快速了解监控样本的分布情况。

image-398

Histogram在一段时间范围内对数据进行采样(通常是请求持续时间或响应大小等),并将其计入可配置的存储桶(bucket)中,后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图。

Histogram类型的样本会提供三种指标(假设指标名称为):

// 在总共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
// 实际含义: 发生的2次 http 请求总的响应时间为 13.107670803000001 秒
io_namespace_http_requests_latency_seconds_histogram_sum{path="/",method="GET",code="200",} 13.107670803000001
// 实际含义: 当前一共发生了 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、浏览器访问测试

image-399

4、Exporter

4.1、简介

在Prometheus的核心组件中,Exporter是重要的组成部分,在实际中监控样本数据的收集都是由Exporter完成的,Prometheus服务器只需要定时从这些Exporter提供的HTTP服务获取数据即可。官方提供了多种常用的Exporter,比如用于对数据库监控的mysqld_exporter和redis_exporter等。

Exporter本质上是将收集的数据转化为对应的文本格式,并提供HTTP接口,供Prometheus定期采集数据。

4.2、Exporter类型
4.3、文本数据格式

在Prometheus的监控环境中,所有返回监控样本数据的Exporter程序,均需要遵守Prometheus规范,即基于文本的数据格式,其特点是具有更好的跨平台和可读性。

# 以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,表示无限制
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

重启服务即可成功关联

image-400

4.4.4、metricts数据采集

对于cpu数据采集的主要监控指标是node_cpu_seconds_total

image-401

可以通过PromQL(后面会介绍这种查询语言)表达式进行查询,计算每核cpu每秒的空闲时间,然后对主机上的所有cpu求平均值

avg without(cpu,mode) (rate(node_cpu_seconds_total {mode="idle"} [1m]))

image-402

[root@server2 node_exporter]# free -b
              total        used        free      shared  buff/cache   available
Mem:     1907970048   112123904  1564102656    10014720   231743488  1609289728
Swap:    2147479552           0  2147479552

总内存:

image-403

空闲内存:

image-404

image-405

image-406

image-407

任意一个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

image-408

4.5.5、metricts数据采集

MySQL数据库的性能状态监控内容非常多,但通常必不可少的内容包括查询吞吐量、查询执行性能、连接情况、缓冲池使用情况等。

image-409

image-410

image-411

image-412

image-413

5、服务发现

Prometheus服务发现能够自动化检测分类,并且能够识别新目标和变更目标。也就是说,可以自动发现并监控目标或变更目标,动态进行数据采集和处理。

5.1、基于文件的服务发现
[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监控系统。

时序数据库的特点如下:

对时序数据库的基本要求如下:

6.2、时序数据

image-414

时间序列数据:按照时间顺序记录系统、设备状态变化的数据,每个数据称为一个样本

数据采集以特定的时间周期进行,随着时间的流逝,将这些样本数据记录下来,将生成一个离散的样本数据序列

把该序列称作为向量,而将多个序列放在同一个坐标系内(以时间为横轴,以序列为纵轴,将形成一个有数据点组成的矩阵)

6.3、时间序列选择器
6.3.1、即时向量选择器

即时向量选择器由两部分组成

匹配器用于定义标签过滤的条件,目前支持如下四种

6.3.2、范围向量选择器

同即时向量选择器唯一不同的地方在于,范围向量选择器需要在表达式后紧跟一个方括号[]来表达需要在时序上返回的样本所处的时间范围

时间范围:以当前时间为基准点,指向过去一个特定的时间长度,例如[5m],便是指过去5分钟之内

可用的时间单位有:

必须使用整数时间,例如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支持以下集合运算符:

*vector1 and vector2* 会产生一个由vector1的元素组成的新的向量。该向量包含vector1中完全匹配vector2中的元素组成。

*vector1 or vector2* 会产生一个新的向量,该向量包含vector1中所有的样本数据,以及vector2中没有与vector1匹配到的样本数据。

*vector1 unless vector2* 会产生一个新的向量,新向量中的元素由vector1中没有与vector2匹配的元素组

6.4.4、操作符优先级

在PromQL操作符中优先级由高到低依次为:

  1. ^
  2. *, /, %
  3. +, -
  4. ==, !=, <=, <, >=, >
  5. and, unless
  6. or
6.5、PromQL聚合操作

Prometheus还提供了下列内置的聚合操作符,这些操作符作用域瞬时向量。可以将瞬时表达式返回的样本数据进行聚合,形成一个新的时间序列。

使用聚合操作的语法如下:

<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类型的监控指标其特点是只增不减,在没有发生重置(如服务器重启,应用重启)的情况下其样本值应该是不断增大的。为了能够更直观的表示样本数据的变化剧烈情况,需要计算样本的增长速率。

如下图所示,样本增长率反映出了样本变化的剧烈程度:

image-415通过增长率表示样本的变化情况

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类型的监控指标,用户可以轻松获取样本数据的分布情况。同时分位数的计算,也可以非常方便的用于评判当前监控指标的服务水平。

image-416获取分布直方图的中位数

需要注意的是通过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

登录以后会提示修改密码

image-417

image-418

7.3、使用Grafana

image-419

image-420

image-421

image-422

image-423

image-424

我们可以添加多个数据展示

image-425

7.4、使用第三方模板

自己创建图表展示比较麻烦,我们可以直接使用别人以及做好的模板

官方模板网站:https://grafana.com/grafana/dashboards/

可以在搜索框中搜索我们想要的模板

image-426

找到想要的模板以后,直接复制模板代码

image-427

12633

回到我们自己的Grafana中直接import导入进去

image-428

image-429

然后就可以看到我们导入的模板了,并且可以选择不同的Exporter

image-430

8、Alertmanager告警

8.1、概述

image-431

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

image-432

8.3.1、关闭node-exporer测试

image-433

image-434

其他告警方式自行扩展,以及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应用的速度、提高可扩展性。

image-435

Memcached 官网:https://memcached.org

1.2、Memcached和Redis对比

我们都知道,把一些热数据存到缓存中可以极大的提高速度,那么问题来了,是用Redis好还是Memcached好呢,以下是它们两者之间一些简单的区别与比较:

  1. Redis不仅支持简单的k/v类型的数据,同时还支持list、set、zset(sorted set)、hash等丰富数据结构的存储,使得它拥有更广阔的应用场景。
  2. Redis最大的亮点是支持数据持久化,它在运行的时候可以将数据备份在磁盘中,断电或重启后,缓存数据可以再次加载到内存中,只要Redis配置的合理,基本上不会丢失数据。
  3. Redis支持主从模式的应用。
  4. Redis单个value的最大限制是1GB,而Memcached则只能保存1MB内的数据。
  5. Memcache在并发场景下,能用cas保证一致性,而Redis事务支持比较弱,只能保证事务中的每个操作连续执行。
  6. 性能方面,根据网友提供的测试,Redis在读操作和写操作上是略领先Memcached的。
  7. 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
[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 的主要特点如下:

  1. 远程访问:Telnet 协议可以让用户远程登录到其他计算机系统,就像直接在那台机器上工作一样。这对于管理远程服务器或设备非常有用。
  2. 文本界面:Telnet 使用纯文本界面进行交互,不需要图形界面。这使它适用于各种类型的终端设备和操作系统。
  3. 简单易用:Telnet 客户端通常内置在操作系统中,或者可以很容易地下载安装。建立连接只需输入远程主机的 IP 地址或主机名即可。
  4. 功能丰富:Telnet 协议支持各种命令和操作,如文件传输、远程执行命令等。用户可以在 Telnet 会话中执行系统管理任务。
  5. 不安全: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;

img

  1. slabclass数组初始化的时候,每个slabclass_t都会分配一个1M大小的slab,slab会被切分为N个小的内存块,这个小的内存块的大小取决于slabclass_t结构上的size的大小
  2. 每个slabclass_t都只存储一定大小范围的数据,并且下一个slabclass切割的chunk块大于前一个slabclass切割的chunk块大小
  3. memcached中slabclass数组默认大小为64,slabclass切割块大小的增长因子默认是1.25 例如:slabclass[1]切割的chunk块大小为100字节,slabclass[2]为125,如果需要存储一个110字节的缓存,那么就需要到slabclass[2] 的空闲链表中获取一个空闲节点进行存储
3.4、item节点分配流程
  1. 根据大小,找到合适的slabclass
  2. slabclass空闲列表中是否有空闲的item节点,如果有直接分配item,用于存储内容
  3. 空闲列表没有空闲的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

相关参数说明:

示例:

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

其中:

4.2.2、CAS 操作流程

CAS 操作的流程如下:

  1. 客户端先使用 get 命令获取某个 key 的值,并记录下返回的 casunique
  2. 客户端准备更新这个值时,会使用 cas 命令,并附带之前获取的 casunique
  3. Memcached 服务器收到 cas 命令后,会先检查当前值的 casunique 是否与客户端传来的一致。
  4. 如果一致,说明这个值自从客户端获取后就没有被其他人修改过,服务器会接受这次更新。
  5. 如果不一致,说明这个值在客户端获取后已经被其他人修改过了,服务器会拒绝这次更新。
4.2.3、CAS 的应用场景

CAS 命令主要用于解决多客户端并发更新同一缓存数据的问题,避免出现"丢失更新"的情况。

例如,在一个电商网站上,多个用户可能同时操作同一个购物车。这时就可以使用 CAS 来确保只有最后一个更新成功的客户端的修改生效。

假设我们有一个电商网站,需要缓存用户的购物车信息。多个用户可能同时操作同一个购物车,此时就需要使用 CAS 来避免"丢失更新"的问题。

案例流程如下:

  1. 用户 A 访问网站,获取自己的购物车信息:
  2. 使用 get 命令从 Memcached 中获取购物车数据
  3. 同时记录下返回的 casunique
  4. 用户 A 添加一件商品到购物车:
  5. 使用 cas 命令更新购物车数据
  6. 同时带上之前获取的 casunique
  7. 与此同时,用户 B 也访问网站,获取自己的购物车信息:
  8. 同样使用 get 命令从 Memcached 中获取购物车数据
  9. 记录下返回的 casunique
  10. 用户 B 也想修改购物车中的商品:
  11. 使用 cas 命令尝试更新购物车数据
  12. 但此时 Memcached 检查发现 casunique 已经不一致了
  13. 因此拒绝了用户 B 的更新请求
  14. 最终只有用户 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

输出信息说明:

4.3、数据查找
4.3.1、get命令

get 命令的基本语法格式如下:

get key

多个 key 使用空格隔开,如下:

get key1 key2 key3

参数说明如下:

4.3.2、gets命令

Memcached gets 命令获取带有 CAS 令牌存 的 value(数据值) ,如果 key 不存在,则返回空。不带的也可以正常获取

语法

gets 命令的基本语法格式如下:

gets key

多个 key 使用空格隔开,如下:

gets key1 key2 key3

参数说明如下:

4.3.3、delete命令

Memcached delete 命令用于删除已存在的 key(键)。

语法

delete 命令的基本语法格式如下:

delete key [noreply]

参数说明如下:

输出信息说明:

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

这里显示了很多状态信息,下边详细解释每个状态项:

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 与 Kickstart 配合使用,可以实现以下自动化安装流程:

  1. 配置 PXE 服务器 在网络上建立一个 PXE 引导服务器,提供 DHCP 和 TFTP 服务,用于为客户端机器提供 PXE 引导环境。
  2. 准备 Kickstart 配置文件 编写好符合您需求的 Kickstart 配置文件,并将其置于 PXE 服务器的 HTTP 共享目录下。
  3. 客户端机器 PXE 引导 配置客户端机器的 BIOS 以支持 PXE 网络引导。在客户端启动时,它会通过 DHCP 获取 PXE 引导所需的信息,然后从 TFTP 服务器下载引导程序。
  4. 自动安装操作系统 引导程序会从 HTTP 服务器下载 Kickstart 配置文件,并根据文件中定义的规则自动安装操作系统。整个过程不需要任何人工干预。

  5. Client/Server的工作模式

image-436

3、批量装机软件

  1. Cobbler: Cobbler 是一款开源的网络安装管理系统,提供基于 PXE 的自动化安装和部署功能。它支持多种操作系统,包括 Red Hat、CentOS、Fedora、Debian、Ubuntu 等。Cobbler 使用简单的配置文件来定义系统安装过程,类似于 Kickstart。它可以与 Puppet、Ansible 等配置管理工具集成使用。

  2. Preseed: Preseed 是 Debian/Ubuntu 系列操作系统的自动化安装工具,与 Kickstart 的功能类似。Preseed 使用一个应答文件来定义安装过程中的各种选项,如磁盘分区、软件包选择等。这个应答文件可以放在网络上供安装程序自动获取。

  3. AutoYaST: AutoYaST 是 SUSE/openSUSE 系统的自动安装工具,同样使用一个配置文件来定义安装过程。与 Kickstart 相似,AutoYaST 配置文件可以指定分区信息、软件包选择、服务配置等。

  4. Kickstart:

Kickstart 是 Red Hat Enterprise Linux (RHEL) 和 CentOS 等发行版中的一个功能,用于实现操作系统的自动化安装。它可以通过一个预先配置好的安装配置文件来完成整个安装过程,省去了手动安装的繁琐步骤。

Kickstart 的主要特点包括:

使用 Kickstart 的典型流程如下:

  1. 编写 Kickstart 配置文件
  2. 将配置文件发布到网络服务器
  3. 在客户端机器上启动 PXE 网络安装
  4. 客户端自动读取配置文件并完成无人值守的安装过程

4、实验部署

[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
[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

image-437

[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))
  [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))
[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/
[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
[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/
[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            # 意思是清空所有磁盘内容并初始化磁盘

image-438

image-439

image-440

image-441

image-442

image-443

七、Rsync

1、什么是RSYNC

rsync是类unix下的一款数据镜像备份工具——remote sync。

Rsync 的基本特点如下:

2、RSYNC原理

2.1、rsync原理

rsynclinux下同步文件的一个高效算法,用于同步更新两处计算机的文件和目录,并适当利用查找文件中的不同块以减少数据传输。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:

  1. 一个叫rolling checksum,是弱checksum32位的checksum(相对粗略,但是快)
  2. 另一个是强checksum128位的,以前用md4,现在用md5 hash算法。

为什么要这样?因为若干年前的硬件上跑md4的算法太慢了,所以,我们需要一个快算法来鉴别文件块的不同,但是弱的adler32算法碰撞概率太高了,所以我们还要引入强的checksum算法以保证两文件块是相同的。也就是说,弱的checksum是用来区别不同,而强的是用来确认相同

2. 传输算法

同步目标端会把fileDst的一个checksum列表传给同步源,这个列表里包括了三个东西,rolling checksum(32bits),md5 checksume(128bits),文件块编号。

同步源机器拿到了这个列表后,会对fileSrc做同样的checksum,然后和fileDstchecksum做对比,这样就知道哪些文件块改变了。

但是,聪明的你一定会有以下两个疑问:

如果我fileSrc这边在文件中间加了一个字符,这样后面的文件块都会位移一个字符,这样就完全和fileDst这边的不一样了,但理论上来说,我应该只需要传一个字符就好了。这个怎么解决? 如果这个checksum列表特别长,而我的两边的相同的文件块可能并不是一样的顺序,那就需要查找,线性的查找起来应该特别慢吧。这个怎么解决? 很好,让我们来看一下同步源端的算法。

3. checksum查找算法

同步源端拿到fileDstchecksum数组后,会把这个数据存到一个hash table(特殊的数据结构体,可以快速检索)中,用rolling checksumhash,以便获得O(1)时间复杂度的查找性能。这个hash table16bits的,所以,hash table的尺寸是2的16次方,对rolling checksumhash会被散列到0 到 2^16 – 1中的某个整数值。

4. 比对算法

image-444

  1. fileSrc的第一个文件块(我们假设的是512个长度),也就是从fileSrc的第1个字节到第512个字节,取出来后做rolling checksum计算。计算好的值到hash表中查。
  2. 如果查到了,说明发现在fileDst中有潜在相同的文件块,于是就再比较md5checksum,因为rolling checksume太弱了,可能发生碰撞。于是还要算md5128bitschecksum,这样一来,我们就有2^-(32+128) = 2^-160的概率发生碰撞,这太小了可以忽略。如果rolling checksummd5 checksum都相同,这说明在fileDst中有相同的块,我们需要记下这一块在fileDst下的文件编号。
  3. 如果fileSrcrolling checksum 没有在hash table中找到,那就不用算md5 checksum了。表示这一块中有不同的信息。总之,只要rolling checksummd5 checksum 其中有一个在fileDstchecksum hash表中找不到匹配项,那么就会触发算法对fileSrcrolling动作。于是,算法会住后step 1个字节,取fileSrc中字节2-513的文件块要做checksum,go to (1.)– 现在你明白什么叫rolling checksum了吧。(主动往后一位)
  4. 这样,我们就可以找出fileSrc相邻两次匹配中的那些文本字符,这些就是我们要往同步目标端传的文件内容了。

5. 传输

image-445 最终在同步源这端,我们的rsync算法可能会得到这个样子的一个数据数组,图中,红色块表示在目标端已匹配上,不用传输(注:我专门在其中显示了两块chunk #5,代表数据中有复制的地方,不用传输),而白色的地方就是需要传输的内容(注意:这些白色的块是不定长的),这样,同步源这端把这个数组(白色的就是实际内容,红色的就放一个标号)压缩传到目的端,在目的端的rsync会根据这个表重新生成文件,这样,同步完成。

参考博客:

https://segmentfault.com/a/1190000018391604?utm_source=tag-newest

3、安装部署服务端

[root@server1 ~]# yum install rsync.x86_64 -y
[root@server1 ~]# yum install xinetd -y
[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
}
[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、六种不同的工作模式

[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指定使用rshssh方式进行数据同步
 --progress 在传输时现实传输过程(显示备份过程) 
 -topg 保持文件原有属性,o=owner,t=time,p=perms(权限)g=group
 -b --backup创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename
 -u --update仅仅进行更新,也就是跳过已经存在的文件
 -l--links保留软连接
 --delete 删除那些DSTSRC没有的文件(就是在目的目录中只保留传输过去的文件,其它的都删除),保持和源文件相同
-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 指定使用rshssh方式进行数据同步 
--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
[root@server2 ~]# mkdir /backup
[root@server2 ~]# touch local.txt 
[root@server2 ~]# rsync local.txt /backup/
[root@server2 ~]# ls /backup/
local.txt
[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
[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
[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
[root@server2 ~]# yum install expect* -y
[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

服务端准备

[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
[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、核心组件

image-443

ansible:ansible核心程序。 HostInventory:记录由ansible管理的主机信息,包括端口、密码、ip等。 Playbooks:“剧本”YAML格式文件,多个任务定义在一个文件中,定义主机需要调用哪些模块来完成的功能。 CoreModules:核心模块,主要操作是通过调用核心模块来完成管理任务。 CustomModules:自定义模块,完成核心模块无法完成的功能,支持多种语言。 ConnectionPlugins:连接插件,ansible和Host通信使用

1.3、任务执行方式
1.4、特点
1.5、执行过程

2、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、常用工具
2.2.2、主要配置文件
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
[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的执行状态

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

在上面的例子中,我们定义了以下几个主要字段:

执行playbook剧本:

ansible-playbook xxxxx.yaml即可

3.2、快速开始
3.2.1、案例:服务安装

安装nginx并且修改配置文件

  1. 先编写一个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
        }
}
  1. 编写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

image-447

3.3、高级配置
3.3.1、fact(事实变量)

Ansible 内置了大量的事实(fact)变量,可以在 Playbook 中使用。这些事实变量可以帮助我们更好地了解目标主机的环境和配置信息,从而编写更加智能和动态的自动化脚本。

常用的内置事实变量包括:

  1. 操作系统信息:
  2. ansible_distribution: 操作系统发行版名称,如 "CentOS"、"Ubuntu"
  3. ansible_distribution_version: 操作系统版本号
  4. ansible_os_family: 操作系统家族,如 "RedHat"、"Debian"
  5. ansible_kernel: 内核版本
  6. 硬件信息:
  7. ansible_processor: CPU 型号
  8. ansible_processor_vcpus: 虚拟 CPU 核数
  9. ansible_memtotal_mb: 内存总量(MB)
  10. ansible_architecture: CPU 架构,如 "x86_64"
  11. 网络信息:
  12. ansible_default_ipv4: 默认 IPv4 地址和网关
  13. ansible_all_ipv4_addresses: 所有 IPv4 地址
  14. ansible_interfaces: 所有网络接口名称
  15. ansible_hostname: 主机名
  16. 其他信息:
  17. ansible_user_id: 当前执行 Ansible 的用户 ID
  18. ansible_date_time: 主机当前日期和时间
  19. ansible_env: 主机环境变量
  20. 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:

  1. 将 php 和 mariadb 软件包安装到 dev、test 和 prod 主机组中的主机上
  2. 将 Development Tools 软件包组安装到 dev 主机组中的主机上
  3. 将 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

在这个例子中:

  1. 第一个任务会在 CentOS 7 系统上安装 httpd 和 mariadb-server 软件包。when 语句确保了只有在 ansible_distribution 等于 'CentOS' 且 ansible_distribution_major_version 等于 7 时,这个任务才会执行。
  2. 第二个任务会在 CentOS 8 系统上安装 nginx 和 mysql-server 软件包。同样的,when 语句确保了只有在 ansible_distribution 等于 'CentOS' 且 ansible_distribution_major_version 等于 8 时,这个任务才会执行。

RHCE真题讲解(2023-10-)

考试原题(第八题):

创建一个名为/home/student/ansible/parted.yml 的playbook,它将在dev主机组上运行下列任务

  1. 如果磁盘/dev/vdd存在,则创建1500m分区
  2. 如果无法创建请求的分区大小,应显示错误消息 Could not create partition of that size,并且应改为使用大小 800m。
  3. 如果磁盘/dev/vdd不存在 ,应显示错误消息 disk /dev/vdd does not exist。
  4. 如果磁盘/dev/vdb存在,则创建1500m分区
  5. 如果无法创建请求的分区大小,应显示错误消息 Could not create partition of that size,并且应改为使用大小 800m。
  6. 最后分区都要格式化为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)

原题(第九题):生成主机文件

  1. 编写模板文件/home/student/ansible/hosts.j2 ,针对每个清单主机包含一行内容,其格式与 /etc/hosts 相同。
  2. 创建名为 /home/student/ansible/hosts.yml 的playbook,它将使用此模板在 dev 主机组中的主 机上生成文件 /etc/myhosts。
  3. 该 playbook 运行后,dev 主机组中主机上的文件/etc/myhosts 应针对每个受管主机包含一行内 容。

题目变更如下:

我们使用jinjia2和ansible内置变量动态的生成hosts文件,并且发送给远程主机

  1. 编写模板文件
[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 %}
  1. 编写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
  1. 检查并验证:
[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的配置文件

  1. 先编写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
  1. 编写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 %}
  1. 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
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
  1. 初始化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
  1. 编写角色中的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
  1. 编写角色中的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>
  1. 编写一个playbook剧本调用该角色
[root@ansible roles]# vim httpd.yaml
- name: Install httpd web server
  hosts: node1
  roles:
  - httpd
  1. 验证
[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                                 :::*

image-448

4.4、RHCE真题讲解(2023-10)

题目(第六题):

根据下列要求,在 /home/student/ansible/roles中创建名为 apache 的角色:

  1. httpd软件包已安装,设为在系统启动时启用
  2. 防火墙已启用并正在运行,并使用允许访问 Web 服务器的规则
  3. 模板文件 index.html.j2 已存在,用于创建具有以下输出的文件 /var/www/html/index.html: Welcome to HOSTNAME on IPADDRESS 其中,HOSTNAME 是受管节点的完全限定域名,IPADDRESS 则是受管节点的 IP 地址。
  4. 按照下方所述,创建一个使用此角色的 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

评论区