让我们在本地环境中使用Terraform创建一个从SNS到Lambda的Pub/Sub的LocalStack环境

总览

我們的目標是在Mac(M2芯片)環境下使用Terraform + LocalStack(免費版)來建立以下環境。

スクリーンショット 2023-08-26 21.09.03.png

你好,凯塔。 (Nǐ , tǎ.)

尽管我想要学习AWS,但个人无法投入那么多金钱,但我还是想要学习…
就在这时,我遇到了LocalStack,于是我决定一起学习Terraform。

为了整理自己的思路,我这次首次写文章。

感谢词

由于笔者对Terraform和LocalStack的经验较浅,因此详细的解释将被省略,请谅解。

因此,这篇文章重视”使用LocalStack和Terraform可以做到这样的事情!”的体验。

我曾经认为如果不做这件事,就无法向前进…

我相信大家都知道,但据说使用M2芯片并在LocalStack上构建的Lambda函数在执行时可能存在无响应的错误(?)。

尝试再次复现,但是没有成功…
也许方法不对,但是我会记下解决问题的详细过程。

スクリーンショット 2023-09-02 17.43.07.png

版本信息-验证环境

检验环境如下所示。

% sw_vers
ProductName:		macOS
ProductVersion:		13.3
BuildVersion:		22E252

% aws --version
aws-cli/2.13.0 Python/3.11.4 Darwin/22.4.0 exe/x86_64 prompt/off

% tfenv --version
tfenv 3.0.0

% terraform -version
Terraform v1.5.4
on darwin_arm64

% docker -v
Docker version 24.0.2, build cb74dfc

% docker compose version
Docker Compose version v2.18.1

[in dokcer]# bin/localstack -v
2.2.0

GitHub: GitHub是一个面向开发者的在线代码托管平台。

本文中提到的资料在这里公开发表了。

 

让我们试试吧

搭建LocalStack的Docker容器

我准备了一个包含docker-compose.yaml文件的示例,请使用它。

version: "3.8"

services:
  localstack:
    container_name: localstack
    image: localstack/localstack:2.2.0-arm64
    ports:
      - "127.0.0.1:4566:4566"
      - "127.0.0.1:4510-4559:4510-4559"
    environment:
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

 

% docker compose ps
NAME                IMAGE                               COMMAND                  SERVICE             CREATED             STATUS                    PORTS
localstack          localstack/localstack:2.2.0-arm64   "docker-entrypoint.sh"   localstack          50 minutes ago      Up 50 minutes (healthy)   127.0.0.1:4510-4559->4510-4559/tcp, 127.0.0.1:4566->4566/tcp, 5678/tcp

AWS CLI的安装和初始设置

请查看官方网站以了解安装信息。
此外,我们将在假设以下设置的基础上继续解释。

% cat ~/.aws/config 
[profile localstack]
region = us-east-1
output = json

% cat ~/.aws/credentials
[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy

安装Terraform

如果使用Mac电脑,可以通过brew进行安装。

还有一种名为tfenv的工具,就像nodenv一样可以进行版本切换,所以我建议你使用这个~

 

$ brew install tfenv

(バージョン選択などは省略)

$ terraform -v
Terraform v1.5.4
on darwin_arm64

用Terraform编写基础设施配置。

构成和脚本

在验证时的配置如下。

% tree
.
├── lambda_ts
│   ├── dist
│   │   ├── index.js
│   │   └── index.zip
│   ├── index.ts
│   ├── node_modules
│   └── package.json
└── tf
    ├── main.tf
    └── resources.tf
{
  "name": "lambdas",
  "version": "1.0.0",
  "description": "aws terraform samples",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@types/aws-lambda": "^8.10.119",
    "esbuild": "^0.18.20"
  },
  "scripts": {
    "prebuild": "rm -rf dist",
    "build": "esbuild index.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=dist/index.js",
    "postbuild": "cd dist && zip -r index.zip index.js*",
    "pretf_init": "cd ../tf",
    "tf_init": "rm -rf .terraform/ .terraform* terraform.tfstate",
    "posttf_init": "cd ../tf && terraform init",
    "tf_apply": "cd ../tf && terraform apply -auto-approve"
  }
}

将以下脚本进行转译并压缩为zip文件后部署到Lambda。

export const handler = async (event, context) => {
  event.Records.forEach((record) => {
    const { body } = record;
    console.log("body from sns-sqs.");
    console.log(body);
    const data = JSON.parse(body);
    console.log(`data.Message: ${data.Message}`);
  });
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "hello world",
    }),
  };
};

由于在package.json中准备了命令,可以使用其中的命令来进行构建。

$ yarn build
主要.tf

这是主要的设置。
endpoints的URL和端口是LocalStack Docker的URL。
我认为基本上不会发生变化,但请根据您实际搭建的环境进行更改。

