python---CMDB配置管理数据库

Posted 山上有风景

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python---CMDB配置管理数据库相关的知识,希望对你有一定的参考价值。

前戏:项目目的

是一个运维自动化管理项目:

  为了减少人工干预,降低人员成本
  ---资产管理
  --操作管理

避免人员直接操作服务器,使用后台去统一操作

一:实现方式

(一)Agent基于shell命令实现(在服务器去上安装Agent,在服务器本机定时自动去获取信息,发送到数据库,然后后台获取数据进行处理)

注意:一般我们不会直接将数据直接传递到数据库,会将数据传递到API接口先进行处理,过滤,然后才会发送到数据库。

注意:数据是由服务器agent主动发送至API

 实现方案:

本地执行cmd命令。
方法一:os.system("命令")    不可以返回数据
方法二:subprocess模块,使用进程执行命令,可以获取到数据Popen("命令"),进程.stdout.read()<py2>或者直接getoutput("命令")<py3>
    def agent(self,cmd):
        import subprocess
        try:
            ret = subprocess.getoutput(cmd)
        except AttributeError:
            sub = subprocess.Popen(args=cmd,shell=True,stdout=subprocess.PIPE)
            sub.wait()
            ret = sub.stdout.read()
        return ret
python实现agent

优点:信息采集快,由服务器自己采集信息传递到API

缺点:每台服务器都必须安装Agent


 (二)SSH方法:使用paramiko模块,通过中控机服务器统一去获取指定服务器的信息。

paramiko模块了解

def ssh(self,cmd):
        import paramiko
        #1.创建SSH对象
        ssh = paramiko.SSHClient()
        #2.加上这句话不用担心选yes的问题,会自动选上
        #3.用ssh连接远程主机时,第一次连接时会提示是否继续进行远程连接,选择yes
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",password="密码")
        #执行命令,获取结果到标准输入\\出\\错误流中
        stdin,stdout,stderr = ssh.exec_command(cmd)
        #4.获取命令结果
        result = stdout.read()
        #5.关闭连接
        ssh.close()
paramiko实现远程命令执行(方法一:使用用户名,密码)
    def ssh(self,cmd):
        import paramiko
        #1.创建SSH对象
        ssh = paramiko.SSHClient()
        #2.加上这句话不用担心选yes的问题,会自动选上
        #用ssh连接远程主机时,第一次连接时会提示是否继续进行远程连接,选择yes
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        #3.获取私钥
        private_key = paramiko.RSAKey.from_private_key_file("文件:其中保存了私钥,用于解密")
        #4.通过私钥去连接远程服务器(前提是自己的公钥已经在对方的authorized_keys文件中,paramiko已实现)
        ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",pkey="私钥private_key")
        #5.执行命令,获取结果到标准输入\\出\\错误流中
        stdin,stdout,stderr = ssh.exec_command(cmd)
        #6.获取命令结果
        result = stdout.read()
        #7.关闭连接
        ssh.close()
        return result
paramiko使用私钥去登录远程服务器执行命令

优点:不需要为服务器安装agent等软件

缺点:速度慢,适用于服务器少得时候

 (三)saltstack:使用master对slave进行操作,基于列队实现(使用广)

# 1. 安装saltstack
#       rpm --import https://repo.saltstack.com/yum/redhat/6/x86_64/latest/SALTSTACK-GPG-KEY.pub
#
#
"""
        Master: yum install salt-master
       Master准备:
            a. 配置文件,监听本机IP
                vim /etc/salt/master
                interface: 本机IP地址
            b. 启动master
                /etc/init.d/salt-master start


        Slave:  yum install salt-minion
        Slave准备:
            a. 配置文件,连接那个master
                vim /etc/salt/minion
                master: 远程master地址
            b. 启动slave
                /etc/init.d/salt-minion start

2. 创建关系
    查看
    Master:salt-key -L
        Accepted Keys:
        Denied Keys:
        Unaccepted Keys:
            c1.com
            c2.com
            c3.com
        Rejected Keys:
    接受
    Master:salt-key -a c1.com
        Accepted Keys:
            c1.com
            c2.com
        Denied Keys:
        Unaccepted Keys:
            c3.com
        Rejected Keys:


3. 执行命令
    master:
        salt \'c1.com\' cmd.run  \'ifconfig\'

    import salt.client
    local = salt.client.LocalClient()
    result = local.cmd(\'c2.salt.com\', \'cmd.run\', [\'ifconfig\'])

"""
saltstack安装
    def salt(self,cmd):
        import subprocess
        result = subprocess.getoutput("Salt \'主机名\' cmd.run \'"+cmd+"\'")

        return result
