本教程包括。
- 定义你的AWS CDK应用程序和AWS Lambda处理器
- 手动构建和部署你的CDK应用程序
- 自动部署
当你构建一个基于云的应用程序时,你可以选择使用云提供商提供的GUI(图形用户界面)或CLI(命令行界面)来部署资源。这种方法在只有少量资源的情况下可以很好地工作,但随着你的应用程序的复杂性增加,手动管理基础设施会变得很困难。
你可以使用像Terraform或AWS CDK这样的解决方案,让你以编程方式管理你的基础设施代码,而不是手动部署你的云资源。借助面向对象编程语言的表现力,AWS CDK可以让你使用现有的技能和工具来开发云基础设施,加快开发过程。使用AWS CDK可以消除上下文切换的需要,因为您可以使用相同的IDE和工具同时定义基础设施代码和运行时代码。AWS CDK还使你的代码更容易与git工作流集成,并允许你使用CI/CD管道来自动化部署过程。
在本教程中,我将指导你使用AWS云开发工具包(CDK)来部署一个与AWS S3和AWS DynamoDB交互的AWS Lambda函数。
前提条件
在本教程中,你需要在你的系统中设置Node.js,以定义你的AWS CDK应用程序和AWS Lambda处理器。你还需要在你的系统上安装AWS CLI和AWS CDK CLI,以便你能够配置AWS凭证并手动构建你的CDK应用程序。你将需要一个AWS账户来部署应用程序,以及一个CircleCI账户来自动部署。下面是一份你需要的所有东西的清单,以便跟随本教程进行。
我们的教程是不分平台的,但以CircleCI为例。 如果你没有CircleCI账户。 在这里注册一个免费账户.
创建一个新的AWS CDK项目
首先,为您的CDK项目创建一个新的目录,并导航到它。
mkdir aws-cdk-lambda-circle-ci
cd aws-cdk-lambda-circle-ci
接下来,使用CDK CLI运行cdk init 命令,它使用TypeScript创建一个新的CDK项目。app 参数指定了初始化项目时要使用的模板。
cdk init app --language typescript
执行这个命令可以创建一个新的CDK项目,里面有一些文件。在本教程的后面,我将解释其中一些文件的意义和其中定义的结构。
注意。 CDK支持多种语言,如TypeScript、Python、Java和C#。你可以选择使用你所熟悉的任何语言。
添加一个NodeJS Lambda函数
在这一部分,你将使用Node.js定义一个AWS Lambda函数。该Lambda函数演示了如何将CSV文件保存到AWS S3,并向DynamoDB表添加条目。
为了开始,在CDK项目的根部创建一个lambda 目录。在lambda 目录中,为lambda handler添加一个index.js 文件,为定义依赖关系添加一个package.json 文件。
在package.json 文件中定义NodeJS项目的名称,并添加一些将被我们的处理器使用的依赖项。以下是该文件应包含的内容。
{
"name": "ddb-s3-lambda-function",
"version": "0.1.0",
"dependencies": {
"csv-stringify": "^6.0.5",
"fs": "0.0.1-security",
"uuid": "^8.3.2"
}
}
在你添加了依赖项之后,在lambda 目录下运行npm install 命令来安装这些包。
接下来,在index.js 文件中,定义一个空函数。在本教程的后面,你将实现这个函数。
exports.handler = async function (event) {
}
现在你可以开始实施了。用假数据创建一个CSV文件,并将其保存为一个临时文件。将这个代码段添加到你先前创建的index.js 文件的底部。
// add imports at the top of the file
var fs = require('fs');
const {stringify} = require('csv-stringify');
function writeCsvToFileAndUpload(filePath, objectKey) {
var data = getCsvData();
var output = stringify(data);
fs.writeFileSync(filePath, output);
// we will add the uploadFile method later
uploadFile(filePath, objectKey);
}
function getCsvData() {
// return some CSV data
return [
['1', '2', '3', '4'],
['a', 'b', 'c', 'd']
];
}
接下来,在index.js 文件中定义另一个函数,该函数接收一个本地文件路径和S3对象路径,并将文件上传到AWS S3。
// add imports at the top of the file
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-west-2' });
const s3 = new AWS.S3();
const BUCKET_NAME = process.env.BUCKET_NAME
const uploadFile = (fileName, objectKey) => {
// Read content from the file
const fileContent = fs.readFileSync(fileName);
// Setting up S3 upload parameters
const params = {
Bucket: BUCKET_NAME,
Key: objectKey,
Body: fileContent
};
// Uploading files to the bucket
s3.upload(params, function (err, data) {
if (err) {
throw err;
}
console.log(`File uploaded successfully. ${data.Location}`);
});
return objectKey;
};
这个函数读取本地文件的内容并使用AWS.S3 SDK的upload 函数来上传文件。
最后,添加你在index.js 文件中创建的空AWS Lambda处理器的实现。该Lambda处理程序在其事件参数中接收jobId 。该处理程序首先将CSV文件上传到AWS S3,然后用jobId 和上传对象的S3路径更新DynamoDB表。
//add import at the top of the file
const { v4: uuidv4 } = require('uuid');
var ddb = new AWS.DynamoDB();
const TABLE_NAME = process.env.TABLE_NAME
exports.handler = async function (event) {
try {
const uploadedObjectKey = generateDataAndUploadToS3()
const jobId = event['jobId']
var params = {
TableName: TABLE_NAME,
Item: {
'jobId': { S: jobId },
'reportFileName': { S: uploadedObjectKey }
}
};
// Call DynamoDB to add the item to the table
await ddb.putItem(params).promise();;
return {
"status": "success",
"jobId": jobId,
"objectKey": uploadedObjectKey
}
} catch (error) {
throw Error(`Error in backend: ${error}`)
}
}
const generateDataAndUploadToS3 = () => {
var filePath = '/tmp/test_user_data.csv'
const objectKey = `${uuidv4()}.csv`;
writeCsvToFileAndUpload(filePath, objectKey)
return objectKey
}
处理程序使用AWS.DynamoDB SDK的putItem 方法,在DynamoDB表中插入一个新项目。
为应用程序定义CDK结构
现在你已经定义了AWS Lambda处理程序,你可以定义所有将在你的应用程序中使用的CDK构造。AWS CDK 构造是云组件,它封装了使用一个或多个AWS服务的配置细节和胶合逻辑。CDK为最常用的AWS服务提供了一个构造库。
使用app 模板生成CDK项目,会创建lib/aws-cdk-lambda-circle-ci-stack.ts 文件。该文件包含AwsCdkLambdaCircleCiStack 类。使用这个文件来定义CDK构造。
// The snippet shows the original contents for reference. You do not need to replace the file contents.
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class AwsCdkLambdaCircleCiStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// we will add all the constructs here
}
}
接下来,回顾一下你的应用程序需要什么来工作。这些任务将在本教程的下一节中描述。
- 创建一个AWS S3桶,用来存放由AWS Lambda函数上传的CSV文件。
- 创建一个DynamoDB表,其中的
jobId和对象键将由AWS Lambda函数更新。 - 定义一个AWS Lambda函数,它将同时使用S3和DymamoDB表。确保AWS Lambda函数有
BUCKET_NAME和TABLE_NAME作为参数。 - 确保AWS Lambda函数有足够的权限来执行对S3桶和DymamoDB表的操作。
定义一个AWS S3桶
要定义一个AWS S3桶,添加一个CDK结构,在lib/aws-cdk-lambda-circle-ci-stack.ts 文件中定义的constructor 内创建一个AWS S3桶。AWS S3桶的名字在所有AWS账户中都是唯一的,所以你需要为你的桶提供一个唯一的名字。
import { Stack,
StackProps,
//update the existing import to add aws_s3
aws_s3 as s3
} from 'aws-cdk-lib';
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// we will add all the constructs here
// provide a unique name for your S3 bucket
const circleCiGwpBucket = new s3.Bucket(this, "circle-ci-gwp-bucket", {
bucketName: "<YOUR_BUCKET_NAME>",
});
}
在这一点上,如果你尝试部署堆栈,它将简单地部署一个CloudFormation应用程序并为你创建一个AWS S3桶。
定义一个DynamoDB表
要定义一个DynamoDB表,添加一个CDK结构,在lib/aws-cdk-lambda-circle-ci-stack.ts 文件中定义的constructor 内创建一个DynamoDB表。
import {
Stack,
StackProps,
aws_s3 as s3,
//update the existing import to add aws_dynamodb
aws_dynamodb as dynamodb
} from 'aws-cdk-lib';
constructor(scope: Construct, id: string, props?: StackProps) {
// other code //
//add the following construct after the existing code in the constructor
const circleCiGwpTable = new dynamodb.Table(this, "CircleCIGwpTable", {
tableName: "CircleCIGwpTable",
partitionKey: { name: "jobId", type: dynamodb.AttributeType.STRING },
});
}
该表的名称在你的AWS账户中必须是唯一的。如果你在AWS账户中已经有一个名为CircleCIGwpTable 的表,请在定义DynamoDB构造的同时更新tableName 。
注意,你已经将jobId 定义为该表的主分区键。这将确保jobId 的值在表中是唯一的。
定义一个AWS Lambda函数
要定义一个AWS Lambda函数,添加一个CDK构造,在lib/aws-cdk-lambda-circle-ci-stack.ts 文件中定义的constructor 内创建一个AWS Lambda函数。AWS Lambda函数将使用NodeJS运行时间,并将使用我们在lambda 目录中定义的代码。另外,S3桶名和DynamoDB表名将作为环境变量传递给该函数。
import {
Stack,
StackProps,
aws_s3 as s3,
aws_dynamodb as dynamodb,
//update the existing import to add aws_lambda and Duration
aws_lambda as lambda,
Duration
} from 'aws-cdk-lib';
constructor(scope: Construct, id: string, props?: StackProps) {
// other code //
//add the following construct after the existing code in the constructor
const circleCiGwpLambda = new lambda.Function(
this,
"CircleCiGwpLambda",
{
runtime: lambda.Runtime.NODEJS_14_X,
handler: "index.handler",
timeout: Duration.seconds(30),
code: lambda.Code.fromAsset("lambda/"),
environment: {
TABLE_NAME: circleCiGwpTable.tableName,
BUCKET_NAME: circleCiGwpBucket.bucketName
},
}
);
}
这个代码片段指定AWS Lambda函数的超时时间为30秒。Lambda函数的最大执行时间可以达到15分钟。
给AWS Lambda函数授予权限
最后,给AWS Lambda函数授予足够的权限。Lambda函数需要S3对象的putObject 权限。通过在lib/aws-cdk-lambda-circle-ci-stack.ts 的构造函数中的现有代码后添加以下构造来授予这些权限。
circleCiGwpBucket.grantPut(circleCiGwpLambda);
Lambda函数还需要DynamoDB表的读/写权限。在lib/aws-cdk-lambda-circle-ci-stack.ts 的构造函数中的现有代码后添加以下构造。
circleCiGwpTable.grantReadWriteData(circleCiGwpLambda);
你可以使用AWS CDK IAM模块定义更复杂的IAM策略。
部署CDK栈
现在你已经在你的堆栈中定义了CDK构造,你可以将应用程序部署到AWS账户中。首先手动部署该项目,以确保一切正常。然后,一旦你验证它的功能,你可以使用CircleCI自动部署。
在第一次部署项目之前,你需要使用cdk CLI来引导项目。引导应用程序提供AWS CDK可能需要的资源,以部署您的应用程序。这些资源可能包括一个用于存储部署相关文件的S3桶和用于授予部署权限的IAM角色。从项目的根部发出这个命令。
cdk bootstrap
确保你的系统上配置了AWS凭证。如果配置了凭证,CDK将自动使用它。
接下来,将应用程序部署到AWS账户中。
cdk deploy
一旦你执行了该命令,你可能会被提示确认将应用于你的账户的IAM角色/政策变化。如果你的应用程序设置正确,而且你的系统上有所有的先决条件,那么部署工作应该成功。
使用CircleCI自动部署应用程序
现在您已经使用命令行手动部署了CDK应用程序,您可以将工作流程自动化。自动化工作流程意味着每次推送代码到主分支时,基础设施的变化可以被自动打包和部署。你将需要完成以下任务。
- 更新
.gitignore - 更新NPM脚本
- 添加一个配置脚本
- 创建一个CircleCI项目
- 设置环境变量
更新.gitignore
由cdk init 命令生成的代码包含一个.gitignore 文件,该文件默认忽略了所有.js 文件。用这个代码片断替换.gitignore 的内容。
!jest.config.js
*.d.ts
node_modules
# CDK asset staging directory
.cdk.staging
cdk.out
更新NPM脚本
CircleCI的部署配置使用NPM脚本来执行deploy 和diff 命令。将这些脚本添加到根级别的package.json 文件中。
// update the aws-cdk-lambda-circle-ci/package.json file with the following scripts
{
...
"scripts": {
...
// add the ci_diff and ci_deploy scripts
"ci_diff": "cdk diff -c env=${ENV:-stg} 2>&1 | sed -r 's/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g' || true",
"ci_deploy": "cdk deploy -c env=${ENV:-stg} --require-approval never"
},
...
}
添加配置脚本
首先,在项目的根部添加一个.circleci/config.yml 脚本,包含CI管道的配置文件。将这个代码片段添加到其中。
version: 2.1
orbs:
aws-cli: circleci/aws-cli@2.0.6
executors:
default:
docker:
- image: 'cimg/node:14.18.2'
environment:
AWS_REGION: 'us-west-2'
jobs:
build:
executor: 'default'
steps:
- aws-cli/setup:
aws-access-key-id: AWS_ACCESS_KEY
aws-secret-access-key: AWS_ACCESS_SECRET
aws-region: AWS_REGION_NAME
- checkout
- run:
name: 'install_lambda_packages'
command: |
cd lambda/authorizer && npm install
cd ../../
cd lambda/processJob && npm install
cd ../../
- run:
name: 'build'
command: |
npm install
npm run build
- run:
name: 'cdk_diff'
command: |
if [ -n "$CIRCLE_PULL_REQUEST" ]; then
export ENV=stg
if [ "${CIRCLE_BRANCH}" == "develop" ]; then
export ENV=prd
fi
pr_number=${CIRCLE_PULL_REQUEST##*/}
block='```'
diff=$(echo -e "cdk diff (env=${ENV})\n${block}\n$(npm run --silent ci_diff)\n${block}")
data=$(jq -n --arg body "$diff" '{ body: $body }') # escape
curl -X POST -H 'Content-Type:application/json' \
-H 'Accept: application/vnd.github.v3+json' \
-H "Authorization: token ${GITHUB_TOKEN}" \
-d "$data" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${pr_number}/comments"
fi
- run:
name: 'cdk_deploy'
command: |
if [ "${CIRCLE_BRANCH}" == "main" ]; then
ENV=prd npm run ci_deploy
elif [ "${CIRCLE_BRANCH}" == "develop" ]; then
ENV=stg npm run ci_deploy
fi
CI脚本使用CircleCI的aws-cli orb来设置AWS配置--访问密钥和秘密。build 工作有几个不同的步骤,安装软件包,计算diff ,并部署变化。cdk_diff 步骤只在拉动请求上执行,并在PR上添加一个评论,总结基础设施的变化。
cdk_deploy 命令检查分支并只在prd 或stg 环境下部署。cdk_deploy 命令执行package.json 文件中定义的ci_deploy 脚本。
管道配置将负责构建、打包和部署CDK栈到指定的AWS账户。提交修改并推送到Github仓库。
注意。 如果您的地区不同,请务必用您自己的地区替换AWS_REGION 。
为该应用程序创建一个CircleCI项目
接下来,使用CircleCI控制台将软件库设置为一个CircleCI项目。在控制台的项目标签上,搜索GitHub repo名称。点击为你的项目设置项目。

你会被提示手动添加一个新的配置文件或使用一个现有的配置文件。你已经向代码库推送了所需的配置文件,所以选择最快的选项,并输入承载你的配置文件的分支的名称。点击Set Up Project继续。

完成设置将自动触发流水线。由于我们没有定义环境变量,管道第一次运行就会失败。
设置环境变量
在项目仪表板上点击项目设置,然后点击环境变量标签。点击添加环境变量。你应该已经创建了AWS访问密钥和秘密,正如本教程的前提条件中提到的。将这些值分别作为AWS_ACCESS_KEY 和AWS_ACCESS_SECRET 。此外,将AWS_REGION_NAME 的环境变量设置为你希望部署应用程序的区域。

一旦环境变量被配置好,再次运行管道。这一次,它应该成功构建。

总结
本教程向你展示了AWS CDK如何使管理基础设施相关的代码变得更容易。通过AWS CDK,你可以使用你熟悉的语言为你的应用程序提供资源。AWS CDK允许你在定义你的应用程序时使用逻辑语句和面向对象技术。另外,AWS CDK使基础设施的代码可以使用行业标准协议进行测试。
本教程带你了解了一个非常常见的用例,即定义一个具有与其他AWS服务互动的包依赖关系的AWS Lambda函数。我希望你同意,用所有的AWS服务定义堆栈,并使用这里列出的步骤为其授予细粒度的权限是多么简单。你可以在GitHub上查看本教程中使用的全部源代码。如果你想定义一个类似的堆栈,GitHub项目也可以作为你的模板来使用。