因为我觉得听到有人说”你们 AWS Lambda 别名弄错了”,所以写在备忘录上

首先

我没有说过这样的话。对不起。

我最近有些想法关于AWS Lambda的别名功能。

在互联网上经常出现的介绍有

可以使用dev别名来搭建开发环境,使用prod别名来搭建生产环境,这样可以将环境进行区分!

你觉得不是这样吗?但是,这个呢,不是不可能吗?

哎呀,如果你想做的话,是可以做到的。

然而,就运维而言,存在一些令人不安的问题,最重要的是与IaC(在这里指的是CDK)的兼容性太差了吧?我觉得,所以我想写下我的想法。

前提概念

我会整理一些前提知识给大家。如果你有一定的AWS Lambda使用经验,可以跳过这一部分。

AWS Lambda: 亚马逊云Lambda

 

这是一个能够在触发任何事件时执行代码的 AWS 无服务器计算服务。

有几种可供选择的运行时环境,比如使用Node.js编写的应用程序可以轻松编写并运行。

AWS Lambda 版本

 

AWS Lambda 允许将代码打包成 zip 文件进行上传,或使用 Docker 镜像进行部署。这样做可以像使用快照一样固定 Lambda 在某个时间点的状态,并进行使用。

我们称之为 Lambda 函数的版本。

在中文中,有一个特殊版本的标识符$LATEST,它指的是Lambda函数在创建版本之前的状态。换句话说,如果改变代码或其他设置,就会对$LATEST进行更改,并从$LATEST生成Lambda版本。

AWS Lambda函数别名

 

您可以定义一个指向已发布的 Lambda 版本的指针标识符。这被称为 Lambda 函数的别名。

就像之前所提到的,如果在互联网上查找Lambda的别名,你可能会看到一些示例,比如创建开发用的“dev”别名和生产用的“prod”别名。


好的,下面我简要介绍一下前提知识。
现在就让我们进入正题吧~。

我认为无法在开发和正式环境中区分别名的原因是。

以下为要点:

    1. 对环境变量的更改让人担心啊。

 

    1. 其他的 AWS 资源(比如 DynamoDB)没有别名呀。

 

    基础设施无法以声明式的方式编写呀。

我将写出关于以下每个方面的考虑。

假定不另行声明的情况下,以下内容中的 dev 别名将引用$LATEST版本,而 prod 别名将引用特定 Lambda 的已发布版本。

理由1:更新环境变量很可怕呀。

根据《十二要素应用程序》的提及,使用环境变量来注入配置信息的方法,可以让应用程序在不同的部署环境(例如生产环境和开发环境)中运行时无需关心其部署情况,这是一种常见的做法。

 

在AWS Lambda上,也具备了设置环境变量的功能。

 

从屏幕上可以看到这样一个东西。 您可以为每个函数进行设置。

スクリーンショット 2023-07-04 15.09.12.png

重要的点在于,当发版后,Lambda的环境变量会被固定。

因此,环境变量的设置更改只能针对$LATEST执行,这是关键。

如果您使用dev/prod别名,并且想手动部署,您需要按照以下步骤进行操作:

    1. 将dev别名($LATEST)的环境变量设置更改为prod。

 

    1. 发布一个新版本。

 

    1. 更新prod别名,并将其配置为引用新发布的版本。

 

    将dev别名($LATEST)的环境变量设置恢复为dev原始值。

上述的1至3步骤看起来似乎没有问题,但我认为第四步骤可能存在相当大的问题。

如果忘记了这个步骤,在进行开发阶段的操作确认时,有可能会错误地操作生产环境的数据,或者造成一些意外影响。

如果发生了这第四步骤,那么我觉得从一开始的”设计”就存在问题。

在这里,“设计”指的是在开发和生产环境中使用Lambda别名来进行区分。

原因2:其他AWS服务(例如DynamoDB)没有别名。

如果旨在实现顺利切换开发和生产环境的意图的话,我认为其他AWS服务应该有类似别名的机制。但事实上并非如此。

如果一个系统仅凭Lambda就能完成,那可能还可以接受。但实际上,我认为我们经常会构建使用数据库等组件的系统。

在这种情况下,准备开发和验证用的数据库实例也是常见的。

我的理解是,也就是说,(当然了)我们需要准备一个专门用于实际使用的数据库。

我对Lambda和DB在”非对称性”方面的区别感到不舒服。

这个方针的最大问题就是无法建立灵活且更安全的开发环境,比如在不同的AWS账户中建立开发环境和生产环境。

