本文最后更新于2021年5月16日,其中的信息可能已经有所发展或变化。

手上有大量的计算资源怎么办?正经业务用不上这么多,又不想浪费它们,不如来挖矿吧。花了两天时间学习了 Azure CLI,写了个一键创建所有机器占满所有配额,且能自动执行命令的 Python 脚本。本脚本适合挖不需要 GPU 的币,比如 XMR(门罗币)。一般矿池都会为你准备好一键脚本。运行本脚本后,只需输入矿池给你的一键脚本,即可批量用完所有配额创建虚拟机。

什么是Azure CLI?

Azure Command-Line Interface(Azure CLI)是一组用来创建和管理 Azure 资源的命令,可用来快速使用 Azure(侧重于自动化)。

思路

  1. 检查配额以确定订阅类型,并确定要开的虚拟机数量。默认每个区域的配额都相同,因此只需查询美国东部地区的配额。
  2. 创建资源组。资源组是用于保存虚拟机等资源的容器。
  3. 创建开机后要运行的脚本。
  4. 批量创建虚拟机并运行挖矿脚本。创建实例类型为 F/FS 系列的虚拟机,因为该系列针对计算优化。在创建虚拟机时创建 shell 脚本并指定 cloud-init 参数即可实现虚拟机创建成功后自动运行脚本。
  5. 信息汇总,显示共创建了多少台虚拟机。

大体思路已经定下来,接下来就是了解 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
      

      1.jpg

    • 查询美国东部地区的配额
      az vm list-usage --location "East US" --query "[?localName == 'Total Regional vCPUs'].limit"
      

      2.jpg

关于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)
print("\n------------------------------------------------------------------------------\n")
print("以下是已创建的虚拟机列表:")
get_default_cli().invoke(['vm', 'list', '--query', '[*].name'])
print("\n\n-----------------------------------------------------------------------------\n")



# 如果想删除脚本创建的所有资源,取消注释以下语句
# get_default_cli().invoke(['group', 'delete', '--name', 'myResourceGroup',
# '--no-wait', '--yes'])
# print("删除资源组成功")

点击下载脚本

运行环境

Python 3.9以下版本,azure-cli 模块,contextlib 模块。

首先确保你的Azure账号开过虚拟机,否则的话先随意创建一台机器,到了创建的最后一步,显示“验证通过”就可以关闭页面了,不需要真正创建。

image.png

运行这个脚本最简单的方法是进入Azure门户,点击右上角的命令行图标  image.png  进入Azure Cloud Shell

选择“创建存储”

QQ截图20210101163906.jpg

选择“Bash”

QQ截图20210101163845.jpg

输入

pip install azure-cli

等待安装完成后,创建一个文件,复制粘贴代码,然后用  python 文件名  运行它

01.jpg02.jpg

2021年3月21日更新:也许是 get_default_cli() 更新后导致 f.getvalue() 取不到值。手动修改第29行 limit = f.getvalue() 中的 “f.getvalue()” 整个部分替换为"区域配额"(包括英文引号)即可。Azure for Students是6,即用即付是10,免费试用订阅是4。虚拟机创建完成后显示虚拟机列表,无需理会最后的报错。

需要注意的是,若你的账号不是老号,或者有一定消费的号,对于这种大量开机器的行为(即使不挖矿)基本上会被匹配为恶意用户,在任何一个云计算平台都会封号,或者要求验证信用卡账单。

对于Azure for Students订阅,新号几乎100%封号,别用了本脚本又来说封号。新号还不如只开几台来挖,老号才适合本脚本。使用本脚本一键把配额用满,因为计费系统有延迟,100美元余额能用到500美元左右订阅才被禁用。(余额更新不及时,最好几天后再去看。第二年续期时余额会重置为100美元。)

故障排查

  • 你的脚本无法运行
    首先看看你的脚本是否能在最新的Ubuntu机器上运行。
  • 你的脚本能在普通的机器上运行,但批量创建机器后无法运行
    使用用户名azureuser和密码6uPF5Cofvyjcew9登录虚拟机,检查  /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行的值。