【Terraform】GoでLambdaからS3を操作するサンプル

Terraformサンプル

bucket

resource "aws_s3_bucket" "s3" {
  bucket = "bucket_name"
  acl = "private"
  versioning {
    enabled = true
  }
}

Lambda・S3間のネットワーク

resource "aws_vpc" "prod" {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "vpc_prod"
  }
}

resource "aws_internet_gateway" "prod" {
  vpc_id = aws_vpc.prod.id
  tags = {
    Name = "internet_gateway_prod"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.prod.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.prod.id
  }

  tags = {
    Name = "route_tablepublic"
  }
}

resource "aws_vpc_endpoint" "private-s3" {
  vpc_id = aws_vpc.prod.id
  service_name = "com.amazonaws.ap-northeast-1.s3"
  route_table_ids = [aws_route_table.public.id]
}

aws_vpc_endpointが鍵でこれがないとLambdaからs3にアクセスできません。

役割としてはパブリックなネットワークを介さずにs3にアクセスするために必要です。

Lambda functionとRole

data "archive_file" "source_zip" {
  type        = "zip"
  source_dir  = "./source"
  output_path = "./source/source.zip"
}

resource "aws_lambda_function" "sample" {
  filename         = data.archive_file.source_zip.output_path
  function_name    = "sample"
  role             = aws_iam_role.sample.arn
  handler          = "main"
  source_code_hash = data.archive_file.source_zip.output_base64sha256
  runtime          = "go1.x"

  memory_size = 128
  timeout     = 10

  environment {
    variables = {
      S3_BUCKET_NAME = aws_s3_bucket.s3.bucket
    }
  }
}

resource "aws_iam_role" "sample" {
  name               = "sample_iam_role"
  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
POLICY
}

resource "aws_iam_role_policy" "sample" {
  name   = "sample_iam_role_policy"
  role   = aws_iam_role.sample.id
  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "ec2:CreateNetworkInterface",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DeleteNetworkInterface",
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": "*"
    }
  ]
}
POLICY
}

resource "aws_cloudwatch_event_target" "output_report_every_month" {
  rule      = aws_cloudwatch_event_rule.every_2m.name
  target_id = "output_report"
  arn       = aws_lambda_function.sample.arn
}

resource "aws_lambda_permission" "allow_cloudwatch_to_call_output_report" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.sample.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.every_2m.arn
}

resource "aws_cloudwatch_event_rule" "every_2m" {
  name                = "every_2m"
  description         = "every_2m"
  schedule_expression = "cron(1/2 * * * ? *)"
}
  • archive_file を使うとterraform plan時に対象のディレクトリをzipにしてくれます
  • aws_cloudwatch_event_ruleを使うと2分ごとに実行などができるようになります

実行スクリプトサンプル

上のarchive_fileで指定したディレクトリ内にビルドしたバイナリを置きましょう

main.go

package main

import (
    "bytes"
    "errors"
    "fmt"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/awserr"
    "github.com/aws/aws-sdk-go/aws/endpoints"
    "github.com/aws/aws-sdk-go/aws/request"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/s3"
    "os"
    "time"
)

func run() (string, error) {
    s3BucketName := os.Getenv("S3_BUCKET_NAME")
    if s3BucketName == "" {
        return "failed", errors.New("must set env value S3_BUCKET_NAME")
    }
    err := uploadToS3(s3BucketName, fmt.Sprintf("file_%s", time.Now().UTC().Format("2006-01-02")))
    if err != nil {
        return "failed", err
    }

    return "success", nil
}

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

func uploadToS3(s3BucketName, fileName string) error {
    sess := session.Must(session.NewSession(&aws.Config{
        Region: aws.String(endpoints.ApNortheast1RegionID),
    }))
    svc := s3.New(sess)

    wb := new(bytes.Buffer)
    _, _ = fmt.Fprint(wb, "テキスト")

    res, err := svc.PutObject(&s3.PutObjectInput{
        Bucket: aws.String(s3BucketName),
        Key:    aws.String(fileName + ".txt"),
        Body:   bytes.NewReader(wb.Bytes()),
    })

    if err != nil {
        fmt.Println(res)
        if err, ok := err.(awserr.Error); ok && err.Code() == request.CanceledErrorCode {
            fmt.Printf("upload canceled due to timeout, %v\n", err)
            return err
        } else {
            fmt.Printf("failed to upload object, %v\n", err)
            return err
        }
    }

    fmt.Printf("successfully uploaded file\n")
    return nil
}

ビルド

go get ./...
GOOS=linux GOARCH=amd64 go build -o ./source/main main.go

main.goをビルドして、mainという名前のバイナリを作成する。

mainという名前はaws_lambda_functionhandler = "main"で指定する名前と同じにします。

コメント

タイトルとURLをコピーしました