クイック エンジニアリングブログ

株式会社クイック Web事業企画開発本部のエンジニアリングチームが運営する技術ブログです。

AWS SAMを使ってバッチシステムを作ってみたぞ!

こんにちは、ソフトウェアエンジニアのiwateaです。

皆さん、AWS使ってますか?

AWSのおかげで複雑な知識がなくてもインフラ構築ができるので、とてもいい時代になりましたよね。

そんなクラウド時代になって、ちょくちょく耳にするキーワード「Serverless」。
今回はそんなServerlessフレームワークの1つであるAWS SAMの話です。

AWS SAMって何?

AWSでServerlessアプリケーションを構築するためのフレームワークです。

aws.amazon.com

ローカルでLambdaの実行環境ができるので開発・テストがしやすく、AWSへのデプロイも1コマンドでできる優れものです。

Serverlessでバッチシステムを作ると何が嬉しいの?

料金が安い

Serverlessでは必要な時に必要な時間だけ実行環境を起動するためコスト削減に繋がります。

小中規模システムのバッチは特定の時間に数分〜数十分だけ動かすケースが多く、逆に言うと何もしない時間が大半です。
そのため、EC2を常時起動してバッチサーバーとしてしまうと無駄な課金が発生してしまいます。

並列化で実行時間の短縮ができる

Lambdaは並列実行が容易にできるので、並列化による処理時間の短縮ができます。

バッチ処理が遅くなるケースの一つに、データ量に伴うループ回数の増加に起因するものがあります。
このケースではコードの最適化による速度改善には限界があり、中々難しいのが実情です。
この問題を解決するために並列化という手法を使うことがありますが、これをインフラレベルで簡単に実現することができます。

早速SAMを動かしてみよう!

今回はシンプルなProducer Consumerパターンを作ります。

f:id:aimstogeek:20200714154150p:plain

Step1. SAMプロジェクトの作成

sam init コマンドでSAMプロジェクトを生成しましょう。
今回はgolangを使いました。

 $ sam init --name blog --runtime go1.x
Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
    1 - Hello World Example
    2 - Step Functions Sample App (Stock Trader)
Template selection: 2

-----------------------
Generating application:
-----------------------
Name: blog
Runtime: go1.x
Dependency Manager: mod
Application Template: step-functions-sample-app
Output Directory: .

Next steps can be found in the README file at ./blog/README.md

Step2. 処理を作る

2つのLambdaファンクションを用意しました。

producer : SQSにメッセージをEnqueueするLambda
consumer : SQSからメッセージを受け取り、標準出力に表示するLambda

├── functions
│   ├── consumer
│   │   ├── go.mod
│   │   ├── go.sum
│   │   └── main.go
│   └── producer
│       ├── go.mod
│       ├── go.sum
│       ├── main.go
│       └── main_test.go
└── template.yaml

Step3. template.ymlを編集する

AWSリソースの定義をします。

AWSコンソールから作ろうとしたらCloudWatchEventやIAM Role等を作る必要がありますが、なんとSAMリソースを使うと必要なものをまとめて作ってくれます
AWS SAM リソースおよびプロパティのリファレンス - AWS サーバーレスアプリケーションモデル

なお、SAMリソースに定義されていないものはCloudFormationの定義をすればOKです。

※Resourcesのみ抜粋

Resources:
  TaskQueue:
    Type: AWS::SQS::Queue

  ProducerFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/producer/
      Handler: producer
      Runtime: go1.x
      Events:
        Timer:
          Type: Schedule
          Properties:
            Schedule: cron(0 9 ? * MON *)
      Environment:
        Variables:
          SQS_ENDPOINT: !Ref TaskQueue

  ConsumerFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/consumer/
      Handler: consumer
      Runtime: go1.x
      ReservedConcurrentExecutions: 10
      Events:
        TaskQueueEvent:
          Type: SQS
          Properties:
            Queue: !GetAtt TaskQueue.Arn
            BatchSize: 10

Step4. ローカルで実行してみる

SQSからメッセージを受け取るConsumerFunctionを実行してみます。

