【问题标题】:How to create AWS resources to run a CDK application locally using SAM?如何创建 AWS 资源以使用 SAM 在本地运行 CDK 应用程序?
【发布时间】:2022-01-22 06:36:31
【问题描述】:

我有一个名为 Example4Be 的应用程序,我有一个创建 AWS 非 Lambda 资源的堆栈(在本例中只是 DynamoDB,但可能是 SQS、SNS 等):

import * as cdk from "aws-cdk-lib"
import {Construct} from "constructs"
import * as dynamodb from "aws-cdk-lib/aws-dynamodb"

export class Example4BeResourcesStack extends cdk.Stack {
    public readonly table: dynamodb.Table

    constructor(scope: Construct, id: string, props?: cdk.StageProps) {
        super(scope, id, props)

        this.table = new dynamodb.Table(this, "Hits", {
            partitionKey: {
                name: "path",
                type: dynamodb.AttributeType.STRING
            }
        })
    }
}

然后有一个堆栈使用它:

import * as cdk from "aws-cdk-lib"
import * as lambda from "aws-cdk-lib/aws-lambda"
import * as apigw from "aws-cdk-lib/aws-apigateway"
import {Construct} from "constructs"
import {Example4BeResourcesStack} from "./example4-be-resources-stack";

export class Example4BeStack extends cdk.Stack {
    public readonly hcEndpoint: cdk.CfnOutput

    constructor(scope: Construct, id: string, props?: cdk.StageProps) {
        super(scope, id, props)

        const resources = new Example4BeResourcesStack(scope, `${id}Resources`)

        const hello = new lambda.Function(this, "HelloHandler", {
            runtime: lambda.Runtime.NODEJS_14_X,
            code: lambda.Code.fromAsset("lambda"),
            handler: "hello.handler",
            environment: {
                HITS_TABLE_NAME: resources.table.tableName
            }
        })

        resources.table.grantReadWriteData(hello)

        // defines an API Gateway REST API resource backed by our "hello" function.
        const gateway = new apigw.LambdaRestApi(this, "Endpoint", {
            handler: hello
        })

        this.hcEndpoint = new cdk.CfnOutput(this, "GatewayUrl", {
            value: gateway.url
        })
    }
}

我有一个文件 app.ts 实例化堆栈:

#!/usr/bin/env node
import * as cdk from "aws-cdk-lib"
import {Example4BeStack} from "../lib/example4-be-stack";

const app = new cdk.App()
new Example4BeStack(app, "Example4BePipeline")

我运行它的方式是运行:

cdk synth --no-staging > template.yaml
sam local start-api

但这似乎并没有创建 DynamoDB 表。我想我错过了一个cdk deploy?这应该怎么做?

如果您愿意,我很乐意添加更多应用程序,实际部署中涉及到 CodePipeline。

【问题讨论】:

  • 嗨,这可能是用于本地测试吗? docs.aws.amazon.com/serverless-application-model/latest/…
  • @jspcal:我打电话给sam local start-api,这是您链接到的文档中描述的方法之一,但该文档没有说明正在测试的 Lambda 函数所需的资源本地,这就是我要问的。我想如果你有一个应用程序不写、读、发送或接收任何东西,纯粹计算的东西,那会工作,但我会有 DynamoDB、Cognito、SQS、SNS。
  • 您想在本地模拟 DynamoDB、Cognito、SQS 和 SNS,还是希望 lambda 函数在您使用 sam local 测试应用程序时对实际云资源进行 API 调用?
  • 我想在开发时使用真正的服务,所以它必须调用AWS。

标签: amazon-web-services aws-cdk aws-sam


【解决方案1】:

SAM-CDK integration 在本地运行模拟的 lambda,根据需要调用云部署的资源。

(1) 使用cdk deploy 部署您的堆栈

我们的 local-lambdas 将与 deploy-resource1 依赖项(表、队列等)交互。使用 separate account 进行测试是 AWS 最佳实践。2

(2) 告诉 SAM Lambda 的云端 environment variables

