Amazon WorkSpacesの「AlwaysOn」「AutoStop」をスケジュールで切り替える仕組みを作ってみた

2年前のブログ記事を少し発展させてみました。
2022.04.30

みなさん、こんにちは!
福岡オフィスの青柳です。

以前、「Amazon WorkSpacesを毎日指定した時刻に自動起動する」という仕組みを作って、ブログ記事として紹介しました。

WorkSpacesの実行モードを「AutoStop」に設定して必要時にのみ起動することで利用料金を抑えることができるのですが、利用する際にWorkSpaceマシンを起動するのに少し時間がかかります。
そこで、Lambda関数とEventBridgeのスケジューリングを使って、始業時刻の少し前にWorkSpaceマシンを起動しておくことで「待ち時間」を無くす仕掛けを作ったという訳です。

しかし、WorkSpacesの料金体系上、月のうち一定時間以上WorkSpacesを利用する場合には、この「自動起動」の仕組みを使ってもコスト的に意味が無いという結論になりました。

(上記ブログより)

WoakSpacesのバンドル (スペック) の選択にもよりますが、概ね、月の利用時間が「80時間」を超えたところで「AlwaysOn」と「AutoStop」の料金が逆転します。
例えば「月曜日から金曜日まで、1日8時間」利用したとすると、月の利用時間は約160~180時間ほどになります。
この場合、価格的に「AutoStop」を使うメリットは無いため、必然的に「AlwaysOn」を採用することになります。

そこで、今回は、もっと極端に「月のうち数日だけWorkSpacesを利用する」というシチュエーションを想定して、新たな仕掛けを作ってみました。

月のうち数日だけ「AlwaysOn」で利用する

例えば、企業で「月初めに事務処理をまとめて行うためのクライアント環境が必要」「でも、月初め以外はほとんど使わない」というシチュエーションを想定します。

そのために、月の始めの数日間は、WorkSpacesの実行モードを「AlwaysOn」に設定して常時起動した状態にしておき、いつでもすぐに利用できるようにしておきます。

期間が終わった後は「AutoStop」に戻すことで、コストを抑えます。

マネジメントコンソールですと、このような操作で実行モードを切り替えることができます。

これを、スケジュールに従って自動実行してみよう!という訳ですね。

月の途中で「AlwaysOn」と「AutoStop」を切り替えると、料金はどうなるの?

WorkSpacesの「よくある質問」ページに、以下の記載があります。

Q: 時間単位の課金と月単位の課金を切り替えられますか?

A: はい。Amazon WorkSpaces の課金方法は、AWS マネジメントコンソールまたは Amazon WorkSpaces API を使用して実行モードを AlwaysOn に切り替えることで、いつでも時間単位から月単位に切り替えられます。切り替えると、すぐに課金方法が時間単位から月単位に変わり、AlwaysON の残りの日数分の使用量は月単位の料金の按分計算となり、その月の切り替え前の AutoStop の基本月額料金と時間単位の使用料に追加されます。実行モードを再び AutoStop に切り替えない限り、引き続き Amazon WorkSpaces は月単位で課金されます。

AWS マネジメントコンソールまたは Amazon WorkSpaces API を使用して実行モードを AutoStop に設定することで、いつでも月単位の課金から時間単位の課金に切り替えられます。その月の Amazon WorkSpaces 料金のお支払いは済んでいるため、月単位の課金から時間単位の課金への切り替えは翌月に有効になります。実行モードを再び AlwaysOn に切り替えない限り、引き続き Amazon WorkSpaces に対して時間単位で課金されます。請求額の更新は、毎月 1 日の 00:00 UTC に行われることにご注意ください。

また、このセルフサービスマネジメント機能が WorkSpaces 管理者によって有効化されている場合、WorkSpaces ユーザーは請求を月単位または時間単位のいずれかで WorkSpaces クライアントから直接切り替えることができます。

要約すると、以下のようになるということですね。

  • 月の途中で「AlwaysOn」と「AutoStop」は切り替えることができる
  • 月の途中で「AlwaysOn」に切り替えると、AlwaysOnの使用量は按分計算になる

WorkSpacesの「料金」ページによると、例えば「東京リージョン、Windowsバンドル、スタンダード (2vCPU/4GiB)」を選択した際の利用料金体系は以下のようになります。

  • AlwaysOnの料金体系: 34.00USD/月
  • AutoStopの料金体系: 固定料金 10.00USD/月 + 従量料金 0.30USD/時間