sam local invoke コマンドに -e オプションをつけてメッセージのパラメーターを渡しますが、パラメーターのフォーマットが分からない事件が度々。。
そんな時は sam local generate-event コマンドで生成できます。

$ sam local generate-event sqs receive-message
{
  "Records": [
    {
      "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
      "receiptHandle": "MessageReceiptHandle",
      "body": "Hello from SQS!",
      "attributes": {
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1523232000000",
        "SenderId": "123456789012",
        "ApproximateFirstReceiveTimestamp": "1523232000001"
      },
      "messageAttributes": {},
      "md5OfBody": "7b270e59b47ff90a553787216d55d91d",
      "eventSource": "aws:sqs",
      "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
      "awsRegion": "us-east-1"
    }
  ]
}

では、ConsumerFunctionを実行してみましょう。

$ sam build && sam local invoke -e functions/consumer/event.json ConsumerFunction
...

Fetching lambci/lambda:go1.x Docker container image......
Mounting /Users/iwata-yoshihiro/workspace/go/blog/.aws-sam/build/ConsumerFunction as /var/task:ro,delegated inside runtime container
START RequestId: ef5f5202-0cc8-19a1-0fcf-014c89d8f258 Version: $LATEST
Hello from SQS!
END RequestId: ef5f5202-0cc8-19a1-0fcf-014c89d8f258
REPORT RequestId: ef5f5202-0cc8-19a1-0fcf-014c89d8f258  Init Duration: 124.26 ms    Duration: 4.11 ms   Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 22 MB

"success"

やったぜ!!

Step5. AWSへデプロイしてみる

最後にAWSへデプロイしてみましょう。

事前にデプロイで使うbucketをS3に作っておきます。
そして sam deploy コマンドを実行すればデプロイ完了です!

楽ちん!

$ sam deploy --stack-name blog --s3-bucket blog-deploy-bucket --capabilities CAPABILITY_IAM

あのtemplate.ymlで、これだけのAWSリソースが生成されます。 f:id:aimstogeek:20200714162556p:plain

実際に運用してみて起きた問題点

Terraformとの共存がしにくい

インフラ構築の際にTerraformがよく使われますが、SAMはCloudFormationを使うことになります。
もしWeb + Batchの様なシステムを構築する場合にどちらで何のAWSリソースを管理するかが課題になります。

Lambdaの実行時間制限

Lambdaは1回あたり15分しか実行できない制限があります。
そのため、15分以上の実行時間が必要なものは運用できません。

こういったケースではECS TaskやAWS Batchを使いますが、SAMプロジェクトでDockerを使ったバッチを作ろうとするとアーキテクチャが違いすぎて複雑化してしまいます。
ある日データ量の増加で15分制限に引っかかり、処理の都合上Lambdaでは限界ということが判明した時は頭を抱えました。

全体像把握のハードルが高い

ServerlessではAWSマネージドサービスを復数組み合わせることになります。
また、イベント駆動で各種リソースを起動することが多いので、AWSに慣れていないと全体像把握のハードルが高くなります。
AWS大好きでコードをゴリゴリ書けるエンジニアを揃えるのは中々難しいところです。

おわりに

例えば「S3にファイルがアップロードされた時」にLambdaで処理をするなんてこともでき、これまで時間決め打ちやポーリングといった柔軟性が無かったり、無駄が多かった設計がイベント駆動のシンプルな設計に置き換えることができるようになりました。

しかし、便利さの裏で如何ともし難い問題点も含んでいますので、自分たちが作るシステムに合わせて技術選定をして頂ければ幸いです。

AWSのおかげでインフラストラクチャがより扱いやすくなり、マネージドサービスの活用で開発コストを削減することができて嬉しい反面、システム設計にアプリ・インフラ両軸の知識が必要になり大変な時代でもあります。

技術の移り変わりは激しいですが、取り残されずに良いエンジニアライフを送りましょう!


\\『真のユーザーファーストでマーケットを創造する』仲間を募集中です!! //

919.jp