我认为如果不能采取这样的安全措施,将dev/prod区分为别名的好处可能很少。

第三个理由:基础设施无法以宣言的形式表达。

最近,像 Terraform 和 CloudFormation 这样的基础设施编码工具越来越成熟。虽然我常常使用 AWS CDK,但是通过代码搭建基础设施非常容易管理,非常有用。

顺便说一下,我理解到 IaC 的一个特点是能够以声明方式定义基础架构。

↑這個「宣言式的」是關鍵,剛才提到的手動逐個修改環境變數的作業無法用宣言式的方式來寫。因為那是一個「程序」。

举例来说,假设我们使用CDK(TypeScript)来定义基础设施。通过参考环境变量ENVIRONMENT,我们打算实现根据不同的部署来构建基础设施。

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

const getEnvironment = () => process.env.ENVIRONMENT || "dev";

export class SampleStack extends cdk.Stack {
	constructor(scope: Construct, id: string, props?: cdk.StackProps) {
		super(scope, id, props);

		new NodejsFunction(
			this,
			"sample-stack-nodejs-function",
			{
				functionName: "sample-stack-nodejs-function",
				handler: "index.handler",
				entry: "lambda/sample-stack-nodejs-function.js",
				environment: {
					ENVIRONMENT: getEnvironment(),
				},
			}
		);
    }
}

我只是先定义了一个使用一个 Node.js 运行时的 Lambda 函数。

在这个例子中,尚未发布 Lambda 函数的版本和别名。因此,我们将直接在$LATEST上设置环境变量ENVIRONMENT。当然,由于是$LATEST,我们可以在部署后自由地更改设置。

那么,现在让我们来定义版本发布和别名。在这里,我想以以下简单的实现为例。

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

const getEnvironment = () => process.env.ENVIRONMENT || "dev";

export class SampleCdkStack extends cdk.Stack {
	constructor(scope: Construct, id: string, props?: cdk.StackProps) {
		super(scope, id, props);

		const sampleFunction = new NodejsFunction(
			this,
			"sample-stack-nodejs-function",
			{
				functionName: "sample-stack-nodejs-function",
				handler: "index.handler",
				entry: "lambda/sample-stack-nodejs-function.js",
				environment: {
					ENVIRONMENT: getEnvironment(),
				},
			}
		);

    // dev エイリアスを定義
    new lambda.Alias(
        this,
        "sample-stack-nodejs-function-dev-alias",
        {
            aliasName: "dev",
            version: sampleFunction.latestVersion,
        },
    );

    // バージョンを発行して、prod エイリアスを定義
    if (getEnvironment() === 'prod') {
        const lambdaVersion = new lambda.Version(
            this,
            "sample-stack-nodejs-function-version",
            { lambda: sampleFunction },
        );

        new lambda.Alias(
            this,
            "sample-stack-nodejs-function-prod-alias",
            {
                aliasName: "prod",
                version: lambdaVersion,
            },
        );
    }
  }
}

这是一个相当简单直接的实现,但是如果 ENVIRONMENT === “prod” 为 true 的情况下,我们将定义 Lambda 的版本和prod别名。同时,dev别名是我们总是使用的,因此总是进行定义。在 dev 别名的定义部分,我们通过指定 sampleFunction.latestVersion 来引用 $LATEST。

单凭这段代码来看,感觉不奇怪地会觉得有些不错的样子。

然而,举个例子,如果

    1. 首先,在环境变量为”dev”的情况下进行部署,进行开发和确认操作。

 

    在上述步骤完成后,再在环境变量为”prod”的情况下进行生产环境部署。

完成这项任务之后,Lambda函数的环境变量将处于什么样的状态?(思考时间)

请用中文将下面的句子表达出来,只需要一种方式:
・・・・
・・・・
・・・・

请将以下句子用中文重新表述,只需一种方式:

是的,确实。这将导致dev别名($LATEST)上设置了与生产环境相同内容的环境变量。

When illustrated, it looks something like this ↓. 一旦被插图,看起来像这样↓。

aws-lambda-alias-dev-prod.png

由于AWS Lambda版本别名的设计,首先需要更新$LATEST,然后进行版本发布和别名更新等步骤,因此会导致更新$LATEST的环境变量。

因此,最终在dev别名中保留了生产环境的环境变量。

如果想要恢复到最新的设置,应该怎么做呢?
最简单的方法是重新部署,将ENVIRONMENT = “dev”再次部署即可。

