Bu örnekte, AWS hesabımızı gelen kutumuza alarm e-postaları gönderecek şekilde yapılandırmak için Terraform'u kullanıyoruz. Mantık basit. AWS EventBridge Scheduler, rastgele bir şeyi günlüğe kaydetmek üzere Lambda'yı aramak için her dakika çalışır. Günlükler INFO, WARN ve ERROR seviyelerini içerecektir. Bu sadece uygulama günlüklerini simüle etmek içindir. Şimdi, asıl kısım daha yeni başlıyor. CloudWatch'u, yeni bir WARN veya ERROR günlük girişi olup olmadığını görmek için, her 30 saniyede bir belirli günlük grubumuzu izleyecek şekilde yapılandırıyoruz. Eğer en azından bir tane varsa, SNS'ye alarm e-postaları göndermesini söyler. Bütün mantık bu.


Yapı


├── cmd
│   └── greeter
│   └── main.go
├── .gitignore
├── go.mod
├── go.sum
├── main.go
└── terraform
└── development
├── consumer.tf
├── main.tf
└── producer.tf

main.go


package main

import (
"context"
"os"
"time"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"golang.org/x/exp/slog"
)

func main() {
lambda.Start(handler)
}

func handler(ctx context.Context, event events.CloudWatchEvent) {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

logger.Info("scheduler is calling")

if sec := time.Now().UTC().Nanosecond(); sec%2 == 0 {
logger.Warn("an even event is detected",
slog.Any("event", event),
)

return
}

logger.Error("an uneven event is detected",
slog.Any("event", event),
)
}

.gitignore


.terraform/
terraform.tfstate*

tmp/
bin/

main.tf


terraform {
required_version = "~> 1.4.4"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.41.0"
}

archive = {
source = "hashicorp/archive"
version = "~> 2.3.0"
}

null = {
source = "hashicorp/null"
version = "~> 3.2.1"
}
}
}

provider "aws" {
profile = "development"
region = "eu-west-1"
}

producer.tf


# -- VARS ----------------------------------------------------------------------

locals {
lambda_function_name = "greeter"

go_source_path = "${path.module}/../../cmd/${local.lambda_function_name}/..."
go_binary_path = "${path.module}/../../bin/${local.lambda_function_name}"
go_zip_path = "${path.module}/../../tmp/${local.lambda_function_name}.zip"
}

# -- LAMBDA --------------------------------------------------------------------

resource "null_resource" "lambda_go_binary" {
provisioner "local-exec" {
command = "GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GOFLAGS=-trimpath go build -mod=readonly -ldflags='-s -w' -o ${local.go_binary_path} ${local.go_source_path}"
}
}

data "archive_file" "lambda_go_zip" {
type = "zip"
source_file = local.go_binary_path
output_path = local.go_zip_path

depends_on = [
null_resource.lambda_go_binary,
]
}

resource "aws_lambda_function" "greeter" {
function_name = local.lambda_function_name
handler = local.lambda_function_name
filename = local.go_zip_path
package_type = "Zip"
runtime = "go1.x"
timeout = 30
memory_size = 128

role = aws_iam_role.lambda_executor.arn
source_code_hash = data.archive_file.lambda_go_zip.output_base64sha256

depends_on = [
aws_cloudwatch_log_group.lambda_log_group,
]
}

resource "aws_iam_role" "lambda_executor" {
name = "greeter-lambda-executor"

managed_policy_arns = [aws_iam_policy.lambda_log.arn]

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
},
]
})
}

resource "aws_iam_policy" "lambda_log" {
name = "greeter-lambda-log"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]
Effect = "Allow"
Resource = [
"arn:aws:logs:*:*:*",
]
},
]
})
}

resource "aws_cloudwatch_log_group" "lambda_log_group" {
name = "/aws/lambda/${local.lambda_function_name}"
retention_in_days = 5
}

# -- CLOUDWATCH ----------------------------------------------------------------