provider "aws" {
    region     = "us-east-1"
    access_key = "dummy"
    secret_key = "dummy"

    skip_requesting_account_id  = true
    skip_credentials_validation = true

    endpoints {
        lambda = "http://localhost:4566"
        sns    = "http://localhost:4566"
        sqs    = "http://localhost:4566"
        iam    = "http://localhost:4566"
    }
}
社交网络服务(SNS)、简单队列服务(SQS)、Lambda函数

每个资源的设置。

# SNS
resource "aws_sns_topic" "test-sns" {
  name = "test-sns"
}
output "test-sns-arn" {
  value = aws_sns_topic.test-sns.arn
}
# SQS
resource "aws_sqs_queue" "test-sqs" {
  name = "test-sqs"
}

resource "aws_sns_topic_subscription" "test" {
  topic_arn = aws_sns_topic.test-sns.arn
  protocol  = "sqs"
  endpoint  = aws_sqs_queue.test-sqs.arn
}

resource "aws_iam_role" "lambda_sqs" {
  name                 = "lambda_sqs"
  max_session_duration = 3600
  description          = "None"
  assume_role_policy   = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sqs:*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}
EOF
}

# Lambda
data "archive_file" "example_zip" {
  type        = "zip"
  source_dir  = "${path.cwd}/../lambda_ts/dist"
  output_path = "${path.cwd}/../lambda_ts/dist/index.zip"
}

resource "aws_lambda_function" "test-lambda" {
  function_name    = "test-lambda"
  description      = "Test Lambda"
  role             = aws_iam_role.lambda_sqs.arn
  handler          = "index.handler"
  runtime          = "nodejs18.x"
  timeout          = 3
  filename         = data.archive_file.example_zip.output_path
  source_code_hash = filebase64sha256(data.archive_file.example_zip.output_path)
}

resource "aws_lambda_event_source_mapping" "test-lambda-trigger" {
  event_source_arn = aws_sqs_queue.test-sqs.arn
  function_name    = "test-lambda"
  batch_size       = 10
}

你注意到出现了一个名为 ${path.cwd} 的陌生变量了吧。

...
  source_dir  = "${path.cwd}/../lambda_ts/dist"
  output_path = "${path.cwd}/../lambda_ts/dist/index.zip"
...

这是一个返回资源目录路径的变量。

 

在这里会被转化为tf并展开如下。

tf/../lambda_ts/dist/index.zip

让我们试着部署

也可以使用package.json文件中的命令来执行。
进行terraform初始化

首先,执行terraform init。

% yarn tf_init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Finding latest version of hashicorp/archive...
...
Terraform has been successfully initialized!
...

一旦成功,将会生成上下文文件等。

...
tf
├ .terraform
├ .terraform.lock.hcl
└ terraform.tfstate
执行 terraform apply

当执行terraform apply后,将显示正在创建的配置的详细信息。
同时,还将通过交互方式确认是否最终部署。

% yarn tf_apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_iam_role.lambda_sqs will be created
  + resource "aws_iam_role" "lambda_sqs" {
  ...
  }

  # aws_lambda_event_source_mapping.test-lambda-trigger will be created
  + resource "aws_lambda_event_source_mapping" "test-lambda-trigger" {
  ...
  }

  # aws_lambda_function.test-lambda will be created
  + resource "aws_lambda_function" "test-lambda" {
  ...
  }

  # aws_sns_topic.test-sns will be created
  + resource "aws_sns_topic" "test-sns" {
  ...
  }

  # aws_sns_topic_subscription.test will be created
  + resource "aws_sns_topic_subscription" "test" {
  ...
  }

  # aws_sqs_queue.test-sqs will be created
  + resource "aws_sqs_queue" "test-sqs" {
  ...
  }

Plan: 6 to add, 0 to change, 0 to destroy.
...
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

你想执行这些操作吗?
Terraform将执行上述所描述的操作。
只有“是”会被接受作为批准。
输入一个值:

如果在这里输入”yes”并执行,将进行部署。

