我们如何对14,000个对象进行网络监控

我们有14,000个对象,包括zabbix,api,python和不愿手动添加对象的对象。 重点介绍了网络管理员如何通过自动添加网络节点来实施监视,以及有关我必须经历的痛苦。

本文的重点是缺乏python经验的网络工程师。 在无需手动更新整个对象群的情况下,帮助实现自动化监视并改善生活和工作质量。



长话短说,我们已经建立了一个监控


你好 我叫Alexander Prokhorov,我们和我们部门的网络工程师团队一起在ITX5#网络中工作。 我们部门开发网络基础架构,网络监控和自动化。 是的,所有与数据传输有关的东西。



我将监视系统分为5个子任务:

  • 下载主数据
  • 获取有关对象状态的信息
  • 触发器和警报
  • 报告中
  • 可视化

在本文中,我们将分享我们如何将监控与公司中的主数据集成在一起。

我们拥有14,000个零售物业,我们要解决的第一个任务是确定不可访问的物品,物品的数量和地理分布。

监视在Zabbix上完成。 简而言之,为什么-幸运和遗产。 其余的:

长话短说。
这一切都始于在桌子下方的计算机上安装监控程序...

当我在2013年为公司工作时,我们没有网络监控,尽管当时的网络很大,大约有4000个对象。 我们从类似雪崩般的应用程序,用户或其他部门的接收中了解到大量(并非如此)下降。



第一个安装了Zabbix 1.8,这是一种越来越流行的产品(时尚,现代,青年),重量轻且价格低廉,可以在大型社区中安装开源。 我们很幸运有这个选择。

没有安装资源,没有人要求。 目前尚不清楚这是否会奏效;没有人有任何实施经验。 但是我们需要将其安装在“桌子下面”的计算机上。 冗余级别-UPS。

安装后的主要问题是如何将所有对象(4k!)倒入监视中,同时设法制作已在Remedy中挂起的应用程序。 Zabbix已经支持使用主机上的数据导入/导出xml。 对象的数量很大,没有必要为该对象创建单独的组(并且它没有出现),因此决定将对象路由器上传为网络节点。 更多的网络设备未得到控制(已经管理)。

完成了使用网络主机(excel中的IPAM)对文件的解析,并将其重新格式化为xml,Zabbix同意将其消化。 这不是第一次,而是所有主机都已加载,每三个月执行一次更新,删除关闭的对象并添加新的对象。 随着时间的流逝,事实证明,Zabbix成为我们的主要且唯一的信息来源,也是有关设备可用性以及最重要的是大规模下降的热线电话。 这样一来,即使在晚上,热线也可以了解大规模事故的发生,并通过电话唤醒工程师(顺便说一句,当没人知道它时,它更容易生活)。 并非总是办公室晚上停电,UPS才能充分维护对我们网络的监视。 在某个时候,我们开始进行备份。 因为 监视是不稳定且断断续续的,两家公司决定只组织一个专门负责此任务的小组。 很快,她开始实施集中式的Zabbix,它不仅检查网络。

我们的旧Zabbix仍然住在桌子底下。 添加一定数量的项目后,数据库开始变慢,网络,队列和轮询延迟都增加了,所以很快我不得不再获得两台计算机作为代理,并将它们全部放在十字架上,这样真正的保留(以及桌子底下的地方就完了) 。 支持其中的生命,以便快速添加某种自定义参数来监视和观察它。 对于其余的,我们转移到一个集中的。

集中监控负责所有IT设备-路由器,服务器,收银机,终端。 过了一会儿,他长满了很多东西。 要访问宝贵的可访问性信息,您必须稍等片刻,甚至可能要喝咖啡。 此外,对于越来越具体和自定义的网络监控,我们有越来越多的要求。 当时有一些使用该系统的经验,我们决定从头开始做旧的实现,但是正确的是-以zabbix.noc.x5.ru为名。它位于数据中心,由我们自己执行必要的功能。 关于本介绍和文章的主体。

Zabbix版本是3.0 LTS 。 仅在LTS版本内更新。

配置-4个虚拟机:服务器+数据库,代理,代理,Web

通过资源,我们尝试遵循zabbix.com上的建议进行大型实施。

