LoginSignup
11
4

More than 3 years have passed since last update.

Go+serverlessでSQS→Lambda→SES

Last updated at Posted at 2020-12-03

本記事はFusic Advent Calendar 2020の3日目の記事です。

普段の仕事ではもっぱらPHPばかりですが、最近はGoの勉強を楽しんでいます。
弊社では最近週一で有志のメンバーでGo+serverlessの勉強会を行っておりまして、今回はその課題で作成した内容の備忘録です。Serverless Frameworkを使用しています。
https://www.serverless.com/

環境

Serverless Framework: 2.14.0
aws-cli(動作確認用): 2.1.4

構成

色々と省略していますが、以下のような構成でSQSに来たメッセージをLambdaで取得してSESでメール通知を行うことを想定します。そして、Goを使いたいという理由でLambdaはGoで記述します。
本来、API Gateway→SQS→Lambda→SES/SNS/DynamoDBみたいなものを作ろうとしているのですが、それの一部を切り抜いている形です。エラー処理とかはあまり考慮せずに書いています。
※API Gateway→SQSの構成も後日追記しました。

aws-diagram.png

準備

Serverless Frameworkの導入等は省略しまして、まずはテンプレートを作成します。

テンプレートファイル作成
serverless create -t aws-go

helloディレクトリとworldディレクトリは不要なので消して、以下のようなディレクトリ構成にします。

ディレクトリ構成
.
├── Makefile
├── serverless.yml
└── sqs
    └── main.go

実装

SQS→Lambda

ひとまずSQSに入ってきたデータをLambdaで取得出来るか確認します。

sqs/main.go
package main

