将 Lambda 基础设施转移到 ECS

问题描述 投票:0回答:1

这就是我在 typescipt 中定义所有 python lambda 函数的方式(屏蔽了所有敏感信息):

import * as cdk from 'aws-cdk-lib';
import { Stack, StackProps } from 'aws-cdk-lib';
import { AuthorizationType, CfnAuthorizer, CfnMethod, LambdaIntegration, Resource, RestApi } from 'aws-cdk-lib/aws-apigateway';
import { Effect, IManagedPolicy, ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Duration } from 'aws-cdk-lib/core';

import { Rule, Schedule } from 'aws-cdk-lib/aws-events';
import { Code, DockerImageFunction, Function, ILayerVersion, LayerVersion, Runtime } from 'aws-cdk-lib/aws-lambda';
import * as eventsources from 'aws-cdk-lib/aws-lambda-event-sources';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import { Queue } from 'aws-cdk-lib/aws-sqs';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
import { Construct } from 'constructs';
import { AMA, APIPaths, EventBridgeRules, Handlers, Lambdas, Methods, Package, SSM_PARAM } from '../constants';
import { createQueueWithDLQ, createSQSEventSource } from '../sqsUtils';
import { createLambdaFunction, getCommonBucketName } from '../utils';
import { CognitoStack } from './cognitoStack';
import { LambdaStack } from './lambdaStack';
import { RDSStack } from './rdsStack';
import { S3BucketStack } from './s3BucketStack';
import { VPCStack } from './vpcStack';

const EMBEDDING_LOG_POLLING_INTERVAL = Duration.minutes(5);

export const ACTIVE_EMBEDDING_LOG_STACKS = [
    { accountId: '<MASKED_ACCOUNT_ID_1>', stageName: 'alpha' },
    { accountId: '<MASKED_ACCOUNT_ID_2>', stageName: 'ui' },
    { accountId: '<MASKED_ACCOUNT_ID_3>', stageName: 'ui2' },
    { accountId: '<MASKED_ACCOUNT_ID_4>', stageName: 'aryan' },
    { accountId: '<MASKED_ACCOUNT_ID_4>', stageName: 'ama' },
];

export const EMBEDDING_POLLING_LAMBDA_DURATION = Duration.seconds(30);

export class AmaStack extends Stack {
    public readonly AmaServiceAPI: RestApi;
    public readonly Amaauthorizer: CfnAuthorizer;

    // Resources
    public readonly ParentFunctionResource: Resource;
    public readonly RedirectionEngineResource: Resource;
    public readonly aiFormFillupResource: Resource;

    // ama
    public readonly amaServiceLambda: Function;
    public readonly getAmaChats: Function;
    public readonly feedbackLambda: Function;
    public readonly redirectionEngine: Function;
    public readonly embeddingLambda: Function;
    public readonly aiFormFillup: Function;
    public readonly aiFormUpdate: Function;
    public readonly aiFormUpdateQueue: Queue;
    public readonly aiFormUpdateDlqQueue: Queue;

    // prompt engine
    public readonly promptEngine: Function;

    // embedding log queue
    public readonly embeddingLogQueue: Queue;
    public readonly embeddingLogDlqQueue: Queue;

    // layer
    public readonly layer: ILayerVersion;
    embeddingPollingLambda: cdk.aws_lambda.Function;

    constructor(scope: Construct, id: string, props: AMAProps) {
        super(scope, id, props);

        this.AmaServiceAPI = new RestApi(this, `AmaServiceAPI-${props.stage}`);

        const baseLayerArn = StringParameter.valueForStringParameter(this, `${SSM_PARAM}-${props.stage}`);
        this.layer = LayerVersion.fromLayerVersionArn(this, `AmaLayerFromArn-${props.stage}`, baseLayerArn);

        const commonEnvironmentVariables = {
            COMPANY_RDS_SECRET: props.rdsStack.DatabaseCluster.secret!.secretValue.unsafeUnwrap(),
        };

        this.Amaauthorizer = new CfnAuthorizer(this, `APIGatewayAuthorizer-${props.stage}`, {
            name: `Ama-authorizer-${props.stage}`,
            identitySource: 'method.request.header.Authorization',
            providerArns: [props.cognitoStack.cognitoPool.userPoolArn],
            restApiId: this.AmaServiceAPI.restApiId,
            type: AuthorizationType.COGNITO,
        });

        this.ParentFunctionResource = this.AmaServiceAPI.root.addResource(APIPaths.AMA);
        this.RedirectionEngineResource = this.AmaServiceAPI.root.addResource(APIPaths.REDIRECTION_ENGINE);

        const openAISecret = Secret.fromSecretNameV2(this, 'OpenAISecret', 'Open-AI-API-Key');
        const openAIKey = openAISecret.secretValueFromJson('OPENAI_API_KEY');
        const AMAFeedbackResource = this.ParentFunctionResource.addResource(APIPaths.AMA_FEEDBACK);

        const managedPolicies = [
            ManagedPolicy.fromAwsManagedPolicyName('AWSLambdaExecute'),
            ManagedPolicy.fromAwsManagedPolicyName('AWSLambda_FullAccess'),
            ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLambdaInsightsExecutionRolePolicy'),
            ManagedPolicy.fromAwsManagedPolicyName('AmazonBedrockFullAccess'),
            ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'),
        ];

        const embeddingQueueAndDlqQueue = createQueueWithDLQ(this, 'EmbeddingLogQueue', props.stage, {
            visibilityTimeout: Duration.seconds(120),
            maxReceiveCount: 3,
        });

        this.embeddingLogQueue = embeddingQueueAndDlqQueue.queue;
        this.embeddingLogDlqQueue = embeddingQueueAndDlqQueue.dlqQueue;

        this.embeddingPollingLambda = createLambdaFunction(
            this,
            Lambdas.EMBEDDING_LOG_POLLING,
            Package.BACKEND,
            Handlers.EMBEDDING_LOG_POLLING,
            props.stage,
            {
                roleName: `${Lambdas.EMBEDDING_LOG_POLLING}-Role`,
                managedPolicies: managedPolicies,
                resources: [this.embeddingLogQueue.queueArn],
                actions: ['sqs:SendMessage'],
            },
            {
                ...commonEnvironmentVariables,
                EMBEDDING_LOG_QUEUE: this.embeddingLogQueue.queueUrl,
            },
            EMBEDDING_POLLING_LAMBDA_DURATION,
            [this.layer],
            props.vpcStack.vpc,
        );

        const rule = new Rule(this, `EventBridge-${EventBridgeRules.AMA_LOG_POLLING}`, {
            description: `Rule to trigger embedding polling lambda every ${EMBEDDING_LOG_POLLING_INTERVAL} minutes`,
            schedule: Schedule.rate(EMBEDDING_LOG_POLLING_INTERVAL),
        });

        if (ACTIVE_EMBEDDING_LOG_STACKS.find((stack) => stack.accountId === props.accountId && stack.stageName === props.stage)) {
            rule.addTarget(new cdk.aws_events_targets.LambdaFunction(this.embeddingPollingLambda));
        }

        const aiFormUpdateQueueAndDldQueue = createQueueWithDLQ(this, 'AiFormUpdateQueue', props.stage, {
            visibilityTimeout: cdk.Duration.seconds(120),
            maxReceiveCount: 3,
        });
        this.aiFormUpdateQueue = aiFormUpdateQueueAndDldQueue.queue;
        this.aiFormUpdateDlqQueue = aiFormUpdateQueueAndDldQueue.dlqQueue;

        this.amaServiceLambda = this.createDockerLambdaFunction(
            Lambdas.AMA_LAMBDA,
            Handlers.AMA_SERVICE,
            props.stage,
            {
                roleName: `${Lambdas.AMA_LAMBDA}-${props.stage}-Role`,
                managedPolicies: managedPolicies,
                actions: ['s3:GetObject', 's3:GetObjectTagging', 's3:ListBucket'],
                resources: [props.lambdaStack.replicateUserProfile.functionArn],
            },
            {
                BUCKET_PREFIX: AMA.BUCKET_PREFIX,
                MODEL: AMA.MODEL,
                EMBEDDING: AMA.EMBEDDING,
                LANGCHAIN_TRACING_V2: 'true',
                LANGCHAIN_ENDPOINT: props.langchainEndpoint,
                LANGCHAIN_API_KEY: props.langchainApiKey,
                LANGCHAIN_PROJECT: `AMA-${props.stage}`,
                STAGE_NAME: props.stage,
                BUCKET_PREFIX: getCommonBucketName(props.accountId, props.stage),
                LLM_TOP_P: '0.2',
                OPENAI_API_KEY: openAIKey.unsafeUnwrap(),
                LLM_TEMPERATURE: '0.2',
                RETRIEVER_K: '3',
                ...commonEnvironmentVariables,
            },
        );

        openAISecret.grantRead(this.amaServiceLambda);

        this.promptEngine = this.createDockerLambdaFunction(
            Lambdas.PROMPT_ENGINE,
            Handlers.PROMPT_ENGINE,
            props.stage,
            {
                roleName: `${Lambdas.PROMPT_ENGINE}-${props.stage}-Role`,
                managedPolicies: managedPolicies,
                actions: ['s3:GetObject', 's3:GetObjectTagging', 's3:ListBucket'],
                resources: [props.lambdaStack.replicateUserProfile.functionArn],
            },
            {
                MODEL: AMA.MODEL,
                EMBEDDING: AMA.EMBEDDING,
            },
        );

        this.feedbackLambda = this.createDockerLambdaFunction(
            Lambdas.FEEDBACK_LAMBDA,
            Handlers.AMA_FEEDBACK,
            props.stage,
            {
                roleName: `${Lambdas.FEEDBACK_LAMBDA}-${props.stage}-Role`,
                managedPolicies: managedPolicies,
                actions: ['s3:GetObject', 's3:GetObjectTagging', 's3:ListBucket'],
                resources: [props.lambdaStack.replicateUserProfile.functionArn],
            },
            {
                BUCKET_PREFIX: AMA.BUCKET_PREFIX,
                MODEL: AMA.MODEL,
                LANGCHAIN_ENDPOINT: '<MASKED_LANGCHAIN_ENDPOINT>',
                LANGCHAIN_API_KEY: '<MASKED_LANGCHAIN_API_KEY>',
            },
        );

        this.configureMethodOnResource(AMAFeedbackResource, Methods.POST, new LambdaIntegration(this.feedbackLambda));

        this.redirectionEngine = this.createDockerLambdaFunction(
            Lambdas.REDIRECTION_ENGINE,
            Handlers.REDIRECTION_ENGINE,
            props.stage,
            {
                roleName: `${Lambdas.REDIRECTION_ENGINE}-${props.stage}-Role`,
                managedPolicies: managedPolicies,
                actions: ['s3:GetObject', 's3:GetObjectTagging', 's3:ListBucket'],
                resources: [
                    props.lambdaStack.replicateUserProfile.functionArn,
                    this.amaServiceLambda.functionArn,
                    this.promptEngine.functionArn,
                ],
            },
            {
                AMA_LAMBDA: this.amaServiceLambda.functionName,
                PROMPT_ENGINE: this.promptEngine.functionName,
                BUCKET_PREFIX: AMA.BUCKET_PREFIX,
                MODEL: AMA.MODEL,
                EMBEDDING: AMA.EMBEDDING,
                OPENAI_API_KEY: openAIKey.unsafeUnwrap(),
                LANGCHAIN_TRACING_V2: 'true',
                LANGCHAIN_ENDPOINT: props.langchainEndpoint,
                LANGCHAIN_API_KEY: props.langchainApiKey,
                LANGCHAIN_PROJECT: `RedirectionEngine-${props.stage}`,
                STAGE_NAME: props.stage,
            },
        );

        this.embeddingLambda = this.createDockerLambdaFunction(
            Lambdas.EMBEDDING_ENGINE,
            Handlers.EMBEDDING_ENGINE,
            props.stage,
            {
                roleName: `${Lambdas.EMBEDDING_ENGINE}-${props.stage}-Role`,
                managedPolicies: managedPolicies,
                actions: ['s3:GetObject', 's3:GetObjectTagging', 's3:ListBucket'],
                resources: [props.lambdaStack.replicateUserProfile.functionArn],
            },
            {
                BUCKET_PREFIX: AMA.BUCKET_PREFIX,
                MODEL: AMA.MODEL,
                EMBEDDING: AMA.EMBEDDING,
                LANGCHAIN_TRACING_V2: 'true',
                LANGCHAIN_ENDPOINT: props.langchainEndpoint,
                LANGCHAIN_API_KEY: props.langchainApiKey,
                LANGCHAIN_PROJECT: `AMA-${props.stage}`,
                STAGE_NAME: props.stage,
                BUCKET_PREFIX: getCommonBucketName(props.accountId, props.stage),
                PINECONE_API_KEY: '<MASKED_PINECONE_API_KEY>',
                LLM_TOP_P: '0.3',
                OPENAI_API_KEY: openAIKey.unsafeUnwrap(),
                LLM_TEMPERATURE: '0.3',
                RETRIEVER_K: '3',
                ...commonEnvironmentVariables,
            },
        );


        // ... [Implementation details for remaining methods]
    }