但是,如果这样做,下一步就会删除prod别名。
由于在环境变量为”dev”的情况下,CDK生成的模板中将没有prod别名存在。

如果开始琢磨着总是保留prod别名之类的东西,突然之间就会变得麻烦起来。

我觉得这个问题应该通过制度来解决。

那么,怎么办呢?

我认为使用 dev/prod 别名的方法可能存在之前提到过的问题。

我想考虑一下如何能够更安全地进行开发和运营。

基本方针

就Lambda而言,我认为最好的做法是将函数资源分开。

换句话说,就是要分别构建开发用的函数和生产用的函数。

只要按照这样做,虽然没有特别提及,但例如添加其他类型的部署,如暂存环境等,也很容易实现。

此外,关于别名,我们考虑了如下图所示的方式。

エイリアス方針.png

主要要点如下:

    • 開発用 Lambda 関数に定義するエイリアスは dev のみ。$LATESTを参照している。

 

    • 本番用 Lambda 関数の$LATESTは、原則触らない。$LATESTを参照するエイリアスは定義しない。

 

    システムのバージョン(Semantic Versioning に則るとする)に対応する名前のエイリアスを定義し、それぞれ別々の Lambda の発行したバージョンを参照するようにする。

根据基本方针的意图

首先,关于开发函数,我没有特别要说的。它可能是一个非常自然的 dev 别名,不是吗?

一方面,关于生产环境的 Lambda 函数,首先的意图是“充分利用 Lambda 的版本”。

Lambda的版本将环境变量和部署的代码等信息固定下来。

如果偶然将$LATEST用作生产环境,则可以直接从AWS管理控制台更改代码,也可以更改环境变量的设置。

我认为,即使团队中没有恶意的人,因为人为错误的可能性仍然存在,所以通过正确地发布 Lambda 的版本来固定部署状态并实现更安全的操作是有价值的。

然而,Lambda 版本的标识符只是简单的自然数连续编号3,这实在令人不太开心。我不想给这个连续编号赋予任何含义,也无需进行管理。

因此,我认为在我们运营系统时,需要给它起一个更易于理解的名称。

我认为作为解决方案,使用Lambda别名会更好。

顺便问一下,我认为在我们自己管理系统发布时,常常会采用类似于语义化版本控制的方式来管理版本。

 

如果是这样的话,例如如果系统版本是1.0.0,可以通过为其添加对应的别名prod_1-0-0,以便更容易理解哪个版本的Lambda正在运行。

这个基本方针解决了什么问题?

如果我们针对刚才提到的问题进行思考的话,

    • 開発用・本番用で Lambda関数を分けたので、それぞれの環境にデプロイする際に、他の環境のことを意識しなくて済む

 

    DBなど、他のAWSリソースたちと同様に、デプロイメントごとにリソースを定義した形になったことで、AWS アカウントを分割するといった体制も容易に実現できる。

我认为在这些方面可以进行改善。

使用 AWS CDK 的实施样例

我打算简单地实施先前的基本方针。

首先讨论一下函数的定义,例如下面的方式可能是正确的。

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

const getEnvironment = () => process.env.ENVIRONMENT || "dev";

export class SampleStack extends cdk.Stack {
	constructor(scope: Construct, id: string, props?: cdk.StackProps) {
		super(scope, id, props);

		const sampleFunction = new NodejsFunction(
			this,
			"sample-stack-nodejs-function",
			{
				functionName: `sample-stack-nodejs-function-${getEnvironment()}`,
				handler: "index.handler",
				entry: "lambda/sample-stack-nodejs-function.js",
				environment: {
					ENVIRONMENT: getEnvironment(),
				},
			},
		);
	}
}

根据 functionName 中定义的 sample-stack-nodejs-function-${getEnvironment()} 的规则,我们根据 ENVIRONMENT 的设置来设计不同的 Lambda 资源。

最后是版本别名的定义。
接下来的部分将与之前的实施示例几乎相同,只是对别名名称进行了一些改进:

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

const getEnvironment = () => process.env.ENVIRONMENT || "dev";

/**
 * 現在のシステムのバージョンを定義
 * あるいは、別のファイルから読み取るなどの工夫をする。
 */
const CURRENT_SYSTEM_VERSION = "0.0.0";