中控机执行命令:subprocess执行salt
    def salt(self,cmd):

        import salt.client
        local = salt.client.LocalClient()
        result = local.cmd(self.hostname,\'cmd.run\',[cmd])
        return result[self.hostname]
中控机执行命令:salt.client

优点:快,开发成本低

缺点:依赖saltstack

(四)使用puppet(使用ruby写的,python不易扩展)

优点:自动汇报

缺点:ruby实现

 二:代码实现(客户端)

 

bin目录(含启动文件)

from src.script import client
import os,sys

BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASEDIR)

if __name__ == "__main__":
    client()
start.py启动文件

conf目录(含配置文件)

import os

BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


MODE = "Agent"  #SSH Salt

PLUGINS = {
    \'basic\':\'src.plugins.basic.BasicPlugin\',
    \'disk\':\'src.plugins.disk.DiskPlugin\',
    \'mem\': \'src.plugins.mem.MemPlugin\',
    # \'nic\': \'src.plugins.nic.NicPlugin\',
}

# 如果采用SSH方式,则需要配置SSH的KEY和USER
SSH_PRIVATE_KEY = "/home/auto/.ssh/id_rsa"
SSH_USER = "root"
SSH_PORT = 22

# 用于API认证的KEY
KEY = \'299095cc-1330-11e5-b06a-a45e60bec08b\'
# 用于API认证的请求头
AUTH_KEY_NAME = \'auth-key\'

ASSET_API = "http://127.0.0.1:8000/API/asset"


