CMDB开发
浅谈ITIL
TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国政府部门CCTA(Central Computing and Telecommunications Agency)在20世纪80年代末制订,现由英国商务部OGC(Office of Government Commerce)负责管理,主要适用于IT服务管理(ITSM)。ITIL为企业的IT服务管理实践提供了一个客观、严谨、可量化的标准和规范。
1、事件管理(Incident Management)
事故管理负责记录、归类和安排专家处理事故并监督整个处理过程直至事故得到解决和终止。事故管理的目的是在尽可能最小地影响客户和用户业务的情况下使IT系统恢复到服务级别协议所定义的服务级别。
目标是:在不影响业务的情况下,尽可能快速的恢复服务,从而保证最佳的效率和服务的可持续性。事件管理流程的建立包括事件分类,确定事件的优先级和建立事件的升级机制。
2、问题管理(Problem Management)
问题管理是指通过调查和分析IT基础架构的薄弱环节、查明事故产生的潜在原因,并制定解决事故的方案和防止事故再次发生的措施,将由于问题和事故对业务产生的负面影响减小到最低的服务管理流程。与事故管理强调事故恢复的速度不同,问题管理强调的是找出事故产生的根源,从而制定恰当的解决方案或防止其再次发生的预防措施。
目标是:调查基础设施和所有可用信息,包括事件数据库,来确定引起事件发生的真正潜在原因,一起提供的服务中可能存在的故障。
3、配置管理(Configuration Management)
配置管理是识别和确认系统的配置项,记录和报告配置项状态和变更请求,检验配置项的正确性和完整性等活动构成的过程,其目的是提供IT基础架构的逻辑模型,支持其它服务管理流程特别是变更管理和发布管理的运作。
目标是:定义和控制服务与基础设施的部件,并保持准确的配置信息。
4、变更管理(Change Management)
变更管理是指为在最短的中断时间内完成基础架构或服务的任一方面的变更而对其进行控制的服务管理流程。变更管理的目标是确保在变更实施过程中使用标准的方法和步骤,尽快地实施变更,以将由变更所导致的业务中断对业务的影响减小到最低。
目标是:以受控的方式,确保所有变更得到评估、批准、实施和评审。
5、发布管理(Release Management)
发布管理是指对经过测试后导入实际应用的新增或修改后的配置项进行分发和宣传的管理流程。发布管理以前又称为软件控制与分发。
目标是:在实际运行环境的发布中,交付、分发并跟踪一个或多个变更。
实际工作场景中自动化工具举例:

CMDB
CMDB --Configuration Management Database 配置管理数据库, CMDB存储与管理企业IT架构中设备的各种配置信息,它与所有服务支持和服务交付流程都紧密相联,支持这些流程的运转、发挥配置信息的价值,同时依赖于相关流程保证数据的准确性。
- 整合是指能够充分利用来自其他数据源的信息,对CMDB中包含的记录源属性进行存取,将多个数据源合并至一个视图中,生成连同来自CMDB和其他数据源信息在内的报告;
- 调和能力是指通过对来自每个数据源的匹配字段进行对比,保证CMDB中的记录在多个数据源中没有重复现象,维持CMDB中每个配置项目数据源的完整性;自动调整流程使得初始实施、数据库管理员的手动运作和现场维护支持工作降至最低;
- 同步指确保CMDB中的信息能够反映联合数据源的更新情况,在联合数据源更新频率的基础上确定CMDB更新日程,按照经过批准的变更来更新 CMDB,找出未被批准的变更;
- 应用映射与可视化,说明应用间的关系并反应应用和其他组件之间的依存关系,了解变更造成的影响并帮助诊断问题。
目前CMDB资产管理的实现有如下方式:
1、SSH (Paramiko类 、ansible 、fabic)
基于CMDB中控机和SSH对远程服务器执行命令实现
优点:无agent
缺点:慢
不适用服务器多的情况

1 import paramiko 2 3 # 创建SSH对象 4 ssh =paramiko.SSHClient() 5 # 允许连接不在know_hosts文件中的主机 6 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 7 # 连接服务器 8 ssh.connect(hostname='c1.salt.com', port=22, username='wupeiqi', password='123') 9 10 # 执行命令 11 stdin, stdout, stderr =ssh.exec_command('df') 12 # 获取命令结果 13 result =stdout.read() 14 15 # 关闭连接 16 ssh.close()
2、依赖第三方工具(SaltStack)
基于SaltStack的master上的pillar以及远程执行命令实现
比SSH快,开发成本低
但是还是有salt agent