原則「AutoStop」に設定しておき、月のうち3日間だけ「AlwaysOn」に切り替えた場合の、月額料金合計は以下のように計算できます。

  • AutoStopの固定料金: 10.00 USD
  • AlwaysOnの「3日間分の」固定料金: 34.00 × 3 ÷ 30 = 3.4 USD
  • 合計: 13.4 USD

実行モードをスケジュールで切り替える仕組み

今回、予め決めておいたスケジュールに従って処理を行うために、「AWS Systems Manager」の「Change Calendar」という機能を利用します。

Change Calendarでは、カレンダー上の特定の日や時間帯に「イベント」を作成することができます。

イベントが作成されている日・時間帯は「OPEN」という状態になり、この期間は「サービスを稼働させる」「バッチ処理を実行する」など処理を行わせることができます。
逆に、イベントが作成されていない日・時間帯は「CLOSED」状態となり、この期間は処理を行わないことになります。

元々「Change Calendar」は、Systems Managerの「Automation」や「Run Command」等から利用する前提の機能でしたが、AWSの他のサービスから利用することも可能です。

利用方法のうちの一つとして「Amazon EventBridgeのイベントソースとしてChange Calendarを指定することができる」というものがあります。
今回はこれを利用して、カレンダー上で「CLOSED」から「OPEN」に状態が変わるタイミング、「OPEN」から「CLOSED」に変わるタイミングでイベントを発生させ、Lambda関数を呼び出してWorkSpacesの実行モードを変更するという仕組みを実装したいと思います。

図にすると以下のような感じです。

仕組みを作ってみる

カレンダーを用意する

まず、Systems Manager Change Calendarでカレンダーを作成します。

Systems Managerのメニューから「Change Calendar」(日本語の場合は「カレンダーの変更」) を選択して、「カレンダーを作成」をクリックします。

「カレンダータイプ」の選択ができますが、「DEFAULT_CLOSED」(日本語の場合は「デフォルトで閉じる」) を選択します。

これで、まだ何もイベントが無い「まっさら」なカレンダーが作られました。(イベントが無い日・時間帯は「DEFAULT_CLOSED」に従って「CLOSED」扱いとなる)

「イベントを作成」をクリックして、イベントを定義します。

イベントの期間を「2022/06/01 09:00」~「2022/06/03 18:00」に設定します。
この期間は、常時WorkSpacesを起動しておくという意味になります。

カレンダーの6/1~6/3に「イベント」が作成されました。

これで、今回使うカレンダーの用意ができました

Lambda関数を用意する

次に、WorkSpacesの「実行モード」変更の処理を行うLambda関数を用意します。

今回はPython 3.9で作成しました。

lambda_function.py

import boto3
import os

client = boto3.client('workspaces')

def lambda_handler(event, context):
    # 環境変数を読み込む
    target_directory_id = os.environ.get('TARGET_DIRECTORY_ID', '')

    # イベントデータから「カレンダーの状態」を読み取る
    calendar_state = event['detail'].get('state')

    # カレンダーの状態に応じて、変更する「実行モード」の値を決定する
    if calendar_state == 'OPEN':
        running_mode = 'ALWAYS_ON'
    else:
        running_mode = 'AUTO_STOP'

    # WorkSpaceのリストを取得する
    if target_directory_id == '':
        response = client.describe_workspaces()
    else:
        response = client.describe_workspaces(DirectoryId=target_directory_id)

    # 全てのWorkSpaceについて繰り返す
    for workspace in response['Workspaces']:
        # WorkSpace ID
        workspace_id = workspace['WorkspaceId']
        # WorkSpaceの「実行モード」を変更する
        r = modify_workspace_running_mode(workspace_id, running_mode)
        if r == 0:
            print('WorkSpace "{0}": Success to change the Running Mode into {1}.'.format(workspace_id, running_mode))
        else:
            print('WorkSpace "{0}": Failed to change the Running Mode into {1}.'.format(workspace_id, running_mode))

def modify_workspace_running_mode(workspace_id, running_mode):
    try:
        response = client.modify_workspace_properties(
            WorkspaceId=workspace_id,
            WorkspaceProperties={
                'RunningMode': running_mode
            }
        )
        return 0
    except Exception as e:
        print('[Error] {0}'.format(e))
        return 1

処理のメインとしてはWorkSpaces.Clientクラスのmodify_workspace_propertiesメソッドを呼び出しているだけです。
WorkSpaces — Boto3 Docs 1.22.4 documentation