// locals.json is used by SAM to resolve lambda env vars
{
  // the lambda resource id from sam-template.yaml 
  "HandlerFunctionEDBA20C2": { 
    // our lambda has an environment variable named TABLE_NAME
    // this is deployed-resource in cloud - tip: define a CfnOutput in your stack.  cdk deploy will emit the values to the terminal
    "TABLE_NAME": "CdkTsPlayLocalTestingStack-SuperTable2CB94566-1VPR5PAXO0GM5" 
  },
  "AnotherLambdaCD21C99": {
    "QUEUE_ARN": "<queue-arn>"
  }
}

或者您可以将一组变量应用于所有函数:

{
  "Parameters": {
    "TABLE_NAME": "CdkTsPlayLocalTestingStack-SuperTable2CB94566-1VPR5PAXO0GM5" 
  }
}

(3) 现在可以本地迭代了

在本地更改您的 lambda,然后让 SAM cli 完成它的工作3

# synth the app template, output a sam format template
npx cdk synth '*' -a 'ts-node ./bin/app.ts' --profile <testing-profile> --no-staging > cdk.out/sam-template.yaml

# call a local function
sam local invoke HandlerFunctionEDBA20C2 \
--template cdk.out/sam-template.yaml \
--profile <testing-profile> \
--event lib/stacks/testing/LocalTestingStack/test-events/create.json \
--env-vars lib/stacks/testing/LocalTestingStack/locals.json

(4) 或与自动化测试集成

start-lambda 启动模拟器,针对本地端点运行自动化测试。 Docs here.

发生了什么?

SAM 目前有 3 个本地测试命令:

SAM command lambda apigw other (dynamo, etc) what for
sam local invoke local cloud test a local lambda
sam local start-lambda local cloud watch mode - call a lambda with SDK or CLI
sam local start-api local local cloud watch mode - test local lambda via a local endpoint

请注意,lambda 是所有这些优点的中心。我们可以将事件输入模拟到本地 lambda 中。我们可以模拟一个 api/sdk 端点。我们可以与 lambda SDK 调用引用的已部署资源进行交互。但目前未涵盖其他设置。例如,我们无法测试 lambda DynamoDB 流处理程序。让我们希望本地功能会随着时间的推移而增长。


(1) 我使用术语“local-lambda”来表示 lambda 的本地版本,sam local 将在容器中运行。 “deployed-resources”是指我们的 lambda 的云部署集成(例如 DynamoDB 表、队列等)。

(2) 此时,测试不会触及您的管道堆栈。完成本地测试后,将更改推送到 github,这将启动管道部署到(staging 和)prod 帐户。你可以有additional testing steps run as part of the pipeline stages

(3) 预览版sam-beta-cdk CLI makes it somewhat easier to integrate CDK with SAM 比原版CLI sam。例如,它似乎为我们创建了 sam .yaml。但是这两个工具似乎具有相同的基础功能。

【讨论】:

  • 非常感谢您提供如此详细的答案。我会尽快处理的。
  • locals.json 中的值从何而来?这个文件是手动构建的吗?
  • 是的,manuallyenv 文件值是已部署的资源 ID(请参阅代码 cmets)。脚注 #3 中链接的 AWS 博客文章也有一个示例。
  • 维护那个文件不是很麻烦吗?我正在努力寻找一条好的发展道路,而一切似乎中间都有巨大的巨石。
  • 关于 sam-beta-cdk。我找不到它。我认为它已经合并到 sam/cdk 中,因为那篇文章已经快一年了?
【解决方案2】:

由于@fedonev 已经回答了这个问题,我将专注于使用 CodePipeline(特别是 Python 中的 CDK 管道)的潜在实现,因为我已经在研究它了。我限制自己不使用任何实验性或预览版。

示例代码:https://github.com/KMK-Git/aws-cdk-sam-testing-demo

流程

阶段

来源

source = pipelines.CodePipelineSource.connection(
    "KMK-Git/aws-cdk-sam-testing-demo",
    "main",
    connection_arn=ssm.StringParameter.value_for_string_parameter(
        self,
        "codestar_connection_arn",
    ),
)