我们面临的第一个问题是将网络节点自动添加到监视中。 定期发现立即消失。 我们的网络范围在10/8超级网络的所有开放空间中。 必须轮询许多地址,并且自动发现将占用大量系统资源,但不能解决添加有关对象的非技术信息的问题。

如何添加对象


解决方案是使用外部脚本添加对象。 在公司使用的SAP中找到有关贸易对象的主数据。 无法直接从SAP上同步访问Web服务以上传数据,对所有对象的请求花费了相当长的时间。 进行了异步调用。 恰好在午夜,将从SAP的全部导出以XML的形式添加到ftp中,并且在白天, XML的最新版本与之不同。

Zabbix使用Zabbix API加载数据,并通过Python与之交互。

在第一阶段,确定了我们创建对象所需的数据集。 我们将所有获得的符号用于系统中对象的正确分类或进一步的便利。 这些标志包括:

  • IP地址 -创建监视接口的最重要字段
  • SAP ID-我们有一个唯一的对象标识符
  • 状态 -打开/关闭
  • 名称 -对象的名称或编号,用户在联系时经常使用
  • 位置 -实际地址
  • 电话 -联系电话
  • -该组组是根据对象的类型和位置形成的



sap-sync.py模块


SAP中的XML
<?xml version="1.0" encoding="ISO-8859-1"?> <werks> <WERKS>1234</WERKS> <NAME1>4321-</NAME1> <PLANT_IP>192.168.1.50</PLANT_IP> <REGION>31</REGION> <PSTLZ>308580</PSTLZ> <CITY1>.</CITY1> <CITY2> .</CITY2> <STREET> .</STREET> <HOUSE_NUM1>1</HOUSE_NUM1> <TEL_NUMBER>(999)777-77-77</TEL_NUMBER> <BRANCH>CH</BRANCH> <REGION>CH_MSK</REGION> <REGION_NAME> </REGION_NAME> <FORMAT>CH_MSK_D</FORMAT> <STATUS> </STATUS> </werks> 


sap-sync.py
 #!/usr/bin/python3 import sys, os, getopt, ipaddress from datetime import datetime as dt from zabbix.api import ZabbixAPI import xml.etree.cElementTree as et from report import report import existhost def ping(ip): if os.system('ping -c 2 -W 1 %s > /dev/null'%ip) == 0: return True else: return False def main(argv): global opath try: opts, args = getopt.getopt(argv,"hp:",["path="]) except getopt.GetoptError: print('sync-sap-chg.py -p <path>') sys.exit(2) for opt, arg in opts: if opt == '-h': print('sync-sap-chg.py -p <path>') sys.exit() elif opt in ("-p", "--path"): opath = arg def asynchronization(file, reportdata): zapi = ZabbixAPI(url='http://z.noc.x5.ru', user='user', password='pwd') #,    left_kidney = '17855' right_kidney = '17856' f_type={'S':'Super','D':'DK','H':'Giper','A':'DK'} format={'D':'13','S':'14','H':'12','A':'13'} region={'CT':'21','UR':'19','SZ':'17',~omit~} try: #  XML tree = et.ElementTree(file=file) root = tree.getroot() for werks in root.iter('werks'): #     Zabbix interfaces=[{ 'main':'1', 'type':'2', 'useip':'1', 'port':'161' }] shop={ 'inventory':{}, 'interfaces':interfaces, 'groups':[], 'templates':[{'templateid':'10194'}], 'inventory_mode':'1' } #,     XML di = { 'WERKS':'', ~omit~, 'STATUS':'Object Opened' } #    for item in werks: di[item.tag] = item.text #  SAPID   if item.tag == 'WERKS': n_proxy = ''.join(filter(lambda x: x.isdigit(), item.text)) # IP    .   /26 try: ipaddress.ip_interface('%s/%s'%(di['PLANT_IP'].strip(),26)) ip_chk = True except: ip_chk = False # ,   IP   if (di['PLANT_IP'] != '1.1.1.1') and ip_chk: #    if di['FORMAT'][-1] in ['D','A','S','H']: shop['inventory']['alias']=di['WERKS'] shop['inventory']['name']=di['NAME1'] shop['inventory']['poc_1_phone_a']=di['TEL_NUMBER'] shop['inventory']['location']=di['STREET'] #    if (di['FORMAT'][-1] in ['H']): shop['host']=f_type[di['FORMAT'][-1]]+di['WERKS'] shop['interfaces'][0]['ip']=str(ipaddress.ip_interface('%s/%s'%(di['PLANT_IP'].strip(),24)).network[1]) # ID     shop['templates'][0]['templateid']='29529' elif (di['FORMAT'][-1] in ['S']): ~omit~ #For D balance in proxies if int(n_proxy[-1]) % 2 == 0: shop['proxy_hostid'] = right_kidney else: shop['proxy_hostid'] = left_kidney # inventory   IP shop['inventory']['oob_ip']=shop['interfaces'][0]['ip'] #    format  region shop['groups']=[{'groupid':'9'}] shop['groups'].append({'groupid':format[di['FORMAT'][-1]]}) shop['groups'].append({'groupid':region[di['FORMAT'][:2]]}) #   if di['STATUS'] == ' ': shop['status']='0' else: shop['status']='1' #   T = dt.date(dt.now()).strftime("%d %B %Y") shop['inventory']['date_hw_decomm'] = T #      Zabbix hostid = existhost.exist(shop['host']) ip = shop['interfaces'][0]['ip'] #  - ! if hostid == 0 and shop['status'] == '0' and ping(ip): zapi.host.create(shop) reportdata['new'].append(di['WERKS']) #   - ! elif hostid != 0 and di['PLANT_IP'] != '1.1.1.1': #  HOSTID  shop['hostid']=hostid #     shop.pop('interfaces') zapi.host.update(shop) reportdata['update'].append(di['WERKS']) #     os.system('mv %s /mnt/ftp/old_data/'%(file)) except: reportdata['error'].append(di['WERKS']) report('   %s.   :%s'%(file, str(reportdata['error'])), 'SAP Sync Failed!') sys.exit() def checking(path): files = os.listdir(path) files.sort() reportdata ={'new':[], 'update':[], 'error':[]} for file in files: asynchronization(path+file, reportdata) if files != []: report(' %s  ! \n  %s . \n  sap:\n %s. \n  %s . \n  sap:\n %s'%(str(files), len(reportdata['new']), str(reportdata['new']), len(reportdata['update']), str(reportdata['update'])), 'SAP Sync Succeed!') if __name__ == "__main__": main(sys.argv[1:]) checking(opath) 