import (
    "context"
    "fmt"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

func Handler(ctx context.Context, sqsEvent events.SQSEvent) error {
    for _, message := range sqsEvent.Records {
        fmt.Printf("The message %s for event source %s = %s \n", message.MessageId, message.EventSource, message.Body)
    }

    return nil
}

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

severless.ymlを記述します。accountId の部分は自分のアカウントのものに置き換えが必要です。

serverless.yml
service: test-sqs-ses-go

provider:
  name: aws
  runtime: go1.x
  region: ap-northeast-1

package:
  exclude:
    - ./**
  include:
    - ./bin/**

functions:
  sqs:
    name: go-queue-worker
    handler: bin/sqs
    timeout: 30
    memorySize: 1024
    reservedConcurrency: 1
    events:
      - sqs:
          arn: arn:aws:sqs:ap-northeast-1:{accountId}:go-queue
          batchSize: 1

resources:
 Resources:
   MyQueue:
     Type: "AWS::SQS::Queue"
     Properties:
       QueueName: "go-queue"

Goのbuild

serverless.ymlのhandlerで指定したパスに実行ファイルを配置します。

GOOS=linux go build -o bin/sqs sqs/main.go

確認

デプロイ

設定したLambdaとSQSをデプロイします。もし、デプロイ時にエラーになる場合はリージョンやロールの設定を確認してみてください。

sls deploy -v
SQSにデータ送信
aws sqs send-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/{accountId}/go-queue  --message-body "test"
Lambdaのデータ処理確認
serverless logs -f sqs
START RequestId: 39c95002-76ee-519c-9a9a-08421fc3db82 Version: $LATEST
The message 5f78d4b3-2228-4324-a780-97ce6225a4b5 for event source aws:sqs = test 
END RequestId: 39c95002-76ee-519c-9a9a-08421fc3db82
REPORT RequestId: 39c95002-76ee-519c-9a9a-08421fc3db82  Duration: 0.99 ms       Billed Duration: 1 ms        Memory Size: 1024 MB    Max Memory Used: 34 MB  Init Duration: 64.94 ms

大丈夫そうですね。
コンソールからLambdaを確認してみると、ちゃんとデプロイされていて、SQSが紐付いているのが分かります。

スクリーンショット 2020-12-03 11.14.17.png

Lambda→SES

※SESの事前設定は省略
GoのSDKを使って、LambdaでSQSから受け取ったメッセージをそのままメールの本文として送信するように書き換えます。
基本的に公式のサンプル通りの記述です。
送信先と送信元のメールアドレスは適宜変える必要があります。

sqs/main.go
package main

import (
    "context"
    "fmt"

    "github.com/aws/aws-lambda-go/events"
    "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/session"
    "github.com/aws/aws-sdk-go/service/ses"
)

const (
    Sender    = "sender@example.com"
    Recipient = "recipient@eample.com"

    Subject = "Amazon SES Mail Send Test"

    CharSet = "UTF-8"
)

func Handler(ctx context.Context, sqsEvent events.SQSEvent) error {
    for _, message := range sqsEvent.Records {
        sendSES(message.Body)
        fmt.Printf("Send message [%s]! \n", message.Body)
    }

    return nil
}

func sendSES(message string) {
    sess, _ := session.NewSession(&aws.Config{
        Region: aws.String("ap-northeast-1")},
    )

    svc := ses.New(sess)

    input := &ses.SendEmailInput{
        Destination: &ses.Destination{
            CcAddresses: []*string{},
            ToAddresses: []*string{
                aws.String(Recipient),
            },
        },
        Message: &ses.Message{
            Body: &ses.Body{
                Text: &ses.Content{
                    Charset: aws.String(CharSet),
                    Data:    aws.String(message),
                },
            },
            Subject: &ses.Content{
                Charset: aws.String(CharSet),
                Data:    aws.String(Subject),
            },
        },
        Source: aws.String(Sender),
    }

    result, err := svc.SendEmail(input)

    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            switch aerr.Code() {
            case ses.ErrCodeMessageRejected:
                fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
            case ses.ErrCodeMailFromDomainNotVerifiedException:
                fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
            case ses.ErrCodeConfigurationSetDoesNotExistException:
                fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
            default:
                fmt.Println(aerr.Error())
            }
        } else {
            fmt.Println(err.Error())
        }

        return
    }
    fmt.Println(result)
}

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

このままだと実行されるLambdaにSESの権限が付いていませんので、serverless.ymlのproviderの箇所を以下に修正します。

serverless.yml
provider
  name: aws
  runtime: go1.x
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - ses:*
      Resource: "*"

確認

再びSQS→Lambdaの時と同様にビルド・デプロイを行って、実際メールが届くか確認します。
うまく行けば本文が"test"のメールが届くはずです。

aws sqs send-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/{accountId}/go-queue  --message-body "test"

OKですね。
ses-test.png

API Gateway→SQS(追記)

serverless-apigateway-service-proxyを利用して、API Gateway→SQSの連携も確認できたので追記します。

まず、プラグインを導入します。

serverless plugin install -n serverless-apigateway-service-proxy

そして、severless.ymlにAPI Gatewayを使う記述を追記するだけです。
queueNameの指定で躓いていたのですが、シンプルな答えでした。

serverless.yml
service: test-sqs-ses-go

provider:
  name: aws
  runtime: go1.x
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - ses:*
      Resource: "*"

package:
  exclude:
    - ./**
  include:
    - ./bin/**

custom:
  apiGatewayServiceProxies:
    - sqs:
        path: /sqs
        method: post
        queueName: { 'Fn::GetAtt': ['MyQueue', 'QueueName'] }

functions:
  sqs:
    name: go-queue-worker
    handler: bin/sqs
    events:
      - sqs:
          arn: arn:aws:sqs:ap-northeast-1:{accountId}:go-queue
          batchSize: 1

resources:
 Resources:
   MyQueue:
     Type: "AWS::SQS::Queue"
     Properties:
       QueueName: "go-queue"

plugins:
  - serverless-apigateway-service-proxy

確認

デプロイが終わったら、リクエストを投げてみて動作を確認します。xxxxxxxxxの部分はデプロイ完了時に表示されるのを見るか、コンソールから確認できます。

curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/sqs -d '{"message": "testtest"}' -H 'Content-Type:application/json'

レスポンスは特に指定していなかったので、MessageId等だけが返ってきます。
また、Lambda側の処理は変えていないので本文がJSONになっていますが、メールも問題なく来ました。

スクリーンショット 2020-12-03 23.42.26.png

片付け

動作が確認出来て、検証が終わったらリソースは削除しておきます。

sls remove -v

まとめ

Go+serverlessでSQS→Lambda→SESの流れを実装しました。このままだと、まだ機能の一部でしか無いのでアプリケーションとして使用する場合はもっと色々考慮する必要がありそうです。
メール送信の部分を並行処理に出来ればもっと良かったかなと思っています。

参照

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4