aws_iam_role.lambda_sqs: Creating...
aws_sqs_queue.test-sqs: Creating...
aws_sns_topic.test-sns: Creating...
aws_iam_role.lambda_sqs: Creation complete after 0s [id=lambda_sqs]
aws_sns_topic.test-sns: Creation complete after 0s [id=arn:aws:sns:us-east-1:000000000000:test-sns]
aws_lambda_function.test-lambda: Creating...
aws_lambda_function.test-lambda: Creation complete after 5s [id=test-lambda]
aws_sqs_queue.test-sqs: Still creating... [10s elapsed]
aws_sqs_queue.test-sqs: Still creating... [20s elapsed]
aws_sqs_queue.test-sqs: Creation complete after 25s [id=http://localhost:4566/000000000000/test-sqs]
aws_sns_topic_subscription.test: Creating...
aws_lambda_event_source_mapping.test-lambda-trigger: Creating...
aws_lambda_event_source_mapping.test-lambda-trigger: Creation complete after 0s [id=fe53c2a8-443d-4a1a-8626-dca76f1f9296]
aws_sns_topic_subscription.test: Creation complete after 0s [id=arn:aws:sns:us-east-1:000000000000:test-sns:9056dac2-0385-4e7d-a85b-e84271e2d626]
...
Apply complete! Resources: 6 added, 0 changed, 0 destroyed.
...

如果显示“应用完成”,则表示部署已完成。

让我们试着动起来

因为在使用 LocalStack,所以无法在 AWS 的控制台上进行操作。让我们使用 aws-cli 来进行确认吧!

 

发布社交网络

由于本次示例采用了将消息发布到SNS并驱动Lambda的架构,因此让我们向SNS发送一条消息。

% aws sns publish \
 --profile=localstack \
 --endpoint-url http://localhost:4566 \
 --topic-arn "arn:aws:sns:us-east-1:000000000000:test-sns" \
 --message 'Hello AWS SNS!! check tail LOG!'

如果返回了MessageId,那就表示成功了。

{
    "MessageId": "0d04b3e2-aa0b-4f5c-ab3a-86f94261b60a"
}

如果使用日志,就可以确认结果。

日志描述了日志组。

首先,我们要检查日志组。

$ aws logs describe-log-groups \
 --profile=localstack \
 --endpoint-url http://localhost:4566
{
    "logGroups": [
        {
            "logGroupName": "/aws/lambda/test-lambda",
            "creationTime": 1698473196925,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:us-east-1:000000000000:log-group:/aws/lambda/test-lambda:*",
            "storedBytes": 1073
        },
        {
            "logGroupName": "sns/us-east-1/000000000000/test-sns",
            "creationTime": 1698473194764,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:us-east-1:000000000000:log-group:sns/us-east-1/000000000000/test-sns:*",
            "storedBytes": 397
        }
    ]
}

接下来,让我们一起来查看Lambda的日志流。

$ aws logs describe-log-streams \
 --profile=localstack \
 --endpoint-url http://localhost:4566 \
 --log-group-name "/aws/lambda/test-lambda"
 --order-by "LastEventTime"
{
    "logStreams": [
        {
            "logStreamName": "2023/10/28/[$LATEST]c13452ba6c7e2b1fc5361590b9de84f0",
            "creationTime": 1698473196936,
            "firstEventTimestamp": 1698473196914,
            "lastEventTimestamp": 1698473196930,
            "lastIngestionTime": 1698473196940,
            "uploadSequenceToken": "1",
            "arn": "arn:aws:logs:us-east-1:000000000000:log-group:/aws/lambda/test-lambda:log-stream:2023/10/28/[$LATEST]c13452ba6c7e2b1fc5361590b9de84f0",
            "storedBytes": 1073
        }
    ]
}

在确认日志流名称(= logStreamName)之后,让我们实际查看日志。

获取日志事件
% aws logs get-log-events \
 --profile=localstack \
 --endpoint-url http://localhost:4566 \
 --log-group-name "/aws/lambda/test-lambda" \
 --log-stream-name '2023/10/28/[$LATEST]c13452ba6c7e2b1fc5361590b9de84f0'
{
    "events": [
        {
            "timestamp": 1698473196914,
            "message": "START RequestId: 17cc5309-deaa-42ad-8c99-b6b13ae342bc Version: $LATEST",
            "ingestionTime": 1698473196940
        },
        {
            "timestamp": 1698473196917,
            "message": "2023-10-28T06:06:36.902Z\t17cc5309-deaa-42ad-8c99-b6b13ae342bc\tINFO\tbody from sns-sqs.",
            "ingestionTime": 1698473196940
        },
        {
            "timestamp": 1698473196920,
            "message": "2023-10-28T06:06:36.905Z\t17cc5309-deaa-42ad-8c99-b6b13ae342bc\tINFO\t{\"Type\": \"Notification\", \"MessageId\"
: \"0d04b3e2-aa0b-4f5c-ab3a-86f94261b60a\", \"TopicArn\": \"arn:aws:sns:us-east-1:000000000000:test-sns\", \"Message\": \"Hello AWS SNS!
! check tail LOG!\", \"Timestamp\": \"2023-10-28T06:06:34.734Z\", \"SignatureVersion\": \"1\", \"Signature\": \"EXAMPLEpH+..\", \"Signin
gCertURL\": \"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem\", \"UnsubscribeURL\": \"http://l
ocalhost:4566/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:000000000000:test-sns:995fff09-56ab-44fc-a92f-00722801199b\"}",
            "ingestionTime": 1698473196940
        },
        {
            "timestamp": 1698473196923,
            "message": "2023-10-28T06:06:36.905Z\t17cc5309-deaa-42ad-8c99-b6b13ae342bc\tINFO\tdata.Message: Hello AWS SNS!! check tail L
OG!",
            "ingestionTime": 1698473196940
        },
        {
            "timestamp": 1698473196927,
            "message": "END RequestId: 17cc5309-deaa-42ad-8c99-b6b13ae342bc",
            "ingestionTime": 1698473196940
        },
        {
            "timestamp": 1698473196930,
            "message": "REPORT RequestId: 17cc5309-deaa-42ad-8c99-b6b13ae342bc\tDuration: 22.53 ms\tBilled Duration: 23 ms\tMemory Size:
 128 MB\tMax Memory Used: 128 MB\t",
            "ingestionTime": 1698473196940
        }
    ],
    "nextForwardToken": "f/00000000000000000000000000000000000000000000000000000005",
    "nextBackwardToken": "b/00000000000000000000000000000000000000000000000000000000"
}

虽然有些难以理解,但是如果查看消息内容,就会发现输入了发布的值。
控制台日志也已经被输出了!

...
            "message": "2023-10-28T06:06:36.902Z\t17cc5309-deaa-42ad-8c99-b6b13ae342bc\tINFO\tbody from sns-sqs.",
...
            "message": "2023-10-28T06:06:36.905Z\t17cc5309-deaa-42ad-8c99-b6b13ae342bc\tINFO\t{\"Type\": \"Notification\", \"MessageId\"
: \"0d04b3e2-aa0b-4f5c-ab3a-86f94261b60a\", \"TopicArn\": \"arn:aws:sns:us-east-1:000000000000:test-sns\", \"Message\": \"Hello AWS SNS!
! check tail LOG!\", \"Timestamp\": \"2023-10-28T06:06:34.734Z\", \"SignatureVersion\": \"1\", \"Signature\": \"EXAMPLEpH+..\", \"Signin
gCertURL\": \"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem\", \"UnsubscribeURL\": \"http://l
ocalhost:4566/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:000000000000:test-sns:995fff09-56ab-44fc-a92f-00722801199b\"}",
...
            "message": "2023-10-28T06:06:36.905Z\t17cc5309-deaa-42ad-8c99-b6b13ae342bc\tINFO\tdata.Message: Hello AWS SNS!! check tail L
...

故障排除

如果出版成功了但没有输出日志,
对应方法
我作为作者也经历过几次,我认为有几个可能的原因。1. Lambda中部署的zip文件内容是否正确?
2. 处理程序的名称是否正确?
3. 尝试使用aws-cli直接部署到Lambda
4. (如果以上无法解决)尝试重新创建Docker容器

对于“1”,您可以尝试在本地解压缩zip文件并检查内容。
index.zip
└ index
├ index.js
└ index.js.map

对于“2”,您可能会在错误日志中注意到处理程序名称错误的粗心错误。

resource.tf

handler = “index.handler” // 声明要在index文件中执行的处理程序函数名

index.ts
export const handler = async (event, context) => { // 确认文件名是否正确

对于“3”,这是一种通过简单的将js文件打包成zip并使用aws-cli直接部署到Lambda进行测试的方法。
/*
* 删除(删除已部署的函数)
*/
$ aws lambda delete-function \
–profile=localstack \
–endpoint-url http://localhost:4566 \
–function-name “test-lambda”

/*
* 部署(zip-file选项的路径是zip文件的完整路径)
*/
$ aws lambda create-function \
–profile=localstack \
–endpoint-url=http://localhost:4566 \
–function-name test-lambda \
–zip-file fileb:///Users/path…/to…/index.zip \
–handler index.handler \
–runtime nodejs18.x \
–role arn:aws:iam::123456789012:role/lambda-execute

/*
* 调用(返回值将输出到本地的response.json文件)
*/
$ aws lambda invoke \
–profile=localstack \
–endpoint-url http://localhost:4566 \
–function-name “test-lambda” \
response.json

如果仍然无法解决问题,请尝试重新构建Docker(包括删除所有镜像)并重新尝试。
(还可以尝试更改为稍旧的版本再次尝试)

尾声

最近,包括基础设施环境在内的源代码使用Git进行版本控制的机会增加了。在DevOps的视角下,避免个人化,使每个人都能够管理资源已成为常识。

当中间件进行版本升级或发生重大更改时,如果依赖关系未可视化,人们就会因为害怕而无法动手,导致长时间被搁置不理…(亲身经历)

虽然如此,这次的基础设施即代码化只是一种方法,所以我们也必须考虑运维方面的问题。

烹饪(有趣的)

我尝试用最早从家庭菜园里种出来的小松菜和牛蒡,将其卷入肉中(售价50日元)。
我用酱油和糖调制成照烧风味,做成了便当的配菜。

undefined
undefined
广告
将在 10 秒后关闭
bannerAds