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常常被认为是构建其它ITIL流程的基础而优先考虑,ITIL项目的成败与是否成功建立CMDB有非常大的关系。
70%~80%的IT相关问题与环境的变更有着直接的关系。实施变更管理的难点和重点并不是工具,而是流程。即通过一个自动化的、可重复的流程管理变更,使得当变更发生的时候,有一个标准化的流程去执行,能够预测到这个变更对整个系统管理产生的影响,并对这些影响进行评估和控制。而变更管理流程自动化的实现关键就是CMDB。
CMDB工具中至少包含这几种关键的功能:整合、调和、同步、映射和可视化。
  • 整合是指能够充分利用来自其他数据源的信息,对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
View Code

自定义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)
boto3 get ec2

 

b、

 

 

参考链接:https://www.cnblogs.com/wupeiqi/articles/6192986.html

坑一,看有没有唯一标识,区分资产

IP会变,SN号虚拟机都一样

用主机名,规则:主机名不能重复,可以修改,把主机名写到文件里面,再根据主机名上报资产

 

分成三部分:

1. 五种采集资产方式
唯一标识

2. API
API验证(tornado源码,加密cookie+时间限制+访问记录)
数据库表结构

3. 后台管理
告别CURD,公共组件(前端+后端配置)

posted @ 2020-03-28 10:24  运维SRE  阅读(207)  评论(0)    收藏  举报