该模块的主要目的是为Zabbix API进行XML解析和JSON转换。 在这种情况下,使用Python字典非常方便,因为 无需额外格式化它们-使用zabbix.api模块, 只需为他提供具有正确结构的字典即可。 JSON的结构如下所示:

 { 'host': 'Hostname' 'groups': [...] 'interfaces': [{},{},{}] 'inventory': {} 'templates': [{},{},{}] 'inventory_mode': '1' 'proxy_hostid': 'INT' 'status': '0' } [] -  {} -  

在我们具有SAP中IP地址的字段中,存储了服务器的地址,而不是路由器的地址,但是使用ipaddress模块,我们考虑了第一个子网地址,在我们的情况下,它始终是路由器。

 str(ipaddress.ip_interface('%s/%s'%(di['PLANT_IP'].strip(),24)).network[1]) 

上次成功更新的日期记录在库存中 ,在有争议的情况下,这有助于了解信息在系统中的相关性。

 #   T = dt.date(dt.now()).strftime("%d %B %Y") shop['inventory']['date_hw_decomm'] = T 

然后,在库存数据中查看有关更新日期的统计信息非常方便:



最重要的字段STATUS“打开” )已添加并开始受到监视,而其他任何状态都将停用主机。 我们不会为了保留历史数据和统计信息而删除对象。

测试之后,我必须添加ping函数,以便在初始添加之前检查主机是否可访问,因为 在实践中, “开放”状态开始出现,虽然还不是很开放,但是几乎是开放的。

 def ping(ip): if os.system('ping -c 2 -W 1 %s > /dev/null'%ip) == 0: return True else: return False 

以前,Zabbix API具有host.exist函数,但是在新版本中,它与host.get结合在一起。 如果该节点存在,那么请求将在zabbix数据库中返回hostid 。 如果未找到,则返回0。为了进行验证,我现在必须添加existhost ,但实际上它是host.get