resource "aws_scheduler_schedule" "schedule" {
name = "greeter-lambda-schedule"

flexible_time_window {
mode = "OFF"
}

schedule_expression = "rate(1 minutes)"

target {
arn = aws_lambda_function.greeter.arn
role_arn = aws_iam_role.schedule_executor.arn
}
}

resource "aws_iam_role" "schedule_executor" {
name = "greeter-lambda-schedule-executor"

managed_policy_arns = [aws_iam_policy.schedule_invoker.arn]

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "scheduler.amazonaws.com"
}
},
]
})
}

resource "aws_iam_policy" "schedule_invoker" {
name = "greeter-lambda-schedule-invoker"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "lambda:InvokeFunction"
Effect = "Allow"
Resource = aws_lambda_function.greeter.arn
},
]
})
}

consumer.tf


# -- CLOUDWATCH ----------------------------------------------------------------

resource "aws_cloudwatch_log_metric_filter" "lambda_log_filter" {
name = "greeter-lambda-log-filter"

pattern = "{ $.level = WARN || $.level = ERROR }"
log_group_name = aws_cloudwatch_log_group.lambda_log_group.name

metric_transformation {
name = "greeter-lambda-warning-and-error-logs"
namespace = "greeter-lambda-issues"
value = "1"
}
}

resource "aws_cloudwatch_metric_alarm" "lambda_log_alarm" {
alarm_name = "greeter-lambda-log-alarm"
alarm_description = "There is an issue with the greeter lambda function"

comparison_operator = "GreaterThanThreshold"
threshold = 0
evaluation_periods = 1
datapoints_to_alarm = 1
period = 30
statistic = "Sum"

alarm_actions = [aws_sns_topic.lambda_log_alarm.arn]
metric_name = aws_cloudwatch_log_metric_filter.lambda_log_filter.metric_transformation[0].name
namespace = aws_cloudwatch_log_metric_filter.lambda_log_filter.metric_transformation[0].namespace
}

# -- SNS -----------------------------------------------------------------------

resource "aws_sns_topic" "lambda_log_alarm" {
name = "greeter-lambda-log-alarm"
}

resource "aws_sns_topic_subscription" "lambda_log_alarm_receiver" {
topic_arn = aws_sns_topic.lambda_log_alarm.arn
protocol = "email"
endpoint = "you@example.com"
}

Kurulum


me:~/aws/terraform/development$ terraform apply \
-replace="null_resource.lambda_go_binary" \
-replace="archive_file.lambda_go_zip" \
-replace="aws_lambda_function.greeter"

Alarm e-postası


You are receiving this email because your Amazon CloudWatch Alarm "greeter-lambda-log-alarm" in the EU (Ireland) region has entered the ALARM state, because "Threshold Crossed: 1 out of the last 1 datapoints [1.0 (14/07/23 20:51:00)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition)." at "Friday 14 July, 2023 20:51:52 UTC".

View this alarm in the AWS Management Console:
https://eu-west-1.console.aws.amazon.com/cloudwatch/......

Alarm Details:
- Name: greeter-lambda-log-alarm
- Description: There is an issue with the greeter lambda function
- State Change: INSUFFICIENT_DATA -> ALARM
- Reason for State Change: Threshold Crossed: 1 out of the last 1 datapoints [1.0 (14/07/23 20:51:00)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).
- Timestamp: Friday 14 July, 2023 20:51:52 UTC
- AWS Account: 124789012345
- Alarm Arn: arn:aws:cloudwatch:eu-west-1:124789012345:alarm:greeter-lambda-log-alarm

Threshold:
- The alarm is in the ALARM state when the metric is GreaterThanThreshold 0.0 for at least 1 of the last 1 period(s) of 30 seconds.

Monitored Metric:
- MetricNamespace: greeter-lambda-issues
- MetricName: greeter-lambda-warning-and-error-logs
- Dimensions:
- Period: 30 seconds
- Statistic: Sum
- Unit: not specified
- TreatMissingData: missing

State Change Actions:
- OK:
- ALARM: [arn:aws:sns:eu-west-1:124789012345:greeter-lambda-log-alarm]
- INSUFFICIENT_DATA: