[Cloud DA] Best Practices for Serverless

Overview

  • How to architect our application for testability
  • How to run our application locally
  • Have multiple envs for our application
  • Implement observability
    • monitoring, logging, distributed tracing
  • Improve performance of our application
    • Cold start issue
  • Security of our application

Vendor lock-in

  • Vendor lock-in is Inability to switch from a selected technology provider to another
  • Discuessed a lot when related to serverless

What can cause a vendor lock-in?

  • Code in not root cause of vendor lock-in
  • Other third party services are the cause
    • Database
    • File storage
    • Messaging systems

How can we defend from a vendor lock-in?

  • Using "Ports and Adapters Architecture" or "hexagonal architecture"

Ports and Adapters Architecture

Main idea is to separate application into two components.

  • Business logic which are independent from external services
  • Ports and adaptor which interact with outside services such as DB, messaging queue.

Ports: You can think of ports as interface which defines the common actions to handle busniess logic

Adapter: You can think or Adapter as the real implementation detail behind the interface, for example database port might have 'read', 'write' interface

adapters are targeting different database, such as mysql, DyanmoDB .... 

Benefits of the Ports and Adapters Architecture

  • Business logic can be easily ported
    • Can move application to another provider
  • Allwe need to do is re-implement ports if switch third party vendor

See Example: Refactoring the code with Ports and Adapters pattern

Multi Stages Deployment

For testing, we can have multistaging env.

For example:

  • CI: For intergion testing
  • Dev: For developers
  • Staging: For testers
  • Prod: for customers

Here is how we can deploy a serverless application to different stages:

# Deploy to production ("prod" stage)
sls deploy -v --stage prod

# Can have additional stages
sls deploy -v --stage staging

Run function locally

Invoke a function in AWS

serverless-offline-plugin

  • Emulates API Gateway and Lambda
  • Starts local web server that calls Lambda functions locally
  • Intergets Authorizers, Lambda integrations, CORS, Velocity templates... etc.
  • Can attach a debugger to a local process
  • Works with plugins that emulate other services...DynamoDB, Kinesis, SNS, etc...
### Run locally

1. install the plugins.
2. Define in yaml:

```yaml
plugins:
  - serverless-webpack
  - serverless-reqvalidator-plugin
  - serverless-aws-documentation
  - serverless-dynamodb-local
  - serverless-offline

custom:
  topicName: ImagesTopic-${self:provider.stage}

  serverless-offline:
    httpPort: 3003

  dynamodb:
    stages:
      - dev
    start:
      port: 8000
      inMemory: true
      migrate: true
```

3. run sls install/start:

```bash
sls dynamodb install
sls dynamodb start

sls offline
```

Seeding a database

We can also automatically seed a local DynamoDB database. To do this, we need to set dynamodb.start.seed to true and provide seed configuration, like this:

custom:
  dynamodb:
    start:
      seed: true

    seed:
      # Categories of data. We can optionally load just some of them
      users:
        sources:
          - table: users
            sources: [./users.json]
          - table: user-roles
            sources: [./userRoles.json]
      posts:
        sources:
          - table: blog-posts
            sources: [./blogPosts.json]

You can then seed a database with the following command:

sls dynamodb seed --seed=users,posts

Or you can start a local DynamoDB database and seed it:

sls dynamodb start --seed=users,posts

Canary Deployment

  • Allows to release a new version slowly
  • Exposes only a small percentage of users to potential issues
  • Quickly rollback if any problems encountered

Lambda Weighted Alias

https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html

### Canary Deployment

```yaml
plugins:
  ...
  - serverless-plugin-canary-deployments

iamRoleStatements:
  ...
  - Effect: Allow
    Action:
      - codedeploy:*
    Resource:
      - "*"

  CreateGroup:
    handler: src/lambda/http/createGroup.handler
    events:
      - http:
          method: post
          path: groups
          authorizer: RS256Auth
          cors: true
          reqValidatorName: RequestBodyValidator
          request:
            schema:
              application/json: ${file(models/create-group-request.json)}
    deploymentSettings: ## add deploymentSetting for canary deployment
      type: Linear10PrecentEveryMinute
      alias: Live ### new alias called Live
```

X-Ray

provider:
  tracing:
    apiGateway: true
    lambda: true
import * as AWS from "aws-sdk";
import {
  APIGatewayProxyEvent,
  APIGatewayProxyHandler,
  APIGatewayProxyResult,
} from "aws-lambda";
import * as AWSXRay from "aws-xray-sdk";
const XAWS = AWSXRay.captureAWS(AWS);
const docClient = new XAWS.DynamoDB.DocumentClient();
const imagesTable = process.env.IMAGES_TABLE;
const imageIdIndex = process.env.IMAGE_ID_INDEX;

