Exemplo 2: Runbook com script
Este exemplo de runbook aborda o seguinte cenário. Emily é engenheira de sistemas na AnyCompany Consultants, LLC. Ela criou anteriormente dois runbooks que são usados em um relacionamento pai-filho para grupos de patches de instâncias do Amazon Elastic Compute Cloud (Amazon EC2) que hospedam bancos de dados primários e secundários. As aplicações acessam esses bancos de dados 24 horas por dia, portanto, uma das instâncias do banco de dados deve estar sempre disponível.
Com base nesse requisito, ela criou uma solução que corrige as instâncias em etapas usando o documento do Systems Manager (SSM) AWS-RunPatchBaseline
. Usando este documento do SSM, seus colegas podem revisar as informações de conformidade de patch associadas após a conclusão da operação de patch.
O grupo principal de instâncias de banco de dados recebem o patch primeiro, seguido pelo grupo secundário de instâncias de banco de dados. Além disso, para evitar custos adicionais, deixando instâncias em execução que foram interrompidas anteriormente, Emily garantiu que a automação retornasse as instâncias corrigidas ao seu estado original antes da aplicação de patches ocorrer. Emily usou tags associadas aos grupos primário e secundário de instâncias de banco de dados para identificar quais instâncias devem ser corrigidas na ordem desejada.
A solução automatizada existente funciona, mas ela quer melhorar a solução, se possível. Para ajudar na manutenção do conteúdo do runbook e facilitar a solução de problemas, ela gostaria de condensar a automação em um único runbook e simplificar o número de parâmetros de entrada. Além disso, ela gostaria de evitar a criação de várias automações filho.
Depois que Emily revisar as ações de automação disponíveis, ela determinará que pode melhorar sua solução usando o aws:executeScript
para executar seus scripts Python personalizados. Ela agora começa a criar o conteúdo para o runbook da seguinte forma:
-
Primeiro, ela fornece valores para o esquema e a descrição do runbook e define os parâmetros de entrada para o runbook pai.
Utilizando o parâmetro
AutomationAssumeRole
, Emily e seus colegas podem usar uma função do IAM existente que permite que o Automation realize ações no runbook em seu nome. Ao contrário do Exemplo 1, o parâmetroAutomationAssumeRole
agora é necessário e não opcional. Como este runbook inclui açõesaws:executeScript
, uma função de serviço (IAM) AWS Identity and Access Management (ou função assumida) será sempre necessária. Este requisito é necessário porque alguns dos scripts Python especificados para as ações chamam as operações de API da AWS.Emily usa os parâmetros
PrimaryPatchGroupTag
eSecondaryPatchGroupTag
para especificar as tags associadas ao grupo secundário de instâncias de banco de dados que será corrigido. Para simplificar os parâmetros de entrada necessários, ela decide usar os parâmetrosStringMap
em vez de usar vários parâmetrosString
como ela usou no runbook de Exemplo 1. Opcionalmente, os parâmetrosOperation
,RebootOption
eSnapshotId
podem ser usados para fornecer valores para documentar os parâmetros paraAWS-RunPatchBaseline
. Para evitar que valores inválidos sejam fornecidos a esses parâmetros do documento, ela define a propriedadeallowedValues
conforme necessário.- 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" } } },
-
Com os elementos de nível superior definidos, Emily prossegue com a criação das ações que compõem as
mainSteps
do runbook. A primeira etapa reúne os IDs de todas as instâncias associadas à tag especificada no parâmetroPrimaryPatchGroupTag
e resulta em um parâmetroStringMap
que contém o ID de instância e o estado atual da instância. A saída desta ação é usada em ações posteriores.Observe que o parâmetro de entrada do
script
é compatível com runbooks JSON. Os runbooks JSON devem fornecer conteúdo de script usando o parâmetro de entrada doattachment
.- 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 usa o resultado da ação anterior em outra ação do
aws:executeScript
para verificar se todas as ocorrências associadas à tag especificada no parâmetroPrimaryPatchGroupTag
estão em um estadorunning
.Se o estado da instância já for
running
oushutting-down
, o script continua a percorrer as instâncias restantes.Se o estado da instância for
stopping
, o script sonda para que a instância atinja o estadostopped
e inicie a instância.Se o estado da instância for
stopped
, o script inicia a instância.- 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 verifica se todas as instâncias associadas com a tag especificada no parâmetro
PrimaryPatchGroupTag
foram iniciadas ou já estão em um estadorunning
. Ele então usa outro script para verificar se todas as instâncias, incluindo aquelas que foram iniciadas na ação anterior, atingiram o estadorunning
.- 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 usa mais dois scripts para retornar valores
String
individuais da chave e valor da tag especificada no parâmetroPrimaryPatchGroupTag
. Os valores retornados por essas ações permitem que ela forneça valores diretamente ao parâmetroTargets
do documentoAWS-RunPatchBaseline
. A automação prossegue então com a aplicação do patch na instância com o documentoAWS-RunPatchBaseline
usando a açãoaws:runCommand
.- 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" },
-
Após a conclusão da operação de patch, Emily deseja que a automação retorne as instâncias de destino associadas à tag especificada no parâmetro
PrimaryPatchGroupTag
para o mesmo estado em que estavam antes da automação ser iniciada. Ela faz isso usando novamente a saída da primeira ação em um script. Com base no estado original da instância de destino, se a instância estava anteriormente em qualquer estado diferente derunning
, ela é interrompida. Caso contrário, se o estado da instância forrunning
o script continuará a percorrer as instâncias restantes.- 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" },
-
A operação de patch é concluída para as instâncias associadas à tag especificada no parâmetro
PrimaryPatchGroupTag
. Agora, Emily duplica todas as ações anteriores em seu conteúdo do runbook para direcionar as instâncias associadas à tag especificada no parâmetroSecondaryPatchGroupTag
.- 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 analisa o conteúdo do runbook com script concluído e cria o runbook na mesma Conta da AWS e Região da AWS das instâncias de destino. Agora ela está pronta para testar seu runbook para garantir que a automação funcione conforme desejado antes de implementá-la em seu ambiente de produção. Abaixo está o conteúdo do runbook com script concluído.
- 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":"..." } } ] }
Para obter mais informações sobre as ações de automação usadas neste exemplo, consulte a Referência de ações do Systems Manager Automation.