存在主机
 #!/usr/bin/python3 import sys, getopt from zabbix.api import ZabbixAPI def main(argv): global aname try: opts, args = getopt.getopt(argv,"hn:",["name="]) except getopt.GetoptError: print('existhost.py -n <name>') sys.exit(2) for opt, arg in opts: if opt == '-h': print('existhost.py -n <name>') sys.exit() elif opt in ("-n", "--name"): aname = arg def exist(name): zapi = ZabbixAPI(url='http://z.noc.x5.ru/', user='user', password='pwd') hostget = zapi.host.get(search={'name':'%s'%name}, output='hostid') if hostget == []: return 0 else: return int(hostget[0]['hostid']) if __name__ == "__main__": main(sys.argv[1:]) print(exist(aname)) 


最后,我们收集有关已完成工作的信息,将其添加到日志,报告中,并将处理后的文件移动到OLD中的存储库中。

report.py
 #!/usr/bin/python3 # -*- coding: utf-8 -*- import smtplib, sys from email.mime.text import MIMEText def report(message, subject): me = 'zbx-scripts@x5.ru' you = 'mail@x5.ru' smtp_server = 'smtp.ru' msg = MIMEText(message) msg['Subject'] = subject msg['From'] = me msg['To'] = you s = smtplib.SMTP(smtp_server) s.sendmail(me, [you], msg.as_string()) s.quit() if __name__ == '__main__': report(sys.argv[2], sys.argv[1]) 


通常,监视库已准备就绪,在cron中运行脚本以进行自动运行,然后将其忘记。



如何填写库存


我们不再是我们,我们是网络人。

第二阶段是用工程师需要处理的数据填充对象。 必须查看所有数据以解决一个系统中的事件,以免跨系统运行,从不同来源收集信息。 并且由于主要技术数据都在监视中,因此我们还将其余部分引入监视中。
为了收集和传输必要的信息,Zabbix编写了stock.py,该文件主要用于从设备收集SNMP数据,并在较小程度上分析Excel文件。

问题是-为什么不使用内置项目 ,并使用Zabbix本身的工具将其结果输入库存 ? 有三个答案:

  1. 动作嵌套不足,例如 通常有必要通过SNMP提取值并在下一个查询中使用结果。
  2. 每天使用外部脚本在所有节点上运行一次数据收集不会加载主监视活动,并且没有项目的时间排队
  3. 存在无法通过SNMP收集的数据

一线和二线工程师无法做到的关于提供商的数据存储在共享网络驱动器上的excel文件中,并由执行通信合同的经理更新。 与文件的集成引起了极大的怀疑-excel解析,手动填写,这可能会更改结构,名称,位置等,很可能会不断抛出错误。 但是由于缺少此类数据的另一个相关来源,我不得不使用它。 为了以某种方式保护自己免受脚本的不断编辑,我们与管理人员就结构和正确的填充达成了一致,并解释了如何执行自动卸载,并且观察当前结构非常重要。 当然,实际上在实践中会发生错误,但是我们迅速对其进行了跟踪,诅咒并加以纠正。