# Agent模式保存服务器唯一ID的文件
CERT_FILE_PATH = os.path.join(BASEDIR, \'conf\', \'cert\')

TEST_MODE = True
setting.py
...
cert含有主机名信息

lib目录(含有链接库)

class BaseResponse(object):
    def __init__(self):
        self.status = True
        self.message = None
        self.data = None
        self.error = None
response.py定义一种数据格式用于在客户端传递
import json as default_json
from .response import BaseResponse

def conv(obj):
    return obj.__dict__


class Json(object):
    @staticmethod
    def dumps(response):
        return default_json.dumps(response,default=conv)
serialize.py序列化上面的数据,将其变为可以在网络上传递的数据

log目录(记录日志)

client.py定义多种类,不同的方法与API交互
from conf import setting
from .client import *

def client():
    if setting.MODE == \'Agent\':  #"Agent"  #SSH Salt
        cli = AutoAgent()
    elif setting.MODE == "SSH":
        cli = AutoSSH()
    elif setting.MODE == "Salt":
        cli = AutoSalt()
    else:
        raise Exception("配置信息出错")
    cli.process()
script.py脚本,根据配置文件去选择实例化那种类,执行process方法获取数据

二级目录:plugins目录

from conf import setting
from lib.response import BaseResponse


def pack(hostname=None):
    response = {}

    for k,v in setting.PLUGINS.items():
        file_name,cls_name = v.rsplit(\'.\',maxsplit=1)
        pk = __import__(file_name,fromlist=True)
        if hasattr(pk,cls_name):
            obj = getattr(pk,cls_name)()
            response[k] = obj.execute(hostname)

    return response
__init__.py定义pack方法去依次获取数据
from conf import setting

class BasePlugin(object):
    def __init__(self):
        mode_list = [\'Agent\',\'Salt\',\'SSH\']
        self.mode = setting.MODE
        if self.mode not in mode_list:
            raise Exception("请选择正确的管理模式")

    def shell_cmd(self,cmd):
        if self.mode == "SSH":
            ret = self.ssh(cmd)
        elif self.mode == "Salt":
            ret = self.salt(cmd)
        else:
            ret = self.agent(cmd)
        return ret

    def execute(self,hostname=None):
        self.hostname = hostname
        #windows:systeminfo详细信息 ver版本号 linux:cat /etc/issue | grep Linux
        sys_ver = self.shell_cmd("ver")
        pat = "command not found"
        if pat in sys_ver: #是linux...还是继续判断吧,应该不需要了
            sys_ver = self.shell_cmd("head -n 1 /etc/issue")

        if "Linux" in sys_ver:
            return self.linux()
        elif "Windows" in sys_ver:
            return self.windows()
        else:
            raise Exception("只支持linux和windows平台")

    def linux(self):
        raise Exception("请重载linux函数")

    def windows(self):
        raise Exception("请实现windows方法")

    def write(self,output):
        import os
        with open(os.path.join(setting.BASEDIR,\'file\',"test.txt"),"w") as fp:
            fp.write(output)


    def agent(self,cmd):
        import subprocess
        try:
            ret = subprocess.getoutput(cmd)
        except AttributeError:
            sub = subprocess.Popen(args=cmd,shell=True,stdout=subprocess.PIPE)
            sub.wait()
            ret = sub.stdout.read()
        return ret

    def salt(self,cmd):
        import subprocess
        result = subprocess.getoutput("Salt \'主机名\' cmd.run \'"+cmd+"\'")

        return result
        # import salt.client
        # local = salt.client.LocalClient()
        # result = local.cmd(self.hostname,\'cmd.run\',[cmd])
        # return result[self.hostname]

    def ssh(self,cmd):
        import paramiko
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        private_key = paramiko.RSAKey.from_private_key_file("文件:其中保存了私钥,用于解密")
        ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",pkey="私钥private_key")
        stdin,stdout,stderr = ssh.exec_command(cmd)
        result = stdout.read()
        ssh.close()
        return result
base.py定义基类,实现部分方法
from .base import BasePlugin
import re
from conf import setting
import traceback
from lib.response import BaseResponse

class BasicPlugin(BasePlugin):
    def __init__(self):
        super(BasicPlugin, self).__init__()
        self.Basic_data = {}  # 存放我们获取的数据

    def windows(self):
        response = BaseResponse()
        try:
            # 获取主机名
            output = self.shell_cmd("hostname")
            self.Basic_data[\'hostname\'] = output

            output = self.shell_cmd("ver")
            #获取操作平台
            ret = re.search("(.*)\\s*\\[",output)
            if ret:
                self.Basic_data[\'os_platform\'] = ret.group(1)

            # 获取系统版本
            ret = re.search("\\[(.*)\\]",output)
            if ret:
                self.Basic_data[\'os_version\'] = ret.group(1)

            response.data = self.Basic_data
        except Exception as e:
            msg = "%s window memory plugins error: %s"
            response.status = False
            response.error = msg % (self.hostname, traceback.format_exc())  # traceback.format_exc()返回前面错误的信息
        return response

    def linux(self):
        # 获取返回的字符串
        output = self.shell_cmd("查看硬盘")
        # 进行正则匹配,放入Mem_data中
        return self.Basic_data
basic.py获取主板信息
from .base import BasePlugin
from conf import setting
import re,traceback
from lib.response import BaseResponse

class DiskPlugin(BasePlugin):
    def __init__(self):
        super(DiskPlugin, self).__init__()
        self.Disk_data = {\'Slot\': \'slot\', \'Raw Size\': \'capacity\', \'Inquiry\': \'model\', \'PD Type\': \'pd_type\'}

    def windows(self):
        response = BaseResponse()
        try:
            if setting.TEST_MODE == True:
                import os
                with open(os.path.join(setting.BASEDIR,\'file\',\'disk.out\'),"r") as fp:
                    output = fp.read()
            else:
                # 获取返回的字符串
                output = self.shell_cmd("wmic logicaldisk")

            response.data = self.parse(output)
        except Exception as e:
            msg = "%s window disk plugin error: %s"
            response.status = False
            response.error = msg % (self.hostname,traceback.format_exc())   #traceback.format_exc()返回前面错误的信息
        return response

    def linux(self):
        # 获取返回的字符串
        output = self.shell_cmd("查看硬盘")
        # 进行正则匹配,放入Mem_data中
        return self.Disk_data

    def parse(self,content):
        response = {}
        result = [] #模拟多台   使用4个\\n分割
        for row_line in content.split("\\n\\n\\n\\n"):
            result.append(row_line)
        for item in result:
            temp_dict = {}
            for row in item.split(\'\\n\'):
                if not row.strip():
                    continue
                if len(row.split(":")) != 2:
                    continue
                key,val = row.split(":")
                name = self.patter_match(key)
                if name:
                    if key == \'Raw Size\':
                        val = re.search("(\\d+\\.\\d+)",val.strip())
                        if not val:
                            val = 0
                        else:
                            val = val.group(1)
                    temp_dict[name] = val
            if temp_dict:
                response[temp_dict[\'slot\']] = temp_dict
        return response

    def patter_match(self,key):
        for k,v in self.Disk_data.items():
            if key.startswith(k):
                return v
        return False
disk.py获取硬盘信息
from .base import BasePlugin
from conf import setting
import traceback
from lib.response import BaseResponse

class MemPlugin(BasePlugin):
    def __init__(self):
        super(MemPlugin, self).__init__()
        self.Mem_data = {
            \'Size\': \'capacity\',
            \'Locator\': \'slot\',
            \'Type\': \'model\',
            \'Speed\': \'speed\',
            \'Manufacturer\': \'manufacturer\',
            \'Serial Number\': \'sn\',
        }

    def windows(self):
        response = BaseResponse()
        try:
            if setting.TEST_MODE == True:
                import os
                with open(os.path.join(setting.BASEDIR, \'file\', \'memory.out\'), "r") as fp:
                    output = fp.read()
            else:
                # 获取返回的字符串
                output = self.shell_cmd("wmic memorychip")

            response.data = self.parse(output)
        except Exception as e:
            msg = "%s window memory plugins error: %s"
            response.status = False
            response.error = msg % (self.hostname, traceback.format_exc())  # traceback.format_exc()返回前面错误的信息
        return response

    def linux(self):
        # 获取返回的字符串
        output = self.shell_cmd("查看内存")
        # 进行正则匹配,放入Mem_data中
        return self.Mem_data


    def parse(self,content):
        response = {}
        result = [] #模拟多台   使用4个\\n分割
        for row_line in content.split("Memory Device"):
            result.append(row_line)
        for item in result:
            for row in item.split(\'\\n\\t\'):
                if not row.strip():
                    continue
                if len(row.split(":")) != 2:
                    continue
                key,val = row.split(":")
                name = self.patter_match(key)
                if name:
                    response[name] = val
        return response

    def patter_match(self,key):
        for k,v in self.Mem_data.items():
            if key.startswith(k):
                return v
        return False
mem.py获取内存信息

file文件目录(含有测试的文件,文件包含各种命令下的数据)

Enclosure Device ID: 32
Slot Number: 0
Drive\'s postion: DiskGroup: 0, Span: 0, Arm: 0
Enclosure position: 0
Device Id: 0
WWN: 5000C5007272C288
Sequence Number: 2
Media Error Count: 0
Other Error Count: 0
Predictive Failure Count: 0
Last Predictive Failure Event Seq Number: 0
PD Type: SAS
Raw Size: 279.396 GB [0x22ecb25c Sectors]
Non Coerced Size: 278.896 GB [0x22dcb25c Sectors]
Coerced Size: 278.875 GB [0x22dc0000 Sectors]
Firmware state: Online, Spun Up
Device Firmware Level: LS08
Shield Counter: 0
Successful diagnostics completion on :  N/A
SAS Address(python--CMDB项目

#VSCode保存插件配置并使用 gist 管理代码片段

SpringCloud系列四:Eureka 服务发现框架(定义 Eureka 服务端Eureka 服务信息Eureka 发现管理Eureka 安全配置Eureka-HA(高可用) 机制Eur(代码片段

YASnippet - emacs 的代码片段管理工具

使用 Git 来管理 Xcode 中的代码片段

VSCode自定义代码片段11——vue路由的配置