utils.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. import logging
  2. import json
  3. import sys
  4. import time
  5. import requests
  6. import asyncio
  7. from aliyunsdkcore.client import AcsClient
  8. from aliyunsdkslb.request.v20140515.AddBackendServersRequest import AddBackendServersRequest
  9. from aliyunsdkslb.request.v20140515.RemoveBackendServersRequest import RemoveBackendServersRequest
  10. from aliyunsdkecs.request.v20140526.RunInstancesRequest import RunInstancesRequest
  11. from aliyunsdkecs.request.v20140526.DescribeInstancesRequest import DescribeInstancesRequest
  12. from aliyunsdkecs.request.v20140526.DescribeNetworkInterfacesRequest import DescribeNetworkInterfacesRequest
  13. from aliyunsdkslb.request.v20140515.DescribeLoadBalancerAttributeRequest import DescribeLoadBalancerAttributeRequest
  14. from aliyunsdkecs.request.v20140526.RunCommandRequest import RunCommandRequest
  15. from aliyunsdkecs.request.v20140526.SendFileRequest import SendFileRequest
  16. from aliyunsdkecs.request.v20140526.StopInstancesRequest import StopInstancesRequest
  17. from aliyunsdkecs.request.v20140526.DeleteInstancesRequest import DeleteInstancesRequest
  18. from aliyunsdkecs.request.v20140526.DescribeInstanceStatusRequest import DescribeInstanceStatusRequest
  19. from aliyunsdkcore.request import CommonRequest
  20. from alibabacloud_alb20200616.client import Client as Alb20200616Client
  21. from alibabacloud_tea_openapi import models as open_api_models
  22. from alibabacloud_alb20200616 import models as alb_models
  23. from alibabacloud_tea_util import models as util_models
  24. logging.basicConfig(level=logging.INFO,
  25. format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
  26. datefmt='%a, %d %b %Y %H:%M:%S')
  27. def send_msg_to_feishu(webhook, key_word, msg_text):
  28. """发送消息到飞书"""
  29. headers = {'Content-Type': 'application/json'}
  30. payload_message = {
  31. "msg_type": "text",
  32. "content": {
  33. "text": '{}: {}'.format(key_word, msg_text)
  34. }
  35. }
  36. response = requests.request('POST', url=webhook, headers=headers, data=json.dumps(payload_message))
  37. logging.info(response.text)
  38. def connect_client(access_key_id, access_key_secret, region_id):
  39. """
  40. 初始化账号,连接客户端
  41. :param access_key_id: access key Id, type-string
  42. :param access_key_secret: access key secret, type-string
  43. :param region_id: region_id
  44. :return: clt
  45. """
  46. try:
  47. clt = AcsClient(ak=access_key_id, secret=access_key_secret, region_id=region_id)
  48. return clt
  49. except Exception as e:
  50. # 失败,记录报错信息,发送通知,停止并退出
  51. logging.error(e)
  52. sys.exit()
  53. def build_create_instances_request(image_id, vswitch_id, security_group_id, zone_id, instance_type, instance_name,
  54. disk_size, disk_category, key_pair_name, tags):
  55. """
  56. 购买服务器参数配置
  57. :param image_id: 使用的镜像信息 type-string
  58. :param vswitch_id: 选择的交换机 type-string
  59. :param security_group_id: 当前vpc类型的安全组 type-string
  60. :param zone_id: 服务器所在区域 type-string
  61. :param instance_type: 实例规格 type-string
  62. :param instance_name: 实例命名 type-string
  63. :param disk_size: 磁盘大小,单位:G,type-string
  64. :param disk_category: 磁盘类型 type-string
  65. :param key_pair_name: 密钥对名称 type-string
  66. :param tags: 标签 type-list, eg: [{"Key": "ecs", "Value": "rov-server.prod"}, ...]
  67. :return: request
  68. """
  69. request = RunInstancesRequest()
  70. request.set_ImageId(image_id)
  71. request.set_VSwitchId(vswitch_id)
  72. request.set_SecurityGroupId(security_group_id)
  73. request.set_ZoneId(zone_id)
  74. request.set_InstanceType(instance_type)
  75. request.set_InstanceName(instance_name)
  76. request.set_SystemDiskSize(disk_size)
  77. request.set_SystemDiskCategory(disk_category)
  78. request.set_KeyPairName(key_pair_name)
  79. request.set_Tags(tags)
  80. return request
  81. def send_request(client, request):
  82. """
  83. 发送API请求
  84. :param client: 客户端连接
  85. :param request: 请求配置
  86. :return: response
  87. """
  88. request.set_accept_format('json')
  89. try:
  90. response = client.do_action_with_exception(request)
  91. response = json.loads(response)
  92. # logging.info(response)
  93. return response
  94. except Exception as e:
  95. # 失败,记录报错信息,发送通知,停止并退出
  96. logging.error(e)
  97. sys.exit()
  98. def send_req(client, request):
  99. """
  100. 发送API请求
  101. :param client: 客户端连接
  102. :param request: 请求配置
  103. :return: response
  104. """
  105. request.set_accept_format('json')
  106. response = client.do_action_with_exception(request)
  107. #print(response)
  108. response = json.loads(response)
  109. print(response)
  110. # logging.info(response)
  111. print(response.get('Code'))
  112. return response
  113. #except Exception as e:
  114. # 失败,记录报错信息,发送通知,停止并退出
  115. #logging.error(e)
  116. #sys.exit()
  117. def check_instance_running(client, instance_ids):
  118. """
  119. 检查服务器运行状态
  120. :param client: 客户端连接
  121. :param instance_ids: 实例id列表, type-list
  122. :return: running_count,Status为Running的实例数
  123. """
  124. try:
  125. request = DescribeInstancesRequest()
  126. request.set_InstanceIds(json.dumps(instance_ids))
  127. request.set_PageSize(100)
  128. response = send_request(client=client, request=request)
  129. if response.get('Code') is None:
  130. instances_list = response.get('Instances').get('Instance')
  131. running_count = 0
  132. running_instances = []
  133. for instance_detail in instances_list:
  134. if instance_detail.get('Status') == "Running":
  135. running_count += 1
  136. running_instances.append(instance_detail.get('InstanceId'))
  137. return running_count, running_instances
  138. else:
  139. # 失败,记录报错信息,发送通知,停止并退出
  140. logging.error(response)
  141. sys.exit()
  142. except Exception as e:
  143. # 失败,记录报错信息,发送通知,停止并退出
  144. logging.error(e)
  145. sys.exit()
  146. def create_multiple_instances(amount, client,
  147. image_id, vswitch_id, security_group_id, zone_id, instance_type, instance_name,
  148. disk_size, disk_category, key_pair_name, tags):
  149. """
  150. 创建多个ECS实例
  151. :param amount: 创建实例数 type-int 取值范围:[1, 100]
  152. :param client: 购买机器客户端连接
  153. :param image_id: 使用的镜像信息 type-string
  154. :param vswitch_id: 选择的交换机 type-string
  155. :param security_group_id: 当前vpc类型的安全组 type-string
  156. :param zone_id: 服务器所在区域 type-string
  157. :param instance_type: 实例规格 type-string
  158. :param instance_name: 实例命名 type-string
  159. :param disk_size: 磁盘大小,单位:G,type-string
  160. :param disk_category: 磁盘类型 type-string
  161. :param key_pair_name: 密钥对名称 type-string
  162. :param tags: 标签 type-list, eg: [{"Key": "ecs", "Value": "rov-server.prod"}, ...]
  163. :return:
  164. """
  165. logging.info(f"create instances start, request amount: {amount}.")
  166. # 1. 连接客户端
  167. # create_instances_clt = connect_client(
  168. # access_key_id=access_key_id, access_key_secret=access_key_secret, region_id=region_id
  169. # )
  170. # 2. 请求参数配置
  171. request = build_create_instances_request(
  172. image_id=image_id, vswitch_id=vswitch_id, security_group_id=security_group_id, zone_id=zone_id,
  173. instance_type=instance_type, instance_name=instance_name, disk_size=disk_size, disk_category=disk_category,
  174. key_pair_name=key_pair_name, tags=tags
  175. )
  176. request.set_Amount(amount)
  177. # 3. 发送API请求,购买机器并启动
  178. response = send_request(client=client, request=request)
  179. if response.get('Code') is None:
  180. instance_ids = response.get('InstanceIdSets').get('InstanceIdSet')
  181. logging.info(f"success amount: {len(instance_ids)}, instance ids: {instance_ids}.")
  182. # 获取机器运行状态
  183. running_amount = 0
  184. while running_amount < amount:
  185. time.sleep(10)
  186. running_amount, running_instances = check_instance_running(client=client, instance_ids=instance_ids)
  187. logging.info(f"running amount: {running_amount}, running instances: {running_instances}.")
  188. return instance_ids
  189. else:
  190. # 失败,记录报错信息,发送通知,停止并退出
  191. logging.error(response)
  192. sys.exit()
  193. def run_command(client, instance_ids, command):
  194. """
  195. 批量执行命令
  196. :param client: 客户端连接
  197. :param instance_ids: 实例id列表, type-list, 最多能指定50台ECS实例ID
  198. :param command: 命令 type-string
  199. :return:
  200. """
  201. for i in range(len(instance_ids) // 50 + 1):
  202. instance_id_list = instance_ids[i * 50:(i + 1) * 50]
  203. if len(instance_id_list) == 0:
  204. return
  205. request = RunCommandRequest()
  206. request.set_accept_format('json')
  207. request.set_Type("RunShellScript")
  208. request.set_CommandContent(command)
  209. request.set_InstanceIds(instance_id_list)
  210. response = send_request(client=client, request=request)
  211. logging.info(response)
  212. def run_per_command(client, instance, command):
  213. """
  214. 批量执行命令
  215. :param client: 客户端连接
  216. :param instance_ids: 实例id列表, type-list, 最多能指定50台ECS实例ID
  217. :param command: 命令 type-string
  218. :return:
  219. """
  220. #for i in range(len(instance_ids) // 50 + 1)
  221. request = RunCommandRequest()
  222. request.set_accept_format('json')
  223. request.set_Type("RunShellScript")
  224. request.set_CommandContent(command)
  225. request.set_InstanceIds([instance])
  226. response = send_req(client=client, request=request)
  227. logging.info(response)
  228. return response
  229. def get_instance_ids(client, slb_id):
  230. """
  231. 获取slb下所有服务器instanceId
  232. :param client: 客户端连接
  233. :param slb_id: 负载均衡id type-string
  234. :return: instance_ids type-list
  235. """
  236. request = DescribeLoadBalancerAttributeRequest()
  237. request.set_accept_format('json')
  238. request.set_LoadBalancerId(slb_id)
  239. response = send_request(client=client, request=request)
  240. instance_ids = [instance["ServerId"] for instance in response["BackendServers"]["BackendServer"]]
  241. return instance_ids
  242. def get_ip_address(client, instance_id):
  243. """
  244. 获取实例IP地址
  245. :param client: 客户端连接
  246. :param instance_id: 实例id, type-string
  247. :return: ip_address, type-string
  248. """
  249. request = DescribeNetworkInterfacesRequest()
  250. request.set_accept_format('json')
  251. request.set_InstanceId(instance_id)
  252. response = send_request(client=client, request=request)
  253. ip_address = response['NetworkInterfaceSets']['NetworkInterfaceSet'][0]['PrivateIpAddress']
  254. return ip_address
  255. def set_weight_for_instances(client, slb_id, instance_id_list, weight):
  256. """
  257. 同时设置多台服务器的slb权重,权重一样
  258. :param client: 客户端连接
  259. :param slb_id: slb_id
  260. :param instance_id_list: 服务器id list
  261. :param weight: 权重值
  262. :return: None
  263. """
  264. for i in range(len(instance_id_list) // 20 + 1):
  265. instances_list = instance_id_list[i * 20:(i + 1) * 20]
  266. if len(instances_list) == 0:
  267. return
  268. BackendServers = [{"ServerId": instance_id, "Weight": weight} for instance_id in instances_list]
  269. request = CommonRequest()
  270. request.set_accept_format('json')
  271. request.set_domain('slb.aliyuncs.com')
  272. request.set_version('2014-05-15')
  273. request.set_method('POST')
  274. request.set_action_name('SetBackendServers')
  275. request.add_query_param('BackendServers', BackendServers)
  276. request.add_query_param('LoadBalancerId', slb_id)
  277. response = send_request(client=client, request=request)
  278. def connect_alb_client(access_key_id, access_key_secret):
  279. """
  280. 初始化ALB客户端
  281. :param access_key_id: access key Id, type-string
  282. :param access_key_secret: access key secret, type-string
  283. :return: alb_client
  284. """
  285. config = open_api_models.Config(
  286. access_key_id=access_key_id,
  287. access_key_secret=access_key_secret,
  288. endpoint='alb.cn-hangzhou.aliyuncs.com'
  289. )
  290. alb_client = Alb20200616Client(config)
  291. return alb_client
  292. def add_servers_to_server_group(alb_client, server_group_id, instance_id, weight):
  293. """
  294. 添加服务器到ALB服务器组
  295. :param alb_client: ALB客户端连接
  296. :param server_group_id: 服务器组ID
  297. :param instance_id: 实例ID
  298. :param weight: 权重
  299. """
  300. server = alb_models.AddServersToServerGroupRequestServers(
  301. server_id=instance_id,
  302. server_type='ecs',
  303. weight=weight
  304. )
  305. request = alb_models.AddServersToServerGroupRequest(
  306. server_group_id=server_group_id,
  307. servers=[server]
  308. )
  309. runtime = util_models.RuntimeOptions()
  310. try:
  311. alb_client.add_servers_to_server_group_with_options(request, runtime)
  312. logging.info(f"Successfully added server {instance_id} to server group {server_group_id} with weight {weight}.")
  313. except Exception as e:
  314. logging.error(f"Failed to add server {instance_id} to server group {server_group_id}: {str(e)}")
  315. def remove_servers_from_server_group(alb_client, server_group_id, instance_id):
  316. """
  317. 从ALB服务器组中移除服务器
  318. :param alb_client: ALB客户端连接
  319. :param server_group_id: 服务器组ID
  320. :param instance_id: 实例ID
  321. """
  322. server = alb_models.RemoveServersFromServerGroupRequestServers(
  323. server_id=instance_id,
  324. server_type='ecs'
  325. )
  326. request = alb_models.RemoveServersFromServerGroupRequest(
  327. server_group_id=server_group_id,
  328. servers=[server]
  329. )
  330. runtime = util_models.RuntimeOptions()
  331. try:
  332. alb_client.remove_servers_from_server_group_with_options(request, runtime)
  333. logging.info(f"Successfully removed server {instance_id} from server group {server_group_id}.")
  334. except Exception as e:
  335. logging.error(f"Failed to remove server {instance_id} from server group {server_group_id}: {str(e)}")
  336. def send_file_to_ecs(client, instance_id_list, target_dir, name, content):
  337. """
  338. 发送文件到ecs
  339. :param client:
  340. :param instance_id_list: 最多能指定50台ECS实例ID
  341. :param target_dir: 文件存放目录 type-string
  342. :param name: 文件名 type-string
  343. :param content: 文件内容 type-string
  344. :return:
  345. """
  346. for i in range(len(instance_id_list) // 50 + 1):
  347. instance_ids = instance_id_list[i * 50:(i + 1) * 50]
  348. if len(instance_ids) == 0:
  349. return
  350. request = SendFileRequest()
  351. request.set_Content(content)
  352. request.set_TargetDir(target_dir)
  353. request.set_Name(name)
  354. request.set_Overwrite(True)
  355. request.set_InstanceIds(instance_ids)
  356. response = send_request(client=client, request=request)
  357. def stop_instances(client, instance_ids, force_stop=False):
  358. """
  359. 停止实例
  360. :param client:
  361. :param instance_ids: 实例ID, type-list
  362. :param force_stop: 是否强制关机, True-强制关机, False-正常关机, type-bool
  363. :return:
  364. """
  365. request = StopInstancesRequest()
  366. request.set_InstanceIds(instance_ids)
  367. request.set_ForceStop(force_stop)
  368. response = send_request(client=client, request=request)
  369. return response
  370. def release_instances(client, instance_ids, force=False):
  371. """
  372. 释放实例
  373. :param client:
  374. :param instance_ids: instance_id, type-list
  375. :param force: 是否强制释放, True-强制释放, False-正常释放, type-bool
  376. :return:
  377. """
  378. request = DeleteInstancesRequest()
  379. request.set_InstanceIds(instance_ids)
  380. request.set_Force(force)
  381. response = send_request(client=client, request=request)
  382. return response
  383. def get_instances_status(client, instance_ids):
  384. """
  385. 获取实例运行状态
  386. :param client:
  387. :param instance_ids: instance_id, type-liist
  388. :return:
  389. """
  390. request = DescribeInstanceStatusRequest()
  391. request.set_InstanceIds(instance_ids)
  392. request.set_PageSize(50)
  393. response = send_request(client=client, request=request)
  394. return response
  395. def set_instance_weight_process(client, slb_id, instance_id_list, weight_list):
  396. """
  397. 修改服务器的权重值
  398. :param client: slb客户端连接·
  399. :param slb_id: slb id
  400. :param instance_id_list: instance id list
  401. :param weight_list: 权重修改列表 type-list [(weight, sleep_time), ...]
  402. :return:
  403. """
  404. for weight, sleep_time in weight_list:
  405. logging.info(f"weight = {weight}")
  406. flag = True
  407. while flag:
  408. try:
  409. set_weight_for_instances(client=client, slb_id=slb_id, instance_id_list=instance_id_list, weight=weight)
  410. time.sleep(sleep_time)
  411. flag = False
  412. except Exception as e:
  413. time.sleep(10)
  414. continue
  415. def add_backend_servers(client, slb_id, instances):
  416. """
  417. 服务器挂载到负载均衡(必须是状态为运行中的后端服务器才可以加入负载均衡实例,每次调用最多可添加20个后端服务器)
  418. :param client:
  419. :param slb_id:
  420. :param instances: 实例列表 [(instance_id, ip), ...]
  421. :return:
  422. """
  423. try:
  424. for i in range(len(instances) // 20 + 1):
  425. instances_list = instances[i * 20:(i + 1) * 20]
  426. if len(instances_list) == 0:
  427. return
  428. request = AddBackendServersRequest()
  429. request.set_accept_format('json')
  430. request.set_LoadBalancerId(slb_id)
  431. backend_servers = [
  432. {"ServerId": instance_id, "Weight": "0", "Type": "ecs", "ServerIp": ip_address}
  433. for instance_id, ip_address in instances_list]
  434. request.set_BackendServers(backend_servers)
  435. response = client.do_action_with_exception(request)
  436. return response
  437. except Exception as e:
  438. logging.error(e)
  439. sys.exit()
  440. def remove_backend_servers(client, slb_id, instances):
  441. """
  442. 服务器从负载均衡移除(一次调用最多可以移除20个后端服务器)
  443. :param client:
  444. :param slb_id:
  445. :param instances: 实例列表 [instance_id, ...]
  446. :return:
  447. """
  448. try:
  449. for i in range(len(instances) // 20 + 1):
  450. instances_list = instances[i * 20:(i + 1) * 20]
  451. if len(instances_list) == 0:
  452. return
  453. request = RemoveBackendServersRequest()
  454. request.set_accept_format('json')
  455. request.set_LoadBalancerId(slb_id)
  456. backend_servers = [
  457. {"ServerId": instance_id, "Weight": "0", "Type": "ecs"}
  458. for instance_id in instances_list]
  459. request.set_BackendServers(backend_servers)
  460. response = client.do_action_with_exception(request)
  461. return response
  462. except Exception as e:
  463. logging.error(e)
  464. sys.exit()
  465. def set_instance_weight_process_with_slbs(client, slb_id_list, instance_id_list, weight_list):
  466. """
  467. 修改服务器的权重值
  468. :param client: slb客户端连接
  469. :param slb_id_list: slb id list
  470. :param instance_id_list: instance id list
  471. :param weight_list: 权重修改列表 type-list [(weight, sleep_time), ...]
  472. :return:
  473. """
  474. for weight, sleep_time in weight_list:
  475. logging.info(f"修改权重中: weight = {weight}")
  476. for slb_id in slb_id_list:
  477. flag = True
  478. while flag:
  479. try:
  480. set_weight_for_instances(client=client, slb_id=slb_id, instance_id_list=instance_id_list, weight=weight)
  481. logging.info(f"slb: {slb_id} finished!")
  482. flag = False
  483. except Exception as e:
  484. time.sleep(10)
  485. continue
  486. time.sleep(sleep_time)
  487. def add_backend_servers_with_slbs(client, slb_id_list, instances):
  488. """
  489. 服务器挂载到负载均衡(必须是状态为运行中的后端服务器才可以加入负载均衡实例,每次调用最多可添加20个后端服务器)
  490. :param client:
  491. :param slb_id_list:
  492. :param instances: 实例列表 [(instance_id, ip), ...]
  493. :return:
  494. """
  495. try:
  496. for i in range(len(instances)//20 + 1):
  497. instances_list = instances[i*20:(i+1)*20]
  498. if len(instances_list) == 0:
  499. return
  500. for slb_id in slb_id_list:
  501. request = AddBackendServersRequest()
  502. request.set_accept_format('json')
  503. request.set_LoadBalancerId(slb_id)
  504. backend_servers = [
  505. {"ServerId": instance_id, "Weight": "0", "Type": "ecs", "ServerIp": ip_address}
  506. for instance_id, ip_address in instances_list]
  507. request.set_BackendServers(backend_servers)
  508. response = client.do_action_with_exception(request)
  509. logging.info(f"slb: {slb_id} add backend servers finished!")
  510. logging.info(f"i: {i}, count: {len(instances_list)}, instances: {instances_list} "
  511. f"add backend servers finished!")
  512. except Exception as e:
  513. logging.error(e)
  514. sys.exit()
  515. def remove_backend_servers_with_slbs(client, slb_id_list, instances):
  516. """
  517. 服务器从负载均衡移除(一次调用最多可以移除20个后端服务器)
  518. :param client:
  519. :param slb_id_list:
  520. :param instances: 实例列表 [instance_id, ...]
  521. :return:
  522. """
  523. try:
  524. for i in range(len(instances)//20 + 1):
  525. instances_list = instances[i*20:(i+1)*20]
  526. if len(instances_list) == 0:
  527. return
  528. for slb_id in slb_id_list:
  529. request = RemoveBackendServersRequest()
  530. request.set_accept_format('json')
  531. request.set_LoadBalancerId(slb_id)
  532. backend_servers = [
  533. {"ServerId": instance_id, "Weight": "0", "Type": "ecs"}
  534. for instance_id in instances_list]
  535. request.set_BackendServers(backend_servers)
  536. response = client.do_action_with_exception(request)
  537. logging.info(f"slb: {slb_id} remove backend servers finished!")
  538. logging.info(f"i: {i}, count: {len(instances_list)}, instances: {instances_list} "
  539. f"remove backend servers finished!")
  540. except Exception as e:
  541. logging.error(e)
  542. sys.exit()