手上有大量的计算资源怎么办?正经业务用不上这么多,又不想浪费它们,不如来挖矿吧。花了两天时间学习了 Azure CLI,写了个一键创建所有机器占满所有配额,且能自动执行命令的 Python 脚本。本脚本适合挖不需要 GPU 的币,比如 XMR(门罗币)。一般矿池都会为你准备好一键脚本。运行本脚本后,只需输入矿池给你的一键脚本,即可批量用完所有配额创建虚拟机。
什么是Azure CLI?
Azure Command-Line Interface(Azure CLI)是一组用来创建和管理 Azure 资源的命令,可用来快速使用 Azure(侧重于自动化)。
思路
- 检查配额以确定订阅类型,并确定要开的虚拟机数量。默认每个区域的配额都相同,因此只需查询美国东部地区的配额。
- 创建资源组。资源组是用于保存虚拟机等资源的容器。
- 创建开机后要运行的脚本。
- 批量创建虚拟机并运行挖矿脚本。创建实例类型为 F/FS 系列的虚拟机,因为该系列针对计算优化。在创建虚拟机时创建 shell 脚本并指定 cloud-init 参数即可实现虚拟机创建成功后自动运行脚本。
- 信息汇总,显示共创建了多少台虚拟机。
大体思路已经定下来,接下来就是了解 Azure-CLI 的使用以及 Azure 的各种订阅配额情况。
- get_default_cli 是一个可以通过 Python 使用 Azure CLI 的函数,但它类似于“print”命令,输出的内容无法简单地赋值给变量。因此需要将输出重定向到 io.StringIO 对象来捕获它:
f = io.StringIO() with redirect_stdout(f): get_default_cli().invoke(['vm', 'list-usage', '--location', 'East US', '--query', '[?localName == \'Total Regional vCPUs\']']) limit = f.getvalue()
- Azure CLI 支持 JMESPath query,通过 JMESPath query 可以精确的把 Json 的内容取出。
- 比如列出所有虚拟机的名称
az vm list --query [*].name
- 查询美国东部地区的配额
az vm list-usage --location "East US" --query "[?localName == 'Total Regional vCPUs'].limit"
- 比如列出所有虚拟机的名称
关于Azure for Students、免费试用和即用即付订阅的配额,我在脚本里已经写得非常详细了。若你的订阅不是其中的一种或另外申请有配额,需要手动修改创建虚拟机的数量。
比如某企业订阅每个区域有50个vCPUs ,FSv2 系列也为50个vCPUs,那么可以创建6个 Standard_F8s_v2 实例(占用48个vCPUs),然后创建一个 Standard_F2s_v2 实例(占用2个vCPUs),这样就用满了所有配额。
2021年2月14日更新:后来发现选择 F/FS 系列 CPU 最少的实例来挖矿,收益更高,因为多核性能不是简单叠加。同样配额下,选择多个 CPU 数量少的实例比一个 CPU 数量多的实例要好。脚本就懒得改了,因为这三种订阅的默认配额都比较低,改了后差别不大,并且手上的计算资源有别的用途,改了我还要一个个测试订阅的运行结果。改起来很简单,F2s_v2 实例配额用完后用 F2s ,然后是 D2s_v4 系列。
完整代码
# -*- coding : utf-8 -*- # @Time : 2021-01-03 # @Author : Github@sslspace # @Blog : https://blog.shelike.me/ # @Software : Pycharm import io import json import time from contextlib import redirect_stdout from azure.cli.core import get_default_cli # 1.检查配额以确定订阅类型,并确定要开的虚拟机数量 # 初始化区域列表,共31个区域 # Azure for Students和即用即付订阅均不支持 South India 和 West India 区域 locations = ['eastus', 'eastus2', 'westus', 'centralus', 'northcentralus', 'southcentralus', 'northeurope', 'westeurope', 'eastasia', 'southeastasia', 'japaneast', 'japanwest', 'australiaeast', 'australiasoutheast', 'australiacentral', 'brazilsouth', 'centralindia', 'canadacentral', 'canadaeast', 'westus2', 'uksouth', 'ukwest', 'koreacentral', 'koreasouth', 'francecentral', 'southafricanorth', 'uaenorth', 'switzerlandnorth', 'germanywestcentral', 'norwayeast', 'westcentralus'] # 捕获 get_default_cli().invoke 的标准输出 f = io.StringIO() with redirect_stdout(f): get_default_cli().invoke(['vm', 'list-usage', '--location', 'East US', '--query', '[?localName == \'Total Regional vCPUs\'].limit']) limit = f.getvalue() # 默认每个区域的配额都相同,因此只需查询美国东部地区的配额 # Azure for Students订阅每个区域的vCPU总数为6, # 标准FSv2系列vCPUs为4,标准FS系列vCPUs为4 # 所以创建一个Standard_F4s_v2实例(占用4个vCPUs), # 一个Standard_F2s实例(占用2个vCPUs) if '6' in limit: print("当前订阅为Azure for Students") size1_name = "Standard_F4s_v2" size1_abbreviation = "F4s_v2" size1_count = 1 size2_name = "Standard_F2s" size2_abbreviation = "F2s" size2_count = 1 type = 0 # 即用即付订阅每个区域的vCPU总数为10,与标准FSv2系列的vCPUs相同 # 因此创建一个Standard_F8s_v2实例(占用8个vCPUs), # 一个Standard_F2s_v2实例(占用2个vCPUs) elif '10' in limit: print("当前订阅为即用即付") size1_name = "Standard_F8s_v2" size1_abbreviation = "F8s_v2" size1_count = 1 size2_name = "Standard_F2s_v2" size2_abbreviation = "F2s_v2" size2_count = 1 type = 1 # 免费试用订阅每个区域的vCPU总数为4,与标准FSv2系列的vCPUs相同 # 因此创建1个Standard_F4s_v2实例(共占用4个vCPUs) elif '4' in limit: print("当前订阅为免费试用,每个区域的配额仅为4 vCPUs,建议升级后再用。" "若升级后仍看到本消息,请等待十分钟再运行脚本。") selection = input("输入Y继续运行,任意键退出") if selection != "Y" or "y": exit(0) size1_name = "Standard_F4s_v2" size1_abbreviation = "F4s_v2" size1_count = 1 type = 2 else: print("未知订阅,请手动修改创建虚拟机的数量") print("若当前订阅为Azure for Students、免费试用或即用即付," "请进入“创建虚拟机”界面,任意填写信息," "一直到“查看+创建”项(创建虚拟机的最后一步)" "显示“验证通过”即可自动刷新配额") print("假如还未解决,请直接修改limit = f.getvalue()中的" "f.getvalue()为\"区域配额\"(包括英文引号)。Azure for" " Students是6,即用即付是10,免费试用订阅是4") exit(0) # 2.创建资源组 # 资源组只是资源的逻辑容器,资源组内的资源不必与资源组位于同一区域 get_default_cli().invoke(['group', 'create', '--name', 'myResourceGroup', '--location', 'eastus']) # 除非订阅被禁用,其他任何情况下创建资源组都会成功(重名也返回成功) print("创建资源组成功") # 3.创建开机后要运行的脚本 init = input("请输入机器开机后要执行的命令(仅一行): ") with open("./cloud-init.txt", "w") as f: f.write("#cloud-config" + "\n") f.write("runcmd:" + "\n") f.write(" - sudo -s" + "\n") f.write(f" - {init}") # 4.批量创建虚拟机并运行挖矿脚本 for location in locations: # Azure for Students订阅不支持 norwayeast 区域 if location == "norwayeast" and type == 0: continue # westcentralus 区域不支持 FSv2 系列, # Azure for Students订阅不支持 F/FS 系列 if location == "westcentralus" and type == 0: size1_name = "Standard_D4ds_v4" size1_abbreviation = "D4ds_v4" size2_name = "Standard_D2s_v4" size2_abbreviation = "D2s_v4" if location == "westcentralus" and type == 1: size1_name = "Standard_F8s" size1_abbreviation = "F8s" size2_name = "Standard_F2s" size2_abbreviation = "F2s" if location == "westcentralus" and type == 2: size1_name = "Standard_F4s" size1_abbreviation = "F4s" count = 0 for a in range(0, size1_count): count += 1 print("正在 " + str(location) + " 区域创建第 " + str(count) + f" 个 {size1_name} 实例,共 " + str(size1_count) + " 个") get_default_cli().invoke( ['vm', 'create', '--resource-group', 'myResourceGroup', '--name', f'{location}-{size1_abbreviation}-{count}', '--image', 'UbuntuLTS', '--size', f'{size1_name}', '--location', f'{location}', '--admin-username', 'azureuser', '--admin-password', '6uPF5Cofvyjcew9', '--custom-data', 'cloud-init.txt', "--no-wait"]) if type != 2: count = 0 for a in range(0, size2_count): count += 1 print("正在 " + str(location) + " 区域创建第 " + str(count) + f" 个 {size2_name} 实例,共 " + str(size2_count) + " 个") get_default_cli().invoke( ['vm', 'create', '--resource-group', 'myResourceGroup', '--name', f'{location}-{size2_abbreviation}-{count}', '--image', 'UbuntuLTS', '--size', f'{size2_name}', '--location', f'{location}', '--admin-username', 'azureuser', '--admin-password', '6uPF5Cofvyjcew9', '--custom-data', 'cloud-init.txt', "--no-wait"]) # 5.信息汇总 # 获取所有vm的名字 print("\n------------------------------------------------------------------------------\n") print("大功告成!在31个区域创建虚拟机的命令已成功执行") for i in range(120, -1, -1): print("\r正在等待Azure生成统计信息,还需等待{}秒".format(i), end="", flush=True) time.sleep(1) f = io.StringIO() with redirect_stdout(f): get_default_cli().invoke(['vm', 'list', '--query', '[*].name']) result = f.getvalue() vmname = json.loads(result) print("\n\n-----------------------------------------------------------------------------\n") print(str(vmname)) print("\n------------------------------------------------------------------------------\n") print("已创建了" + str(len(vmname)) + "台虚拟机") # 如果想删除脚本创建的所有资源,取消注释以下语句 # get_default_cli().invoke(['group', 'delete', '--name', 'myResourceGroup', # '--no-wait', '--yes']) # print("删除资源组成功")
运行环境
Python 3.9以下版本,azure-cli 模块,contextlib 模块。
首先确保你的Azure账号开过虚拟机,否则的话先随意创建一台机器,到了创建的最后一步,显示“验证通过”就可以关闭页面了,不需要真正创建。
运行这个脚本最简单的方法是进入Azure门户,点击右上角的命令行图标 进入Azure Cloud Shell
选择“创建存储”
选择“Bash”
输入
pip install azure-cli
等待安装完成后,创建一个文件,复制粘贴代码,然后用 python 文件名 运行它
2021年3月21日更新:也许是 get_default_cli() 更新后导致 f.getvalue() 取不到值。手动修改第29行 limit = f.getvalue() 中的 f.getvalue() 为"区域配额"(包括英文引号)即可。Azure for Students是6,即用即付是10,免费试用订阅是4。虚拟机创建完成后显示虚拟机列表,无需理会最后的报错。
需要注意的是,若你的账号不是老号,或者有一定消费的号,对于这种大量开机器的行为(即使不挖矿)基本上会被匹配为恶意用户,在任何一个云计算平台都会封号,或者要求验证信用卡账单。
故障排查
- 你的脚本无法运行
首先看看你的脚本是否能在最新的Ubuntu机器上运行。 - 你的脚本能在普通的机器上运行,但批量创建机器后无法运行
检查 /var/log/cloud-init-output.log 和 /var/log/cloud-init.log 的错误日志。可能是环境变量的问题。由于在初始化阶段没有环境变量,会导致对环境变量的调用失败。解决方法:修改你的脚本,声明环境变量;或者在输入命令时使用 export 环境变量=值 && 你的脚本 。比如 c3pool 的一键脚本用到 $HOME ,那么解决方法就是 export HOME=/root && 一键脚本 。
其他情况
- 有多个目录或订阅?
若在 portal.azure.com 使用 Azure Cloud Shell 运行此脚本,所有命令默认在当前所在目录的默认订阅中执行。若有多个订阅,需要手动指定订阅。
查询所有目录下的所有订阅:az account list --output table
切换到你要使用的订阅:
az account set --subscription "订阅 ID"
然后再运行脚本。
- 不是这三种订阅?
你可以任意修改25-63行的值。
叨叨几句... 22 条评论
「 该评论为私密评论 」
@nobb
你要问些什么问题呢
主机创建成功,脚本无法执行,报错ERROR: Please define HOME environment variable to your home directory,大佬帮帮忙
@fdhfgh
修改你的脚本,声明HOME环境变量;或者在输入命令时使用 export 环境变量=值 && 你的脚本
@admin
可以了,谢谢大佬,但是貌似活不长,我要修改cpu限制才行
@jsoos
新号别用,很快就封。这跟cpu限制没关系,我从来都不限制cpu。你可以试试不挖矿会不会照样封
@admin
那student账户怎么才能挖的长,大佬给点意见呗
@jsoos
这种账号我挖过,拿用了一年,第二年再续期的老号来挖。结论:不封号,但是100美元余额在一天半内用完,收益还不到80元人民币。参考这一时间,你的账号假如在一天后才封,那其实也跟用完100美元差不多的。
@admin
好的,谢谢博主,那用普通的老号开应该比较稳
「 该评论为私密评论 」
@qaz
hostloc经常有人收各种老号,或许可以去那里发帖。不过一般来说很难收到。
@admin
老号有什么要求吗
@qaz
200美元免费套餐用完,即用即付使用3个月以上(可以没有消费记录,一直使用12月内的免费额度)
@admin
大哥 有什么联系方式吗 tg之类的 有些问题想请教
@qaz
「 该评论为私密评论 」
「 该评论为私密评论 」
@阿浪
可以写成 命令1 && 命令2 && 命令3 这种形式,或者修改脚本的第三部分(3.创建开机后要运行的脚本),删除第一行(init = input…),删除最后一行(f.write(f…)),然后按照
f.write(" - 命令" + "\n")
的格式写你要执行的命令,每行一条@admin
收到,感谢大佬,我立刻去试试
「 该评论为私密评论 」
@Amos
我认为老号不会被封,正规的订阅肯定没问题
不是老号千万别用!!你会体会到什么叫做一键封号脚本!!
50收了个老号 挖了大半个月还没被封 赚了千把块 大佬666