Ejemplo 2: manual de procedimientos con scripts
En este manual de procedimientos de ejemplo, se aborda el siguiente escenario. Emily se desempeña como ingeniera en sistemas en AnyCompany Consultants, LLC. Anteriormente, creó dos manuales de procedimientos que se utilizan en una relación de manuales de tipo principal-secundario a fin de aplicar parches a grupos de instancias de Amazon Elastic Compute Cloud (Amazon EC2) que alojan bases de datos primarias y secundarias. Las aplicaciones acceden a estas bases de datos las 24 horas del día, por lo que una de las instancias de base de datos siempre debe estar disponible.
Teniendo en cuenta este requisito, creó una solución que aplica parches a las instancias en etapas mediante el documento AWS-RunPatchBaseline
de Systems Manager (SSM). Mediante el uso de este documento de SSM, sus colegas pueden revisar la información de conformidad de los parches asociados una vez finalizada la operación de aplicación de parches.
Primero se aplican parches al grupo principal de instancias de base de datos y, luego, al grupo secundario. Además, para evitar incurrir en costos adicionales por dejar en ejecución instancias que se habían detenido anteriormente, Emily se aseguró de que la automatización devolviera las instancias a las que se aplicaron parches a su estado original antes de que se aplicaran los parches. Emily utilizó etiquetas asociadas a los grupos primarios y secundarios de instancias de base de datos para identificar a qué instancias se deben aplicar parches en el orden deseado.
Su solución automatizada existente funciona, pero, de ser posible, quiere mejorarla. Para ayudar con el mantenimiento del contenido del manual de procedimientos y facilitar la solución de problemas, le gustaría condensar la automatización en un solo manual de procedimientos y simplificar el número de parámetros de entrada. Además, le gustaría evitar la creación de múltiples automatizaciones secundarias.
Después de que Emily revisa las acciones de automatización disponibles, determina que puede mejorar su solución mediante la acción aws:executeScript
para ejecutar sus scripts de Python personalizados. Ahora, comienza a crear el contenido del manual de procedimientos de la siguiente manera:
-
En primer lugar, proporciona valores para el esquema y la descripción del manual de procedimientos, y define los parámetros de entrada para el manual de procedimientos principal.
A través del parámetro
AutomationAssumeRole
, Emily y sus colegas pueden utilizar un rol de IAM existente que permite a Automation realizar las acciones en el manual de procedimientos en su nombre. A diferencia del ejemplo 1, ahora el parámetroAutomationAssumeRole
es obligatorio en lugar de opcional. Debido a que este manual de procedimientos incluye accionesaws:executeScript
, siempre se requiere un rol de servicio de AWS Identity and Access Management (IAM) (o rol de asunción). Este requisito es necesario, ya que algunos de los scripts de Python especificados para las acciones llaman las operaciones de la API de AWS.Emily utiliza los parámetros
PrimaryPatchGroupTag
ySecondaryPatchGroupTag
para especificar las etiquetas asociadas al grupo principal y secundario de instancias de base de datos a las cuales se aplicarán parches. Para simplificar los parámetros de entrada necesarios, decide utilizar los parámetrosStringMap
en lugar de varios parámetrosString
, como hizo con el manual de procedimientos del ejemplo 1. De forma opcional, los parámetrosOperation
,RebootOption
ySnapshotId
se pueden utilizar para proporcionar valores a los parámetros del documento paraAWS-RunPatchBaseline
. Para evitar que se proporcionen valores no válidos a esos parámetros del documento, define losallowedValues
como se necesiten.- 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" } } },
-
Con los elementos de nivel superior definidos, Emily continúa con la creación de las acciones que componen los
mainSteps
del manual de procedimientos. El primer paso recopila los ID de todas las instancias asociadas a la etiqueta especificada en el parámetroPrimaryPatchGroupTag
y genera un parámetroStringMap
que contiene el ID de instancia y su estado actual. La salida de esta acción se utiliza en acciones posteriores.Tenga en cuenta que el parámetro de entrada
script
no es compatible con los manuales de procedimientos JSON. Los manuales de procedimientos JSON deben proporcionar contenido de script a través del parámetro de entradaattachment
.- 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 utiliza la salida de la acción anterior en otra acción
aws:executeScript
para verificar que todas las instancias asociadas a la etiqueta especificada en el parámetroPrimaryPatchGroupTag
se encuentren en el estadorunning
.Si el estado de la instancia ya es
running
oshutting-down
, el script sigue recorriendo las instancias restantes.Si el estado de la instancia es
stopping
, el script realiza un sondeo para que la instancia llegue al estadostopped
y activa la instancia.Si el estado de la instancia es
stopped
, el script activa la instancia.- 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 que todas las instancias asociadas a la etiqueta especificada en el parámetro
PrimaryPatchGroupTag
ya estén en funcionamiento o se encuentren en el estadorunning
. A continuación, usa otro script para verificar que todas las instancias, incluidas las que se activaron con la acción anterior, hayan alcanzado el 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 dos scripts más para devolver los valores
String
individuales de la clave y el valor de la etiqueta especificada en el parámetroPrimaryPatchGroupTag
. Los valores que devuelven estas acciones le permiten proporcionar valores al parámetroTargets
para el documentoAWS-RunPatchBaseline
de manera directa. A continuación, la automatización continúa con la aplicación de parches a la instancia con el documentoAWS-RunPatchBaseline
a través de la acciónaws: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" },
-
Una vez finalizada la operación de aplicación de parches, Emily quiere que la automatización devuelva las instancias de destino asociadas a la etiqueta que se especificó en el parámetro
PrimaryPatchGroupTag
al mismo estado en que estaban antes de que se iniciara la automatización. Para hacerlo, usa otra vez la salida de la primera acción en un script. En función del estado original de la instancia de destino, si la instancia estaba anteriormente en un estado distinto derunning
, se detiene. En cambio, si el estado de la instancia esrunning
, el script sigue recorriendo las instancias 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" },
-
La operación de aplicación de parches se completa para las instancias asociadas a la etiqueta especificada en el parámetro
PrimaryPatchGroupTag
. Ahora, Emily duplica todas las acciones anteriores en el contenido de su manual de procedimientos para indicar como destino las instancias asociadas a la etiqueta que se especificó en el 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 revisa el contenido del manual de procedimientos en script completado y crea el manual en la misma Cuenta de AWS y Región de AWS que las instancias de destino. Ahora, está lista para probar su manual de procedimientos para asegurarse de que la automatización funciona como desea antes de implementarlo en su entorno de producción. A continuación, se muestra el contenido del manual de procedimientos completado con scripts.
- 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 obtener más información acerca de las acciones de automatización que se utilizan en este ejemplo, consulte Referencia de acciones de Automatización de Systems Manager.