1 from salt import client 2 local = client.LocalClient() 3 result = local.cmd('*', 'cmd.run', ['whoami'])
3、Puppet
基于Puppet的factor和report功能实现,老牌公司
ruby写的
puppet报表功能,默认slave每三十分钟要汇报一次自己状态

1 puppet中默认自带了5个report,放置在【/usr/lib/ruby/site_ruby/1.8/puppet/reports/】路径下。如果需要执行某个report,那么就在puppet的master的配置文件中做如下配置: 2 3 ######################## on master ################### 4 /etc/puppet/puppet.conf 5 [main] 6 reports = store #默认 7 #report = true #默认 8 #pluginsync = true #默认 9 10 11 ####################### on client ##################### 12 13 /etc/puppet/puppet.conf 14 [main] 15 #report = true #默认 16 17 [agent] 18 runinterval = 10 19 server = master.puppet.com 20 certname = c1.puppet.com 21 22 如上述设置之后,每次执行client和master同步,就会在master服务器的 【/var/lib/puppet/reports】路径下创建一个文件,主动执行:puppet agent --test
自定义factor示例
1 在 /etc/puppet/modules 目录下创建如下文件结构: 2 3 modules 4 └── cmdb 5 ├── lib 6 │ └── puppet 7 │ └── reports 8 │ └── cmdb.rb 9 └── manifests 10 └── init.pp 11 12 ################ cmdb.rb ################ 13 # cmdb.rb 14 require 'puppet' 15 require 'fileutils' 16 require 'puppet/util' 17 18 SEPARATOR = [Regexp.escape(File::SEPARATOR.to_s), Regexp.escape(File::ALT_SEPARATOR.to_s)].join 19 20 Puppet::Reports.register_report(:cmdb) do 21 desc "Store server info 22 These files collect quickly -- one every half hour -- so it is a good idea 23 to perform some maintenance on them if you use this report (it's the only 24 default report)." 25 26 def process 27 certname = self.name 28 now = Time.now.gmtime 29 File.open("/tmp/cmdb.json",'a') do |f| 30 f.write(certname) 31 f.write(' | ') 32 f.write(now) 33 f.write("\r\n") 34 end 35 36 end 37 end 38 39 40 ################ 配置 ################ 41 /etc/puppet/puppet.conf 42 [main] 43 reports = cmdb 44 #report = true #默认 45 #pluginsync = true #默认
1 $LOAD_PATH.unshift(File.dirname(__FILE__)) unless $LOAD_PATH.include?(File.dirname(__FILE__)) 2 require "rubygems" 3 require 'pp' 4 require 'json' 5 require 'utils' 6 7 def dmi_get_ram(cmd) 8 9 ram_slot = [] 10 11 key_map = { 12 'Size' => 'capacity', 13 'Serial Number' => 'sn', 14 'Type' => 'model', 15 'Manufacturer' => 'manufactory', 16 'Locator' => 'slot', 17 } 18 19 output = Utils.facter_exec(cmd) 20 devices = output.split('Memory Device') 21 22 devices.each do |d| 23 next if d.strip.empty? 24 segment = {} 25 d.strip.split("\n\t").each do |line| 26 key, value = line.strip.split(":") 27 if key_map.has_key?(key.strip) 28 if key.strip == 'Size' 29 segment[key_map['Size']] = value.chomp("MB").strip.to_i / 1024.0 # unit GB 30 else 31 segment[key_map[key.strip]] = value ? value.strip : '' 32 end 33 end 34 end 35 36 ram_slot.push(segment) unless segment.empty? 37 end 38 39 return ram_slot 40 41 end 42 43 Facter.add("ram") do 44 confine :kernel => "Linux" 45 setcode do 46 47 ram_slot = [] 48 cmd = "dmidecode -q -t 17 2>/dev/null" 49 ram_slot = dmi_get_ram(cmd) 50 51 JSON.dump(ram_slot) 52 53 end 54 end 55 56 57 Facter.add("ram") do 58 confine :kernel => 'windows' 59 setcode do 60 61 ram_slot = [] 62 63 if Facter.value(:manufacturer) =~ /.*HP.*/i 64 cli = 'C:\cmdb_report\dmidecode.exe' 65 cmd = "#{cli} -q -t 17" 66 ram_slot = dmi_get_ram(cmd) if File.exist?(cli) 67 68 else 69 70 require 'facter/util/wmi' 71 Facter::Util::WMI.execquery("select * from Win32_PhysicalMemory").each do | item | 72 73 if item.DeviceLocator 74 slot = item.DeviceLocator.strip 75 else 76 slot = '' 77 end 78 79 if item.PartNumber 80 model = item.PartNumber.strip 81 else 82 model = '' 83 end 84 85 if item.SerialNumber 86 sn = item.SerialNumber.strip 87 else 88 sn = '' 89 end 90 91 if item.Manufacturer 92 manufactory = item.Manufacturer.strip 93 else 94 manufactory = '' 95 end 96 97 ram_slot.push({ 98 'capacity' => item.Capacity.to_i / (1024**3), # unit GB 99 'slot' => slot, 100 'model' => model, 101 'sn' => sn, 102 'manufactory' => manufactory, 103 }) 104 105 end 106 end 107 108 JSON.dump(ram_slot) 109 110 end 111 end
4、Agent
基于shell命令实现
优点:快
缺点:有agent


