예제 2: 스크립팅된 실행서 - AWS Systems Manager

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

예제 2: 스크립팅된 실행서

이 예제 실행서는 다음 시나리오를 다룹니다. Emily는 AnyCompany Consultants, LLC의 시스템 엔지니어입니다. 그녀는 이전에 주 및 보조 데이터베이스를 호스팅하는 Amazon Elastic Compute Cloud(Amazon EC2) 인스턴스의 패치 그룹에 대한 상위-하위 관계에서 사용되는 2개의 실행서를 생성했습니다. 애플리케이션은 하루 24시간 이러한 데이터베이스에 액세스하므로 데이터베이스 인스턴스 중 하나를 항상 사용할 수 있어야 합니다.

이 요구 사항에 따라 그녀는 AWS-RunPatchBaseline Systems Manager(SSM) 문서를 사용하여 단계적으로 인스턴스를 패치하는 솔루션을 구축했습니다. 이 SSM 문서를 사용하여 동료는 패치 작업이 완료된 후 관련 패치 규정 준수 정보를 검토할 수 있습니다.

데이터베이스 인스턴스의 주 그룹이 먼저 패치된 다음 데이터베이스 인스턴스의 보조 그룹이 패치됩니다. 또한 이전에 중지된 인스턴스를 계속 실행하여 추가 비용이 발생하지 않도록 Emily는 자동화가 패치된 인스턴스를 패치되기 전의 원래 상태로 되돌리도록 했습니다. Emily는 데이터베이스 인스턴스의 주 및 보조 그룹과 연결된 태그를 사용하여 원하는 순서로 패치해야 하는 인스턴스를 식별했습니다.

기존 자동화 솔루션은 작동하지만 가능한 경우 솔루션을 개선하고자 합니다. 실행서 콘텐츠의 유지 관리를 지원하고 문제 해결을 용이하게 하기 위해 자동화를 단일 실행서로 축소하고 입력 파라미터의 수를 단순화하려고 합니다. 또한 여러 하위 자동화를 생성하지 않으려고 합니다.

Emily는 사용 가능한 자동화 작업을 검토한 후 aws:executeScript 작업으로 사용자 정의 Python 스크립트를 실행하여 솔루션을 개선할 수 있다고 판단합니다. 이제 다음과 같이 실행서의 콘텐츠를 작성하기 시작합니다.

  1. 먼저 실행서의 스키마 및 설명 값을 제공하고 하위 실행서에 대한 입력 파라미터를 정의합니다.

    Emily와 동료는 AutomationAssumeRole 파라미터를 사용하여 Automation이 대신 실행서에서 작업을 수행할 수 있도록 하는 기존 IAM 역할을 사용할 수 있습니다. 예 1과 달리 AutomationAssumeRole 파라미터는 이제 옵션이 아닌 필수 항목입니다. 이 실행서에는 aws:executeScript 작업이 포함되어 있으므로 AWS Identity and Access Management(IAM) 서비스 역할(또는 수임 역할)이 항상 필요합니다. 이 요구 사항은 작업에 대해 지정된 일부 Python 스크립트가 AWS API 작업을 호출하기 때문에 필요합니다.

    Emily는 PrimaryPatchGroupTagSecondaryPatchGroupTag 파라미터를 사용하여 패치될 데이터베이스 인스턴스의 주 및 보조 그룹과 연결된 태그를 지정합니다. 필요한 입력 파라미터를 단순화하기 위해 예제 1 실행서와 같이 String 파라미터를 여러 개 사용하는 대신 StringMap 파라미터를 사용하기로 결정했습니다. 필요에 따라 Operation, RebootOptionSnapshotId 파라미터를 사용하여 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" } } },
  2. 최상위 요소가 정의되면 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" },
  3. 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" },
  4. 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" },
  5. 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" },
  6. 패치 작업이 완료된 후 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" },
  7. 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":"..." } } ] }
  8. 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 작업 참조 섹션을 참조하세요.