LoginSignup
1
1

More than 1 year has passed since last update.

LambdaでWindows インスタンスの Nitro 世代移行を自動スケジューリング化してみた

Posted at

はじめに

r4からr5など、XenベースからNitroベースの最新世代へのEC2インスタンスタイプの変更時には
ドライバーのアップグレード等ダウンタイムが生じるOS内部作業が伴います。

必然的にダウンタイムが長くなるため平日日中帯を避けたいケースも多いのではないでしょうか。
Lambda(python)で自動スケジューリング化に挑戦してみました。

環境

EC2インスタンスタイプ:r4.largeからr5.largeへ変更
EC2のOS:Windows server 2012 R2
Lambdaで使うランタイム:Python3.9

作業全体像

今回の作業は以下ドキュメントに沿って行います。

  1. AWS PV ドライバーのインストールとアップグレード
  2. ENA のインストールとアップグレード
  3. AWS NVMe ドライバーをアップグレード
  4. EC2Config および EC2Launch の更新
  5. ベアメタルインスタンスのシリアルポートドライバーのインストール
  6. 電源管理設定の更新
  7. 新しいインスタンスタイプ用のインテルチップセットドライバーの更新

上記のうちパート1〜3はSSMオートメーションの AWSSupport-UpgradeWindowsAWSDrivers を使用することで自動化可能です。
AWSSupport-UpgradeWindowsAWSDrivers はドライバインストール作業前に変更前AMIを取得してくれる事もありがたいです。

多くの場合パート5、パート7は不要になります。パート4、パート6はSSMのRun Commandから実行可能です。
EC2LaunchはWindows Server 2016 および 2019向けの為、今回はEC2Configの更新になります。

以下の流れをLambdaで実行することでWindows インスタンスの Nitro 世代移行を自動スケジューリング化できます。

  1. SSM オートメーションで AWSSupport-UpgradeWindowsAWSDrivers を実行
  2. EC2を停止⇨タイプ変更⇨起動
  3. SSM Run CommandでEC2Configの更新、電源管理設定の更新を実行

作業前確認

現在のバージョンや設定内容を確認します

NVMEドライバ
PVデバイスマネージャー

AWS PV ドライバー

PS C:\Users\Administrator> Get-ItemProperty HKLM:\SOFTWARE\Amazon\PVDriver | Select-Object Version

Version
-------
8.4.0

ENAサポートを確認

aws ec2 describe-instances --instance-ids インスタンスID  --query "Reservations[].Instances[].EnaSupport" 
[
    true
]

電源設定

PS C:\Users\Administrator> powercfg -q 381b4222-f694-41f0-9685-ff5bb260df2e 7516b95f-f776-4464-8c53-06167f40cc99
電源設定の GUID: 381b4222-f694-41f0-9685-ff5bb260df2e  (バランス)
  GUID エイリアス: SCHEME_BALANCED
  サブグループの GUID: 7516b95f-f776-4464-8c53-06167f40cc99  (ディスプレイ)
    GUID エイリアス: SUB_VIDEO
    電源設定の GUID: 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e  (次の時間が経過後ディスプレイの電源を切る)
      GUID エイリアス: VIDEOIDLE
      利用可能な設定の最小値: 0x00000000
      利用可能な設定の最大値: 0xffffffff
      利用可能な設定の増分: 0x00000001
      利用可能な設定の単位: 秒
    現在の AC 電源設定のインデックス: 0x00000258
    現在の DC 電源設定のインデックス: 0x0000012c

IAMロール、Lambdaの設定

作業対象のEC2にSSMから操作できるようにIAMロールを付与します。
ロールに付与する権限はAmazonSSMManagedInstanceCoreを使用しました
スクリーンショット 2021-11-15 17.46.36.png

ここからはLambda関数の作成です
ランタイムはpython3.9を使用しました
スクリーンショット 2021-11-15 17.55.35.png

実行ロール作成後に作成したロールがSSMを利用可能なように権限を修正します
設定>アクセス権限から作成したロール名がリンクになっているのでクリックします
スクリーンショット 2021-11-15 17.57.46.png

IAMロールにAmazonEC2FullAccess,AmazonSSMFullAccessを付与します
スクリーンショット 2021-11-15 18.05.52.png

信頼関係を追加します
スクリーンショット 2021-11-15 18.02.25.png

Lambdaはデフォルトのタイムアウト時間が3秒です。
今回は10分以上かかる見込みのため最大値の15分に設定します
スクリーンショット 2021-11-15 18.07.45.png

実装

Lambda関数のコードには以下を記述します
今回の作業ではAWSSupport-UpgradeWindowsAWSDriversの実行に8分程度かかった為、
AWSSupport-UpgradeWindowsAWSDriversの実行ステータスを確認して終了次第、次のStepに進むようにしています

EC2のスペック変更も停止状態のステータスを確認後に実施するようにしています
作業を自動化する場合、特に失敗時には通知が欲しいのでSNSに通知するようにしました。

import boto3
import os 
import time

ssm = boto3.client('ssm')
sns = boto3.client('sns')
ec2 = boto3.client('ec2')
#変更対象のEC2
Instance = 'インスタンスID'
disired_type = '変更後のインスタンスタイプ'

def lambda_handler(event, context):
    try:
        upgrade_drivers()
    except:
        sns.publish(
            TopicArn='通知先SNSのARN',
            Subject=os.environ['AWS_LAMBDA_FUNCTION_NAME'] + "のエラーが発生しました",
            Message='詳細はCloudwatch logsを参照'
            ) 