对于Agent的版本的实现思路:
- Agent采集硬件资产
- API提供相关处理的接口
- 管理平台为用户提供可视化操作
5. 云端cli,云服务商提供接口,json收集处理 or python sdk (boto)
a、boto获取各类资源信息
裁剪获取数据,存储需要字段
1 import boto3 2 import re 3 from libs.db_context import DBContext 4 from models.server import Server, ServerDetail, AssetConfigs, model_to_dict 5 from opssdk.operate import MyCryptV2 6 from libs.web_logs import ins_log 7 import fire 8 9 10 class Ec2Api(): 11 def __init__(self, access_id, access_key, region, default_admin_user): 12 self.access_id = access_id 13 self.access_key = access_key 14 self.region = region 15 self.client = boto3.client('ec2', region_name=self.region, aws_access_key_id=self.access_id, 16 aws_secret_access_key=self.access_key) 17 self.default_admin_user = default_admin_user 18 self.account = 'AWS' # 标记AWS机器 19 self.state = 'auto' # auto状态:标记为自动获取的机器 20 21 def get_response(self): 22 """ 23 获取返回 24 :return: 25 """ 26 try: 27 response = self.client.describe_instances() 28 return response 29 except Exception as e: 30 ins_log.read_log('error', e) 31 # print(e) 32 return False 33 34 def get_server_info(self): 35 36 response = self.get_response() 37 if not response: 38 ins_log.read_log('error', 'Not fount response, please check your access_id and access_key...') 39 # print('[Error]: Not fount response, please check your access_id and access_key...') 40 return False 41 42 ret = response['Reservations'] 43 server_list = [] 44 if ret: 45 for r in ret: 46 for i in r['Instances']: 47 asset_data = dict() 48 try: 49 # asset_data['hostname'] = i.get('Tags')[0].get('Value') #这是旧的 50 # AWS里面支持多个标签,我们只要Key为Name的 51 tag_list = i.get('Tags') 52 # tag_list = [i for i in tag_list if i["Key"] == "Name"] 53 tag_list = filter(lambda x: x["Key"] == "Name", tag_list) 54 asset_data['hostname'] = list(tag_list)[0].get('Value') 55 56 except (KeyError, TypeError, IndexError): 57 asset_data['hostname'] = i.get('InstanceId', 'Null') # 拿不到hostnameg给instance_id 58 59 asset_data['region'] = i['Placement'].get('AvailabilityZone', 'Null') 60 asset_data['instance_id'] = i.get('InstanceId', 'Null') 61 asset_data['instance_type'] = i.get('InstanceType', 'Null') 62 asset_data['instance_state'] = i['State'].get('Name', '') 63 asset_data['private_ip'] = i.get('PrivateIpAddress', 'Null') 64 asset_data['public_ip'] = i.get('PublicIpAddress', asset_data['private_ip']) # 没有公网就给私网IP 65 # print(asset_data) 66 server_list.append(asset_data) 67 68 return server_list 69 70 def sync_cmdb(self): 71 """ 72 写入CMDB 73 :return: 74 """ 75 76 server_list = self.get_server_info() 77 if not server_list: 78 ins_log.read_log('info', 'Not fount server info...') 79 # print('Not Fount Server Info') 80 return False 81 with DBContext('w') as session: 82 for server in server_list: 83 ins_log.read_log('info', '资产信息:{}'.format(server)) 84 ip = server.get('public_ip') 85 private_ip = server.get('private_ip') 86 instance_id = server.get('instance_id', 'Null') 87 hostname = server.get('hostname', instance_id) 88 if not hostname.strip(): 89 hostname = instance_id 90 if re.search('syncserver', hostname): 91 hostname = '{}_{}'.format(hostname, private_ip) 92 93 region = server.get('region', 'Null') 94 instance_type = server.get('instance_type', 'Null') 95 instance_state = server.get('instance_state', 'Null') 96 # AWS=接口没看到CPU这类信息 97 cpu = server.get('cpu', 'Null') # CPU型号 98 cpu_cores = server.get('cpu_cores', 'Null') 99 memory = server.get('memory', 'Null') 100 disk = server.get('disk', 'Null') 101 os_type = server.get('os_type', 'Null') 102 os_kernel = server.get('os_kernel', 'Null') 103 sn = server.get('sn', 'Null') 104 105 exist_hostname = session.query(Server).filter(Server.hostname == hostname).first() 106 # exist_ip = session.query(Server).filter(Server.ip == ip).first() 107 if exist_hostname: 108 session.query(Server).filter(Server.hostname == hostname).update( 109 {Server.ip: ip, Server.public_ip: ip, Server.private_ip: private_ip, Server.idc: 'AWS', 110 Server.region: region}) 111 112 else: 113 new_server = Server(ip=ip, public_ip=ip, private_ip=private_ip, hostname=hostname, port=22, 114 idc=self.account, 115 region=region, 116 state=self.state, admin_user=self.default_admin_user) 117 session.add(new_server) 118 119 exist_ip = session.query(ServerDetail).filter(ServerDetail.ip == ip).first() 120 if exist_ip: 121 session.query(ServerDetail).filter(ServerDetail.ip == ip).update( 122 {ServerDetail.instance_id: instance_id, ServerDetail.instance_type: instance_type, 123 ServerDetail.instance_state: instance_state}) 124 else: 125 new_serve_detail = ServerDetail(ip=ip, instance_id=instance_id, instance_type=instance_type, 126 instance_state=instance_state, cpu=cpu, cpu_cores=cpu_cores, 127 memory=memory, disk=disk, os_type=os_type, os_kernel=os_kernel, 128 sn=sn) 129 session.add(new_serve_detail) 130 session.commit() 131 132 def test_auth(self): 133 """ 134 测试接口权限等信息是否异常 135 :return: 136 """ 137 response = self.client.describe_instances() 138 return response 139 140 141 def get_configs(): 142 """ 143 get id / key / region info 144 :return: 145 """ 146 147 aws_configs_list = [] 148 with DBContext('r') as session: 149 aws_configs_info = session.query(AssetConfigs).filter(AssetConfigs.account == 'AWS', 150 AssetConfigs.state == 'true').all() 151 for data in aws_configs_info: 152 data_dict = model_to_dict(data) 153 data_dict['create_time'] = str(data_dict['create_time']) 154 data_dict['update_time'] = str(data_dict['update_time']) 155 aws_configs_list.append(data_dict) 156 return aws_configs_list 157 158 159 def main(): 160 """ 161 从接口获取已经启用的配置 162 :return: 163 """ 164 165 mc = MyCryptV2() 166 aws_configs_list = get_configs() 167 if not aws_configs_list: 168 ins_log.read_log('error', '没有获取到AWS资产配置信息,跳过') 169 return False 170 for config in aws_configs_list: 171 access_id = config.get('access_id') 172 access_key = mc.my_decrypt(config.get('access_key')) # 解密后使用 173 region = config.get('region') 174 default_admin_user = config.get('default_admin_user') 175 176 obj = Ec2Api(access_id, access_key, region, default_admin_user) 177 obj.sync_cmdb() 178 179 180 # def test(): 181 # obj = Ec2Api('','','us-east-1', '') 182 # obj.sync_cmdb() 183 184 185 if __name__ == '__main__': 186 fire.Fire(main)
b、
参考链接:https://www.cnblogs.com/wupeiqi/articles/6192986.html
坑一,看有没有唯一标识,区分资产
IP会变,SN号虚拟机都一样
用主机名,规则:主机名不能重复,可以修改,把主机名写到文件里面,再根据主机名上报资产
分成三部分:
1. 五种采集资产方式
唯一标识
2. API
API验证(tornado源码,加密cookie+时间限制+访问记录)
数据库表结构
3. 后台管理
告别CURD,公共组件(前端+后端配置)
浙公网安备 33010602011771号