stock.py
 #!/usr/bin/python3 # -*- coding: utf-8 -*- import sys, json, pysnmp, ipaddress, xlrd from datetime import datetime as dt from pysnmp.entity.rfc3413.oneliner import cmdgen def snmp(host, operation, *oid): generator = cmdgen.CommandGenerator() auth_data = cmdgen.UsmUserData('user', 'pwd', 'hash') transport = cmdgen.UdpTransportTarget((host, 161)) getAtt = getattr(generator, '%sCmd'%operation) rst = (errorIndication, errorStatus, errorIndex, varBinds) = getAtt(auth_data, transport, *oid) if not errorIndication is None or errorStatus is True: return "Error: %s %s %s %s" % rst else: if operation=='get': return varBinds elif operation=='next': result=[] for var in varBinds: result.append(var) return result def xlsdata(file, sap): rb = xlrd.open_workbook(file) sheet = rb.sheet_by_index(0) base = {} for i in range(0, sheet.nrows-1): sapnum = str(round(sheet.cell(i,4).value)) if isinstance(sheet.cell(i,4).value,float) else sheet.cell(i,4).value name = str(round(sheet.cell(i,8).value)) if isinstance(sheet.cell(i,8).value,float) else sheet.cell(i,8).value if sap.upper() == sapnum.upper(): base = { 'type' : (sheet.cell(i,2).value), 'serialno_a' : (sheet.cell(i,13).value), 'serialno_b' : (sheet.cell(i,20).value), 'tag' : ('2') if sheet.cell(i, 20).value != '' else ('1'), 'macaddress_a' : (sheet.cell(i, 15).value), 'macaddress_b' : (sheet.cell(i, 22).value) } base['date_hw_purchase'] = dt.date(dt.now()).strftime("%d %B %Y") return (base) def inventory(host, sap): BGPASBASE={} for line in open('/path/a.prokhorov/integration/BGP-AS-BASE.cfg'): if ':' in line: line = line.split(':') BGPASBASE[line[0]]='%s(%s)'%(line[1].rstrip(), line[0]) ### Get Data from Operator shop = xlsdata('/mnt/oprf/providers_base.xlsx', sap) shop['date_hw_expiry'] = 'Failed' ### Get SNMP data shop['host_router'] = 'None' shop['host_netmask'] = 'None' shop['host_networks'] = 'None' try: ### Get Networks from router networks = '' for ip,mask in snmp(host, 'next', 'iso.3.6.1.2.1.4.20.1.1', 'iso.3.6.1.2.1.4.20.1.3'): networks = networks+str(ipaddress.ip_interface(u'%s/%s'%(ip[1].prettyPrint(), mask[1].prettyPrint())))+'\n' shop['host_networks']=networks ### Get BGP information bgppeers, ispnames = '','' for peer,asbgp in snmp(host, 'next', 'iso.3.6.1.2.1.15.3.1.7', 'iso.3.6.1.2.1.15.3.1.9'): asbgp = asbgp[1].prettyPrint() bgppeers = bgppeers+peer[1].prettyPrint()+'\n' ispnames = ispnames+(BGPASBASE.get(asbgp) if BGPASBASE.get(asbgp)!=None else asbgp)+'\n' shop['host_router'] = bgppeers.strip()[:38] shop['host_netmask'] = ispnames.strip()[:38] ### Get Vendor name and Model type hardware = snmp(host, 'get', 'iso.3.6.1.2.1.47.1.1.1.1.13.1', 'iso.3.6.1.2.1.47.1.1.1.1.10.1', 'iso.3.6.1.2.1.47.1.1.1.1.12.1', 'iso.3.6.1.2.1.1.1.0', 'iso.3.6.1.2.1.47.1.1.1.1.7.1') if str(hardware[0][1]) == '0235A325': shop['model'] = hardware[4][1].prettyPrint() else: shop['model'] = hardware[0][1].prettyPrint() shop['os_short'] = hardware[1][1].prettyPrint() shop['vendor'] = hardware[2][1].prettyPrint() version = hardware[3][1].prettyPrint() os = version.split('\n')[0] shop['os_full'] = version[:250] shop['os'] = ''.join(os.split(',')[:2])[:60] ### Make indicators shop['date_hw_expiry'] = 'Success' shop['date_hw_install'] = dt.date(dt.now()).strftime("%d %B %Y") except: shop.pop('host_router') shop.pop('host_netmask') shop.pop('host_networks') return shop #return json.dumps(dict([('inventory',shop)]), sort_keys=True, indent=4) if __name__ == "__main__": print(inventory(sys.argv[1], sys.argv[2])) 

BGP-AS-BASE.cfg
3216:Beeline
9002:Retn
2854:Orange
~omit~
8359:MTS


BGP-AS-BASE.cfg文件表示AS编号与提供程序名称的对应关系。 必须确定与BGP一起安装的提供程序(合同中的文件突然出现错误)。 未使用外部基座,因为 有许多私人AS编号。

SNMP而言

  • 我们在一个请求中通过OID 1.3.6.1.2.1.4.20.1.1向路由器子网提出请求,并通过OID 1.3.6.1.2.1.4.20.1.3向子网掩码提出请求。 我们对其进行处理,将其转换为xxxx / xx并将其写入host_networks单元中。
  • 我们在BGP对等体的IP地址及其ASN上请求数据,我们在创建的数据库中按编号查找提供者名称。 我们将它们写在host_routerhost_netmask字段中 。 重要的是立即限制为38个字符,因为 这些字段不支持更多。 我们数据库中的字段名称并不总是与其存储的数据一致,因为 使用Zabbix数据库中的现有字段,以免在表中创建新字段。 正确的字段名称已在WEB中更正,没有混淆。
  • 我们上载有关供应商,模型和软件设备的数据。 Parsim,写入变量。 与编写硬件模型相关的设计是由于以下事实:对于某些模型, Cisco用不同的OID编写名称(通常是针对机箱),因此我必须进行其他数据检查。

