如何在亚马逊云科技上大幅降低无服务器网页应用冷启动时间(上篇)
背景
我们在云端搭建无服务器(serverless)开发架构时,经常会被冷启动(cold start)带来的应用延迟所困扰。冷启动是指当无服务器资源在一段时间内未被调用,或需要扩展以处理新请求时,系统需要初始化一个新的执行环境,这个过程会引入额外的延迟。通常造成冷启动的原因是无服务器资源初始化函数所花费的时间,这里包括加载函数代码、启动运行环境以及初始化函数代码的过程。
这种冷启动给开发者的日常开发带来了诸多的不便,最主要的就是应用访问延迟带来用户体验差,函数响应时间可能从从平时的毫秒级增加到数百毫秒甚至数秒。其次,由于冷启动的时间是不确定的并且难以控制,造成大规模系统中的应用程序的性能可能会出现波动,特别是在对响应时间敏感的应用。
在本系列文章中,我将和大家分享如何在消除电商场景下的无服务器应用架构的冷启动时间。同时学习如何将本地Java应用,进行无服务器云原生改造迁移到亚马逊云科技的Lambda上。在本系列上篇中我将带大家搭建一个本地话电商场景应用。电商场景下的无服务器网页应用架构如下:
方案所需知识
1. Lambda:无服务器计算的核心
AWS Lambda 是一种无服务器计算服务,可让开发者无需管理基础设施,按需运行代码。Lambda 的执行模型基于事件触发,支持多种编程语言,并与 AWS 服务无缝集成。开发者只需专注于业务逻辑,其余资源管理、扩展和高可用性由亚马逊云科技自动处理。
优势:开发者可以显著降低运维成本,实现更高的开发效率,同时按实际运行时间付费,经济高效。
2. Lambda Web Adapter:简化传统 Web 应用迁移
Lambda Web Adapter 是 亚马逊云科技提供的官方开源工具,用于帮助开发者将传统的 Java Web 应用快速迁移到无服务器架构。通过适配器,开发者可以继续使用现有的 Web 框架(如 Spring Boot、Javalin 等),无需对代码进行大规模改动。
优势:Web Adapter桥接了传统应用和无服务器架构之间的差距,让开发者轻松实现应用现代化,同时保留已有的技术栈。
3. Amazon Lambda SnapStart:显著优化冷启动性能
Amazon Lambda SnapStart 是 AWS 提供的功能,用于优化函数的启动性能。Lambda 会对已初始化的[执行环境]的内存和磁盘状态创建Firecracker microVM 快照、加密该快照并对其进行缓存以实现低延迟访问。在实际调用或者无服务器资源纵向扩展时快速加载快照,从而显著减少冷启动延迟,尤其对 Java 等需要较长初始化时间的语言效果显著。
优势:SnapStart 能将冷启动时间减少高达 90%,使 Java 应用在无服务器架构中具备更快的响应速度,为延迟敏感型应用提供了理想解决方案。
本文适用人群
软件开发工程师(Java)、DevOps、亚马逊云科技运维、云原生开发者等。
本实践包括的内容
1. 了解亚马逊云科技上的云原生无服务器Serverless架构设计,和亚马逊云科技无服务器服务(如Lambda、API Gateway、Aurora Serverless、OpenSearch Serverless等)
2. 使用Lambda web apdater工具将电商场景下的传统springboot应用,从本地迁移到Lambda,实现Java应用云原生现代化改造。
3. 通过Lambda Snapshot功能降低无服务器开发服务的冷启动时间(启动性能提升10倍以上)。
项目实操步骤
1. 初始化云端开发环境
本项目将运行在Ubuntu系统的EC2中,在系统中我们将安装GitHub开源项目Code Server在网页段进行项目开发。
1)首先我们登录控制台进入EC2服务主页。
2)在控制台并选择左侧“实例“ ,将目前处于”已停止“的实例“启动”,等待启动完成。
3)在控制台中进入“Cloudformation”
4)创建一个名为“CloudlabCampaign”的堆栈(第一个),并点击名称进入。堆栈YAML配置脚本如下;
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda Web Adpater workshop environment.
Parameters:
InstanceVolumeSize:
Type: Number
Description: The volume size in GB
Default: 30
InstanceType:
Description: Type of EC2 instance to launch
Type: String
Default: c6i.xlarge
AllowedValues: [
c6i.large, c6i.xlarge, c6i.2xlarge, c6i.4xlarge,
m6a.large, m6a.xlarge, m6a.2xlarge, m6a.4xlarge,
t3.nano, t3.micro, t3.small, t3.medium, t3.large, t3.xlarge, t3.2xlarge,
]
HomeFolder:
Type: String
Description: The home folder in the VSCodeInstance
Default: /Workshop
DevServerBasePath:
Type: String
Description: The base path for the application to be added to nginx sites-available list for code-server
Default: app
DevServerPort:
Type: Number
Description: The port for the DevServer
Default: 8081
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Instance Configuration
Parameters:
- InstanceVolumeSize
- Label:
default: Code Server Configuration
Parameters:
- HomeFolder
- DevServerBasePath
- DevServerPort
ParameterLabels:
InstanceVolumeSize:
default: Attached volume size
HomeFolder:
default: Folder to open in code server when launching
DevServerBasePath:
default: BasePath where the application runs
DevServerPort:
default: Port where the application runs
Mappings:
Subnets:
VPC:
CIDR: 10.0.0.0/16
PublicOne:
CIDR: 10.0.1.0/24
PublicTwo:
CIDR: 10.0.2.0/24
PrivateOne:
CIDR: 10.0.3.0/24
PrivateTwo:
CIDR: 10.0.4.0/24
# aws ec2 describe-managed-prefix-lists --region <REGION> | jq -r '.PrefixLists[] | select (.PrefixListName == "com.amazonaws.global.cloudfront.origin-facing") | .PrefixListId'
AWSRegions2PrefixListID:
ap-northeast-1:
PrefixList: pl-58a04531
ap-northeast-2:
PrefixList: pl-22a6434b
ap-south-1:
PrefixList: pl-9aa247f3
ap-southeast-1:
PrefixList: pl-31a34658
ap-southeast-2:
PrefixList: pl-b8a742d1
ca-central-1:
PrefixList: pl-38a64351
eu-central-1:
PrefixList: pl-a3a144ca
eu-north-1:
PrefixList: pl-fab65393
eu-west-1:
PrefixList: pl-4fa04526
eu-west-2:
PrefixList: pl-93a247fa
eu-west-3:
PrefixList: pl-75b1541c
sa-east-1:
PrefixList: pl-5da64334
us-east-1:
PrefixList: pl-3b927c52
us-east-2:
PrefixList: pl-b6a144df
us-west-1:
PrefixList: pl-4ea04527
us-west-2:
PrefixList: pl-82a045eb
Resources:
TrialUseOnlyRoleManagedPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: TrialUseOnlyRoleManagedPolicy
Path: "/"
PolicyDocument: '{"Version":"2012-10-17","Statement":[{"Sid":"VisualEditor1","Effect":"Allow","Action":["xray:*","cloudformation:*","cloudwatch:*","ec2:*","s3:*","lambda:*","rds:*","apigateway:*","elasticache:*","ssm:*","ssm:StartSession","ssm-guiconnect:GetConnection","ssm-guiconnect:CancelConnection","ssm-guiconnect:StartConnection","iam:CreateInstanceProfile","iam:DeleteInstanceProfile","iam:PassRole","iam:RemoveRoleFromInstanceProfile","iam:AddRoleToInstanceProfile"],"Resource":"*"}]}'
DeletionPolicy: Delete
TrialUseOnlyRole:
Type: AWS::IAM::Role
Properties:
RoleName: TrialUseOnly-ContentGeneratedByGenAIDoesNotRepresentViewsOfAWS
AssumeRolePolicyDocument:
Statement:
- Sid: 'AllowCloudlabDelegateAssumeThisRole'
Effect: Allow
Principal:
AWS:
- 'arn:aws:iam::976193244280:root'
Action:
- 'sts:AssumeRole'
Path: "/"
ManagedPolicyArns:
- Ref: TrialUseOnlyRoleManagedPolicy
DeletionPolicy: Delete
########### VPC Resources ###########
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !FindInMap [Subnets, VPC, CIDR]
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-VPC
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnetOne:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !FindInMap [Subnets, PublicOne, CIDR]
VpcId: !Ref VPC
MapPublicIpOnLaunch: true
AvailabilityZone: !Select [0, !GetAZs '']
PublicOneRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
PublicOneRoute:
Type: AWS::EC2::Route
DependsOn: GatewayAttachment
Properties:
RouteTableId: !Ref PublicOneRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicOneRouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicOneRouteTable
SubnetId: !Ref PublicSubnetOne
PublicSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !FindInMap [Subnets, PublicTwo, CIDR]
VpcId: !Ref VPC
MapPublicIpOnLaunch: true
AvailabilityZone: !Select [1, !GetAZs '']
PublicTwoRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
PublicTwoRoute:
Type: AWS::EC2::Route
DependsOn: GatewayAttachment
Properties:
RouteTableId: !Ref PublicTwoRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicTwoRouteTableAssoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicTwoRouteTable
SubnetId: !Ref PublicSubnetTwo
PrivateSubnetOne:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !FindInMap [Subnets, PrivateOne, CIDR]
VpcId: !Ref VPC
MapPublicIpOnLaunch: true
AvailabilityZone: !Select [0, !GetAZs '']
PrivateSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !FindInMap [Subnets, PrivateTwo, CIDR]
VpcId: !Ref VPC
MapPublicIpOnLaunch: true
AvailabilityZone: !Select [1, !GetAZs '']
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SG for Developer Machine - only allow CloudFront ingress
SecurityGroupIngress:
- Description: Allow HTTP from com.amazonaws.global.cloudfront.origin-facing
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourcePrefixListId: !FindInMap [AWSRegions2PrefixListID, !Ref 'AWS::Region', PrefixList]
SecurityGroupEgress:
- Description: Allow all outbound traffic
IpProtocol: -1
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SG for Developer Machine - only allow traffic from specific ELB
SecurityGroupIngress:
- Description: Allow HTTP from ELB
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
SecurityGroupEgress:
- Description: Allow all outbound traffic
IpProtocol: -1
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
PrivateSubnetOneParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /litemall/prod/vpc/private-subnet-one
Type: String
Value: !Ref PrivateSubnetOne
Description: Private Subnet One's ID
PrivateSubnetTwoParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /litemall/prod/vpc/private-subnet-two
Type: String
Value: !Ref PrivateSubnetTwo
Description: Private Subnet Two's ID
ApplicationSecurityGroupParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /litemall/prod/vpc/application-security-group
Type: String
Value: !Ref ApplicationSecurityGroup
Description: Application Security Group ID
########### SSM Resources ###########
SSMLogBucket:
Type: AWS::S3::Bucket
Metadata:
cfn_nag:
rules_to_suppress:
- id: W35
reason: Access logs aren't needed for this bucket
DeletionPolicy: Delete
Properties:
AccessControl: Private
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VSCodeInstanceSSMDoc:
Type: AWS::SSM::Document
Properties:
DocumentType: Command
Content:
schemaVersion: '2.2'
description: Bootstrap VSCode code-server instance
parameters:
architecture:
type: String
default: amd64
description: Instance architecture type
allowedValues:
- arm64
- amd64
ubuntuVersion:
type: String
default: jammy
allowedValues:
- focal
- bionic
- jammy
nodeVersion:
type: String
default: node_20.x
allowedValues:
- node_21.x
- node_20.x
- node_19.x
dotNetVersion:
type: String
default: dotnet-sdk-8.0
allowedValues:
- dotnet-sdk-8.0
- dotnet-sdk-7.0
- dotnet-sdk-8.0
mainSteps:
- action: aws:runShellScript
name: InstallAWSCLI
inputs:
runCommand:
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y curl unzip
- curl -fsSL https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip -o /tmp/aws-cli.zip
- unzip -q -d /tmp /tmp/aws-cli.zip
- sudo /tmp/aws/install
- rm -rf /tmp/aws
- aws --version
- action: aws:runShellScript
name: InstallDocker
inputs:
runCommand:
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
- echo "deb [signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu {{ ubuntuVersion }} stable" >> /etc/apt/sources.list.d/docker.list
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io
- usermod -aG docker ubuntu
- docker --version
- action: aws:runShellScript
name: InstallGit
inputs:
runCommand:
- add-apt-repository ppa:git-core/ppa
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y git
- sudo -u ubuntu git config --global user.email "participant@workshops.aws"
- sudo -u ubuntu git config --global user.name "Workshop Participant"
- sudo -u ubuntu git config --global init.defaultBranch "main"
- git --version
- action: aws:runShellScript
name: InstallNode
inputs:
runCommand:
- curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource-keyring.gpg
- echo "deb [arch={{ architecture }} signed-by=/usr/share/keyrings/nodesource-keyring.gpg] https://deb.nodesource.com/{{ nodeVersion }} {{ ubuntuVersion }} main" >> /etc/apt/sources.list.d/nodesource.list
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs
- action: aws:runShellScript
name: InstallPython
inputs:
runCommand:
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y python3-pip python3.10-venv python3-boto3 python3-pytest
- echo 'alias pytest=pytest-3' >> /home/ubuntu/.bashrc
- python3 --version
- pip3 --version
- action: aws:runShellScript
name: UpdateProfile
inputs:
runCommand:
- '#!/bin/bash'
- echo LANG=en_US.utf-8 >> /etc/environment
- echo LC_ALL=en_US.UTF-8 >> /etc/environment
- echo 'PATH=$PATH:/home/ubuntu/.local/bin' >> /home/ubuntu/.bashrc
- echo 'export PATH' >> /home/ubuntu/.bashrc
- !Sub echo 'export AWS_REGION=${AWS::Region}' >> /home/ubuntu/.bashrc
- !Sub echo 'export AWS_ACCOUNTID=${AWS::AccountId}' >> /home/ubuntu/.bashrc
- echo 'export NEXT_TELEMETRY_DISABLED=1' >> /home/ubuntu/.bashrc
- action: aws:runShellScript
name: ConfigureCodeServer
inputs:
runCommand:
- '#!/bin/bash'
- export HOME=/home/ubuntu
- apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y nginx
- curl -fsSL https://code-server.dev/install.sh | sh
- sudo systemctl enable --now code-server@ubuntu
- !Sub |
sudo tee /etc/nginx/sites-available/code-server <<EOF
server {
listen 80;
listen [::]:80;
server_name ${CloudFrontDistribution.DomainName};
location / {
proxy_pass http://localhost:8080/;
proxy_set_header Host \$host;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
}
location /${DevServerBasePath} {
proxy_pass http://localhost:${DevServerPort}/${DevServerBasePath};
proxy_set_header Host \$host;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
}
}
EOF
- |
sudo tee /home/ubuntu/.config/code-server/config.yaml <<EOF
cert: false
auth: password
hashed-password: "$(echo -n $(aws sts get-caller-identity --query "Account" --output text) | sudo npx argon2-cli -e)"
EOF
- sudo -u ubuntu --login mkdir -p /home/ubuntu/.local/share/code-server/User/
- sudo -u ubuntu --login touch /home/ubuntu/.local/share/code-server/User/settings.json
- !Sub |
sudo tee /home/ubuntu/.local/share/code-server/User/settings.json <<EOF
{
"extensions.autoUpdate": false,
"extensions.autoCheckUpdates": false,
"terminal.integrated.cwd": "${HomeFolder}",
"telemetry.telemetryLevel": "off",
"security.workspace.trust.startupPrompt": "never",
"security.workspace.trust.enabled": false,
"security.workspace.trust.banner": "never",
"security.workspace.trust.emptyWindow": false,
"editor.tabSize": 2,
"python.testing.pytestEnabled": true,
"auto-run-command.rules": [
{
"command": "workbench.action.terminal.new"
}
]
}
EOF
- sudo systemctl restart code-server@ubuntu
- sudo ln -s ../sites-available/code-server /etc/nginx/sites-enabled/code-server
- sudo systemctl restart nginx
- sudo -u ubuntu --login code-server --install-extension AmazonWebServices.amazon-q-vscode --force
- sudo -u ubuntu --login code-server --install-extension synedra.auto-run-command --force
- sudo -u ubuntu --login code-server --install-extension mtxr.sqltools --force
- sudo -u ubuntu --login code-server --install-extension mtxr.sqltools-driver-mysql --force
- sudo -u ubuntu --login code-server --install-extension vmware.vscode-boot-dev-pack --force
- sudo chown ubuntu:ubuntu /home/ubuntu -R
- action: aws:runShellScript
name: InstallCDK
inputs:
runCommand:
- npm install -g aws-cdk
- cdk --version
- action: aws:runShellScript
name: InstallJavaTools
inputs:
runCommand:
- wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg
- echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list
- sudo apt-get update && sudo apt-get install -y java-17-amazon-corretto-jdk && sudo apt-get install -y java-1.8.0-amazon-corretto-jdk
- sudo apt-get install -y maven
- mvn -version
- action: aws:runShellScript
name: InstallAWSSamCli
inputs:
runCommand:
- wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
- unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
- sudo ./sam-installation/install
- aws --version
- rm aws-sam-cli-linux-x86_64.zip && rm -rf sam-installation
- action: aws:runShellScript
name: InstallTools
inputs:
runCommand:
- sudo apt install -y mysql-client-core-8.0 redis-tools jq
VSCodeInstanceSSMAssociation:
Type: AWS::SSM::Association
Properties:
Name: !Ref VSCodeInstanceSSMDoc
OutputLocation:
S3Location:
OutputS3BucketName: !Ref SSMLogBucket
OutputS3KeyPrefix: bootstrap
Targets:
- Key: tag:SSMBootstrap
Values: [True]
### Empty S3 bucket resources ###
EmptyS3BucketExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: !Sub EmptyS3BucketPolicy-${AWS::Region}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:DeleteObject
Resource: '*'
EmptyS3Bucket:
Type: AWS::Lambda::Function
Metadata:
cfn_nag:
rules_to_suppress:
- id: W58
reason: EmptyS3BucketExecutionRole has the AWSLambdaBasicExecutionRole managed policy attached, allowing writing to CloudWatch logs
- id: W89
reason: Bootstrap function does not need the scaffolding of a VPC or provisioned concurrency
- id: W92
reason: Bootstrap function does not need provisioned concurrency
Properties:
Description: Empty S3 bucket CloudFormation custom resource
Handler: index.lambda_handler
Role:
Fn::GetAtt:
- EmptyS3BucketExecutionRole
- Arn
Runtime: python3.11
MemorySize: 1024
Timeout: 400
Code:
ZipFile: |
import boto3
import cfnresponse
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info('event: {}'.format(event))
logger.info('context: {}'.format(context))
if event['RequestType'] == 'Delete':
try:
AssetsBucketName = (event['ResourceProperties']['S3Bucket'])
s3 = boto3.resource('s3')
logger.info('S3 Object initialized')
bucket = s3.Bucket(AssetsBucketName)
logger.info('S3 bucket: ' + AssetsBucketName)
bucket.objects.all().delete()
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData={}, reason='S3 bucket emptied: ' + AssetsBucketName )
except Exception as e:
logger.error(e, exc_info=True)
cfnresponse.send(event, context, cfnresponse.FAILED, responseData={}, reason=str(e))
else:
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData={}, reason='No action to take')
EmptyS3BucketLogGroup:
Metadata:
cfn_nag:
rules_to_suppress:
- id: W84
reason: KMS Key not required for encrypting this non sensitive data
Type: AWS::Logs::LogGroup
DeletionPolicy: Delete
UpdateReplacePolicy: Delete
Properties:
LogGroupName: !Sub /aws/lambda/${EmptyS3Bucket}
RetentionInDays: 7
EmptyS3BucketCustomResource:
Type: Custom::EmptyS3Bucket
Properties:
ServiceToken: !GetAtt EmptyS3Bucket.Arn
S3Bucket: !Ref SSMLogBucket
########### EC2 Resources ###########
VSCodeInstanceRole:
Metadata:
cfn_nag:
rules_to_suppress:
- id: W11
reason: CodeWhisperer requires '*' as a resource, reference https://docs.aws.amazon.com/codewhisperer/latest/userguide/cloud9-setup.html#codewhisperer-IAM-policies
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
- ssm.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess
Policies:
- PolicyName: !Sub CDKAssumeRolePolicy-${AWS::Region}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource:
- !Sub arn:${AWS::Partition}:iam::*:role/cdk-*
- PolicyName: !Sub Codewhisperer-${AWS::Region}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- codewhisperer:GenerateRecommendations
Resource: '*'
VSCodeInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref VSCodeInstanceRole
VSCodeInstanceEC2Instance:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- Ebs:
VolumeSize: !Ref InstanceVolumeSize
VolumeType: gp3
DeleteOnTermination: true
Encrypted: true
DeviceName: /dev/sda1
Monitoring: true
SubnetId: !Ref PublicSubnetOne
ImageId: >-
{{resolve:ssm:/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id}}
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !Ref EC2SecurityGroup
IamInstanceProfile: !Ref VSCodeInstanceProfile
UserData:
Fn::Base64: !Sub |
#cloud-config
hostname: dev
runcmd:
- mkdir -p ${HomeFolder} && chown ubuntu:ubuntu ${HomeFolder}
Tags:
- Key: SSMBootstrap
Value: True
VSCodeLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
SecurityGroups:
- !Ref ApplicationSecurityGroup
Subnets:
- !Ref PublicSubnetOne
- !Ref PublicSubnetTwo
Type: application
VSCodeTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: vs-code-tg
Port: 80
Protocol: HTTP
TargetType: instance
VpcId: !Ref VPC
Targets:
- Id: !Ref VSCodeInstanceEC2Instance
Port: 80
HealthCheckPath: /
HealthyThresholdCount: 2
UnhealthyThresholdCount: 2
HealthCheckTimeoutSeconds: 5
HealthCheckIntervalSeconds: 30
Matcher:
HttpCode: 200-399
VSCodeListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref VSCodeTargetGroup
LoadBalancerArn: !Ref VSCodeLoadBalancer
Port: 80
Protocol: HTTP
LambdaEC2Role:
Type: AWS::IAM::Role
Properties:
#RoleName: LambdaEC2StartStopRole
Description: IAM Role for Lambda to Start Stop EC2 instances
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
Policies:
- PolicyName: LambdaEC2StartStopPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ec2:StartInstances
- ec2:StopInstances
Resource: arn:aws:ec2:*:*:instance/*
- Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:DescribeTags
- ec2:DescribeInstanceStatus
Resource: '*'
CustomResourceLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: EC2StopTrigger
Runtime: python3.9
Handler: index.lambda_handler
Role: !GetAtt LambdaEC2Role.Arn
Code:
ZipFile: |
import boto3
import logging
import cfnresponse
import os
logger = logging.getLogger()
logger.setLevel(logging.INFO)
region = os.environ['AWS_REGION']
ec2 = boto3.resource('ec2', region_name=region)
def lambda_handler(event, context):
filters = [
{
'Name': 'instance-state-name',
'Values': ['running']
}
]
instances = ec2.instances.filter(Filters=filters)
RunningInstances = [instance.id for instance in instances]
print("Running Instances with AutoStop Tag : " + str(RunningInstances))
if len(RunningInstances) > 0:
for instance in instances:
if instance.state['Name'] == 'running':
print("Stopping Instance : " + instance.id)
AutoStopping = ec2.instances.filter(InstanceIds=RunningInstances).stop()
print("Stopped Instances : " + str(RunningInstances))
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData={}, reason='' )
else:
print("Instance not in Running state or AutoStop Tag not set...")
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData={}, reason='No action to take')
Description: >-
Auto Stop EC2 Instance (from tag : AutoStop)
EC2StopTrigger:
Type: Custom::EC2StopTrigger
Properties:
ServiceToken: !GetAtt CustomResourceLambda.Arn
EC2InstanceId: !Ref VSCodeInstanceEC2Instance
DependsOn: VSCodeInstanceEC2Instance
########### CloudFront Resources ###########
VSCodeInstanceCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
DefaultTTL: 86400
MaxTTL: 31536000
MinTTL: 1
Name: !Join ['-', ['VSCodeServer', !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]]
ParametersInCacheKeyAndForwardedToOrigin:
CookiesConfig:
CookieBehavior: all
EnableAcceptEncodingGzip: False
HeadersConfig:
HeaderBehavior: whitelist
Headers:
- Accept-Charset
- Authorization
- Origin
- Accept
- Referer
- Host
- Accept-Language
- Accept-Encoding
- Accept-Datetime
QueryStringsConfig:
QueryStringBehavior: all
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: True
HttpVersion: http2
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
- PUT
- PATCH
- POST
- DELETE
CachePolicyId: !Ref VSCodeInstanceCachePolicy
OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3 # Managed-AllViewer - see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#:~:text=When%20using%20AWS,47e4%2Db989%2D5492eafa07d3
TargetOriginId: !Sub CloudFront-${AWS::StackName}
ViewerProtocolPolicy: allow-all
Origins:
- DomainName: !GetAtt VSCodeLoadBalancer.DNSName
Id: !Sub CloudFront-${AWS::StackName}
CustomOriginConfig:
OriginProtocolPolicy: http-only
########### Aurora MySQL Serverless Resources ###########
DbSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SG for Database - only allow ingress from Code Server
SecurityGroupIngress:
- Description: Allow access from Code Server
IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref EC2SecurityGroup
- Description: Allow access from Code Server
IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
SecurityGroupEgress:
- Description: Allow all outbound traffic
IpProtocol: -1
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
DbSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: DBSubnetGroup for Aurora Serverless DB
SubnetIds:
- Ref: PrivateSubnetOne
- Ref: PrivateSubnetTwo
DBClusterParameterGroup:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Description: "Litemall DB Cluster Parameter Group"
Family: aurora-mysql8.0
Parameters:
character_set_server: utf8mb4
collation_server: utf8mb4_unicode_ci
DBMasterUsername:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /litemall/prod/db/master-username
Type: String
Value: admin
Description: DB Master Username
DBMasterPassword:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /litemall/prod/db/master-password
Type: String
Value: !Sub LiteMall-${AWS::AccountId}
Description: DB Master Password
DatabaseCluster:
Type: AWS::RDS::DBCluster
DeletionPolicy: Snapshot
Properties:
Engine: aurora-mysql
EngineVersion: "8.0.mysql_aurora.3.05.2"
DBClusterParameterGroupName: !Ref DBClusterParameterGroup
MasterUsername: !GetAtt DBMasterUsername.Value
MasterUserPassword: !GetAtt DBMasterPassword.Value
ServerlessV2ScalingConfiguration:
MinCapacity: 0.5
MaxCapacity: 16
DBSubnetGroupName:
Ref: DbSubnetGroup
VpcSecurityGroupIds:
- Ref: DbSecurityGroup
DatabaseName: litemall
DatabaseDefaultInstance:
Type: AWS::RDS::DBInstance
Properties:
Engine: aurora-mysql
DBInstanceClass: db.serverless
DBClusterIdentifier: !Ref DatabaseCluster
MonitoringInterval: 0
DBClusterEndpoint:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /litemall/prod/db/cluster-endpoint
Type: String
Value: !GetAtt DatabaseCluster.Endpoint.Address
Description: DB Cluster Endpoint
########### ElastiCache Redis Resources ###########
RedisSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SG for Redis - only allow ingress from Code Server
SecurityGroupIngress:
- Description: Allow access from Code Server
IpProtocol: tcp
FromPort: 6379
ToPort: 6379
SourceSecurityGroupId: !Ref EC2SecurityGroup
- Description: Allow access from Code Server
IpProtocol: tcp
FromPort: 6379
ToPort: 6379
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
SecurityGroupEgress:
- Description: Allow all outbound traffic
IpProtocol: -1
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
ServerlessRedisCache:
Type: AWS::ElastiCache::ServerlessCache
Properties:
ServerlessCacheName: !Sub ServerlessRedisCache-${AWS::AccountId}
Engine: redis
CacheUsageLimits:
DataStorage:
Maximum: 10
Unit: GB
ECPUPerSecond:
Maximum: 5000
SubnetIds:
- Ref: PrivateSubnetOne
- Ref: PrivateSubnetTwo
SecurityGroupIds:
- Ref: RedisSecurityGroup
ServerlessRedisCacheEndpoint:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /litemall/prod/redis/endpoint
Type: String
Value: !GetAtt ServerlessRedisCache.Endpoint.Address
Description: Redis Cache Endpint
Outputs:
CodeServerPassword:
Description: VSCode-Server Password
Value: !Ref AWS::AccountId
Export:
Name: !Sub ${AWS::StackName}-CodeServerPassword
CodeServerURL:
Description: VSCode-Server URL
Value: !Sub https://${CloudFrontDistribution.DomainName}/?folder=${HomeFolder}
Export:
Name: !Sub ${AWS::StackName}-CodeServerURL
DatabaseName:
Description: Database Name
Value: litemall
Export:
Name: !Sub ${AWS::StackName}-DatabaseName
DBMasterUsername:
Description: DB Master Username
Value: !GetAtt DBMasterUsername.Value
Export:
Name: !Sub ${AWS::StackName}-DBMasterUsername
DBMasterPassword:
Description: DB Master Password
Value: !GetAtt DBMasterPassword.Value
Export:
Name: !Sub ${AWS::StackName}-DBMasterPassword
DBClusterEndpoint:
Description: DB Cluster Endpoint
Value: !GetAtt DBClusterEndpoint.Value
Export:
Name: !Sub ${AWS::StackName}-DBClusterEndpoint
RedisCacheEndpoint:
Description: Redis Cache Endpint
Value: !GetAtt ServerlessRedisCacheEndpoint.Value
Export:
Name: !Sub ${AWS::StackName}-RedisCacheEndpoint
PrivateSubnetOneID:
Description: Private Subnet One ID
Value: !Ref PrivateSubnetOne
Export:
Name: !Sub ${AWS::StackName}-PrivateSubnetOneID
PrivateSubnetTwoID:
Description: Private Subnet Two ID
Value: !Ref PrivateSubnetTwo
Export:
Name: !Sub ${AWS::StackName}-PrivateSubnetTwoID
ApplicationSecurityGroupID:
Description: Application Security Group ID
Value: !Ref ApplicationSecurityGroup
Export:
Name: !Sub ${AWS::StackName}-ApplicationSecurityGroupID
5)在浏览器中打开堆栈的“输出”页面中"CodeServerURL"字段值.
6)打开链接后,显示出 Code Server 的开发环境的登录界面。登录密码是 “输出” 中"CodeServerPassword"的值。
7)点击“SUBMIT”登录,就可以看到Code Server的IDE开发界面了
2. 下载示例代码库,并修改配置文件
接下来我们将下载电商应用代码库,配置数据库和本地开发环境。
下载 Github 代码库
1)在Code Server网页IDE中运行以下命令
git clone https://github.com/aws-samples/build-serverless-ecommerce-application-with-genai.git .
初始化 MySQL 数据库
2) 回到Cloudformation堆栈的“输出中”中找到 "DatabaseName", "DBClusterEndpoint", "DBMasterPassword", 和"DBMasterUsername"字段的值。
3)打开 IDE 左侧的 SQLTools 插件,点击 "Add New Connection" 按钮,点击 MySQL 图标。
4) 将取出的值输入connection name, Server Address, Database 和 Username。
5) 点击 "TEST CONNECTION"测试连接是否成功,点击 "ALLOW"
6)在弹出框中输入刚提取的"DBMasterPassword" 的值, 然后回车。
7)测试成功会显示“successfully connected”,这时点击“save connection”
8)双击 SQLTools 插件中新建好的 connection,连接到数据库。
9)在 Explorer 中打开 "litemall-db/sql/litemal_table.sql", 然后点击 "Run on active connection" 来新建 litemall 的数据库表。
10)在 Explorer 中打开 "litemall-db/sql/litemal_data.sql", 然后点击 "Run on active connection" 来导入 litemall 的演示数据。
配置.env 文件
11) 为了方便本地开发测试,我们使用 .env 文件来配置项目的环境变量。运行下面的命令,并填入之前堆栈中的“输出”字段数据对应的值。
cd /Workshop
cp .env.example .env
填入后如图所示
Maven 下载依赖、编译、打包
在 VS Code 中打开终端并进入项目根目录下,执行 maven 命令进行项目编译
mvn clean
mvn install
mvn clean package
编译成功后终端内如下图所示
以上就是亚马逊云科技上消除电商场景下的无服务器应用架构的冷启动时间的上篇内容,在本篇中我们在云端编译了我们的电商场景化应用项目,配置了数据库,下篇中我们将介绍利用web adapter云迁移和无服务器服务冷启动的方案。欢迎大家关注小李哥和本系列的下篇,不要错过未来更多国际前沿的AWS云开发/云架构方案。