    // ... [Rest of the class implementation]
}

export interface AMAProps extends StackProps {
    readonly stage: string;
    readonly region: string;
    readonly accountId: string;
    readonly langchainEndpoint: string;
    readonly langchainApiKey: string;
    readonly lambdaStack: LambdaStack;
    readonly cognitoStack: CognitoStack;
    readonly rdsStack: RDSStack;
    readonly vpcStack: VPCStack;
    readonly s3BucketStack: S3BucketStack;
}

export interface IAMRoleProps {
    readonly roleName: string;
    readonly managedPolicies: IManagedPolicy[];
    readonly actions: string[];
    readonly resources: string[];
}

我尝试将 lambda 作为服务,但由于冷启动而遇到麻烦,想转向 EKS/ECS。无法将所有内容转移到 Web 服务器,我如何通过 CDK 以最少的代码更改来完成此操作。

我想将基础设施转移到 EKS/ECS ,还建议它所带来的差异,我主要使用 lambda,没有使用 ECS/EKS。

typescript amazon-web-services aws-lambda amazon-ecs amazon-eks
1个回答
0
投票

当您为函数启用预置并发时,Lambda 服务将初始化请求数量的执行环境,以便它们可以准备好响应调用。

参考:https://aws.amazon.com/blogs/aws/new-provisioned-concurrency-for-lambda-functions/

