LambdaでWorkSpacesを自動再起動する

2016.02.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ベルリンの半瀬です。

はじめに

「Workspaceに対してステータスチェックを行い、Unhealthyなステータスのものがあれば再起動を実行する」というフローを自動で行ないたいという相談を受けました。
EC2インスタンスのステータス変更をトリガーとするときは CloudWatch Eventsが便利ですが、WorksSpacesについては今のところ用意されていません。

そのため、今回は CloudWatch Alarm → SNS → Lambdaで作成する必要があります。
20160217-13

※ 参考 AWS Blackbelt 2015シリーズ Amazon CloudWatch & Amazon CloudWatch Logs 「CloudWatchを介したシステム運用の自動化」

手順

それではやってみましょう。

Amazon WorkSpacesを起動

テスト用のWorkSpaceを同ディレクトリ配下に2つ起動します。(testhanse, testhanse2)
20160218-01

CloudWatchのメトリックスを確認

Workspacesのメトリックスが作成されていることを確認します。
今回は組織名別(ディレクトリID別)のメトリックス「異常(UnHealthy)」を利用することになります。 20160214-01

CloudWatch Alarmを作成

  1. <CloudWatch>→<アラーム>で「アラーム作成」を選択します
  2. <メトリックスの選択>→<WorkSpacesメトリックス>→<ディレクトリID 別>を選択
  3. 対象となるディレクトリIDのメトリックス「異常(UnHealthy)」を指定し<次へ>をクリック
  4. 閾値は、「ディレクトリ配下のワークスペースに1台でも異常があった場合」を検知するため、「>0」とします。
    要調整ですが、今回は「連続5回の失敗」でアラーム検知とします。
  5. 名前にはディレクトリIDを入れておきます。「{DirectoryId}_unhealthy-sns-lambda」としておきます。
  6. アクションの設定は後ほど行います。
    20160217-01

SNS Topicを作成

Lambdaに渡すためのTopicです。今回は「topic-lambda-ws-reboot」と名付けます。
20160217-02

IAM Roleを作成

Lambda設定の前に、LambdaにWorkSpacesの操作権限を持たせるため、IAM Roleを準備します。
1. ロールタイプの選択で「AWS Lambda」を選択
20160213-05
2. ポリシー「AmazonWorkSpacesAdmin」を選択し作成します。
3. 作成完了。今回は「lambda-workspaces」と名付けています。
20160213-06

Lambdaを作成

メインとなるLambdaを設定していきます。
今回はpythonで記述をします。
1. <Create a Lambda function>
2. Step1: Select blueprintで「sns-message-python」を指定して<Next>
3. Step2: Configure event sourcesで先ほど作成したLambda用のSNS-Topic「topic-lambda-ws-reboot」を指定し、<Next>
20160217-03
4. Step3: Configure functionのLambda function codeで以下のコードを記載します。
下記コードですが、AWS APIの仕様上の上限により、25台以上のAD構成では機能しない可能性があります。25台以上の構成に対応したコードはただいま確認中です。 - 20160421

from __future__ import print_function

import json
import urllib
import boto3

print('Loading function')

client = boto3.client('workspaces')

def lambda_handler(event, context):
    message = event['Records'][0]['Sns']['Message']
    message = json.loads(message)
    dimensions = message['Trigger']['Dimensions'][0]['value']
    directoryId = dimensions
    describe = client.describe_workspaces(
        DirectoryId = directoryId
    )
    workspacesList = describe['Workspaces']
    for workspace in workspacesList:
        workspaceId = workspace['WorkspaceId']
        checkState = workspace['State']
        if checkState == "UNHEALTHY":
            reboot = client.reboot_workspaces(
                RebootWorkspaceRequests=[
                    {
                        'WorkspaceId': workspaceId
                    },
                ]
            )
            return reboot

5. 同Step3: Configure functionのLambda function handler and roleでLambda用のIAM Role「lambda-workspaces」を指定し、<Next>
20160217-04
6. Step4: Review のEvent sourcesで「Enable event source」を「Enable now」とし、<Create Function>。
20160213-08
7. Lambdaの設定は完了です

CloudWatch Alarmでアクション設定

最後にアラームのアクションを設定しておきます。
先ほど作成したCloudWatchアラーム「{DirectoryId}-unhealthy-lambda」内の通知アクションで、「警告」時の送信先をSNS-Topic「topic-lambda-ws-reboot」とします。
20160217-05

テスト

設定はすべて完了です。次に動作テストを行います。

設置したlambdaは以下のような動作をする様に作成しています。
1. SNS MessageからWorkspaceのDirectoryIdを受け取る
2. DirectoryIdから、当該ディレクトリ配下のWorkspaceId一覧を取得する
3. WorkspaceIdごとにStateをチェックし、"UNHEALTHY"なものはreboot実行
4. reboot実行結果を返り値として返す
 ※ 1台でもreboot実行されれば「200」、実行されなければ「null」になります。

通常時のテスト

