1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IBM Cloud: SoftLayer APIによるベアメタルサーバ管理

Last updated at Posted at 2025-02-25

はじめに

IBM Cloud ClassicのAPI機能を利用して、Classic環境におけるベアメタルサーバの管理をご紹介します。
多数のベアメタルサーバを管理していると定期的に発生するファームウェアバージョンアップ等の負担が大きいかと思います。本機能にて、ベアメタルサーバのバージョン確認及びそれらのアップデート操作をAPI等で実現する事が可能となります。
また、これらAPI機能を利用する事で、カスマイズした運用管理ツールに組み込み自身の運用に最適化した環境も構築する事が可能となります。

本稿で利用する環境

  • ベアメタルサーバ:IBM Classic環境にて払い出されたベアメタルサーバ
  • SoftLayer API インタフェース:Classic(旧SoftLayer)環境のAPI管理機能
  • Pythonスクリプト:SoftLayer APIサイトで公開されている「Hardware components and versions」を利用

SoftLayer APIについて

最初にAPIリファレンス概要をご確認下さい。また、SoftLayer API Getting Startedにて動画説明やスライド資料が掲載されておりますので、SoftLayer APIの理解にご活用下さい。
SoftLayer APIにて実現できる機能は、こちらのSoftLayer API Referenceにて検索、確認する事ができます。本Referenceに記載されているServiceとData Typeに関しては、少し分かりづらいので以下のQiita三部作記事をご参考下さい。

Pythonスクリプトについて

SoftLayer APIは、プロトコルとしてSOAP,XML-RPC,RESTをサポートしております。今回は、Pythonのスクリプトを利用して、XML-RPCを利用します。
こちらにPythonのサンプルスクリプトが掲載されてます。今回は、ベアメタルサーバのハードウェア情報を収集表示、ファームウェアのアップデートを行いたいので、Hardware components and versionsを利用します。以下、Pythonファイルとなります。

ch_hw_info.py
import SoftLayer
import click
from prettytable import PrettyTable


