AWS CDKでAWS SAMのようにリソースを定義してみた
AWS CDKでもAWS SAMのようにリソースを定義したい時もある
こんにちは、のんピ(@non____97)です。
皆さんはAWS CDKを使いたい。でも、AWS SAMのようにリソースを定義したいと思ったことはありますか? 私はあります。
例えば、AWS Step Functionsのステートマシンを定義するとき、AWS CDKでは以下のようにTypeScriptやPythonなどのプログラミング言語で定義する必要があります。
import { Stack, StackProps, Duration, aws_lambda as lambda, aws_stepfunctions as sfn, aws_stepfunctions_tasks as tasks, } from "aws-cdk-lib"; import { Construct } from "constructs"; declare const submitLambda: lambda.Function; declare const getStatusLambda: lambda.Function; export class AwsSamStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const submitJob = new tasks.LambdaInvoke(this, "Submit Job", { lambdaFunction: submitLambda, // Lambda's result is in the attribute `Payload` outputPath: "$.Payload", }); const waitX = new sfn.Wait(this, "Wait X Seconds", { time: sfn.WaitTime.secondsPath("$.waitSeconds"), }); const getStatus = new tasks.LambdaInvoke(this, "Get Job Status", { lambdaFunction: getStatusLambda, // Pass just the field named "guid" into the Lambda, put the // Lambda's result in a field called "status" in the response inputPath: "$.guid", outputPath: "$.Payload", }); const jobFailed = new sfn.Fail(this, "Job Failed", { cause: "AWS Batch Job Failed", error: "DescribeJob returned FAILED", }); const finalStatus = new tasks.LambdaInvoke(this, "Get Final Job Status", { lambdaFunction: getStatusLambda, // Use "guid" field as input inputPath: "$.guid", outputPath: "$.Payload", }); const definition = submitJob .next(waitX) .next(getStatus) .next( new sfn.Choice(this, "Job Complete?") // Look at the "status" field .when(sfn.Condition.stringEquals("$.status", "FAILED"), jobFailed) .when( sfn.Condition.stringEquals("$.status", "SUCCEEDED"), finalStatus ) .otherwise(waitX) ); new sfn.StateMachine(this, "StateMachine", { definition, timeout: Duration.minutes(5), }); } }
管理したいステートマシンのワークフローが複雑でない場合は問題ありませんが、分岐やループ、並列処理が複数あると、定義するのがなかなか大変です。
一方、AWS SAMではステートマシンを定義する際に、ASL形式で記述されたワークフローを読み込むことができます。これにより、AWS Step Functions Workflow Studioで設計したワークフローを流用することが出来ます。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: StateMachineName: Description: Please type the Step Functions StateMachine Name. Type: String Default: 'sfn-sam-app-statemachine' LambdaFunctionName: Description: Please type the Lambda Function Name. Type: String Default: 'sfn-sam-app-function' Resources: LambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub ${LambdaFunctionName} CodeUri: functions/hello_world/ Handler: app.lambda_handler Runtime: python3.8 Timeout: 60 Role: !GetAtt LambdaFunctionRole.Arn LambdaFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSStepFunctionsReadOnlyAccess StateMachine: Type: AWS::Serverless::StateMachine Properties: Name: !Sub ${StateMachineName} DefinitionUri: statemachine/sfn.asl.json DefinitionSubstitutions: LambdaFunction: !GetAtt LambdaFunction.Arn Role: !GetAtt StateMachineRole.Arn Logging: Level: ALL IncludeExecutionData: True Destinations: - CloudWatchLogsLogGroup: LogGroupArn: !GetAtt StateMachineLogGroup.Arn StateMachineLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName : !Join [ "", [ '/aws/states/', !Sub '${StateMachineName}', '-Logs' ] ] StateMachineRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - states.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaRole - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
抜粋 : Step Functionsの構築はAWS SAMを利用すると捗りそうです
そんなある時、AWS CDKでもAWS Step Functions Workflow Studioで設計したワークフローを読みこんで、ステートマシンを定義したいなと思い、AWS CDKのAPI Referenceを漁っていると、見つけてしまいました。
aws-cdk-lib.aws_sam module を
そこで今回は、aws-cdk-lib.aws_sam moduleを紹介します。
aws-cdk-lib.aws_sam module とは
2021/2/16時点の最新のAWS CDK v2.12.0 ではこのモジュールはプレビューです。ご注意ください
aws-cdk-lib.aws_sam moduleはAWS SAMのConstructライブラリーです。
v2.12.0 時点ではL2 Constructはなく、L1 Constructのみとなっています。そのため、基本的にはAWS SAMのテンプレートを書くときと同じような書き味になります。
L1 ConstructとAWS SAMのTypeとの対応は以下の通りです。
L1 Construct | AWS SAM Type |
---|---|
CfnApi | AWS::Serverless::Api |
CfnApplication | AWS::Serverless::Application |
CfnFunction | AWS::Serverless::Function |
CfnHttpApi | AWS::Serverless::HttpApi |
CfnLayerVersion | AWS::Serverless::LayerVersion |
CfnSimpleTable | AWS::Serverless::SimpleTable |
CfnStateMachine | AWS::Serverless::StateMachine |
やってみた
ワークフローの設計
それではaws-cdk-lib.aws_sam module
を使って、実際にステートマシンを作成してみます。
まずはワークフローの設計を行います。
Step Functionsのコンソールより、ステートマシン
- ステートマシン
の作成をクリックします。
ワークフローを視覚的に設計
を選択して、次へ
をクリックします。
AWS Step Functions Workflow Studioでステートマシンのワークフローを設計します。設計後は定義をクリックし、生成された定義(ASL形式)を控えておきます。
生成された定義は以下になります。
{ "Comment": "A description of my state machine", "StartAt": "Pass 1", "States": { "Pass 1": { "Type": "Pass", "Next": "Wait" }, "Wait": { "Type": "Wait", "Seconds": 1, "Next": "Parallel 1" }, "Parallel 1": { "Type": "Parallel", "Branches": [ { "StartAt": "Pass 2", "States": { "Pass 2": { "Type": "Pass", "Next": "Parallel 2" }, "Parallel 2": { "Type": "Parallel", "Branches": [ { "StartAt": "Pass 4", "States": { "Pass 4": { "Type": "Pass", "End": true } } }, { "StartAt": "Pass 5", "States": { "Pass 5": { "Type": "Pass", "End": true } } } ], "Next": "Pass 6" }, "Pass 6": { "Type": "Pass", "End": true } } }, { "StartAt": "Pass 3", "States": { "Pass 3": { "Type": "Pass", "End": true } } } ], "Next": "Choice" }, "Choice": { "Type": "Choice", "Choices": [ { "Variable": "$", "IsPresent": true, "Next": "Success" } ], "Default": "Fail" }, "Success": { "Type": "Succeed" }, "Fail": { "Type": "Fail" } } }
AWS CDKでステートマシンをデプロイ
それでは、AWS CDKでステートマシンをデプロイします。
まず、先程生成された定義を保存します。今回は、./src/stepFunctions/workflow.asl.json
に保存しました。
. ├── .gitignore ├── .npmignore ├── README.md ├── bin │ └── aws-sam.ts ├── cdk.json ├── jest.config.js ├── lib │ └── aws-sam-stack.ts ├── package-lock.json ├── package.json ├── src │ └── stepFunctions │ └── workflow.asl.json ←生成された定義のファイル ├── test │ └── aws-sam.test.ts └── tsconfig.json
次に、生成された定義を読み込むようにステートマシンを定義します。
定義する際は、API ReferenceとAWS::Serverless::StateMachineのドキュメントを確認しながら行います。
なお、生成された定義を読み込む際は、ローカルに存在する定義を直接読み込むことはできず、S3バケットに一度アップロードする必要があります。S3バケットに定義ファイルをアップロードする処理もAWS CDKで行います。
実際のコードは以下の通りです。
import { Fn, Stack, StackProps, aws_s3 as s3, aws_s3_deployment as s3deploy, aws_logs as logs, aws_iam as iam, aws_sam as sam, } from "aws-cdk-lib"; import { Construct } from "constructs"; export class AwsSamStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const stackUniqueId = Fn.select(2, Fn.split("/", this.stackId)); // S3 buckets to store AWS Step Function Workflow const sfnWorkflowBucket = new s3.Bucket(this, "SfnWorkflowBucket", { encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: new s3.BlockPublicAccess({ blockPublicAcls: true, blockPublicPolicy: true, ignorePublicAcls: true, restrictPublicBuckets: true, }), }); // Deploy AWS Step Function Workflow new s3deploy.BucketDeployment(this, "DeployFilesToSfnWorkflowBucket", { sources: [ s3deploy.Source.asset("./src/stepFunctions/", { exclude: [".DS_Store"], }), ], destinationBucket: sfnWorkflowBucket, }); // CloudWatch Logs for State Machine Logs const stateMachineLogGroup = new logs.LogGroup( this, "StateMachineLogGroup", { logGroupName: `/aws/vendedlogs/states/CfnStateMachine-${stackUniqueId}-Logs`, retention: logs.RetentionDays.TWO_WEEKS, } ); // IAM Role for State Machine const stateMachineIamRole = new iam.Role(this, "StateMachineIamRole", { assumedBy: new iam.ServicePrincipal("states.amazonaws.com"), managedPolicies: [ new iam.ManagedPolicy(this, "CloudWatchLogsDeliveryFullAccessPolicy", { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, resources: ["*"], actions: [ "logs:CreateLogDelivery", "logs:GetLogDelivery", "logs:UpdateLogDelivery", "logs:DeleteLogDelivery", "logs:ListLogDeliveries", "logs:PutResourcePolicy", "logs:DescribeResourcePolicies", "logs:DescribeLogGroups", ], }), ], }), new iam.ManagedPolicy(this, "XRayAccessPolicy", { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, resources: ["*"], actions: [ "xray:PutTraceSegments", "xray:PutTelemetryRecords", "xray:GetSamplingRules", "xray:GetSamplingTargets", ], }), ], }), ], }); // AWS Step Functions State Machine new sam.CfnStateMachine(this, "CfnStateMachine", { definitionSubstitutions: { definitionSubstitutionsKey: "definitionSubstitutions", }, definitionUri: { bucket: sfnWorkflowBucket.bucketName, key: "workflow.asl.json", }, logging: { destinations: [ { cloudWatchLogsLogGroup: { logGroupArn: stateMachineLogGroup.logGroupArn, }, }, ], includeExecutionData: true, level: "ALL", }, name: "CfnStateMachine", role: stateMachineIamRole.roleArn, tags: { Name: "CfnStateMachine", }, tracing: { enabled: true, }, type: "STANDARD", }); } }
準備ができたら、npx cdk deploy
でステートマシンなど各種リソースをデプロイします。
Step Functionsのコンソールを確認すると、AWS Step Functions Workflow Studioで設計したワークフローのステートマシンが作成されたことが確認できます。
こちらのステートマシンを実行してみます。
エラーは発生せず、正常に終了しました。
X-Rayトレースマップも正常に確認できます。
CloudWatch Logsへのログ出力も正常にされていることが確認できます。
AWS CDKがますます便利になっていく
AWS CDKでもAWS SAMのようにリソースを定義したいという要望を叶えてくれるモジュール aws-cdk-lib.aws_sam module を紹介しました。
上手く使えばAWS CDKとAWS SAMの良いとこ取りのような形になるので、こちらのモジュールが早くGAになることを楽しみに待っています。
また、今回のデモで使ったコードは以下リポジトリにアップロードしています。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!