源代码存储库是使用 CodeStar 连接配置的。连接是事先手动完成的,我从 SSM 参数存储中获取 ARN。

构建、SelfMutate 和 UploadAssets

cdk_codepipeline = pipelines.CodePipeline(
    self,
    "Pipeline",
    synth=pipelines.ShellStep(
        "Synth",
        input=source,
        install_commands=[
            "pip install -r requirements.txt",
            "npm install -g aws-cdk",
        ],
        commands=[
            "cdk synth",
        ],
    ),
)
  • Synth 阶段用于将 CDK 代码合成到 CloudFormation 模板中。
  • SelfMutate 阶段是一项 CDK 功能,用于更新管道本身内部的管道。即使在初始部署之后,您也可以对管道进行更改。
  • Assets 阶段使用 cdk-assets 命令将所有 cdk 资产上传到其目的地。由于我已将堆栈分为两个单独的阶段,因此每个阶段都有自己的资产上传步骤。

如果您不使用 CDK 管道,这里是所有阶段的构建规范:

合成器:

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "pip install -r requirements.txt",
        "npm install -g aws-cdk"
      ]
    },
    "build": {
      "commands": [
        "cdk synth"
      ]
    }
  },
  "artifacts": {
    "base-directory": "cdk.out",
    "files": "**/*"
  }
}

对于自我变异:

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "npm install -g aws-cdk"
      ]
    },
    "build": {
      "commands": [
        "cdk -a . deploy PipelineStack --require-approval=never --verbose"
      ]
    }
  }
}

对于资产:

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "npm install -g cdk-assets"
      ]
    },
    "build": {
      "commands": [
        "cdk-assets --path \"assembly-LambdaStage/LambdaStageLambdasStackABCD123.assets.json\" --verbose publish \"longrandomstring:current_account-current_region\""
      ]
    }
  }
}

单元测试

testing = pipelines.CodeBuildStep(
    "UnitTesting",
    input=source,
    install_commands=[
        "pip install -r requirements.txt -r requirements-dev.txt",
    ],
    commands=[
        "pytest --cov",
    ],
    env={
        "QUEUE_URL": "SampleQueue",
        "TABLE_NAME": "SampleTest",
    },
    build_environment=codebuild.BuildEnvironment(
        build_image=codebuild.LinuxBuildImage.STANDARD_5_0,
        privileged=True,
        compute_type=codebuild.ComputeType.SMALL,
    ),
)

这是一个简单的步骤,它会在您的代码中运行任何单元测试。

如果您不使用 CDK 管道,这里是构建规范:

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "pip install -r requirements.txt -r requirements-dev.txt"
      ]
    },
    "build": {
      "commands": [
        "pytest --cov"
      ]
    }
  }
}

部署支持资源

cdk_codepipeline.add_stage(
    supporting_resources_stage,
    pre=[
        testing,
        pipelines.ConfirmPermissionsBroadening(
            "CheckSupporting", stage=supporting_resources_stage
        ),
    ],
)

这将部署支持资源,这是您的sam local 测试正常运行所必需的。如果 IAM 权限有任何扩展,权限扩展步骤会停止管道并强制手动批准。您也可以在单独的阶段添加单元测试。

如果您不使用 CDK 管道,这里是构建规范:

{
  "version": 0.2,
  "phases": {
    "build": {
      "commands": [
        "npm install -g aws-cdk",
        "export PIPELINE_NAME=\"$(node -pe '`${process.env.CODEBUILD_INITIATOR}`.split(\"/\")[1]')\"",
        "payload=\"$(node -pe 'JSON.stringify({ \"PipelineName\": process.env.PIPELINE_NAME, \"StageName\": process.env.STAGE_NAME, \"ActionName\": process.env.ACTION_NAME })' )\"",
        "ARN=$CODEBUILD_BUILD_ARN",
        "REGION=\"$(node -pe '`${process.env.ARN}`.split(\":\")[3]')\"",
        "ACCOUNT_ID=\"$(node -pe '`${process.env.ARN}`.split(\":\")[4]')\"",
        "PROJECT_NAME=\"$(node -pe '`${process.env.ARN}`.split(\":\")[5].split(\"/\")[1]')\"",
        "PROJECT_ID=\"$(node -pe '`${process.env.ARN}`.split(\":\")[6]')\"",
        "export LINK=\"https://$REGION.console.aws.amazon.com/codesuite/codebuild/$ACCOUNT_ID/projects/$PROJECT_NAME/build/$PROJECT_NAME:$PROJECT_ID/?region=$REGION\"",
        "export PIPELINE_LINK=\"https://$REGION.console.aws.amazon.com/codesuite/codepipeline/pipelines/$PIPELINE_NAME/view?region=$REGION\"",
        "if cdk diff -a . --security-only --fail $STAGE_PATH/\\*; then aws lambda invoke --function-name PipelineStack-PipelinePipelinesSecurityCheckCDKalpha-numeric --invocation-type Event --payload \"$payload\" lambda.out; export MESSAGE=\"No security-impacting changes detected.\"; else [ -z \"${NOTIFICATION_ARN}\" ] || aws sns publish --topic-arn $NOTIFICATION_ARN --subject \"$NOTIFICATION_SUBJECT\" --message \"An upcoming change would broaden security changes in $PIPELINE_NAME.\nReview and approve the changes in CodePipeline to proceed with the deployment.\n\nReview the changes in CodeBuild:\n\n$LINK\n\nApprove the changes in CodePipeline (stage $STAGE_NAME, action $ACTION_NAME):\n\n$PIPELINE_LINK\"; export MESSAGE=\"Deployment would make security-impacting changes. Click the link below to inspect them, then click Approve if all changes are expected.\"; fi"
      ]
    }
  },
  "env": {
    "exported-variables": [
      "LINK",
      "MESSAGE"
    ]
  }
}

sam 本地测试

sam_cli_test_step = pipelines.CodeBuildStep(
    "SAMTesting",
    input=source,
    env_from_cfn_outputs={
        "QUEUE_URL": supporting_resources_stage.stack.queue_url,
        "TABLE_NAME": supporting_resources_stage.stack.table_name,
    },
    install_commands=[
        "pip install -r requirements.txt",
        "npm install -g aws-cdk",
        "mkdir testoutput",
    ],
    commands=[
        'cdk synth -a "python synth_lambdas_stack.py" -o sam.out',
        'echo "{\\""SqsLambdaFunction\\"": {\\""QUEUE_URL\\"": \\""$QUEUE_URL\\""},'
        + '\\""DynamodbLambdaFunction\\"": {\\""TABLE_NAME\\"": \\""$TABLE_NAME\\"" }}"'
        + " > locals.json",
        'sam local invoke -t "sam.out/LambdasStack.template.json" --env-vars locals.json'
        + ' --no-event "DynamodbLambdaFunction"',
        'sam local invoke -t "sam.out/LambdasStack.template.json" --env-vars locals.json'
        + ' --no-event "SqsLambdaFunction"',
        "nohup sam local start-api -t sam.out/LambdasStack.template.json"
        + " --env-vars locals.json > testoutput/testing.log & ",
        "",
        "sleep 30",
        "curl --fail http://127.0.0.1:3000/sqs",
        "curl --fail http://127.0.0.1:3000/dynamodb",
    ],
    build_environment=codebuild.BuildEnvironment(
        build_image=codebuild.LinuxBuildImage.STANDARD_5_0,
        privileged=True,
        compute_type=codebuild.ComputeType.SMALL,
    ),
    primary_output_directory="testoutput/",
    role_policy_statements=[
        iam.PolicyStatement(
            actions=[
                "sqs:SendMessage",
                "sqs:GetQueueAttributes",
                "sqs:GetQueueUrl",
            ],
            resources=["*"],
        ),
        iam.PolicyStatement(
            actions=[
                "dynamodb:BatchWriteItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
            ],
            resources=["*"],
        ),
    ],
)
  • 在您的支持资源堆栈中,您需要定义 Lambda 代码作为堆栈输出所需的任何参数,例如资源名称和 ARN。我已使用环境变量将这些值提供给我的 Lambda 函数。
  • 在我的堆栈代码中,我指定了 CloudFormation Lambda 函数资源的逻辑 ID,而不是依赖于 CDK 自动生成的值。