def ssm_hundler():
    ec2_ssm_response = ssm.describe_instance_information(
    InstanceInformationFilterList=[
            {
            'key': 'InstanceIds',
            'valueSet': [
                    Instance,]},
                    ])

    if any(ec2_ssm_response['InstanceInformationList']):
        update_config()
        update_power_settings()
    else:
        print('SSMが実行可能な状態まで待機します')
        time.sleep(30)
        ssm_hundler()

def update_config():
    ec2_config_response = ssm.send_command(
    InstanceIds=[f'{Instance}'],
    DocumentName='AWS-UpdateEC2Config',
    DocumentVersion='1'
    )

def update_power_settings():
    ec2_config_response = ssm.send_command(
    InstanceIds=[f'{Instance}'],
    DocumentName='AWS-RunPowerShellScript',
    DocumentVersion='1',
    Parameters={
    'commands': [
        'powercfg /setacvalueindex 381b4222-f694-41f0-9685-ff5bb260df2e 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0',
        'powercfg /setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0',
        'powercfg /setacvalueindex a1841308-3541-4fab-bc81-f71556f20b4a 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0'
    ]   } )

def change_instance_type():
    ec2_stop_responce = ec2.stop_instances(
    InstanceIds=[Instance,])
    time.sleep(90)

    ec2_stop_status = ec2_stop_responce['StoppingInstances'][0]['CurrentState']['Name']

    while ec2_stop_status == 'stopping' or ec2_stop_status ==  'pending':
        time.sleep(30)
        ec2_current_response = ec2.describe_instances(InstanceIds=[Instance,])
        ec2_stop_status = ec2_current_response['Reservations'][0]['Instances'][0]['State']['Name']
        print('EC2の状態は'+ec2_stop_status)


    if ec2_stop_status == 'stopped':
        ec2_modify_response = ec2.modify_instance_attribute(
        InstanceId=Instance,
        InstanceType={'Value': disired_type})
        time.sleep(10)
        ec2_start_responce = ec2.start_instances(
        InstanceIds=[Instance])
        time.sleep(30)
        print('SSMコマンドに移ります')
        ssm_hundler()


def upgrade_drivers():
    automation_response = ssm.start_automation_execution(
    DocumentName='AWSSupport-UpgradeWindowsAWSDrivers',
    DocumentVersion='$DEFAULT',
    Parameters={
        'InstanceId': [Instance]
        }
    )
    automation_id = automation_response['ResponseMetadata']['RequestId']


    automation_check = ssm.describe_automation_executions(
    Filters=[
        {
            'Key': 'ExecutionId',
            'Values': [
                automation_id
                ]},
        ]
        )
    automation_status = automation_check['AutomationExecutionMetadataList'][0]['AutomationExecutionStatus']

    while automation_status == 'Pending' or automation_status == 'InProgress':
        time.sleep(60)
        automation_check = ssm.describe_automation_executions(
        Filters=[
            {
                'Key': 'ExecutionId',
                'Values': [
                    automation_id
                    ]},
            ])
        automation_status = automation_check['AutomationExecutionMetadataList'][0]['AutomationExecutionStatus']
        print(automation_status)


    if automation_status == 'Success':
        change_instance_type()

スケジューリング設定

トリガーを追加します
スクリーンショット 2021-11-15 18.10.54.png

Event Bridge(Cloudwatch Events)を選びます
選んだ後にcron方式などで日時のスケジューリングができます。
スクリーンショット 2021-11-15 18.14.49.png

ここまでで設定は完了です!

作業後確認

作業後のバージョンや設定内容を確認します

NVMEドライバー

作業後PVデバイスマネージャー

AWS PV ドライバー

PS C:\Users\Administrator> Get-ItemProperty HKLM:\SOFTWARE\Amazon\PVDriver | Select-Object Version

Version
-------
8.4.0

ENAサポートを確認

aws ec2 describe-instances --instance-ids インスタンスID  --query "Reservations[].Instances[].EnaSupport" 
[
    true
]

電源設定

PS C:\Users\Administrator> powercfg -q 381b4222-f694-41f0-9685-ff5bb260df2e 7516b95f-f776-4464-8c53-06167f40cc99
電源設定の GUID: 381b4222-f694-41f0-9685-ff5bb260df2e  (バランス)
  GUID エイリアス: SCHEME_BALANCED
  サブグループの GUID: 7516b95f-f776-4464-8c53-06167f40cc99  (ディスプレイ)
    GUID エイリアス: SUB_VIDEO
    電源設定の GUID: 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e  (次の時間が経過後ディスプレイの電源を切る)
      GUID エイリアス: VIDEOIDLE
      利用可能な設定の最小値: 0x00000000
      利用可能な設定の最大値: 0xffffffff
      利用可能な設定の増分: 0x00000001
      利用可能な設定の単位: 秒
    現在の AC 電源設定のインデックス: 0x00000000
    現在の DC 電源設定のインデックス: 0x00000000

おわりに

普段の業務ではプログラミングを使わないのですが、自己学習していたPythonを活用したい!と思い挑戦してみました。
boto3のレスポンスは多次元配列になっていて、最初は初心者の自分には取扱が難しかったです。
根気強く取り組む内にboto3のDocumentのレスポンスシンタックスから取得したい値を抽出できるようになってきました。
今後もSSMやLambdaをうまく使って作業の自動化を進めていきたいと思います。

1
1
0

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
1