カレンダー上で「状態」が変更した際にEventBridgeから受け取るイベントデータに「カレンダーがどの状態に変更したのか」の情報が含まれていますので、それを確認して、実行モードを「AlwaysOn」にするのか「AutoStop」にするのかを判断しています。

これらの処理を、全てのWorkSpaceマシンに対して繰り返し行うというものです。

環境変数TARGET_DIRECTORY_IDからWorkSpacesのディレクトリIDを取得するようになっていますので、環境変数を適切に設定しておいてください。

Lambda実行ロールには、以下のIAMポリシーを付与しておきます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "workspaces:DescribeWorkspaces",
                "workspaces:ModifyWorkspaceProperties"
            ],
            "Resource": "*"
        }
    ]
}

WorkSpacesの実行モードを変更するためにworkspaces:ModifyWorkspacePropertiesの権限を付与しています。

これでLambda関数の用意ができました。

EventBridgeの設定

最後に、EventBridgeの「ルール」を作成します。

カレンダー上の状態が変化したタイミングで発生するイベントを検知して、Lambda関数を呼び出すように設定します。

ルールの名前などを適切に入力します。

ルールタイプは「イベントパターンを持つルール」を選択します。

イベントソースは「AWSイベントまたはEventBridgeパートナーイベント」を選択します。

サンプルイベントを確認することができます。

ルールを作成する上で必須ではありませんが、ここでサンプルイベントを確認してテキストファイル等に保存しておくと、Lambda関数のテストを行う際などに助かります

サンプルイベントを参照する場合は、イベントの種類で「AWSイベント」を選択して、サンプルイベントの種類に「Systems Manager」の「Calendar State Change」を選択します。

イベントパターンを設定します。

  • イベントソース: AWSのサービス
  • AWSのサービス: Systems Manager
  • イベントタイプ: Change Calendar
  • リソース: 任意のリソース
  • リソースARN: Systems Manager Change CalendarのARNを入力 (例:arn:aws:ssm:ap-northeast-1:123456789012:document/workspaces-operation-calendar)
  • 状態: 任意の状態
  • イベント: 任意のイベント

今回は「状態」として「任意の状態」を選択しています。

これはすなわち、「OPEN」「CLOSED」いずれの状態に変化した場合であってもイベントを発生させるという意味です。
今回用意したLambda関数は「OPEN」「CLOSED」いずれの状態のイベントにも対応しており、関数の中でイベントデータを参照することで、どちらの状態に変化したのかを判定するようになっています。

ターゲットとして、用意したLambda関数を指定します。

  • ターゲットタイプ: AWSサービス
  • ターゲットを選択: Lambda関数
  • 機能: (用意したLambda関数を選択)

これでEventBridgeのルールが作成されました。

動作を確認する

カレンダーで設定したイベントの「イベント開始日」のタイミング、「イベント終了日」のタイミングで、EventBridgeがイベント発生を検知してLambda関数を呼び出します。

まず、カレンダー上の状態が「CLOSED」から「OPEN」に変わるタイミングでの動作を確認します。

各WorkSpaceマシンの「実行モード」が「AlwaysOn」になったことが分かります。

この時、WorkSpaceマシンのステータスが「STOPPED」(停止) であった場合、実行モードが「AlwaysOn」に変更されたタイミングで強制的に起動されます。
(上の図では、3台が既に起動完了しており、1台は起動中となっています)

次に、カレンダー上の状態が「OPEN」から「CLOSED」に変わるタイミングでの動作を確認します。

各WorkSpaceマシンの「実行モード」が「AutoStop」になったことが分かります。

実行モードが「AutoStop」に変更されても、WorkSpaceマシンのステータスは変わりません。(この時点で強制的に停止したりはしません)

ただし、AutoStopモードの設定に応じて、一定時間 (デフォルトは1時間) アクセスが行われなかったWorkSpaceマシンは、自動的に停止されます。

おわりに

今回は、「Systems Manager Change Calendar」「EventBridge」「Lambda」を連係させることで、スケジュールに従ってWorkSpacesの設定を自動的に変更できることを紹介しました。

もちろん、WorkSpaces以外のAWSサービスにも応用ができると思います。

そういう意味では、今回も「実用的な仕組みの紹介」というよりは「技術的な実験デモ」になってしまった気がしなくもないですね。
まあいいか(笑)