当前位置: 首页 > article >正文

如何在亚马逊云科技上大幅降低无服务器网页应用冷启动时间(上篇)

背景

我们在云端搭建无服务器(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云开发/云架构方案。 


http://www.kler.cn/a/507951.html

相关文章:

  • python-44-嵌入式数据库SQLite和DuckDB
  • MySQL(七)MySQL和Oracle、PostgreSQL的区别
  • Unity-Mirror网络框架-从入门到精通之RigidbodyPhysics示例
  • 【网络编程】基础知识
  • 港科夜闻 | 香港科大与微软亚洲研究院签署战略合作备忘录,推动医学健康教育及科研协作...
  • Subprocess check_output returned non-zero exit status 1
  • 在Mac m2系统下安装InSAR软件isce2
  • Python根据图片生成学生excel成绩表
  • [创业之路-254]:《华为数字化转型之道》-1-华为是一个由客户需求牵引、高度数字化、高度智能化、由无数个闭环流程组成的价值创造、评估、分配系统。
  • 学习微信小程序的下拉列表控件-picker
  • NC65增加按钮打开其他单据
  • DX12 快速教程(3) —— 画矩形
  • Java 数据结构 队列之双端队列 常用方法 示例代码 及其实现
  • vue+arcgis api for js实现地图经纬网格显示
  • 大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(6)
  • Qt 使用共享内存的方式限制程序单一启动
  • 深入内核讲明白Android Binder【二】
  • 【JVM-8】使用 IBM Thread and Monitor Dump Analyzer for Java (TMDA) 分析线程转储
  • 深入了解卷积神经网络(CNN):图像处理与深度学习的革命性技术
  • 修复5.0.0r 64位版本浏览器和一些库找不到的问题
  • Flink (九):DataStream API (六) Process Function
  • 如何在 Google Cloud Shell 中使用 Visual Studio Code (VS Code)?
  • Spring Boot与MyBatis
  • FPGA:Quartus软件与操作系统版本对照表
  • Java 开发常见面试题3
  • ORB-SLAM2源码学习: Frame.cc: cv::Mat Frame::UnprojectStereo将某个特征点反投影到三维世界坐标系中