class HardwareComponents():

    def __init__(self):
        self.client = SoftLayer.create_client_from_env()

    def hardware_information(self, hostname):
        """
        Get the Hardware Server Information.
        """
        mask = 'mask[hardwareStatus,lastOperatingSystemReload,datacenter,billingItem[hourlyFlag],' \
               'lastTransaction[transactionGroup],processorPhysicalCoreAmount,memoryCapacity,' \
               'operatingSystem[softwareLicense[softwareDescription]]]'
        hardware_id = self.get_hardware_detail(hostname).get('id')
        result = self.client['SoftLayer_Hardware_Server'].getObject(id=hardware_id, mask=mask)

        table = PrettyTable()
        table.title = "Hardware Server details"
        table.field_names = ['name', 'value']
        table.align['name'] = 'r'
        table.align['value'] = 'l'

        table.add_row(['Name', result['fullyQualifiedDomainName']])
        table.add_row(['Status', result['hardwareStatus']['status']])
        table.add_row(['Id', result['id']])
        table.add_row(['Started', result['provisionDate']])
    #    table.add_row({'Reloaded', result['lastOperatingSystemReload']['modifyDate']})
        table.add_row(['MFR Serial #', result['manufacturerSerialNumber']])
        table.add_row(['Location', result['datacenter']['longName']])
        table.add_row(['Serial #', result['serialNumber']])
        table.add_row(['Billing', "Hourly" if result['billingItem']['hourlyFlag'] else "Monthly"])
        table.add_row(['Last transaction', result['lastTransaction']['transactionGroup']['name']])
        table.add_row(['cores', result['processorPhysicalCoreAmount']])
        table.add_row(['memory', result['memoryCapacity']])
    #    table.add_row(['public_ip', result['primaryIpAddress']])
        table.add_row(['private_ip', result['primaryBackendIpAddress']])
        table.add_row(['os', result['operatingSystem']['softwareLicense']['softwareDescription']['manufacturer']])
        table.add_row(['os_version', result['operatingSystem']['softwareLicense']['softwareDescription']['version']])
        print(table)

    def list_hardware_components(self, hostname):
        """
        Get the Hardware Server Components.
        """
        mask = 'mask[activeComponents[hardwareComponentModel[hardwareGenericComponentModel[hardwareComponentType]]]]'
        hardware_id = self.get_hardware_detail(hostname).get('id')
        result = self.client['SoftLayer_Hardware_Server'].getObject(id=hardware_id, mask=mask)

        table = PrettyTable()
        table.title = "Hardware Components"
        table.field_names = ['id', 'hardwareComponentType', 'capacity', 'manufacturer', 'name', 'version']

        capacity = None
        component_type = None
        for component in result['activeComponents']:
            if 'hardwareGenericComponentModel' in component['hardwareComponentModel']:
                capacity = component['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']
                component_type = component['hardwareComponentModel']['hardwareGenericComponentModel'][
                    'hardwareComponentType']['type']
                table.add_row([component['hardwareComponentModel']['id'],
                               component_type,
                               capacity,
                               component['hardwareComponentModel']['manufacturer'],
                               component['hardwareComponentModel']['name'],
                               component['hardwareComponentModel']['version']
                               ])

        print(table)

    def list_hardware_component_firmware(self, hostname, component_id=None):
        """
        Get the last firmware version updated for a specific hardware component.
        """
        mask = 'mask[hardwareComponentModel[firmwares]]'
        hardware_id = self.get_hardware_detail(hostname).get('id')

        if component_id:
            result = self.client['SoftLayer_Hardware_Server'].getComponents(mask=mask, id=hardware_id)

            table = PrettyTable()
            table.title = "Hardware Component Firmware"
            table.field_names = ['id', 'createDate', 'hardwareComponentModelId', 'version']

            list_firmware = list()
            for component in result:
                if component_id == component['hardwareComponentModel']['id']:
                    for firmware in component['hardwareComponentModel']['firmwares']:
                        list_firmware.append(firmware)

            if not len(list_firmware) == 0:
                list_result = sorted(list_firmware, key=lambda x: x['createDate'])
                last_firmware = list_result[len(list_result) - 1]
                table.add_row([last_firmware['id'],
                               last_firmware['createDate'],
                               last_firmware['hardwareComponentModelId'],
                               last_firmware['version'],
                               ])

            print(table)
        else:
            print('The hardware component id is required.')

    def update_firmware(self, hostname, ipmi=0, raid_controller=0, bios=0, hard_drive=0):
        """
        Firmware Update.
        """
        result = None
        try:
            hardware_id = self.get_hardware_detail(hostname).get('id')
            result = self.client['SoftLayer_Hardware_Server'].createFirmwareUpdateTransaction(ipmi,
                                                                                              raid_controller,
                                                                                              bios,
                                                                                              hard_drive,
                                                                                              id=hardware_id)
        except SoftLayer.SoftLayerAPIError as e:
            print("Update failed. faultCode=%s, faultString=%s" % (
                e.faultCode, e.faultString))

        if result:
            print("Update firmware successfully")
        else:
            print("Update firmware failed")

    def get_hardware_detail(self, hostname):
        """
        Get an specific hardware server detail from the account through the hostname.
        """
        object_filter = {
            "hardware": {
                "hostname": {
                    "operation": hostname
                }
            }
        }
        user_detail = self.client['SoftLayer_Account'].getHardware(filter=object_filter)

        return user_detail[0]

@click.command()
@click.argument('hostname')
@click.option('--component-id', type=click.INT,
              help='Hardware Component Id')
@click.option('--ipmi', type=click.INT,
              help='Set this hardware Component to 1 (to upgrade) or 0 (not upgrade)')
@click.option('--raid-controller', type=click.INT,
              help='Set this hardware Component to 1 (to upgrade) or 0 (not upgrade)')
@click.option('--bios', type=click.INT,
              help='Set this hardware Component to 1 (to upgrade) or 0 (not upgrade)')
@click.option('--hard_drive', type=click.INT,
              help='Set this hardware Component to 1 (to upgrade) or 0 (not upgrade)')