首先,您应该配置 Lambda 冷启动问题预配置并发

这是分配给您的函数的预初始化执行环境的数量。这些执行环境已准备好立即响应传入的功能请求。预配置并发对于减少函数的冷启动延迟很有用。配置预置并发会导致您的 AWS 账户产生额外费用。

如果您想提高 Lambda 函数的性能,那么您需要增加其内存

想要转向 EKS/ECS。无法将所有内容转移到 Web 服务器,我如何通过 CDK 以最少的代码更改来完成此操作。

是否选择 EKS 还是 ECS 将取决于您的应用程序、您管理它们的专业知识、控制和定价以及可移植性(供应商锁定)

但是看到你的问题我认为 ECS 会是更好的选择,因为:

如果您不熟悉容器编排和部署,ECS 是一个很好的起点,因为它成本较低,并且几乎不需要或不需要管理 Kubernetes 集群的专业知识。如果您熟悉 AWS 平台,AWS ECS 也是一个不错的选择,因为它提供与 Amazon 服务的紧密集成。 ECS 您无需为控制平面付费,这意味着它可能会更便宜。

如果您选择 EKS,那么:

如果您正在寻找多云功能和容器化工作负载的可移植性,EKS 是首选,因为它不会将您锁定在 Amazon 云中。 EKS 还提供附加功能、更多自定义选项以及对容器化应用程序的细粒度控制。请记住,与 ECS 相比,每个集群需要支付额外费用,因为 AWS 收取控制平面成本。

当然,多年来 EKS 也变得非常简单,最近他们推出了 EKS Auto,它可以自动化集群基础设施。但如果您进入 EKS,那么您需要了解部署、监控、扩展、网络、安全性。

所以我的建议是首先使用 ECS,但请记住供应商锁定点,然后如果您想要更多的定制和控制,请选择 EKS 或 Kubernetes

如何通过 CDK 以最少的代码更改来完成此操作。

这是非常主观的,但一个好的起点是这里(来自aws文档)

还建议它将采取的差异,

  • 您正在转向容器世界,因此您需要对您的应用程序进行 docker 化并推送到 AWS ECR 存储库,以便 ECS 可以访问它。
  • 您需要构建到 docker 镜像的 CI/CD 管道并部署到 ECR,然后自动部署到 ECS。
  • 您需要考虑使用 VPC 进行网络连接以实现安全性和负载均衡器
  • 尽管 ecs 与 cloudwatch 监控和容器洞察紧密集成,但您仍需要启用监控。
  • 当您想要更新生产/实时流量应用程序时,还要考虑 B/G 部署 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/blue-green.html
  • 如果已经上线,请考虑在路由 53 的帮助下进行 B/G 部署,以避免停机。

也请检查此示例:https://conermurphy.com/blog/aws-lambda-to-ecs-fargate-migration-guide

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.