本项目基于Linux系统搭建系统集群,使用Ansible实现Linux集群下的批量部署和自动化管理,实现Web形式的自动化运维系统,集中批量控制服务器,最终实现能支撑1000台实例的环境提供管理和自动化任务,提高运维工程师的工作效率和质量。项目基于HTTP实现自动化任务接受和响应接口设计,基于MySQL用作的关系型数据存取,基于Redis的任务锁机制和消息队列, 基于MongoDB的事件日志记录, 最终实现邮件通知功能、敏感数据加密功能、日志事件记录功能。
主要目标是实现自动化资产扫描, 扫描指定网段的服务器资产信息,后续功能会进一步完善。
如果有云服务器时执行下面的操作,没有云服务器的操作在这个之后进行说明
cd /data/www/devops
virtualenv
-p /usr/bin/python3 env
source env/bin/active
pip install Django==2.2
# sqlite版本问题
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).
解决方式:不使用sqlite存储数据,使用mysql
# CMDB/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devopsProject',
'USER': 'devops',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}
python manage.py runserver
如果没有云服务器直接进行虚拟环境的创建
执行下面命令:
witch python3 # /usr/bin/python3
virtualenv -p /usr/bin/python3 env #创建虚拟环境
source env/bin/active #切换到虚拟环境
pip install Django==2.2
[root@foundation0 ~]# systemctl start mariadb
[root@foundation0 ~]# systemctl enable mariadb
[root@foundation0 ~]# mysql -uroot -pServer version: 5.5.52-MariaDB MariaDB Server
# 创建数据库
MariaDB [(none)]> create database if not exists devopsProject default charset
utf8;
Query OK, 1 row affected (0.01 sec)
# 新建用户
MariaDB [(none)]> create user devops@'%' identified by 'westos';
Query OK, 0 rows affected (0.03 sec)
# 用户授权
MariaDB [(none)]> grant all on devopsProject.* to devops@'%';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> Bye
[root@foundation0 ~]# mysql -udevops -pwestos -hIP
Server version: 5.5.52-MariaDB MariaDB Server
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| devopsProject
|
+--------------------+
2 rows in set (0.01 sec)
# CMDB/settings.py
ALLOWED_HOSTS = ['*']
#
配置数据库: 使用mysql数据库,而不是默认的sqlite数据库。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devopsProject',
'USER': 'devops',
'PASSWORD': 'devops',
'HOST': '47.92.255.98',
'PORT': '3306',
}
}
# 语言和时区的设置
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
python manage.py makemigrations # 生成迁移脚本
python manage.py migrate
# 写入数据库, 创建关于用户和用户组等数据表信息
python manage.py createsuperuser # 创建超级用户
# 启动项目, 访问网址http://IP:8000/admin
python manage.py runserver 0.0.0.0:8000
配置数据库信息
如果是本地数据库,Host填写127.0.0.1即可
运行项目时,出现报错如下,是因为缺少mysqlclient安装包.
解决方法:
pip install mysqlclient
解决方法
yum install mariab-devel -y
yum install python-devel -y
记录HTTP访问的IP及用户UA信息
$ python manage.py startapp scanhosts
# settings.py
# 1). 将新建的APP加入到项目中
INSTALLED_APPS = [
......省略部分
'django.contrib.staticfiles',
'scanhosts',
]
# 2). 配置数据库: 使用mysql数据库,而不是默认的sqlite数据库。
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devopsProject',
# 数据库名称 'USER': 'devops', # 用户名
'PASSWORD': 'westos', # 用户密码
'HOST': '127.0.0.1', # 数据库服务器所在主机名
'PORT': '3306', # 数据库端口
}
}
# 3). 语言和时区的设置(根据自己项目的需求, 选择修改)
# LANGUAGE_CODE = 'en-us'
# TIME_ZONE = 'UTC'
LANGUAGE_CODE = 'zh-hans' # 语言选择中文
TIME_ZONE = 'Asia/Shanghai' # 时区选择亚洲/上海
安装数据库开发软件
$ yum install mariadb-devel -y
安装数据库连接模块(必须先安装mariadb-devel, 否则会报错)
$ pip install mysqlclient
编辑数据库模型文件:
# models.py
"""
- 一个类对应一个数据库表;
- 类的一个属性对应数据库表的一个表头;
- max_length: 字符串最大长度, 对应数据库的varchar类型
- default: 指定默认值
- verbose_name: 指定Django后台显示的列头信息
- auto_now: 每次修改记录时自动更新为当前时间
- Meta类的设置
- verbose_name: 指定Django后台显示的表名称单数
- verbose_name_plural: 指定Django后台显示的表名称复数
- db_table: 指定数据库表的名称, 默认是APP名称_类名称.
"""
class UserIPInfo(models.Model):
ip = models.CharField(max_length=150, default='', verbose_name='IP地址')
time = models.DateTimeField(verbose_name='更新时间', auto_now=True)
class Meta:
verbose_name = '用户访问地址信息表'
verbose_name_plural = verbose_name db_table = 'user_IP_info'
class BrowseInfo(models.Model):
# null=True: 是针对数据库而言,True表示数据库的该字段可以为空。
user_agent = models.CharField(max_length=100, default='',
verbose_name='用户浏览器信息', null=True)
disk_id = models.CharField(max_length=256, default='', verbose_name='唯
一设备ID')
"""
ForeignKey是一种关联字段,将两张表进行关联的方式
on_delete: 是否级联删除, Django1.x默认级联删除, Django2.x必须手动指定
on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五个可选择的值
CASCADE:此值设置,是级联删除。
PROTECT:此值设置,是会报完整性错误。
SET_NULL:此值设置,会把外键设置为null,前提是允许为null。
SET_DEFAULT:此值设置,会把设置为外键的默认值。
SET():此值设置,会调用外面的值,可以是一个函数。
"""
user_ip = models.ForeignKey('UserIPInfo', on_delete=models.DO_NOTHING)
class Meta:
verbose_name = '用户浏览器信息表'
verbose_name_plural = verbose_name
db_table = 'browse_info'
根据ORM(对象关系映射)将面向对象形式的模型进行迁移, 生成中间代码
$ python manage.py
makemigrations
# 代码执行效果, 生成迁移文件,所在位置: scanhosts/migrations
Migrations for 'scanhosts':
scanhosts/migrations/0001_initial.py
- Create model UserIPInfo
将生成的迁移文件转成SQL语句并执行SQL语句, 创建对应的数据库及数据库表
$ python manage.py migrate
$ python manage.py
createsuperuser
Username (leave blank to use 'kiosk'): admin
Email address: admin@qq.com
Password:
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
启动Django项目,默认开启的端口是8000
$ python manage.py runserver
Django version 2.2.5, using settings 'first_devops.settings'
Starting development server at http://127.0.0.1:8000/
访问项目后台管理界面, 输入超级用户名称和密码即可进入后台界面.
# first_devops/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^sendinfos/$', user_infos),
]
# scanhosts/views.py
def user_info(request):
# request.META 是一个Python字典,包含了所有本次HTTP请求的Header信息,比如用户IP
地址和用户Agent(通常是浏览器的名称和版本号)
ip = request.META.get('REMOTE_ADDR')
user_agent = request.META.get('HTTP_USER_AGENT')
# 使用filter()方法对数据进行过滤, 返回的是列表, 列表元素是符合条件的对象。
user_obj = UserIPInfo.objects.filter(ip=ip)
# 如果没有找到,则新建UserIPInfo对象,并获取对象编号(为了和BrowseInfo表关联)
if not user_obj:
res = UserIPInfo.objects.create(ip=ip)
user_ip_id = res.id
else:
user_ip_id = user_obj[0].id
# 新建BrowseInfo对象
BrowseInfo.objects.create(user_agent=user_agent, user_ip_id=user_ip_id)
# 字典封装返回的数据信息
result = {
'STATUS': 'success',
'INFO': 'User Info',
'IP': ip,
'User-Agent': user_agent
}
# 以json的方式封装返回, 下面的两种方式任选一种.
# return
HttpResponse(json.dumps(result),
content_type='application/json')
return JsonResponse(result)
[root@foundation0 ~]# mysql -udevops -p
Welcome to the MariaDB monitor.
Commands end with ; or \g.
MariaDB [(none)]> use devopsProject;
MariaDB [devopsProject]> select * from user_IP_info;
MariaDB [devopsProject]> select * from browse_info;
# first_devops/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^sendinfos/$', user_info),
url(r'^getinfos/$', user_history),
]
# scanhosts/views.py
def user_history(request):
# 获取UserIPInfo表的所有对象信息;
ip_lists = UserIPInfo.objects.all()
infos = {}
# 获取每个IP访问网站浏览器的信息, 格式如下:
"""
infos = {
'127.0.0.1' : ['UA-1', 'ua-2'],
'172.25.254.1' : ['UA-1', 'ua-2'],
}
"""
for item in ip_lists:
infos[item.ip] = [b_obj.user_agent for b_obj in
BrowseInfo.objects.filter(user_ip_id=item.id)]
result = {
'STATUS': 'success',
'INFO': infos
}
return JsonResponse(result)
在编写程序过程中,很难免的会出现一些问题,程序并非按照我们预想的那样运行,这个时候我们通常会对程序进行调试,来看看到底是哪边出了问题。而程序日志是来帮助我们记录程序运行过程的帮手,善用日志的程序员也就能很快找出自己程序的问题所在从而快速解决问题。
在服务器级别的组件中都有对应的日志文件,例如MySQL、Redis、nginx、uWSGI都会在运行过程中将一些信息写到日志文件中。
Django使用python的内置模块logging来管理自己的日志, 包含四大组件: 日志记录器Loggers、日志处理器Handlers、日志过滤器Filters和日志格式化工具Formatters。
Django项目日志管理详情查看官方文档:https://docs.djangoproject.com/en/2.2/topics/logging/
# first_devops/settings.py
# 日志管理的配置
LOGGING = {
'version': 1,
# disable_existing_loggers是否禁用已经存在的logger实例。默认值是True.
'disable_existing_loggers': False,
# formatters: 定义输出的日志格式。
'formatters': {
'verbose': {
# 格式化属性查看资料:
https://docs.python.org/3/library/logging.html#logrecord-attributes
'format': '{levelname} {asctime} {module} : {lineno} {message}',
# 变量的风格
'style': '{',
# 日期显示格式
'datefmt': '%Y-%m-%d %H:%M:%S',
},
},
# handlers定义处理器。
'handlers': {
'file': {
# 日志处理级别
'level': 'INFO',
# 日志处理类, 详细的请查看网站:
https://docs.python.org/3/library/logging.handlers.html
'class': 'logging.FileHandler',
# 日志处理后输出格式
'formatter': 'verbose',
# 记录日志的文件名, 存储在当前项目目录下的devops.log文件
'filename': os.path.join(BASE_DIR, 'devops.log')
},
}, # loggers定义logger实例。
'loggers': {
'django': {
# 对应的handles对象列表
'handlers': ['file'],
# logger实例输出的日志级别
'level': 'INFO',
# 日志是否向上级传递。True 向上级传,False 不向上级传。
'propagate': True,
},
}
}
在web应用中,服务器对客户发送邮件来通知用户一些信息,可以使用邮件来实现。Django中提供了邮件接口,使我们可以快捷的建设一个邮件发送系统。通常用于发送自定义消息或者通知告警等信息(当然
也可以通过短信接口或者微信接口, 便于维护人员快速响应)。
# first_devops/settings.py
# 邮件配置
EMAIL_HOST = 'smtp.qq.com'
EMAIL_HOST_USER = 'QQ邮箱'
EMAIL_HOST_PASSWORD = '登录授权码(注意: 不是登录密码)'
EMAIL_PORT = 465
EMAIL_SUBJECT_PREFIX = 'Python开发社区'
EMAIL_USE_SSL = True
$ python manage.py
shell
>>> from django.core.mail import send_mail
>>> help(send_mail)
>>> send_mail(subject="Django邮件发送测试代码", message='邮件发送测试成功',
from_email='发送人的邮箱地址', recipient_list=['接收人的邮箱地址1', '接收人的邮箱地址2'])
1
发送邮件在很多业务场景都会适用, 为了方便操作, 将邮件发送的内容封装成一个工具, 减少开发过程中的重复操作, 提高效率。 操作如下:
# first_devops/scanhosts/utils/tools.py
import logging
from django.core.mail import send_mail
from datetime import datetime
from first_devops import settings
class SendMail(object):
"""发送邮件的封装类"""
def __init__(self, subject, message, recipient_list, ):
# 给每个邮件的标题加上当前时间, 时间格式为年月日_小时分钟秒_传入的邮件标题
subject_time = datetime.now().strftime('%Y%m%d_%H%M%S_')
self.recipient_list = recipient_list
self.subject = subject_time + subject
self.message = message
def send(self):
try:
send_mail(
subject=self.subject,
message=self.message,
from_email=settings.EMAIL_HOST_USER,
recipient_list=self.recipient_list,
fail_silently=False
)
return True
except Exception as e:
logging.error(str(e))
return False
$ python manage.py
shell
>>> from scanhosts.utils.tools import SendMail
>>> mail = SendMail('Django 测试标题', '邮件正文内容', ['[email protected]'])
>>> mail.send()
True
from .base import *
DEBUG = True
from .base import *
# 开发环境一定要关闭调试模式
DEBUG = False
# 允许所有主机访问
ALLOWED_HOSTS = ['*']
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'devops.settings.development')
# ......此处省略代码
if __name__ == '__main__':
main()
如果项目将来需要上线, 修改启动项目访问的配置文件为生产环境的配置文件即可, 如下:
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'devops.settings.production')
# ......此处省略代码
if __name__ == '__main__':
main()
$ python manage.py runserver
Django version 2.2.5, using settings 'devops.settings.development'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
为了方便在一个大的Django项目中,管理实现不同的业务功能, 我们会在项目中创建多个APP实现功能。为了更加方便管理APP, 项目结构更加清晰。可以专门创建apps目录存储项目应用, 专门创建extra_apps存储项目第三方APP, 项目结构如下所示:
但项目运行时, 不会自动寻找apps和extra_apps子目录中创建的APP, 需要手动在配置文件中配置,修改devops/settings/base.py文件, 添加内容如下:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 将apps目录和extra_apps添加到python的搜索模块的路径集中
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))
添加成功后, 进入Django工程的交互式环境, 查看sys.path变量的值, 判断是否添加成功?
$ python manage.py shell
In [1]: import sys
In [2]: sys.path
Out[2]:
['/home/kiosk/PycharmProjects/devops/devops/extra_apps',
'/home/kiosk/PycharmProjects/devops/devops/apps',
'/home/kiosk/PycharmProjects/devops',
# .......此处为了美观, 省略部分路径........
]
Nmap,也就是Network Mapper,最早是Linux下的网络扫描和嗅探工具包。是一款用于网络发现和安全审计的网络安全工具。
# 使用-sP开关(Arp ping)执行PING命令,与windows / linux ping命令类似。
$ ping -c1 -w1 172.25.254.197
# 探测主机IP是否存活,
&>/dev/null并将所有的输入重定向到垃圾箱
# && 如果前面的指令执行成功, 做什么操作(echo ok)
# || 如果前面的指令执行失败, 做什么操作(echo fail)
$ ping -c1 -w1 172.25.254.250 &>/dev/null && echo ok || echo fail
# 使用nmap命令, 如果报错-bash: nmap: command not found, 则yum 安装nmap安装包
$ nmap -n -sP 172.25.254.197
$ nmap -n -sP 172.25.254.0/24
# Nmap默认端口的扫描范围1-10000
$ nmap -n -p 172.25.254.197
# 具体指定要扫描的端口为50-80
$ nmap -n -p50-80 172.25.254.197
# 具体指定要扫描的端口为22和80
$ nmap -n -p22,80 172.25.254.197
# -O是检测操作系统交换机
$ nmap -O 172.25.254.197
查看172.25.254.197这台 主机是否开启?
查看172.25.254.0/24局域网内存活的主机信息及存活主机个数。
python-nmap是一个使用nmap进行端口扫描的python库,它可以很轻易的生成nmap扫描报告,并且可以帮助系统管理员进行自动化扫描任务和生成报告。同时,它也持nmap脚本输出。
# 安装nmap的第三方模块
$ pip install python-nmap
具体的代码调用如下:
import nmap
# 实例化对象, portScanner()类用于实现对指定主机进行端口扫描
nm = nmap.PortScanner()
# 以指定方式扫描指定主机或网段的指定端口
result = nm.scan(hosts='172.25.254.0/24', arguments='-n -sP')
print("扫描结果: ", result)
# 返回的扫描具体的nmap命令行
print("nmap命令行: ", nm.command_line())
# 返回nmap扫描的主机清单,格式为列表类型
print("主机清单: ", nm.all_hosts())
# 查看指定主机信息
print('172.25.254.197的主机信息: ', nm['172.25.254.197'])
代码执行效果如下图所示:
扫描结果:
{'nmap': {'command_line': 'nmap -oX - -n -sP 172.25.254.0/24', 'scaninfo': {},
'scanstats': {'timestr': 'Wed Dec 25 16:14:47 2019', 'elapsed': '6.06',
'uphosts': '2', 'downhosts': '254', 'totalhosts': '256'}}, 'scan':
{'172.25.254.197': {'hostnames': [{'name': '', 'type': ''}], 'addresses':
{'ipv4': '172.25.254.197'}, 'vendor': {}, 'status': {'state': 'up', 'reason':
'syn-ack'}}, '172.25.254.250': {'hostnames': [{'name': '', 'type': ''}],
'addresses': {'ipv4': '172.25.254.250'}, 'vendor': {}, 'status': {'state': 'up',
'reason': 'syn-ack'}}}}
nmap命令行:
主机清单:
nmap -oX - -n -sP 172.25.254.0/24
['172.25.254.197', '172.25.254.250']
172.25.254.197的主机信息:
{'hostnames': [{'name': '', 'type': ''}], 'addresses': {'ipv4':
'172.25.254.197'}, 'vendor': {}, 'status': {'state': 'up', 'reason': 'syn-ack'}}
$ telnet 172.25.254.34 22
# 实例化对象
tn = telnetlib.Telnet(host='172.25.254.34', port=22)
# read_until读取直到遇到了换行符或超时秒数。默认返回bytes类型,通过decode方法解码为字符串。
result = tn.read_until(b'\n', timeout=5).decode('utf-8')
# 通过正则匹配且忽略大小写, 寻找是否ssh服务开启。
searchObj = re.search('ssh', result, re.I)
# 如果能匹配到内容, 说明ssh服务开启, 是Linux服务器.
if searchObj:
print("ssh服务是开启的,且是Linux操作系统")
else:
print('ssh服务未开启或者不是Linux服务器')
用一系列的验证方式循环进行SSH登录, 得到争取的登录方式。
SSH常用来远程登录到远程机器,有两种常用的方法
Pexpect 用来实现与 ssh、ftp 、telnet 等程序的自动交互。是 Expect 语言的一个 Python 实现,是一个用来启动子程序,并使用正则表达式对程序输出做出特定响应,以此实现与其自动交互的 Python模块。
pexpect的核心类和函数。
import pexpect
cmd_result, exitstatus = pexpect.run('hostname', withexitstatus=True)
print("命令执行结果: ", cmd_result.decode('utf-8'))
print("命令执行的状态码: ", exitstatus)
执行结果如下:
命令执行结果:foundation0.ilt.example.com
命令执行的状态码: 0
import pexpect
# 1.通过pexpect执行指令(无交互)
# 执行命令并返回命令执行结果和状态码(0代表成功,其他-执行失败)
(command_output, exitstatus) = pexpect.run('hostname', withexitstatus=1)
command_output = command_output.decode('utf-8')
if exitstatus == 0:
print("命令执行成功:", command_output)
else:
print("命令执行失败:", command_output)
def login_ssh_password(user, host, password, port=22):
# 2. 通过pexpect执行指令(有交互)
command = 'ssh -p22 [email protected]'
# command = 'ssh -p%s %s@%s' %(port, user, host)
# spawn开启一个子进程处理交互式操作
ssh = pexpect.spawn(command=command, timeout=3)
# 匹配交互信息,返回的是匹配到的信息的索引
match_index = ssh.expect(['Are you sure you want to continue connecting (yes/no)? ', 'password:'])
print(match_index)
# 如果索引为0代表第一次连接,如果索引为1代表非第一次连接
if match_index == 0:
print("第一次连接")
ssh.sendline('yes')
ssh.expect(['password:'])
ssh.sendline(password)
elif match_index == 1:
print('非第一次连接')
ssh.sendline(password)
login_index = ssh.expect(['Last login: ', pexpect.EOF, pexpect.TIMEOUT])
print(login_index)
if login_index == 0:
print("用户登录成功")
# 进入元成服务器的命令行
ssh.interact()
elif login_index == 1:
print("登录失败:Logout")
elif login_index == 2:
print('登录超时')
if __name__ == '__main__':
login_ssh_password(user='root',host='172.25.254.34', password='Asimov',port=22)
# 使用豆瓣的镜像源, 安装paramiko模块并指定安装版本为2.6.0.
$ pip install -i https://pypi.douban.com/simple paramiko==2.6.0
paramiko包含两个核心组件:SSHClient和SFTPClient(sftp=ssh file transfer protocol)。
SSHClient的作用类似于Linux的ssh命令,是对SSH会话的封装,该类封装了传输(Transport),通道(Channel)及SFTPClient建立的方法(open_sftp),通常用于执行远程命令。
SFTPClient的作用类似与Linux的sftp命令,是对SFTP客户端的封装,用以实现远程文件操作,如文件上传、下载、修改文件权限等操作。
项目代码:基于paramiko实现ssh客户端密钥远程登录
测试之前生成公钥和私钥进行测试:
# 生成公钥和私钥, 默认存储在 ~/.ssh/目录下. id_rsa私钥, id_rsa.pub公钥
ssh-keygen
# 希望我的主机可以无密码连接其他主机(需要将公钥分发给其他主机)
ssh-copy-id -i ~/.ssh/id_rsa.pub user@ip
# 测试无密码连接是否成功
ssh user@ip
ssh -i 私钥位置 user@ip
import paramiko
def login_ssh_password(hostname, port, username, password, command):
# 实例化SSH客户端对象
with paramiko.SSHClient() as client:
#自动添加当前主机到远程服务器的known_hosts,远程连接不再询问yes/no
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 通过用户名和密码连接远程服务器
client.connect(
hostname=hostname,
port=port,
username=username,
password=password,
)
# 连接成功后执行的命令
stdin,stdout,stderr = client.exec_command(command)
# 获取命令执行的正确输出
# return stdin, stdout,stderr
return stdout.read().decode('utf-8')
def login_ssh_key(hostname, port, username, keyfile, command):
# 实例化SSH客户端对象
with paramiko.SSHClient() as client:
# 配置私人密钥文件位置
private = paramiko.RSAKey.from_private_key_file(keyfile)
#自动添加当前主机到远程服务器的known_hosts,远程连接不再询问yes/no
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 通过用户名和密码连接远程服务器
client.connect(
hostname=hostname,
port=port,
username=username,
password=private,
)
# 连接成功后执行的命令
stdin,stdout,stderr = client.exec_command(command)
# 获取命令执行的正确输出
# return stdin, stdout,stderr
return stdout.read().decode('utf-8')
if __name__ == '__main__':
# stdout = login_ssh_password(hostname='172.25.254.34',port=22,username='root',password='Asimov',command='uname')
# print(stdout)
stdout = login_ssh_key(hostname='172.25.254.34',port=22,username='root',keyfile='/home/kiosk/.ssh/id_rsa',command='uname')
print(stdout)
from_transport(cls,t)
客户端通道
put(localpath, remotepath, callback=None, confirm=True) 将本地文件上传到服务
器 参数confirm:
是否调用stat()方法检查文件状态,返
回ls -l的结果
get(remotepath, localpath, callback=None)
从服务器下载文件到本
地
mkdir() 在服务器上创建目录
remove() 在服务器上删除目录
rename() 在服务器上重命名目录
stat() 查看服务器文件状态
listdir() 列出服务器目录下的文件
具体的实例代码如下:
import paramiko
# 获取Transport实例
with
paramiko.Transport(('172.25.254.197', 22)) as tran:
# 连接SSH服务端,使用password
tran.connect(username="root", password='westos')
# # 或使用
# # 配置私人密钥文件位置
# private = paramiko.RSAKey.from_private_key_file('./id_rsa')
# # 连接SSH服务端,使用pkey指定私钥
# tran.connect(username="root", pkey=private)
# 获取SFTP实例
sftp = paramiko.SFTPClient.from_transport(tran)
# 设置上传的本地/远程文件路径
localpath = "/etc/passwd" remotepath = "/mnt/passwd"
# 执行上传动作
sftp.put(localpath, remotepath)
# # 执行下载动作
# sftp.get(remotepath, localpath)
命令 | 作用 | 举例 |
---|---|---|
主机名 | 通过名称识别资产的作用、位置等信息 | Nginx01、KVM、aliyun01 |
MAC地址 | 记录网卡的信息,可以作为主机的唯一标识 | 6e:40:08:f9:84:00 |
SN | 物理服务器、网络设备有唯一的资产标识 | J156R12 |
系统版本 | 查看服务器系统和具体的版本 | Redhat7.0、Centos7.0、Ubuntu12.04 |
服务器机型 | 查看主机或者服务器类型 | Dell R610、HP、DL580 |
为什么要获取这些信息
获取信息的Linux命令介绍
# apps/scanhost/models.py
class Server(models.Model):
"""服务器设备"""
sub_asset_type_choice = (
(0, 'PC服务器'),
(1, '刀片机'),
(2, '小型机'),
) created_by_choice = (
('auto', '自动添加'),
('manual', '手工录入'),
)
sub_asset_type = models.SmallIntegerField(choices=sub_asset_type_choice,
default=0, verbose_name="服务器类型")
created_by = models.CharField(choices=created_by_choice, max_length=32,
default='auto', verbose_name="添加方式")
hosted_on = models.ForeignKey('self', related_name='hosted_on_server',
blank=True, null=True, verbose_name="宿主机",
on_delete=models.CASCADE) # 虚拟机专用字段
IP = models.CharField('IP地址', max_length=30, default='')
MAC
= models.CharField('Mac地址', max_length=200, default='')
model = models.CharField(max_length=128, null=True, blank=True,
verbose_name='服务器型号')
hostname = models.CharField(max_length=128, null=True, blank=True,
verbose_name="主机名")
os_type = models.CharField('操作系统类型', max_length=64, blank=True,
null=True)
os_distribution = models.CharField('发行商', max_length=64, blank=True,
null=True)
os_release = models.CharField('操作系统版本', max_length=64, blank=True,
null=True)
def __str__(self):
return '%s-%s' % (self.id, self.hostname)
class Meta:
verbose_name = '服务器'
verbose_name_plural = "服务器"
python manage.py makemigrations
python manage.py migrate
# 创建超级用户用于后台登录
python manage.py createsuperuser
# devops\settings\base.py
scanhosts = [
# '127.0.0.1',
'172.25.254.0/24',]
# '47.92.255.98']
commands = {
'hostname': 'hostname',
'os_type': 'uname',
'os_distribution': 'dmidecode -s system-manufacturer',
'os_release': 'cat /etc/redhat-release',
'MAC': 'cat /sys/class/net/`[^vtlsb]`*/address',
}
import re
import telnetlib
import nmap
import paramiko
from django.http import HttpResponse
from django.shortcuts import render
# Create your views here.
from CMDB.settings import base
from apps.scanhost.models import Server
def get_active_hosts(hosts):
"""根据提供的网段或者IP返回存活的主机IP"""
# 实例化对象, portScanner()类用于实现对指定主机进行端口扫描
nm = nmap.PortScanner()
# 以指定方式扫描指定主机或网段的指定端口
result = nm.scan(hosts=hosts, arguments='-n ')
return nm.all_hosts()
def is_ssh_up(host, port=22, timeout=5):
# 实例化对象
tn = telnetlib.Telnet(host=host, port=port)
# read_until读取直到遇到了换行符或超时秒数。默认返回bytes类型,通过decode方法解码为字符串。
result = tn.read_until(b'\n', timeout=timeout).decode('utf-8')
# print(result) #SSH-2.0-OpenSSH_7.4
# 通过正则匹配且忽略大小写, 寻找是否ssh服务开启。
searchObj = re.search('ssh', result, re.I)
# 如果能匹配到内容, 说明ssh服务开启, 是Linux服务器.
if searchObj:
return True
else:
return False
def login_ssh_key(hostname, port, username, keyfile, command):
# 实例化SSH客户端对象
with paramiko.SSHClient() as client:
# 配置私人密钥文件位置
private = paramiko.RSAKey.from_private_key_file(keyfile)
# 自动添加当前主机到远程服务器的known_hosts,远程连接不再询问yes/no
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 通过用户名和密码连接远程服务器
client.connect(
hostname=hostname,
port=port,
username=username,
password=private,
)
# 连接成功后执行的命令
stdin, stdout, stderr = client.exec_command(command)
# 获取命令执行的正确输出
# return stdin, stdout,stderr
return stdout.read().decode('utf-8')
def scanhost(request):
# 访问所有要扫描的网段/IP
for host in base.scanhosts:
print("正在扫描%s......" % (host))
# 获取所有可以ping通的主机IP
active_hosts = get_active_hosts(hosts=host)
# 一次遍历判断ssh服务是否开启
for active_host in active_hosts:
if is_ssh_up(active_host):
server = Server()
# 设置IP地址
server.IP = active_host
# 执行指令
for attr, command in base.commands.items():
# attr ='hostname' , command = 'hostname'
# 存储主机名、操作系统.....指令执行的结果
result = login_ssh_key(active_host, 22, 'root', '/home/kiosk/.ssh/id_rsa', command)
setattr(server, attr, result)
server.save()
return HttpResponse('扫描成功')
# devops/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('scan/', scanhost)
]
# apps/scanhost/admin.py
# 可以在admin后台管理服务器信息
admin.site.register(Server)
运行项目python manage.py runserver 0.0.0.0:8000