def main(hostname, component_id, ipmi, raid_controller, bios, hard_drive):
    main = HardwareComponents()
    # Uncomment this to print out the API calls made.
    """
    Hardware Server Detail.
    """
    main.hardware_information(hostname)

    """
    Hardware Server Components.
    """
    main.list_hardware_components(hostname)

    """
    Show the last firmware version updated for a specific hardware component.
    Get the component_id from the second request.
    """
    main.list_hardware_component_firmware(hostname, component_id=component_id)

    """
    Update Firmware.
    Set the options impi, raid-controller, bios or hard-drive with "1" if you 
    want to update the component or "0" if you do not want to update.
    """
    main.update_firmware(hostname, ipmi=ipmi, raid_controller=raid_controller, bios=bios, hard_drive=hard_drive)

if __name__ == "__main__":
    main()

SoftLayer APIアクセスの為の認証情報

アクセスの為の認証情報を確認します。
SL_USERNAMEは、「アカウントID_メールアドレス」となります。具体的には、Cloudコンソールの「IAM」→「ユーザー」部分に表示される「VPNパスワード」欄に利用される「ユーザ名」と同じになります。

SL_API_KEYは、Classic InfrastructureのAPI Keyを利用します。IAM環境用ではなく、Classic環境用を利用しますのでご注意下さい。

詳細は、IBM Cloud Block Storage 大掃除-許可ホストのないものをリスト・削除するをご参照下さい。

USERNAME/API Key環境変数設定
$export SL_USERNAME=XXXXXXX_masaharu.esaki@ibm.com
$export SL_API_KEY='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXe8b02e3d483e4a3b45c5'

ベアメタルサーバ情報の表示

Classicベアメタルサーバの作成部分の説明は省略させて頂きます。
サーバ名「baremetal01-easki」についてハードウェア情報をPythonスクリプトで表示します。Pythonスクリプトをサーバ名を引数に実行します。

ハードウェア情報の表示
firmware_ver_check % python3 ch_hw_info.py baremetal01-easki
+-------------------------------------------------------+
|                Hardware Server details                |
+------------------+------------------------------------+
|             name | value                              |
+------------------+------------------------------------+
|             Name | baremetal01-easki.ibmcloud.private |
|           Status | ACTIVE                             |
|               Id | 3241136                            |
|          Started | 2025-02-19T18:37:21-06:00          |
|     MFR Serial # | C8150LK20NT0047                    |
|         Location | Tokyo 5                            |
|         Serial # | SL01HVUG                           |
|          Billing | Hourly                             |
| Last transaction | Firmware Update                    |
|            cores | 4                                  |
|           memory | 64                                 |
|       private_ip | 10.193.139.115                     |
|               os | CentOS                             |
|       os_version | 9-64                               |
+------------------+------------------------------------+
+-------------------------------------------------------------------------------------------------------------------+
|                                                Hardware Components                                                |
+------+-----------------------+----------+--------------+------------------------+---------------------------------+
|  id  | hardwareComponentType | capacity | manufacturer |          name          |             version             |
+------+-----------------------+----------+--------------+------------------------+---------------------------------+
| 1993 |       Hard Drive      |   4000   |   Seagate    |  Enterprise Capacity   |           ST4000NM0035          |
| 1388 |    Drive Controller   |    8     |     LSI      | Avago MegaRAID 9361-8i | SATA/SAS - MegaRAID SAS 9361-8i |
| 1993 |       Hard Drive      |   4000   |   Seagate    |  Enterprise Capacity   |           ST4000NM0035          |
| 3398 |      Network Card     |    4     |  SuperMicro  |     AOC-STG-i4T-O      |               2.00              |
| 3244 |          RAM          |    32    |    Hynix     | 32GB DDR4 2Rx8 NON REG |   32GB DDR4 2Rx8 2666 NON REG   |
| 3095 |      Motherboard      |    1     |  SuperMicro  |     X11SCW-F_R1.02     |    Intel Xeon SingleProc SATA   |
| 3147 |        Battery        |    1     |   Broadcom   |        49571-15        |                1                |
| 2369 |    Remote Mgmt Card   |    1     |    Aspeed    |   AST2500 - Onboard    |            IPMI - KVM           |
| 3244 |          RAM          |    32    |    Hynix     | 32GB DDR4 2Rx8 NON REG |   32GB DDR4 2Rx8 2666 NON REG   |
| 3970 |      Power Supply     |    1     |  SuperMicro  |      PWS-504P-1R       |               2.0               |
| 1871 |       Backplane       |    4     |  SuperMicro  |     BPN-SAS3-815TQ     |          4 Port Passive         |
| 3163 |       Processor       |   3.8    |    Intel     |    Xeon-CoffeeLake     |         E-2174G-Quadcore        |
| 852  |    Drive Controller   |    2     |  Mainboard   |        Onboard         |   SATAIII IDE/AHCI Controller   |
| 3970 |      Power Supply     |    1     |  SuperMicro  |      PWS-504P-1R       |               2.0               |
| 2509 |    Security Device    |    1     |  SuperMicro  |     AOM-TPM-9671V      |       Infineon TPM Module       |
+------+-----------------------+----------+--------------+------------------------+---------------------------------+
The hardware component id is required.
Update failed. faultCode=SoftLayer_Exception_MissingParameter, faultString=No firmware updates selected to be executed.
Update firmware failed