export const handler: APIGatewayProxyHandler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  const imageId = event.pathParameters.imageId;
  const result = await docClient
    .query({
      TableName: imagesTable,
      IndexName: imageIdIndex,
      KeyConditionExpression: "imageId= :imageId",
      ExpressionAttributeValues: {
        ":imageId": imageId,
      },
    })
    .promise();

  if (result.Count === 0) {
    return {
      statusCode: 404,
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
      body: "",
    };
  }

  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
    body: JSON.stringify({
      item: result.Items[0],
    }),
  };
};

Lambda Cold start

  • Common problem for all FaaS Solutions
  • Functions are provisioned only when they are used
  • Instancees removed when not used
  • Take time to provision

When does cold start happen?

  • Only experienced when a new function is started
    • When no instances are running
    • When an additional instance is required
  • Mostly a problem for
    • Processing customers requests
    • Infrequent requests
      • A new function instance for every request
    • A spike of requests
      • Not enough function instnaces running
  •  Not a problem if you have a steady stream of requests

Optimize - Code Size

  • Smaller functions have a faster cold start
    • AWS won't ship devDependencies
    • We don't need to ship aws-sdk, lambda has it built-in
    • Use Serverless plugins to optimize functions
  • Split one function into multiple function 
    • One function pre task

Avoid Cold start altoghther

  • Call your function periodically
    • Send a special payload (warm-up request)
  • If it is a warm-up request, your function should do nothing
    • Enough to keep it ready to process incoming requests
  • Serverless has a plugin for this

Optimize - Function Code

  • Re-use state between executions
  • Avoid invocation (for example, function should only being called when new object added to S3, then use S3 events filters to only invocation Lambda for create object event)
  • Avoid sleep() calls in Lambda function
    • You are paying for waiting
    • Use AWS Step Function for orchestration
  • Send multiple request in parallel

Optimize - Avoid Running in VPC

  • Only run a function in a VPC if it needs an access a protected endpoint
  • Running in VPC in creases cold start dtastically

Optimize - Bootstrapping New Runtime

  • Select a different runtime
    • Go, Node.js Python have lowest cold start
    • Java, .NET have the highest cold start
  • Provision more memory
    • Provides a faster CPU
    • Allows to bootstrap a runtime faster

Optimize - More Memory

  • Can allow to save money
    • May seem conterintuitive
    • We pay more if we provision more memory
  • More memory => faster CPU
    • Spend less time
    • Pay less for execution time

Lambda Power Tuning

  • Selecting an optimal memory size can be tricky
  • aws-lambda-power-tuning
    • Run a function with different memory sizes
    • Fidns the most cost efficient memory sizes

 

Optimize - functions size

  • Package functions individually
    • Supported by Webpack and Serverless
  • Re-build our application

When use Serverless framework, it packages all the deps used by at least one Lambda function, and create a single zip package. So the single zip package is used by all the Lambda funtion.

The only difference is that the hander for each function is different.

// serverless.yaml

package:
  individually: true

[Notice]: A small size reduces the cold start time but we might pay for longer compilation time.

Security

Lambda

  • Application security (OWASP Top 10 still applies)
  • Configuring IAM permissions
    • Define what function / role can access what resources
    • Use the principle of least privilege
  • Data encryption
    • Enable encryption at rest for services like DynamoDB and Kinesis
  • Secrets handling
    • Use KMS, secrets manager, SSM

API

  • Use throttling
    • Can be configured per stage or per method
    • Throttle individual customers if applicable
  • Validate incoming data
    • Do it in API Gateway or in your code
  • Use a firewall to limit access to your API
    • AWS Web Application Firewall (WAF)

https://docs.aws.amazon.com/lambda/latest/dg/lambda-security.html

Lambda Minimal Privileges for Serverless

  • All our functions have the same set of permissions
  • We need to set minimal required privileges per function
  • Will use serverless-iam-roles-per-function for this
  GetImages:
    handler: src/lambda/http/getImages.handler
    events:
      - http:
          method: get
          path: group/{groupId}/images
          cors: true
    iamRoleStatements:
      - Effect: Allow
        Action:
          - dynamodb:Query
        Resource: arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.IMAGES_TABLE}
      - Effect: Allow
        Action:
          - dynamodb:GetItem
        Resource: arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.GROUPS_TABLE}
原文地址:https://www.cnblogs.com/Answer1215/p/14811646.html