例 2: スクリプト化されたランブック
このランブック例では、次のシナリオに対処します。Emily は AnyCompany Consultants, LLC のシステムエンジニアです。彼女は先に、プライマリデータベースとセカンダリデータベースをホスティングする Amazon Elastic Compute Cloud (Amazon EC2) インスタンスのパッチグループに対して、親子関係で使用されるランブックを 2 つ作成しました。アプリケーションはこれらのデータベースに 24 時間アクセスするため、データベースインスタンスのいずれかは常に利用可能でなければなりません。
この要件に基づいて、彼女は AWS-RunPatchBaseline
Systems Manager (SSM) ドキュメントを使用してインスタンスを段階的にパッチするソリューションを構築しました。この SSM ドキュメントを使用することで、同僚は、パッチ適用操作の完了後に、関連するパッチコンプライアンス情報を確認できます。
まずはデータベースインスタンスのプライマリグループにパッチが適用され、続いてデータベースインスタンスのセカンダリグループにパッチが適用されます。また、以前に停止したインスタンスを実行したままにすることで追加コストが発生しないように、Emily は、オートメーションがパッチ適用されたインスタンスをパッチ適用前の元の状態に戻したことを確認しました。Emily は、データベースインスタンスのプライマリグループとセカンダリグループに関連付けられたタグを使用して、パッチを適用する必要があるインスタンスを希望する順序で特定しました。
既存の自動ソリューションは機能しますが、可能であればソリューションを改善したいと考えています。ランブックコンテンツのメンテナンスを支援し、トラブルシューティングを容易にするため、オートメーションを 1 つのランブックにまとめて、入力パラメータの数を簡素化したいと考えています。また、複数の子オートメーションは作成しないようにしたいと考えています。
利用可能なオートメーションアクションを確認した後、Emily は、aws:executeScript
アクションを使用すればソリューションをさらに改善し、カスタム Python スクリプトを実行できると判断しました。次のようにランブックのコンテンツの作成を開始しました。
-
まず、ランブックのスキーマの値と説明を提供し、親ランブックの入力パラメータを定義します。
AutomationAssumeRole
パラメータを使用すると、Emily とその同僚は、ランブックで彼らに代わってアクションを実行することをオートメーションに許可する既存の IAM ロールを使用できます。例 1 とは異なり、AutomationAssumeRole
パラメータはオプションではなく必須になりました。このランブックにはaws:executeScript
アクションが含まれるため、AWS Identity and Access Management (IAM) サービスロール (または継承ロール) が常に必要です。アクションに指定された Python スクリプトの一部が AWS API オペレーションを呼び出すため、この要件が必要になります。Emily は
PrimaryPatchGroupTag
とSecondaryPatchGroupTag
のパラメータを使用して、パッチを適用するデータベースインスタンスのプライマリグループとセカンダリグループに関連付けられたタグを指定します。必要な入力パラメータを単純化するために、例 1 のランブックで使用したように複数のString
パラメータを使用するのではなく、StringMap
パラメータを使用することにしました。オプションで、Operation
、RebootOption
、およびSnapshotId
パラメータを使用して、AWS-RunPatchBaseline
のドキュメントパラメータに値を提供できます。これらのドキュメントパラメータに無効な値が提供されるのを防ぐために、必要に応じてallowedValues
を定義します。- YAML
-
description: 'An example of an Automation runbook that patches groups of Amazon EC2 instances in stages.' schemaVersion: '0.3' assumeRole: '{{AutomationAssumeRole}}' parameters: AutomationAssumeRole: type: String description: '(Required) The Amazon Resource Name (ARN) of the IAM role that allows Automation to perform the actions on your behalf. If no role is specified, Systems Manager Automation uses your IAM permissions to operate this runbook.' PrimaryPatchGroupTag: type: StringMap description: '(Required) The tag for the primary group of instances you want to patch. Specify a key-value pair. Example: {"key" : "value"}' SecondaryPatchGroupTag: type: StringMap description: '(Required) The tag for the secondary group of instances you want to patch. Specify a key-value pair. Example: {"key" : "value"}' SnapshotId: type: String description: '(Optional) The snapshot ID to use to retrieve a patch baseline snapshot.' default: '' RebootOption: type: String description: '(Optional) Reboot behavior after a patch Install operation. If you choose NoReboot and patches are installed, the instance is marked as non-compliant until a subsequent reboot and scan.' allowedValues: - NoReboot - RebootIfNeeded default: RebootIfNeeded Operation: type: String description: '(Optional) The update or configuration to perform on the instance. The system checks if patches specified in the patch baseline are installed on the instance. The install operation installs patches missing from the baseline.' allowedValues: - Install - Scan default: Install
- JSON
-
{ "description":"An example of an Automation runbook that patches groups of Amazon EC2 instances in stages.", "schemaVersion":"0.3", "assumeRole":"{{AutomationAssumeRole}}", "parameters":{ "AutomationAssumeRole":{ "type":"String", "description":"(Required) The Amazon Resource Name (ARN) of the IAM role that allows Automation to perform the actions on your behalf. If no role is specified, Systems Manager Automation uses your IAM permissions to operate this runbook." }, "PrimaryPatchGroupTag":{ "type":"StringMap", "description":"(Required) The tag for the primary group of instances you want to patch. Specify a key-value pair. Example: {\"key\" : \"value\"}" }, "SecondaryPatchGroupTag":{ "type":"StringMap", "description":"(Required) The tag for the secondary group of instances you want to patch. Specify a key-value pair. Example: {\"key\" : \"value\"}" }, "SnapshotId":{ "type":"String", "description":"(Optional) The snapshot ID to use to retrieve a patch baseline snapshot.", "default":"" }, "RebootOption":{ "type":"String", "description":"(Optional) Reboot behavior after a patch Install operation. If you choose NoReboot and patches are installed, the instance is marked as non-compliant until a subsequent reboot and scan.", "allowedValues":[ "NoReboot", "RebootIfNeeded" ], "default":"RebootIfNeeded" }, "Operation":{ "type":"String", "description":"(Optional) The update or configuration to perform on the instance. The system checks if patches specified in the patch baseline are installed on the instance. The install operation installs patches missing from the baseline.", "allowedValues":[ "Install", "Scan" ], "default":"Install" } } },
-
最上位の要素が定義された状態で、Emily はランブックの
mainSteps
を構成するアクションの作成に進みます。最初のステップでは、PrimaryPatchGroupTag
パラメータで指定されるタグに関連付けられたすべてのインスタンスの ID を収集し、インスタンス ID とインスタンスの現在の状態を含むStringMap
パラメータを出力します。このアクションの出力は、後のアクションで使用します。script
入力パラメータは、JSON ランブックではサポートされていませんのでご注意ください。JSON ランブックでは、attachment
入力パラメータを使用してスクリプトコンテンツを指定する必要があります。- YAML
-
mainSteps: - name: getPrimaryInstanceState action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: getInstanceStates InputPayload: primaryTag: '{{PrimaryPatchGroupTag}}' Script: |- def getInstanceStates(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') tag = events['primaryTag'] tagKey, tagValue = list(tag.items())[0] instanceQuery = ec2.describe_instances( Filters=[ { "Name": "tag:" + tagKey, "Values": [tagValue] }] ) if not instanceQuery['Reservations']: noInstancesForTagString = "No instances found for specified tag." return({ 'noInstancesFound' : noInstancesForTagString }) else: queryResponse = instanceQuery['Reservations'] originalInstanceStates = {} for results in queryResponse: instanceSet = results['Instances'] for instance in instanceSet: instanceId = instance['InstanceId'] originalInstanceStates[instanceId] = instance['State']['Name'] return originalInstanceStates outputs: - Name: originalInstanceStates Selector: $.Payload Type: StringMap nextStep: verifyPrimaryInstancesRunning
- JSON
-
"mainSteps":[ { "name":"getPrimaryInstanceState", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"getInstanceStates", "InputPayload":{ "primaryTag":"{{PrimaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"originalInstanceStates", "Selector":"$.Payload", "Type":"StringMap" } ], "nextStep":"verifyPrimaryInstancesRunning" },
-
Emily は、前のアクションの出力を別の
aws:executeScript
アクションで使用して、PrimaryPatchGroupTag
パラメータで指定されたタグに関連付けられたすべてのインスタンスがrunning
の状態にあることを検証します。インスタンスの状態がすでに
running
またはshutting-down
の場合、スクリプトは残りのインスタンスをループし続けます。インスタンスの状態が
stopping
の場合、スクリプトはstopped
の状態になるまでインスタンスにポーリングし、インスタンスを起動します。インスタンスの状態が
stopped
の場合、スクリプトはインスタンスを起動します。- YAML
-
- name: verifyPrimaryInstancesRunning action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: verifyInstancesRunning InputPayload: targetInstances: '{{getPrimaryInstanceState.originalInstanceStates}}' Script: |- def verifyInstancesRunning(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped': print("The target instance " + instance + " is stopped. The instance will now be started.") ec2.start_instances( InstanceIds=[instance] ) elif instanceDict[instance] == 'stopping': print("The target instance " + instance + " is stopping. Polling for instance to reach stopped state.") while instanceDict[instance] != 'stopped': poll = ec2.get_waiter('instance_stopped') poll.wait( InstanceIds=[instance] ) ec2.start_instances( InstanceIds=[instance] ) else: pass nextStep: waitForPrimaryRunningInstances
- JSON
-
{ "name":"verifyPrimaryInstancesRunning", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"verifyInstancesRunning", "InputPayload":{ "targetInstances":"{{getPrimaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"waitForPrimaryRunningInstances" },
-
Emily は、
PrimaryPatchGroupTag
パラメータで指定されたタグに関連付けられたすべてのインスタンスが開始されているか、もしくは既にrunning
の状態にあることを検証します。次に、別のスクリプトを使用して、前のアクションで開始されたインスタンスも含め、すべてのインスタンスがrunning
の状態に到達していることを確認します。- YAML
-
- name: waitForPrimaryRunningInstances action: 'aws:executeScript' timeoutSeconds: 300 onFailure: Abort inputs: Runtime: python3.7 Handler: waitForRunningInstances InputPayload: targetInstances: '{{getPrimaryInstanceState.originalInstanceStates}}' Script: |- def waitForRunningInstances(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: poll = ec2.get_waiter('instance_running') poll.wait( InstanceIds=[instance] ) nextStep: returnPrimaryTagKey
- JSON
-
{ "name":"waitForPrimaryRunningInstances", "action":"aws:executeScript", "timeoutSeconds":300, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"waitForRunningInstances", "InputPayload":{ "targetInstances":"{{getPrimaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"returnPrimaryTagKey" },
-
Emilyは、さらに 2 つのスクリプトを使用して、
PrimaryPatchGroupTag
パラメータで指定された個々のキーString
の値とのタグの値を返します。これらのアクションで返された値により、AWS-RunPatchBaseline
ドキュメントのTargets
パラメータに直接値を提供できます。その後、aws:runCommand
アクションを使用するAWS-RunPatchBaseline
ドキュメントで、インスタンスにパッチを適用しオートメーションが進められます。- YAML
-
- name: returnPrimaryTagKey action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: primaryTag: '{{PrimaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['primaryTag'] tagKey = list(tag)[0] stringKey = "tag:" + tagKey return {'tagKey' : stringKey} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: primaryPatchGroupKey Selector: $.Payload.tagKey Type: String nextStep: returnPrimaryTagValue - name: returnPrimaryTagValue action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: primaryTag: '{{PrimaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['primaryTag'] tagKey = list(tag)[0] tagValue = tag[tagKey] return {'tagValue' : tagValue} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: primaryPatchGroupValue Selector: $.Payload.tagValue Type: String nextStep: patchPrimaryInstances - name: patchPrimaryInstances action: 'aws:runCommand' onFailure: Abort timeoutSeconds: 7200 inputs: DocumentName: AWS-RunPatchBaseline Parameters: SnapshotId: '{{SnapshotId}}' RebootOption: '{{RebootOption}}' Operation: '{{Operation}}' Targets: - Key: '{{returnPrimaryTagKey.primaryPatchGroupKey}}' Values: - '{{returnPrimaryTagValue.primaryPatchGroupValue}}' MaxConcurrency: 10% MaxErrors: 10% nextStep: returnPrimaryToOriginalState
- JSON
-
{ "name":"returnPrimaryTagKey", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "primaryTag":"{{PrimaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"primaryPatchGroupKey", "Selector":"$.Payload.tagKey", "Type":"String" } ], "nextStep":"returnPrimaryTagValue" }, { "name":"returnPrimaryTagValue", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "primaryTag":"{{PrimaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"primaryPatchGroupValue", "Selector":"$.Payload.tagValue", "Type":"String" } ], "nextStep":"patchPrimaryInstances" }, { "name":"patchPrimaryInstances", "action":"aws:runCommand", "onFailure":"Abort", "timeoutSeconds":7200, "inputs":{ "DocumentName":"AWS-RunPatchBaseline", "Parameters":{ "SnapshotId":"{{SnapshotId}}", "RebootOption":"{{RebootOption}}", "Operation":"{{Operation}}" }, "Targets":[ { "Key":"{{returnPrimaryTagKey.primaryPatchGroupKey}}", "Values":[ "{{returnPrimaryTagValue.primaryPatchGroupValue}}" ] } ], "MaxConcurrency":"10%", "MaxErrors":"10%" }, "nextStep":"returnPrimaryToOriginalState" },
-
パッチ適用操作が完了した後、Emily はオートメーションが、
PrimaryPatchGroupTag
パラメータで指定したタグに関連付けられたターゲットインスタンスを、オートメーション開始前と同じ状態に戻すようにしたいと考えています。これは、スクリプトの最初のアクションでの出力を再び使用して行います。ターゲットインスタンスの元の状態に基づいて、インスタンスが以前running
以外の状態にあった場合、インスタンスは停止します。インスタンスの状態がrunning
であれば、スクリプトは残りのインスタンスをループし続けます。- YAML
-
- name: returnPrimaryToOriginalState action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: returnToOriginalState InputPayload: targetInstances: '{{getPrimaryInstanceState.originalInstanceStates}}' Script: |- def returnToOriginalState(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped' or instanceDict[instance] == 'stopping': ec2.stop_instances( InstanceIds=[instance] ) else: pass nextStep: getSecondaryInstanceState
- JSON
-
{ "name":"returnPrimaryToOriginalState", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnToOriginalState", "InputPayload":{ "targetInstances":"{{getPrimaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"getSecondaryInstanceState" },
-
PrimaryPatchGroupTag
パラメータで指定したタグに関連付けられたインスタンスのパッチ適用操作は完了しました。これで、Emily はランブックコンテンツ内の以前のアクションをすべて複製し、SecondaryPatchGroupTag
パラメータで指定したタグに関連付けられたインスタンスをターゲットにできるようになりました。- YAML
-
- name: getSecondaryInstanceState action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: getInstanceStates InputPayload: secondaryTag: '{{SecondaryPatchGroupTag}}' Script: |- def getInstanceStates(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') tag = events['secondaryTag'] tagKey, tagValue = list(tag.items())[0] instanceQuery = ec2.describe_instances( Filters=[ { "Name": "tag:" + tagKey, "Values": [tagValue] }] ) if not instanceQuery['Reservations']: noInstancesForTagString = "No instances found for specified tag." return({ 'noInstancesFound' : noInstancesForTagString }) else: queryResponse = instanceQuery['Reservations'] originalInstanceStates = {} for results in queryResponse: instanceSet = results['Instances'] for instance in instanceSet: instanceId = instance['InstanceId'] originalInstanceStates[instanceId] = instance['State']['Name'] return originalInstanceStates outputs: - Name: originalInstanceStates Selector: $.Payload Type: StringMap nextStep: verifySecondaryInstancesRunning - name: verifySecondaryInstancesRunning action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: verifyInstancesRunning InputPayload: targetInstances: '{{getSecondaryInstanceState.originalInstanceStates}}' Script: |- def verifyInstancesRunning(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped': print("The target instance " + instance + " is stopped. The instance will now be started.") ec2.start_instances( InstanceIds=[instance] ) elif instanceDict[instance] == 'stopping': print("The target instance " + instance + " is stopping. Polling for instance to reach stopped state.") while instanceDict[instance] != 'stopped': poll = ec2.get_waiter('instance_stopped') poll.wait( InstanceIds=[instance] ) ec2.start_instances( InstanceIds=[instance] ) else: pass nextStep: waitForSecondaryRunningInstances - name: waitForSecondaryRunningInstances action: 'aws:executeScript' timeoutSeconds: 300 onFailure: Abort inputs: Runtime: python3.7 Handler: waitForRunningInstances InputPayload: targetInstances: '{{getSecondaryInstanceState.originalInstanceStates}}' Script: |- def waitForRunningInstances(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: poll = ec2.get_waiter('instance_running') poll.wait( InstanceIds=[instance] ) nextStep: returnSecondaryTagKey - name: returnSecondaryTagKey action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: secondaryTag: '{{SecondaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['secondaryTag'] tagKey = list(tag)[0] stringKey = "tag:" + tagKey return {'tagKey' : stringKey} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: secondaryPatchGroupKey Selector: $.Payload.tagKey Type: String nextStep: returnSecondaryTagValue - name: returnSecondaryTagValue action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: secondaryTag: '{{SecondaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['secondaryTag'] tagKey = list(tag)[0] tagValue = tag[tagKey] return {'tagValue' : tagValue} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: secondaryPatchGroupValue Selector: $.Payload.tagValue Type: String nextStep: patchSecondaryInstances - name: patchSecondaryInstances action: 'aws:runCommand' onFailure: Abort timeoutSeconds: 7200 inputs: DocumentName: AWS-RunPatchBaseline Parameters: SnapshotId: '{{SnapshotId}}' RebootOption: '{{RebootOption}}' Operation: '{{Operation}}' Targets: - Key: '{{returnSecondaryTagKey.secondaryPatchGroupKey}}' Values: - '{{returnSecondaryTagValue.secondaryPatchGroupValue}}' MaxConcurrency: 10% MaxErrors: 10% nextStep: returnSecondaryToOriginalState - name: returnSecondaryToOriginalState action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: returnToOriginalState InputPayload: targetInstances: '{{getSecondaryInstanceState.originalInstanceStates}}' Script: |- def returnToOriginalState(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped' or instanceDict[instance] == 'stopping': ec2.stop_instances( InstanceIds=[instance] ) else: pass
- JSON
-
{ "name":"getSecondaryInstanceState", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"getInstanceStates", "InputPayload":{ "secondaryTag":"{{SecondaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"originalInstanceStates", "Selector":"$.Payload", "Type":"StringMap" } ], "nextStep":"verifySecondaryInstancesRunning" }, { "name":"verifySecondaryInstancesRunning", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"verifyInstancesRunning", "InputPayload":{ "targetInstances":"{{getSecondaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"waitForSecondaryRunningInstances" }, { "name":"waitForSecondaryRunningInstances", "action":"aws:executeScript", "timeoutSeconds":300, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"waitForRunningInstances", "InputPayload":{ "targetInstances":"{{getSecondaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"returnSecondaryTagKey" }, { "name":"returnSecondaryTagKey", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "secondaryTag":"{{SecondaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"secondaryPatchGroupKey", "Selector":"$.Payload.tagKey", "Type":"String" } ], "nextStep":"returnSecondaryTagValue" }, { "name":"returnSecondaryTagValue", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "secondaryTag":"{{SecondaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"secondaryPatchGroupValue", "Selector":"$.Payload.tagValue", "Type":"String" } ], "nextStep":"patchSecondaryInstances" }, { "name":"patchSecondaryInstances", "action":"aws:runCommand", "onFailure":"Abort", "timeoutSeconds":7200, "inputs":{ "DocumentName":"AWS-RunPatchBaseline", "Parameters":{ "SnapshotId":"{{SnapshotId}}", "RebootOption":"{{RebootOption}}", "Operation":"{{Operation}}" }, "Targets":[ { "Key":"{{returnSecondaryTagKey.secondaryPatchGroupKey}}", "Values":[ "{{returnSecondaryTagValue.secondaryPatchGroupValue}}" ] } ], "MaxConcurrency":"10%", "MaxErrors":"10%" }, "nextStep":"returnSecondaryToOriginalState" }, { "name":"returnSecondaryToOriginalState", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnToOriginalState", "InputPayload":{ "targetInstances":"{{getSecondaryInstanceState.originalInstanceStates}}" }, "Script":"..." } } ] }
-
Emily は完成したスクリプトのランブックコンテンツをレビューし、ターゲットインスタンスと同じ AWS アカウント と AWS リージョン でランブックを作成します。これで、ランブックをテストして、オートメーションが希望どおりに動作していることを確認してから、本番環境に実装する準備が整いました。以下は、完成したスクリプト化されたランブックコンテンツです。
- YAML
-
description: An example of an Automation runbook that patches groups of Amazon EC2 instances in stages. schemaVersion: '0.3' assumeRole: '{{AutomationAssumeRole}}' parameters: AutomationAssumeRole: type: String description: '(Required) The Amazon Resource Name (ARN) of the IAM role that allows Automation to perform the actions on your behalf. If no role is specified, Systems Manager Automation uses your IAM permissions to operate this runbook.' PrimaryPatchGroupTag: type: StringMap description: '(Required) The tag for the primary group of instances you want to patch. Specify a key-value pair. Example: {"key" : "value"}' SecondaryPatchGroupTag: type: StringMap description: '(Required) The tag for the secondary group of instances you want to patch. Specify a key-value pair. Example: {"key" : "value"}' SnapshotId: type: String description: '(Optional) The snapshot ID to use to retrieve a patch baseline snapshot.' default: '' RebootOption: type: String description: '(Optional) Reboot behavior after a patch Install operation. If you choose NoReboot and patches are installed, the instance is marked as non-compliant until a subsequent reboot and scan.' allowedValues: - NoReboot - RebootIfNeeded default: RebootIfNeeded Operation: type: String description: '(Optional) The update or configuration to perform on the instance. The system checks if patches specified in the patch baseline are installed on the instance. The install operation installs patches missing from the baseline.' allowedValues: - Install - Scan default: Install mainSteps: - name: getPrimaryInstanceState action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: getInstanceStates InputPayload: primaryTag: '{{PrimaryPatchGroupTag}}' Script: |- def getInstanceStates(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') tag = events['primaryTag'] tagKey, tagValue = list(tag.items())[0] instanceQuery = ec2.describe_instances( Filters=[ { "Name": "tag:" + tagKey, "Values": [tagValue] }] ) if not instanceQuery['Reservations']: noInstancesForTagString = "No instances found for specified tag." return({ 'noInstancesFound' : noInstancesForTagString }) else: queryResponse = instanceQuery['Reservations'] originalInstanceStates = {} for results in queryResponse: instanceSet = results['Instances'] for instance in instanceSet: instanceId = instance['InstanceId'] originalInstanceStates[instanceId] = instance['State']['Name'] return originalInstanceStates outputs: - Name: originalInstanceStates Selector: $.Payload Type: StringMap nextStep: verifyPrimaryInstancesRunning - name: verifyPrimaryInstancesRunning action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: verifyInstancesRunning InputPayload: targetInstances: '{{getPrimaryInstanceState.originalInstanceStates}}' Script: |- def verifyInstancesRunning(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped': print("The target instance " + instance + " is stopped. The instance will now be started.") ec2.start_instances( InstanceIds=[instance] ) elif instanceDict[instance] == 'stopping': print("The target instance " + instance + " is stopping. Polling for instance to reach stopped state.") while instanceDict[instance] != 'stopped': poll = ec2.get_waiter('instance_stopped') poll.wait( InstanceIds=[instance] ) ec2.start_instances( InstanceIds=[instance] ) else: pass nextStep: waitForPrimaryRunningInstances - name: waitForPrimaryRunningInstances action: 'aws:executeScript' timeoutSeconds: 300 onFailure: Abort inputs: Runtime: python3.7 Handler: waitForRunningInstances InputPayload: targetInstances: '{{getPrimaryInstanceState.originalInstanceStates}}' Script: |- def waitForRunningInstances(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: poll = ec2.get_waiter('instance_running') poll.wait( InstanceIds=[instance] ) nextStep: returnPrimaryTagKey - name: returnPrimaryTagKey action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: primaryTag: '{{PrimaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['primaryTag'] tagKey = list(tag)[0] stringKey = "tag:" + tagKey return {'tagKey' : stringKey} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: primaryPatchGroupKey Selector: $.Payload.tagKey Type: String nextStep: returnPrimaryTagValue - name: returnPrimaryTagValue action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: primaryTag: '{{PrimaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['primaryTag'] tagKey = list(tag)[0] tagValue = tag[tagKey] return {'tagValue' : tagValue} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: primaryPatchGroupValue Selector: $.Payload.tagValue Type: String nextStep: patchPrimaryInstances - name: patchPrimaryInstances action: 'aws:runCommand' onFailure: Abort timeoutSeconds: 7200 inputs: DocumentName: AWS-RunPatchBaseline Parameters: SnapshotId: '{{SnapshotId}}' RebootOption: '{{RebootOption}}' Operation: '{{Operation}}' Targets: - Key: '{{returnPrimaryTagKey.primaryPatchGroupKey}}' Values: - '{{returnPrimaryTagValue.primaryPatchGroupValue}}' MaxConcurrency: 10% MaxErrors: 10% nextStep: returnPrimaryToOriginalState - name: returnPrimaryToOriginalState action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: returnToOriginalState InputPayload: targetInstances: '{{getPrimaryInstanceState.originalInstanceStates}}' Script: |- def returnToOriginalState(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped' or instanceDict[instance] == 'stopping': ec2.stop_instances( InstanceIds=[instance] ) else: pass nextStep: getSecondaryInstanceState - name: getSecondaryInstanceState action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: getInstanceStates InputPayload: secondaryTag: '{{SecondaryPatchGroupTag}}' Script: |- def getInstanceStates(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') tag = events['secondaryTag'] tagKey, tagValue = list(tag.items())[0] instanceQuery = ec2.describe_instances( Filters=[ { "Name": "tag:" + tagKey, "Values": [tagValue] }] ) if not instanceQuery['Reservations']: noInstancesForTagString = "No instances found for specified tag." return({ 'noInstancesFound' : noInstancesForTagString }) else: queryResponse = instanceQuery['Reservations'] originalInstanceStates = {} for results in queryResponse: instanceSet = results['Instances'] for instance in instanceSet: instanceId = instance['InstanceId'] originalInstanceStates[instanceId] = instance['State']['Name'] return originalInstanceStates outputs: - Name: originalInstanceStates Selector: $.Payload Type: StringMap nextStep: verifySecondaryInstancesRunning - name: verifySecondaryInstancesRunning action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: verifyInstancesRunning InputPayload: targetInstances: '{{getSecondaryInstanceState.originalInstanceStates}}' Script: |- def verifyInstancesRunning(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped': print("The target instance " + instance + " is stopped. The instance will now be started.") ec2.start_instances( InstanceIds=[instance] ) elif instanceDict[instance] == 'stopping': print("The target instance " + instance + " is stopping. Polling for instance to reach stopped state.") while instanceDict[instance] != 'stopped': poll = ec2.get_waiter('instance_stopped') poll.wait( InstanceIds=[instance] ) ec2.start_instances( InstanceIds=[instance] ) else: pass nextStep: waitForSecondaryRunningInstances - name: waitForSecondaryRunningInstances action: 'aws:executeScript' timeoutSeconds: 300 onFailure: Abort inputs: Runtime: python3.7 Handler: waitForRunningInstances InputPayload: targetInstances: '{{getSecondaryInstanceState.originalInstanceStates}}' Script: |- def waitForRunningInstances(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: poll = ec2.get_waiter('instance_running') poll.wait( InstanceIds=[instance] ) nextStep: returnSecondaryTagKey - name: returnSecondaryTagKey action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: secondaryTag: '{{SecondaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['secondaryTag'] tagKey = list(tag)[0] stringKey = "tag:" + tagKey return {'tagKey' : stringKey} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: secondaryPatchGroupKey Selector: $.Payload.tagKey Type: String nextStep: returnSecondaryTagValue - name: returnSecondaryTagValue action: 'aws:executeScript' timeoutSeconds: 120 onFailure: Abort inputs: Runtime: python3.7 Handler: returnTagValues InputPayload: secondaryTag: '{{SecondaryPatchGroupTag}}' Script: |- def returnTagValues(events,context): tag = events['secondaryTag'] tagKey = list(tag)[0] tagValue = tag[tagKey] return {'tagValue' : tagValue} outputs: - Name: Payload Selector: $.Payload Type: StringMap - Name: secondaryPatchGroupValue Selector: $.Payload.tagValue Type: String nextStep: patchSecondaryInstances - name: patchSecondaryInstances action: 'aws:runCommand' onFailure: Abort timeoutSeconds: 7200 inputs: DocumentName: AWS-RunPatchBaseline Parameters: SnapshotId: '{{SnapshotId}}' RebootOption: '{{RebootOption}}' Operation: '{{Operation}}' Targets: - Key: '{{returnSecondaryTagKey.secondaryPatchGroupKey}}' Values: - '{{returnSecondaryTagValue.secondaryPatchGroupValue}}' MaxConcurrency: 10% MaxErrors: 10% nextStep: returnSecondaryToOriginalState - name: returnSecondaryToOriginalState action: 'aws:executeScript' timeoutSeconds: 600 onFailure: Abort inputs: Runtime: python3.7 Handler: returnToOriginalState InputPayload: targetInstances: '{{getSecondaryInstanceState.originalInstanceStates}}' Script: |- def returnToOriginalState(events,context): import boto3 #Initialize client ec2 = boto3.client('ec2') instanceDict = events['targetInstances'] for instance in instanceDict: if instanceDict[instance] == 'stopped' or instanceDict[instance] == 'stopping': ec2.stop_instances( InstanceIds=[instance] ) else: pass
- JSON
-
{ "description":"An example of an Automation runbook that patches groups of Amazon EC2 instances in stages.", "schemaVersion":"0.3", "assumeRole":"{{AutomationAssumeRole}}", "parameters":{ "AutomationAssumeRole":{ "type":"String", "description":"(Required) The Amazon Resource Name (ARN) of the IAM role that allows Automation to perform the actions on your behalf. If no role is specified, Systems Manager Automation uses your IAM permissions to operate this runbook." }, "PrimaryPatchGroupTag":{ "type":"StringMap", "description":"(Required) The tag for the primary group of instances you want to patch. Specify a key-value pair. Example: {\"key\" : \"value\"}" }, "SecondaryPatchGroupTag":{ "type":"StringMap", "description":"(Required) The tag for the secondary group of instances you want to patch. Specify a key-value pair. Example: {\"key\" : \"value\"}" }, "SnapshotId":{ "type":"String", "description":"(Optional) The snapshot ID to use to retrieve a patch baseline snapshot.", "default":"" }, "RebootOption":{ "type":"String", "description":"(Optional) Reboot behavior after a patch Install operation. If you choose NoReboot and patches are installed, the instance is marked as non-compliant until a subsequent reboot and scan.", "allowedValues":[ "NoReboot", "RebootIfNeeded" ], "default":"RebootIfNeeded" }, "Operation":{ "type":"String", "description":"(Optional) The update or configuration to perform on the instance. The system checks if patches specified in the patch baseline are installed on the instance. The install operation installs patches missing from the baseline.", "allowedValues":[ "Install", "Scan" ], "default":"Install" } }, "mainSteps":[ { "name":"getPrimaryInstanceState", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"getInstanceStates", "InputPayload":{ "primaryTag":"{{PrimaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"originalInstanceStates", "Selector":"$.Payload", "Type":"StringMap" } ], "nextStep":"verifyPrimaryInstancesRunning" }, { "name":"verifyPrimaryInstancesRunning", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"verifyInstancesRunning", "InputPayload":{ "targetInstances":"{{getPrimaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"waitForPrimaryRunningInstances" }, { "name":"waitForPrimaryRunningInstances", "action":"aws:executeScript", "timeoutSeconds":300, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"waitForRunningInstances", "InputPayload":{ "targetInstances":"{{getPrimaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"returnPrimaryTagKey" }, { "name":"returnPrimaryTagKey", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "primaryTag":"{{PrimaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"primaryPatchGroupKey", "Selector":"$.Payload.tagKey", "Type":"String" } ], "nextStep":"returnPrimaryTagValue" }, { "name":"returnPrimaryTagValue", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "primaryTag":"{{PrimaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"primaryPatchGroupValue", "Selector":"$.Payload.tagValue", "Type":"String" } ], "nextStep":"patchPrimaryInstances" }, { "name":"patchPrimaryInstances", "action":"aws:runCommand", "onFailure":"Abort", "timeoutSeconds":7200, "inputs":{ "DocumentName":"AWS-RunPatchBaseline", "Parameters":{ "SnapshotId":"{{SnapshotId}}", "RebootOption":"{{RebootOption}}", "Operation":"{{Operation}}" }, "Targets":[ { "Key":"{{returnPrimaryTagKey.primaryPatchGroupKey}}", "Values":[ "{{returnPrimaryTagValue.primaryPatchGroupValue}}" ] } ], "MaxConcurrency":"10%", "MaxErrors":"10%" }, "nextStep":"returnPrimaryToOriginalState" }, { "name":"returnPrimaryToOriginalState", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnToOriginalState", "InputPayload":{ "targetInstances":"{{getPrimaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"getSecondaryInstanceState" }, { "name":"getSecondaryInstanceState", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"getInstanceStates", "InputPayload":{ "secondaryTag":"{{SecondaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"originalInstanceStates", "Selector":"$.Payload", "Type":"StringMap" } ], "nextStep":"verifySecondaryInstancesRunning" }, { "name":"verifySecondaryInstancesRunning", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"verifyInstancesRunning", "InputPayload":{ "targetInstances":"{{getSecondaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"waitForSecondaryRunningInstances" }, { "name":"waitForSecondaryRunningInstances", "action":"aws:executeScript", "timeoutSeconds":300, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"waitForRunningInstances", "InputPayload":{ "targetInstances":"{{getSecondaryInstanceState.originalInstanceStates}}" }, "Script":"..." }, "nextStep":"returnSecondaryTagKey" }, { "name":"returnSecondaryTagKey", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "secondaryTag":"{{SecondaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"secondaryPatchGroupKey", "Selector":"$.Payload.tagKey", "Type":"String" } ], "nextStep":"returnSecondaryTagValue" }, { "name":"returnSecondaryTagValue", "action":"aws:executeScript", "timeoutSeconds":120, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnTagValues", "InputPayload":{ "secondaryTag":"{{SecondaryPatchGroupTag}}" }, "Script":"..." }, "outputs":[ { "Name":"Payload", "Selector":"$.Payload", "Type":"StringMap" }, { "Name":"secondaryPatchGroupValue", "Selector":"$.Payload.tagValue", "Type":"String" } ], "nextStep":"patchSecondaryInstances" }, { "name":"patchSecondaryInstances", "action":"aws:runCommand", "onFailure":"Abort", "timeoutSeconds":7200, "inputs":{ "DocumentName":"AWS-RunPatchBaseline", "Parameters":{ "SnapshotId":"{{SnapshotId}}", "RebootOption":"{{RebootOption}}", "Operation":"{{Operation}}" }, "Targets":[ { "Key":"{{returnSecondaryTagKey.secondaryPatchGroupKey}}", "Values":[ "{{returnSecondaryTagValue.secondaryPatchGroupValue}}" ] } ], "MaxConcurrency":"10%", "MaxErrors":"10%" }, "nextStep":"returnSecondaryToOriginalState" }, { "name":"returnSecondaryToOriginalState", "action":"aws:executeScript", "timeoutSeconds":600, "onFailure":"Abort", "inputs":{ "Runtime":"python3.7", "Handler":"returnToOriginalState", "InputPayload":{ "targetInstances":"{{getSecondaryInstanceState.originalInstanceStates}}" }, "Script":"..." } } ] }
この例で使用されているオートメーションアクションの詳細については、「Systems Manager Automation アクションのリファレンス」を参照してください。