Blog
Die Erstellung von benutzerdefinierten CloudFormation-Ressourcen ist schlicht und einfach

Ich habe festgestellt, dass die meisten Blogbeiträge und Dokumentationen über Custom Resources für CloudFormation sehr kompliziert sind. Sie sind perfekt für erfahrene Benutzer, aber es ist ziemlich schwierig, sie zum ersten Mal zu verwenden. Dieser Blog-Beitrag ist wirklich einfach als Ihr erstes CloudFormation Custom Resource-Projekt zu verwenden und eignet sich im Allgemeinen gut für die meisten Anwendungsfälle. Spoiler: Der gesamte Custom Resource Stack ist eine einzige Datei. Er muss nicht gepackt und auf S3 hochgeladen werden und wird mit einem einzigen Befehl bereitgestellt.
Intro
Betrachten Sie die folgende CloudFormation-Vorlage. BucketName ist keine erforderliche Eigenschaft, aber nehmen wir an, dass sie es ist und Sie möchten, dass sie zufällig generiert wird. Einige andere Ressourcen zwingen Sie dazu, einen festen Namen einzugeben. Sie können dieselbe Vorlage nicht noch einmal bereitstellen, oder Sie müssen einen zufälligen Namen als Parameter angeben. Ich möchte, dass alle meine Ressourcen eine zufällige Endung erhalten, also brauche ich eine Art RandomString-Funktion in meiner CloudFormation-Vorlage.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: "myfixedbucketname"
Bereitstellen einer benutzerdefinierten Ressource Lambda
Es ist nun an der Zeit, die Lambda-Funktion für die benutzerdefinierte Ressource bereitzustellen. In vielen Fällen besteht eine Lambda-Funktion aus mehreren Dateien und Build- und Bereitstellungsschritten. In diesem Beispiel handelt es sich nur um eine einzige CloudFormation-Datei mit Inline-Python-Code.
Resources:
RandomString:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
import base64
import json
import logging
import string
import random
import boto3
from botocore.vendored import requests
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def random_string(size=6):
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(size))
def lambda_handler(event, context):
logger.info('got event {}'.format(event))
responseData = {}
if event['RequestType'] == 'Create':
number = int(event['ResourceProperties'].get('Number', 6))
rs = random_string(number)
responseData['upper'] = rs.upper()
responseData['lower'] = rs.lower()
else: # delete / update
rs = event['PhysicalResourceId']
responseData['upper'] = rs.upper()
responseData['lower'] = rs.lower()
logger.info('responseData {}'.format(responseData))
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, responseData['lower'])
FunctionName: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:random-string'
Handler: "index.lambda_handler"
Timeout: 30
Role: !GetAtt 'LambdaRole.Arn'
Runtime: python3.6
# The LambdaRole is very simple for this use case, because it only need to have access to write logs
# If the lambda is going to access AWS services using boto3, this role must be
# extended to give lambda the appropriate permissions.
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: "lambda-logs"
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- "arn:aws:logs:*:*:*"
Verteilen Sie diese Vorlage nun mit dem folgenden Befehl.
aws cloudformation create-stack
--stack-name cfn-custom-resources
--template-body file://template.yaml
--capabilities CAPABILITY_IAM
Verwenden Sie die benutzerdefinierte Ressource
Jetzt, wo die Lambda-Funktion Custom Resource zu funktionieren scheint, ist es an der Zeit, unsere CloudFormation-Vorlage so zu ändern, dass sie die Custom Resource verwendet, um einen zufälligen String für unseren S3-Bucket zu generieren.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "mybucket-${RandomString.lower}"
# returns attributes: lower and upper
RandomString:
Type: Custom::RandomString
Properties:
ServiceToken:
!Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:random-string"
Number: 8
Durch die Angabe von Number: 8 können Sie die Vorgabe von 6 Ziffern in eine längere zufällige Zeichenfolge ändern. Wir verwenden in diesem Beispiel die Zeichenkette "lower", da ein S3-Bucket klein geschrieben werden muss.
Probieren Sie es aus und sehen Sie, was passiert. Sie sollten nun einen Bucket haben, dessen Bucketname mit "mybucket-" beginnt, gefolgt von einer zufälligen Zeichenfolge. Sie können eine Aktualisierung der Ressource RandomString erzwingen, indem Sie Ihrem CloudFormation Stack Tags hinzufügen. Die zufällige Zeichenkette wird nicht neu generiert, da die PhysicalResourceId die Zufallszahl ist, können wir diese einfach als Eingabe verwenden.
Nächste Schritte & Schlussfolgerung
Einige Schlussfolgerungen:
- Es ist einfach, eine benutzerdefinierte CloudFormation-Ressource in einer einzigen CloudFormation-Vorlage zu erstellen
- Es ist einfach, die Lambda-Funktion zu schreiben, bereitzustellen und zu testen, einschließlich der Rolle und der IAM-Richtlinie für den Zugriff auf die AWS-Ressourcen
- Das Aktualisieren des Stacks und das Ausführen des Tests können Sie ganz einfach mit einem einzigen Bash-Befehl erledigen, oder indem Sie die Bereitstellung in das Testskript einfügen
- Mit dieser benutzerdefinierten RandomString-Ressource ist es einfach, mehrere Vorlagen gleichzeitig zu testen, da jede Ressource einen eindeutigen Namen hat.
Einzelne Vorlage
Jemand hat mich gefragt, ob es auch möglich ist, eine benutzerdefinierte Ressource zusammen mit dem Rest Ihres Stacks hinzuzufügen. Die Antwort ist ja, und dies ist die Beispielvorlage:
AWSTemplateFormatVersion: '2010-09-09'
Resources:
InputBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'yourbucket-${RandomName}'
RandomName:
Type: Custom::RandomNameGenerator
Properties:
ServiceToken: !GetAtt 'RandomNameGenerator.Arn'
RandomNameGenerator:
Type: AWS::Lambda::Function
Properties:
Handler: index.lambda_handler
Timeout: 30
Role: !GetAtt 'LambdaBasicExecutionRole.Arn'
Runtime: python3.6
Code:
ZipFile: |
import base64
import json
import logging
import string
import random
import boto3
from botocore.vendored import requests
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def random_string(size=6):
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(size))
def lambda_handler(event, context):
logger.info('got event {}'.format(event))
responseData = {}
if event['RequestType'] == 'Create':
number = int(event['ResourceProperties'].get('Number', 6))
rs = random_string(number)
responseData['upper'] = rs.upper()
responseData['lower'] = rs.lower()
else: # delete / update
rs = event['PhysicalResourceId']
responseData['upper'] = rs.upper()
responseData['lower'] = rs.lower()
logger.info('responseData {}'.format(responseData))
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, responseData['lower'])
LambdaBasicExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Condition: {}
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Verfasst von
Martijn van Dongen
Unsere Ideen
Weitere Blogs
Contact