与SNMP相关的整个块都包含在try-except中 ,因此,如果设备上没有SNMP,则脚本不会丢失,并且至少由提供商从Excel中获取数据。 为了了解脚本的哪个部分已执行而哪些部分没有执行,我们在date_hw_expiry字段中写下 SNMP块的成功信息 ,并写下最后一次可以删除SNMP上所有数据的日期以及最后一次可以在Excel文件中找到数据的日期。


所有这些都以准备好Zabbix的JSON形式返回。

每天一次开始更新所有清单数据,卸载所有主机并为每个对象启动清单 .py。

mp-update.py
 #!/usr/bin/python3 # -*- coding: utf-8 -*- from multiprocessing import Pool import time from zabbix.api import ZabbixAPI from inventory import inventory from report import report def updating(shop): try: shop['inventory'] = inventory(shop['interfaces'][0]['ip'], shop['host'][-4:]) shop.pop('interfaces') shop['inventory_mode'] = '1' shop.pop('host') print (shop['hostid']) return zapi.host.update(shop) except: print(">>>",shop['hostid']) with open ('/home/local/integration/error.txt', 'a') as err: err.write(shop['hostid']) err.write("\n") if __name__ =='__main__': t = time.time() zapi = ZabbixAPI(url='http://z.noc.x5.ru/', user='user', password='pwd') shopbase = zapi.host.get(output=['host', 'hostid'], groupids= ['12', '13', '14'], monitored="1", selectInterfaces=['ip']) pool = Pool(processes=10) p=[0 for x in range(0,len(shopbase))] for i in range(0, len(shopbase), 10): print ("Index:", i,"\n",shopbase[i],"\n") pool.map(updating,shopbase[i:i+10]) pool.close() pool.join() print(time.time()-t) report('    Zabbix.', 'Inventory updating succeed') 


使用了多处理 (一个例子是从互联网的广阔领域中提取的)。 要进行搜索,我们使用主机名中的SAP ID 。 结果输出在Zabbix中写入update'ohm。

总结


对于所有内置机制的集成,自动更新和更新的实现,Zabbix API绰绰有余。 使用的主要功能是host.gethost.createhost.update ,它们一起使您可以完全控制监视对象数据库的创建和更新。 这些功能的输入数据可以从任何可用的系统和源中提交。

帮助我们完成此任务的主要python模块: pysnmpxlrdzabbix.apixmlipaddressjson
xlrd-解析Excel。
xml -XML解析。
pysnmp-从设备提取SNMP数据。

SNMP交互比SSH更容易,至少是因为在实践中,硬件对SNMP的响应比对SSH的响应要快,解析SNMP响应实际上是不必要的,尽管不同供应商的CLI往往彼此非常不同,并且结论是相同的即使同一供应商的不同型号,团队也可能有所不同。

主要应用的OID
iso.3.6.1.2.1.4.20.1.1-所有路由器接口的地址
iso.3.6.1.2.1.4.20.1.3-所有路由器接口的子网掩码
iso.3.6.1.2.1.15.3.1.7-路由器的所有BGP对等体
iso.3.6.1.2.1.15.3.1.9-路由器的所有BGP对等体的AS
iso.3.6.1.2.1.47.1.1.1.1.13.1-模型
iso.3.6.1.2.1.47.1.1.1.1.10.1-软件的简短版本
iso.3.6.1.2.1.47.1.1.1.1.1.12.1-供应商
iso.3.6.1.2.1.1.1.0-详细的软件版本
iso.3.6.1.2.1.47.1.1.1.1.7.1-型号,底盘类型

必须添加仪表板一点,以分配配电网络上的问题,并且不要在堆中干扰它们。 还要添加几个字段,例如ip,提供程序名称和地址,以便在发生大规模事故的情况下,只要将提供程序所需的所有数据立即从浏览器复制粘贴到消息中的所有有问题的对象就足够了。


“ Workview”单独编写的,在其中我们可以找到所收集的所有信息以及为此对象收集的图形。

Source: https://habr.com/ru/post/zh-CN438754/


All Articles