ステータスが"AVAILABLE"(異常なし)のときに作動しないか念のため確認します。
1. テスト用jsonを読み込ませて実行します。
2. <Actions>→<Configure test event>→<Input test event>:<template>で以下を貼り付けます

{
  "Records": [
    {
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:EXAMPLE",
      "EventSource": "aws:sns",
      "Sns": {
        "SignatureVersion": "1",
        "Timestamp": "1970-01-01T00:00:00.000Z",
        "Signature": "EXAMPLE",
        "SigningCertUrl": "EXAMPLE",
        "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
        "Message": "{\"AlarmName\":\"AAAAAA\",\"AlarmDescription\":\"AAAAAA\",\"AWSAccountId\":\"NNNNNNNNNNNN\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (0.0) was greater than or equal to the threshold (0.0).\",\"StateChangeTime\":\"2016-02-13T18:15:41.677+0000\",\"Region\":\"APAC - Tokyo\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"Unhealthy\",\"Namespace\":\"AWS/WorkSpaces\",\"Statistic\":\"AVERAGE\",\"Unit\":null,\"Dimensions\":[{\"name\":\"DirectoryId\",\"value\":\"d-XXXXXXXXXX\"}],\"Period\":60,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":0.0}}",
        "MessageAttributes": {
          "Test": {
            "Type": "String",
            "Value": "TestString"
          },
          "TestBinary": {
            "Type": "Binary",
            "Value": "TestBinary"
          }
        },
        "Type": "Notification",
        "UnsubscribeUrl": "EXAMPLE",
        "TopicArn": "arn:aws:sns:EXAMPLE",
        "Subject": "TestInvoke"
      }
    }
  ]
}

 ※ "Message"の箇所をSNSから受け取るフォーマットに合わせています。
 ※ "Message"内 "d-XXXXXXXXXX"はテスト対象のディレクトリIDとしておく必要があります。
3. 上記を準備した上で、<Test>実行をします。
4. 対象ディレクトリ配下のWorkspaceが全て"AVAILABLE"であれば、Rebootは実行されませんので以下のような「null」が返されます。
20160217-06
5. 指定ディレクトリID配下のworkspaceが全て"AVAILABLE"であれば、Rebootされないことが確認できました。

Unhealthy時の動作テスト

  1. 意図的にWorkspaceを"UNHEALTHY"状態にしなければならないのですが、今回は対象のWorkspaceに対して無理やりネットワークアダプタを全て無効にする方法をとります。
    ※ これにより、管理ポートが閉じられてしまいますので、実際のReboot実行指示は対象のWorkspace側に届きません。また、テスト実行後にAVAILABLEに戻す場合は、Rebuildをする必要があります。
  2. "testhanse"と名付けたWorkspaceのみに実施し、約10分後に管理画面上で"UNHEALTHY"で検知されました。
    20160217-07
  3. Cloudwatch上でもUnhealthyがputされ始め、閾値を超過(5回連続でUnhealthyが0異常)するまで待ちました。
    20160217-08
  4. 閾値を超過し、アラーム状態となります。
    20160217-09
  5. アラーム状態をトリガーに、SNS-topic「topic-lambda-ws-reboot」への通知が成功していることを履歴から確認します。
    20160217-10
  6. WorkSpaces管理コンソール上で、"UNHEALTHY"となっていたWorkspace(testhanse)のみ管理画面上でRebootが実行されていることが確認できます。
    20160217-11
  7. Lambda実行に問題がないことが確認できました。
  8. (テスト対象のネットワークアダプタを全て落としたので、"REBOOTING"から"UNHEALTHY"に戻りますが、1.で上げた通りです。。

まとめ

Reboot実行をするべきステータスのWorkspaceに対して、Lambdaによる自動再起動処理が走るところまで確認できました。

ポイントはアラーム検知の対象として、ディレクトリIDのメトリックスを指定するところです。
ワークスペースIDのメトリックスを指定すると、ワークスペース単位でしかアラームが発報されないため、ディレクトリID配下に数十台のワークスペースを運用する場合は、1台ずつアラーム設定をしていくことになってしまいます。
ディレクトリIDに対するチェックを行い、Lambdaで回すようにすれば、アラーム設定が1つですみます。

なお、CloudWatchアラームの閾値は「2〜3回連続で失敗」あたりで調整すると良いかと思います。
(テスト時に5回連続を指定すると再起動実行まで30分かかりました..

補足

AWSによると、現状Workspace側のStateは
PENDING | AVAILABLE | IMPAIRED | UNHEALTHY | REBOOTING | REBUILDING | TERMINATING | TERMINATED | SUSPENDED | ERROR
の10種類があり、そのうち「IMPAIRED」、「UNHEALTHY」がCloudWatch側で「異常(Unhealthy)」とカウントされるとのこと。 CloudWatchメトリクスが PUT されるタイミングと、WorkSpaces の State の変化のタイミングによっては、CloudWatch「Unhealthy」時に Workspaces のStateが 「Available」となる可能性はありますが、今回のLambdaファンクションでは、明示的にstateが"UNHEALTHY"なものを対象としている為、問題となりません。

それでは〜