ファームウェアのバージョンを表示するには、コンポーネントIDとしてMotherboardのIDを指定します。

ファームウェアバージョン表示
firmware_ver_check % python3 ch_hw_info.py --component-id 3095 baremetal01-easki
                            << snip >>
                            
+-------------------------------------------------------------------------------+
|                          Hardware Component Firmware                          |
+--------+---------------------------+--------------------------+---------------+
|   id   |         createDate        | hardwareComponentModelId |    version    |
+--------+---------------------------+--------------------------+---------------+
| 281979 | 2024-08-06T07:57:25-07:00 |           3095           | 2.1 2-10-2023 |
+--------+---------------------------+--------------------------+---------------+

ファームウェアのバージョンアップ

ファームウェアのバージョンアップを実施する場合は、「--ipmi」オプションを「1」に指定して実行します。

ファームウェアバージョンアップ実施失敗
firmware_ver_check % python3 ch_hw_info.py --component-id 3095 --ipmi 1 
                            << snip >>
                            
+-------------------------------------------------------------------------------+
|                          Hardware Component Firmware                          |
+--------+---------------------------+--------------------------+---------------+
|   id   |         createDate        | hardwareComponentModelId |    version    |
+--------+---------------------------+--------------------------+---------------+
| 281979 | 2024-08-06T07:57:25-07:00 |           3095           | 2.1 2-10-2023 |
+--------+---------------------------+--------------------------+---------------+
Update failed. faultCode=SoftLayer_Exception_Public, faultString=Server must be offline before firmware update.

ファームウェアのバージョンアップに際して、サーバの停止が必要な為、失敗したようです。curlコマンドでサーバを停止します。id(3241136)は、Hardware Server Detailsに表示されているIDを利用します。

サーバ停止
firmware_ver_check % curl --location 'https://api.softlayer.com/rest/v3.1/SoftLayer_Hardware_Server/3241136/powerOff' \
--header 'Authorization: Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXe8b02e3d483e4a3b45c5'

再度、Pythonファイルを実行すると、サーバのファームウェアアップデートが開始されます。
以下のようにデバイスリスト上で、ファームウェアのアップデート更新が実施されている事が確認できます。
update1.png

update2.png

その後、1時間30分程度で、ファームウェアの更新が完了しました。update3.png

まとめ

SoftLayer APIは、IBM Cloudコンソールのバックエンドとして稼働している環境となります。Cloud コンソールで提供していない機能、またはAPI操作を組み合わせてご利用環境の運用の自動化が可能となります。頻繁に実施する定形的な操作は、APIによる自動化が可能となります。是非、API Referenceをご参照頂き、ご利用環境に適した運用環境を構築下さい。

参考サイト

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?