sqs_lambda_base: _lambda.CfnFunction = sqs_lambda.node.default_child
sqs_lambda_base.override_logical_id("SqsLambdaFunction")
  • 我创建了一个单独的应用程序文件,它只合成我的 Lambda 堆栈,而不是完整的管道堆栈。从理论上讲,您应该能够使用完整堆栈的合成器输出,但这更易于配置。
  • 我添加了我的 Lambda 所需的权限,以便与我的支持资源堆栈创建的资源进行交互。
  • sam-beta-cdk 可用于简化此工作流程,但我没有在这里使用它,因为它仍处于预览阶段。

如果您不使用 CDK 管道,这里是构建规范:

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "pip install -r requirements.txt",
        "npm install -g aws-cdk",
        "curl --version",
        "mkdir testoutput"
      ]
    },
    "build": {
      "commands": [
        "cdk synth -a \"python synth_lambdas_stack.py\" -o sam.out",
        "echo \"{\\\"\"SqsLambdaFunction\\\"\": {\\\"\"QUEUE_URL\\\"\": \\\"\"$QUEUE_URL\\\"\"},\\\"\"DynamodbLambdaFunction\\\"\": {\\\"\"TABLE_NAME\\\"\": \\\"\"$TABLE_NAME\\\"\" }}\" > locals.json",
        "sam local invoke -t \"sam.out/LambdasStack.template.json\" --env-vars locals.json --no-event \"DynamodbLambdaFunction\"",
        "sam local invoke -t \"sam.out/LambdasStack.template.json\" --env-vars locals.json --no-event \"SqsLambdaFunction\"",
        "nohup sam local start-api -t sam.out/LambdasStack.template.json --env-vars locals.json > testoutput/testing.log & ",
        "",
        "sleep 30",
        "curl --fail http://127.0.0.1:3000/sqs",
        "curl --fail http://127.0.0.1:3000/dynamodb"
      ]
    }
  },
  "artifacts": {
    "base-directory": "testoutput/",
    "files": "**/*"
  }
}

部署 Lambda 函数

cdk_codepipeline.add_stage(
    lambdas_stage,
    pre=[
        sam_cli_test_step,
        pipelines.ConfirmPermissionsBroadening(
            "CheckLambda", stage=lambdas_stage
        ),
    ],
)

这类似于部署支持资源阶段。您也可以在其自己的单独阶段添加 sam 本地测试。

注意事项:

  • 如果您不想先部署,DynamoDB 特别支持local testing
  • 虽然我的管道仅部署一组资源,但您可以创建不同的阶段以部署到不同的环境/帐户。
  • 此处定义的 sam 本地测试步骤非常简单。您可以使用 API 测试工具等功能来运行一整套测试用例。 Here 是一个官方的 AWS 示例,它使用 selenium 来测试 Web 服务器,API 在 sam local 上运行。

限制

  • API 网关授权方是 not supported 用于 sam local 测试,因此如果您打算使用 Cognito 授权方,您将无法测试它们。

【讨论】:

  • 我决定尝试使用 LocalStack 作为替代方案,因为这似乎在复杂性方面失控了。我将现有的赏金奖励给了上一个问题,在 24 小时内,我将奖励你同样数量的如此惊人的详细响应和代码示例。太感谢了。我可能会发现自己又回到了这个问题上,但由于我无法选择一个获胜的正确答案,我至少想确保赏金得到公平奖励。
  • @pupeno 谢谢,我没想到!另外,出于好奇,您打算将 localstack 用作 CI 管道的一部分还是仅用于本地开发?
  • 我试过 localstack 但它甚至不能做cdk bootstrap,所以我不确定那是 路径。
猜你喜欢
  • 2021-10-05
  • 2021-12-29
  • 2019-10-02
  • 2020-07-14
  • 1970-01-01
  • 2021-01-05
  • 2020-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多