💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:Linux运维老纪的首页,持续学习,不断总结,共同进步,活到老学到老
导航剑指大厂系列:全面总结 运维核心技术:系统基础、数据库、网路技术、系统安全、自动化运维、容器技术、监控工具、脚本编程、云服务等。
常用运维工具系列:常用的运维开发工具, zabbix、nagios、docker、k8s、puppet、ansible等
数据库系列:详细总结了常用数据库 mysql、Redis、MongoDB、oracle 技术点,以及工作中遇到的 mysql 问题等
懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨
服务器自动巡检
技能目标: - 熟悉项目开发流程
- 熟悉服务器硬件资源采集方法
9.1 案例分析
9.1.1 案例概述
随着业务量增加,服务器也会越来越多,如何高效管理上百台服务器是一个重要的问题。
大公司在管理服务器过程中经常有这么一个需求,统计这些服务器基本信息,例如 CPU
几核的,内存多少 G、磁盘多大的等等,然后参考统计的结果做好资源规划。小李入职到新
公司后,经理发来几个电子文档,要求他确认并维护公司所有服务器信息。小周打开文件后
发现竟有百十来台服务器的信息。如果一台台服务器登录,查看获取信息,显然效率很低。
现在,根据这样的需求编写一个服务器自动巡检工具,帮助小李完成这个任务。
9.1.2 案例前置知识点
1. 服务器自动巡检
服务器自动巡检是通过技术手段自动获取目标服务器指定信息,并对结果处理,以表格、
图表形式展示,这种方式极大提高了工作效率。
2. 什么是标准库
在 Python 发行版中包含一系列非常有用的模块,这些模块也称为标准库;Python 模块
是一个 Python 文件,以.py 结尾,包含了 Python 对象定义和语句。模块让编程者能够有逻
辑地组织代码,使编写出来的代码更易懂、可复用。
3. 标准库如何使用
标准库的模块可以立刻使用,无需额外安装。以内建 sys 模块为例。
[root@localhost ~]# python3
Python 3.6.5 (default, Jun 9 2020, 11:14:59)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> help(sys)
>>> dir(sys)
进入 Python 解释器,直接 import sys 即可导入。可以使用 help()方法查看模板帮助文
档,dir()方法查看模块方法、属性信息了解该模块信息。
Python 的标准库应用非常广泛,提供了一系列功能帮助解决日常编程,非常有必要学习下,为以后打好基础。
4. os 模块
Python 的 os 模块提供一种操作系统接口,主要用于管理目录或者文件。
os 模块方法非常多,表 9-1 是常用的方法。
表 9-1 os 模块中常用的方法
方法 | 描述 | 示例 |
os.environ | 以字典形式返回系统变量 | >>> os.environ >>> os.environ['HOSTNAME'] |
os.listdir(path) | 以列表形式返回目录下所有目录 和文件名 | >>> os.listdir("/tmp") |
os.getcwd() | 获取当前路径 | >>> os.getcwd() '/root' |
os.chdir(path) | 改变当前工作目录到指定目录 | >>> os.chdir('/tmp) >>> os.getcwd() '/tmp' |
os.mkdir(path, mode=0777]) | 创建目录 | >>> os.mkdir('/tmp/abc') |
os.makedirs(path, mode=0777]) | 递归创建目录 | >>> os.makedirs('/tmp/cde/123') |
os.rmdir(path) | 移除空目录 | >>> os.rmdir ('/tmp/cde/123' |
os.remove(path) | 移除文件 | >>> os.remove("/tmp/a.txt") |
os.rename(old, new) | 重命名文件或目录 | >>> os.rename("/tmp/old.txt","/tmp/new.txt") |
方法 | 描述 | 示例 |
>>> os.stat("/tmp") | 获取文件或目录属性 | >>> os.stat("/tmp") |
os.chown(path, uid, gid) | 改变文件或目录所有者 | 改变文件或目录所有者 |
os.chmod(path, mode) | 改变文件访问权限 | >>> os.chmod("/tmp/abc",777) |
os.symlink(src, dst) | 创建软链接 | >>> os.symlink("/tmp/abc","/tmp/abc2") |
os.unlink(path) | 移除软链接 | >>> os.unlink('/tmp/abc2') |
os.urandom(n) | 返回随机字节,适合加密使用 | >>> os.urandom(2) '%\xec |
os.getuid() | 返回当前进程 UID | >>> os.getuid() 0 |
os.getlogin() | 返回登录用户名 | >>> os.getlogin() 'root' |
os.getpid() | 返回当前进程 ID | >>> os.getpid() 3079 |
5. paramiko 模块
paramiko 是用 Python 语言写的一个模块,遵循 SSH2 协议,支持以加密和认证的方
式,进行远程服务器的连接。
由于使用的是 Python 这样的能够跨平台运行的语言,所以所有 Python 支持的平台,
如 Linux, Solaris, BSD, MacOS X, Windows 等,paramiko 都可以支持,因此,如果需要使
用 SSH 从一个平台连接到另外一个平台,进行一系列的操作时,paramiko 是最佳工具之一。
9.1.3 案例环境
本案例环境如表 9-2 所示。
表 9-2 服务器配置
主机 | 操作系统 | 主机名/IP 地址 | 主要软件及版本 |
自动巡检工具 | CentOS 7.3 | Center /192.168.0.10 | Python 3 |
被检服务器 | CentOS 7.3 | localhost /192.168.0.11 | / |
被检服务器 | CentOS 7.3 | localhost /192.168.0.12 | / |
案例拓扑如图 9.1 所示。
图 9.1 自动巡检服务拓扑图
1. 案例需求
收集服务器指标如下:
运行时间(当前时间、最后启动时间)
主机名
系统版本/内核版本
CPU
内存
硬盘
监听端口
是否允许 root 远程登录
2. 实现思路
使用 paramiko 模块将收集程序上传到目标主机并执行,处理返回的数据写到 csv 文件
中。
9.2 案例实施
9.2.1 功能模块规划
根据本案例的需求,设计 Python 工程目录结构中所包含的模块信息,如表 9-3 所示。
表 9-3 Python 工程目录结构中所包含的模块信息
模块 | 描述 |
run.py | 主程序 |
collect.py | 收集主机信息程序 |
host.info | 存放主机信息文件 |
存放主机信息文件 | 处理结果 |
9.2.2 服务器指标收集
Linux 文件系统的/proc 目录是一个虚拟目录,在 Linux 系统启动后生成的,数据存储在
内存中,存放内核运行时的参数、网络信息、进程状态、资源利用率等等。
服务器指标收集就是从这个目录下的文件获取各项数据,如表 9-4 所示。
表 9-4 服务器指标收集文件
文件路径 | 描述 |
/etc/issue /etc/redhat-release | 操作系统版本信息 |
/proc/cpuinfo | CPU 信息 |
/proc/stat | CPU 使用情况 |
/proc/meminfo | 内存信息 |
/etc/mtab | 硬盘分区 |
/dev | Linux 系统使用的所有外部设备 |
/sys/class/net | 网络设备连接符号 |
/proc/net/dev | 网卡统计信息 |
/proc/net/tcp /proc/net/udp | 网络连接信息 |
9.2.3 功能实现
运行本案例的 Python 脚本需使用 paramiko 模块,而 paramiko 模块内部依赖 pycrypto,
所以需先下载安装 pycrypto。
[root@center ~]# pip3 install pycrypto
[root@center ~]# pip3 install paramiko
1. 主程序
[root@center ~]# vim run.py
#!/usr/bin/python3
import os,sys
import paramiko
base_dir = os.path.dirname(os.path.abspath(__file__))
host_file = "%s/host.info" %base_dir
collect_file = "%s/collect.py" %base_dirimport csv, codecs
# 字段名称
with codecs.open('result.csv', 'w', 'gbk') as f1:
csv_file = csv.writer(f1)
csv_file.writerow(['IP 地址', '主机名', '操作系统', '内存', 'CPU', '硬盘', '网络连接状态统计', \
'监听端口', '网卡流量'])
with open(host_file) as f:
# 遍历主机列表
while f:
line = f.readline()
if not line: break
line = line.split()
hostname = line[0]
port = int(line[1])
username = line[2]
password = line[3]
if not os.path.isfile(collect_file):
print(collect_file + " 文件不存在!")
sys.exit(1)
try:
s = paramiko.Transport((hostname, port))
s.connect(username=username, password=password)
except Exception as e:
print(e)
continue
# 上传收集程序到目标主机
remote_file = "/tmp/collect.py"
sftp = paramiko.SFTPClient.from_transport(s)
sftp.put(collect_file, remote_file)
try:
sftp.file(remote_file)
# 在目标主机执行收集程序
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port, username, password)
stdin, stdout, stderr = client.exec_command('python %s' %remote_file)
stdout = stdout.read()
error = stderr.read()
if not error:
# 处理从主机收集的数据
result = eval(stdout.decode('utf-8'))
ip = result["ip"]
os_info = result["os"]
memory = result["memory"]
cpu = result["cpu"]
disk = result["disk"]
network = result["network"]
# 系统信息
host_name = os_info["host_name"]
os_version = "os_version: %s" % os_info["os_version"] + "\n" \
+ "kernel: %s" %os_info["kernel"] + "\n" \
+ "start_time: %s" %os_info["start_time"] + "\n" \
+ "current_time: %s" %os_info["current_time"]
# 内存
m = ''
for k, v in memory.items():
m +="%s: %sM" %(k, v) + "\n"
# CPU
c = ''
for k, v in cpu.items():
c +="%s: %s" %(k, v) + "\n"
# 硬盘
d = ''
for k, v in disk.items():
fs = k
mount = v["mount"]
total = v["total"]
avail = v["free"]
used = v["used"]
d += "%s -> %s,total: %s, avail: %s, used: %s" %(fs, mount, total, avail, \
used) + "\n"
# 连接状态统计
status_count = ''
for k, v in network["tcp_status_count"].items():
status_count +="%s: %s" %(k, v) + "\n"
status_count + "\n" + "%s: udp_status_count" %(network["udp_status_count"])
# 监听端口
listen = ''
for k, v in network["listen"].items():
v = list(set(v))
listen +="%s: %s" %(k, v) + "\n"
# 网卡流量
traffic = ''
for k, v in network["traffic"].items():
traffic +="%s: %sKB" %(k, v) + "\n"
# 写入 csv
with codecs.open('result.csv', 'a', 'gbk') as f2:
csv_file = csv.writer(f2)
data = [
(ip, host_name, os_version, m, c, d, status_count, listen, traffic)
]
csv_file.writerows(data)
else:
print(error)
# 删除上传到目标主机的收集程序
#client.exec_command('rm -f %s' %remote_file)
#client.close()
except Exception as e:
print(e)
continue
finally:
s.close()
2. 收集主机信息程序
[root@center ~]# vim collect.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Only support OS CentOS or Ubuntu
from datetime import datetime, date
import os, sys, time, re, math, socket
import subprocess
################# 获取本地外网 IP #################
def extranet_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('10.255.255.255', 0))
ip = s.getsockname()[0]
return ip
################# 操作系统版本/主机名/内核版本 #################
def os_info():
with open("/etc/issue") as f:
f = f.read()
if "\S" in f or "CentOS" in f:
with open("/etc/redhat-release") as f:
os_version = f.read().replace('\n', '')
else:
os_version = f.split("\n")[0]
host_name, kernel = os.uname()[1:3]
os_info = {'os_version': os_version, 'host_name': host_name, 'kernel': kernel}
return os_info
################# 运行时间 #################
# 最后系统启动时间
def start_time():
p = subprocess.Popen("date -d \"$(awk -F. '{print $1}' /proc/uptime) second ago\" +'%F %T'", \
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
outs, errs = p.communicate()
# bytes to str
outs = outs.decode('utf-8').replace('\n','')
# 当前时间
date_time = date.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')
time = {'start_time': outs, 'current_time': date_time}
return time
################# CPU 核心数量/CPU 占用时间百分比 #################
#from multiprocessing import cpu_count
#print(cpu_count())
def cpu_count():
cpu_count = {}
with open("/proc/cpuinfo") as f:
for i in f.readlines():
if "processor" in i:
cpu_count["cpu_count"] = cpu_count.get("cpu_count", 0) + 1
return cpu_count
# user nice system idle iowait irq softirq steal guest (从系统启动开始累计到当前)
def cpu_use_time_percent():
with open('/proc/stat') as f:
cpu = [int(n) for n in f.readline().split()[1:]]
total_old = sum(cpu)
user_old = cpu[0] + cpu[1]
system_old = cpu[2]
idle_old = cpu[3]
iowait_old = cpu[4]
time.sleep(2)
with open('/proc/stat') as f:
cpu = [int(n) for n in f.readline().split()[1:]]
total_new = sum(cpu)
user_new = cpu[0] + cpu[1]
system_new = cpu[2]
idle_new = cpu[3]
iowait_new = cpu[4]
cpu_total = float(total_new - total_old)
cpu_user = float(user_new - user_old)
cpu_system = float(system_new - system_old)
cpu_idle = float(idle_new - idle_old)
cpu_iowait = float(iowait_new - iowait_old)
# used = float("%.1f" %((1-cpu_idle / cpu_total) * 100))
user = round(cpu_user / cpu_total * 100, 1)
system = round(cpu_system / cpu_total * 100, 1)
iowait = round(cpu_iowait / cpu_total * 100, 1)
cpu_time = {'user': user, 'system': system, 'iowait': iowait}
return cpu_time
################# 内存利用率 #################
def memory():
memory = {}
with open("/proc/meminfo") as f:
for n in range(5):
mem = f.readline().split()
memory[mem[0]] = int(mem[1])
total = memory["MemTotal:"] // 1024
free = (memory['MemFree:'] + memory['Buffers:'] + memory['Cached:']) // 1024
used = total - free
memory = {'total': total, 'used': used, 'free': free}
return memory
################# 硬盘分区利用率 #################
def disk_partitions():
part = {}
with open("/etc/mtab") as f:
for p in f.readlines():
if p.startswith("/dev"):
p = p.split()
fs = p[0]
mount = p[1]
fs_info = os.statvfs(mount)
# 如果数除不尽则为 0,所以要用 float 取结果浮点数,单位 G
# total = "%.1f" %(float(fs_info.f_bsize * fs_info.f_blocks / 1024 / 1024) \
/ float(1024))
# bsize * block = bytes
total = round(float(fs_info.f_bsize * fs_info.f_blocks / 1024 / 1024) / float(1024), 1)
# bfree: 可用块数量
used = round(float(fs_info.f_bsize * fs_info.f_bfree / 1024 / 1024) / float(1024), 1)
used = round(total - used, 1)
# bavail: 非超级用户可用块
free = round(float(fs_info.f_bsize * fs_info.f_bavail / 1024 / 1024) / float(1024), 1)
# 如果键存在就返回对应的值,否则新增(以读取的第一个文件系统为准)
part[fs] = part.get(fs, {'mount':mount, 'total': total, 'used': used, 'free': free})
return part
################# 网卡流量 #################
# 通过 IP 获取网卡名
ip = extranet_ip()
def nic_traffic():
p = subprocess.Popen("ifconfig |awk -F'[: ]' '/^em|^eth|^br|^p3p1|^en/{nic=$1}/%s/{ \
print nic}'" %ip, stdout=subprocess.PIPE, shell=True)
outs, errs = p.communicate()
nic = outs.decode("utf-8").replace("\n","")
with open("/proc/net/dev") as f:
for s in f.readlines():
if s.strip().startswith(nic):
s = s.replace(":"," ")
in_old = s.split()[1]
out_old = s.split()[9]
time.sleep(1)
with open("/proc/net/dev") as f:
for s in f.readlines():
if s.strip().startswith(nic):
s = s.replace(":"," ")
in_new = s.split()[1]
out_new = s.split()[9]
# // 整除去尾
traffic_in = (int(in_new) - int(in_old)) // 1024
traffic_out = (int(out_new) - int(out_old)) // 1024
traffic = {'traffic': {'in': traffic_in, 'out': traffic_out}}
return traffic
################# 网络连接 #################
def network_status():
tcp_status = {
'01':'ESTABLISHED',
'02':'SYN_SENT',
'03':'SYN_RECV',
'04':'FIN_WAIT1',
'05':'FIN_WAIT2',
'06':'TIME_WAIT',
'07':'CLOSE',
'08':'CLOSE_WAIT',
'09':'LAST_ACK',
'0A':'LISTEN',
'0B':'CLOSING'
}
tcp_status_count = {}
udp_status_count = 0
listen = {"tcp":[], "tcp6":[], "udp":[], "udp6":[]}
for t in listen.keys():
if os.path.exists('/proc/net/%s' %t):
with open('/proc/net/%s' %t) as f:
while f:
line = f.readline()
if not line: break
status_line = line.split()
status = status_line[3]
if status == "st": continue
# 统计监听端口
# TCP
if status == "0A" and t.startswith("tcp"):
# 16 to 10
listen_port = int(status_line[1].split(':')[1], 16)
listen[t] = listen.get(t, []) + [listen_port]
# UDP
elif status == "07" and t.startswith("udp"):
listen_port = int(status_line[1].split(':')[1], 16)
listen[t] = listen.get(t, []) + [listen_port]
# 统计 TCP 连接状态
status_name = tcp_status[status]
if t.startswith("tcp"):
tcp_status_count[status_name]=tcp_status_count.get(status_name,0)+1
else:
udp_status_count += 1
network_status = {'listen': listen, 'tcp_status_count': tcp_status_count, \
'udp_status_count': udp_status_count}
return network_status
if __name__ == '__main__':
result = {}
os_info = os_info()
os_info.update(start_time())
cpu_count = cpu_count()
cpu_count.update(cpu_use_time_percent())
network_status = network_status()
network_status.update(nic_traffic())
result["ip"] = extranet_ip()
result["os"] = os_info
result["memory"] = memory()
result["cpu"] = cpu_count
result["disk"] = disk_partitions()
result["network"] = network_status
print(result)
3. 存放主机信息文件
[root@center ~]# vim host.info
192.168.0.11 22 root 123456
192.168.0.12 22 root 123456
在所需巡检的主机信息文件中输入主机 IP 地址、SSH 端口、用户名、密码,需要巡检
多少台主机,就添加多少台的主机信息。本例巡检两台 192.168.0.11 和 192.168.0.12。
9.2.4 测试
在已安装 Python 3.6 以上版本的目标主机上,将以上三个文件 run.py,collect.py,
host.info 保存到信息采集的计算机上,然后运行主程序:
[root@center ~]# python3 run.py
所有主机逐一处理完成后,可以使用 cat 命令直接查看巡检记录,生成的 result.csv 文
件的内容如下:
[root@center ~]# cat result.csv
192.168.0.11,localhost.localdomain,"os_version:CentOS
Linux
release
7.3.1611
(
Core)
kernel: 3.10.0-514.el7.x86_64
start_time: 2020-07-20 08:56:47
current_time: 2020-07-20 10:09:27","total: 976M
free: 594M
used: 382M
","iowait: 0.0
cpu_count: 1
user: 0.0
system: 0.0
","/dev/mapper/cl-root -> /,total: 17.0, avail: 14.3, used: 2.7
/dev/sda1 -> /boot,total: 1.0, avail: 0.9, used: 0.1
","ESTABLISHED: 2
LISTEN: 5
","udp6: [63217, 323]
udp: [323, 68, 36501]
tcp6: [25, 3306, 22]
tcp: [25, 22]
","out: 0KB
in: 1KB
"
192.168.0.12,localhost.localdomain,"os_version:
CentOS
Linux
release
7.3.1611
(
Core)
kernel: 3.10.0-514.el7.x86_64
start_time: 2020-07-20 08:56:50
current_time: 2020-07-20 10:09:31","total: 976M
free: 597M
used: 379M
","iowait: 0.0
cpu_count: 1
user: 0.5system: 0.5
","/dev/mapper/cl-root -> /,total: 17.0, avail: 14.3, used: 2.7
/dev/sda1 -> /boot,total: 1.0, avail: 0.9, used: 0.1
","ESTABLISHED: 2
LISTEN: 5
","udp6: [63217, 323]
udp: [323, 68, 36501]
tcp6: [25, 3306, 22]
tcp: [25, 22]
","out: 0KB
in: 0KB
"
如果将 result.csv 文件传送到 Windows 服务器以 Excel 表格打开,就会以更加直观的
图表形式展示,如图 9.2 所示。
图 9.2 图标展示服务器巡检信息
通过本案例编写的服务器自动巡检工具 Python 脚本,可以很轻易收集所需管理的服务
器信息,提高工作效率。