export class SampleStack extends cdk.Stack {
	constructor(scope: Construct, id: string, props?: cdk.StackProps) {
		super(scope, id, props);

		const sampleFunction = new NodejsFunction(
			this,
			"sample-stack-nodejs-function",
			{
				functionName: `sample-stack-nodejs-function-${getEnvironment()}`,
				handler: "index.handler",
				runtime: lambda.Runtime.NODEJS_18_X,
				entry: "lambda/sample-stack-nodejs-function.js",
				environment: {
					ENVIRONMENT: getEnvironment(),
				},
			},
		);

        if (getEnvironment() === 'prod') {
            const lambdaVersion = new lambda.Version(
                this,
                `sample-stack-nodejs-function-version-${CURRENT_SYSTEM_VERSION}`,
                { lambda: sampleFunction },
            );

            new lambda.Alias(
                this,
                "sample-stack-nodejs-function-prod-alias",
                {
                    aliasName: `prod_${CURRENT_SYSTEM_VERSION.replace(/\./g, "-")}`,
                    version: lambdaVersion,
                },
            );
        } else {
            new lambda.Alias(
                this,
                "sample-stack-nodejs-function-dev-alias",
                {
                    aliasName: "dev",
                    version: sampleFunction.latestVersion,
                },
            );
        }
	}
}

在类定义的上方定义了一个名为CURRENT_SYSTEM_VERSION的常量。(注意:在这里我们硬编码了,但是希望您可以根据方便管理的需求进行一些调整)

这次我们只专注于Lambda,并进行了非常简单的实现,但基本上可以按照这个形式来进行实现。

AWS Lambda 的别名是用来做什么的?

让我们回到这里,思考一下 Lambda 函数的别名功能的目的是什么。

从公式文件中引用如下:

您可以为Lambda函数创建一个或多个别名。Lambda别名类似于指向特定函数版本的指针。用户可以使用别名Amazon资源名称(ARN)访问函数版本。

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

嗯,说实话,这并没有什么特别重要的哈哈。

然而,重要的是在这里是用户可以使用别名 ARN 来访问函数的版本。

我理解的意思是,Lambda函数的版本是我想要使用的实体,但作为访问它的手段,我可以自己命名别名来使用〜。

因此,我认为使用别名的意图不是为了有效地管理自然数,而是为了给它们起一个容易理解的名字,这并不是一种奇怪的设计。6

这个方针的缺点

我认为基本上,我们的方针是在开发环境和生产环境中彻底分离资源,所以在安全和灵活性方面,在环境搭建方面几乎没有什么缺点。

如果要举一个例子的话,我认为必须要在团队内明确制定自己系统的版本管理方针。

在此次基本方针中涉及的代码示例中,直接在常量CURRENT_SYSTEM_VERSION中写入了版本号。

如果CDK的代码之外还有其他需要管理的东西,可能需要考虑参考这些东西。
或者,可能还有要求不仅保留当前版本,而且还要保留以前版本的情况。在这种情况下,我认为需要提前确定并管理如何保留版本历史记录的策略。

总结

总结起来,我认为最好的方法是,直接将Lambda函数分开,进一步说,甚至将其与AWS帐户分开。

我很好奇经常使用 AWS 的工程师们是如何管理这些事情的呢。如果您能告诉我,我将不胜感激。?‍♂️

感谢您耐心阅读我冗长的废话至此。

如果你理解这些事情并且写下来,然后读了,那就太笨了!建议点击浏览器返回。至少在我作为一个初学者时,我一直认为“哦~原来是这样的吗~(笑)”,所以我想写给当时的我。顺便说一下,实际上对于运行时的设置(例如Node.js的版本)和实际使用上,我认为最好还是更加准确地指定。本文省略了很多与本文主题无关的内容,请谅解。在这里,自然数指的是正整数。对于别名的名称可用字符类型有一些限制。详细信息请参考此处。此外,我还认为对于Stack的名称,也有必要将其命名为prod、dev等名称,以便在不同的Stack中定义。否则,在尝试构建生产环境时,可能会将开发环境删除。反过来说,只要这样做,即使开发和生产使用相同的AWS账户,由于Stack或Lambda函数本身完全分开,混淆的风险也相当低。关于别名的另一个功能是加权路由功能。我认为这也是实现更灵活的部署策略的功能,在生产环境中也非常有用,所以我认为它并不是为了区分dev/prod等做法而存在的功能。如果像本次一样只管理一个CURRENT_SYSTEM_VERSION的方法,例如当尝试部署为0.0.1时,之前以0.0.0部署的Lambda函数版本将会消失。因此,我认为在不希望做出这样的事情时,需要考虑保留历史记录。
广告
将在 10 秒后关闭
bannerAds