[AWS] API Gateway + Lambda for Ruby + S3 + Slack通知をSAMで構築

[AWS] API Gateway + Lambda for Ruby + S3 + Slack通知をSAMで構築:

これはFusic Advent Calendar 2018の8日目の記事です。

昨日の記事は...なんか上がってないので、もう少しお待ちくださいませ。

ちなみに一昨日6日目の記事はAWS re:Invent2018に参加してきたメンバーが発表されたサービスの中で一番ときめいたという「Transit Gateway」について説明しています。どうぞ併せてご覧ください。


社内ツール『バボちゃん3』は今日も元気です



68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f32323536372f62376132643263642d663037352d356134352d353664352d6533393533646234313030392e706e67.png

Backlog WebhookをAmazon API Gateway+AWS LambdaでSlackに通知する

昔こんな記事を書きましたが、弊社ではプロジェクトでBacklogSlackをよく使用します。

Backlogでのチケット起票やコメントは知りたいけど、逐一メールチェックはしたくない、そんな気持ちから最初はHeroku上で稼働するアプリとして生まれました。

その後、Herokuの無料枠が変わって、24時間稼働が難しくなったタイミングでAWS Lambda for Node.jsで作り直したのが『バボちゃん2』でした。

※ちなみにバボちゃんは、ックログ・ットの略称です^^

その後、AWS LambdaがPython3に対応したので、嬉しくなってつい作り直しちゃったのが『バボちゃん3』で、いまも元気にたくさんのプロジェクトのBacklogチケットをせっせとSlackにお届けしております。


AWS re:Invent2018で一番ときめいたのはLambda for Ruby

Announcing Ruby Support for AWS Lambda

ラスベガスに行っていた弊社メンバーから「なんかLambdaでなんかあるかも!」という情報が来たのが日本時間11/29 お昼すぎ。そわそわしながらも熟睡して翌朝(11/30)起きると社内Slackチャンネルでリリースを知りました。喜びですね。


やるしかない!『バボちゃん4』

ということで、本ポストではバボちゃんで必要な要素を一通り試してみようと思います。

さらにやろうやろうと思いつつ、やれていなかったAWS SAMも使ってみました。


環境構築

開発・デプロイ環境はMacです。


AWS SAM

以前はaws-sam-localというやつでしたが、先日バージョンアップしてaws-sam-cliになったようです。
https://aws.amazon.com/about-aws/whats-new/2018/04/aws-sam-cli-releases-new-init-command/

そのため以前使っていた場合はアンインストールする必要があります。

自分はここでちょっとハマりました。後述するbrewでのインストールは問題なくできてしまいますが、バージョンが0.2.11のままでRuntimeにRubyが使えません。

$ sam 
A newer version of the AWS SAM CLI is available! 
Your version:   0.2.11 
Latest version: 0.8.1 
See https://github.com/awslabs/aws-sam-local for upgrade instructions 
(snip) 
 
$ $ npm -g ls | grep sam 
├─┬ aws-sam-local@0.2.11 
自分の場合はnpmでglobalにインストールしていたようです。

入れるだけ入れて何もやってなかったので、記憶になく、ここに至るまでちょっと手間取りました(汗


aws-sam-localのアンインストール

$ npm uninstall -g aws-sam-local 


aws-sam-cliインストール

$ brew update 
$ brew tap aws/tap 
$ brew install aws-sam-cli 
$ sam --version 
SAM CLI, version 0.8.1 
(参考)


Docker for macOS

こちらは公式やその他記事を参照ください。


(参考)


Ruby2.5+

AWS Lambda上では ruby2.5 と書いてますが、手元にあった 2.5.1 でも特に問題はなく

こちらもインストールについては公式やその他記事を参照ください。

bundlerのインストールもお忘れなく。

(参考)


aws-cli

これは不要なのかもしれないですが、あったほうが検証とかもろもろしやすいですよね。あとsamでパッケージしたり、デプロイしたりするときのクレデンシャル情報としてaws-cliのprofileを使用しました。

$ brew install awscli 


Hello world

まずはsamでテンプレートとかをgenerateします。

$ sam init --runtime ruby2.5 --name hello_ruby 
[+] Initializing project structure... 
[SUCCESS] - Read hello_ruby/README.md for further instructions on how to proceed 
[*] Project initialization is now complete 
$ tree . 
. 
└── hello_ruby 
    ├── Gemfile 
    ├── README.md 
    ├── hello_world 
    │   └── app.rb 
    ├── template.yaml 
    └── tests 
        └── unit 
            └── test_handler.rb 
 
4 directories, 5 files 
おぉ、RuntimeでRubyを選択できた!

更にtemplate.yamlを見ると、デフォルトでAPI Gateway + Lambda連携になってました。便利。

丁寧にREAMD.mdが書かれているので、その通りにすれば大体うまくいきます。

せっかくなので、このサンプルでローカル実行とデプロイして動作確認してみましょう。


ローカル実行

生成されたRubyアプリを見ると、

require 'httparty' 
require 'json' 
 
def lambda_handler(event:, context:) 
begin 
    response = HTTParty.get('http://checkip.amazonaws.com/') 
  rescue HTTParty::Error => error 
    puts error.inspect 
    raise error 
  end 
 
  return { 
    :statusCode => response.code, 
    :body => { 
      :message => "Hello World!", 
      :location => response.body 
    }.to_json 
  } 
end 
httpartyが使われているので、Gemインストールが必要ですね。

$ bundle install --path hello_world/vendor/bundle 
ではLambda単体で動かしてみましょう。

$ sam local invoke HelloWorldFunction --no-event 
2018-12-07 08:32:15 Invoking app.lambda_handler (ruby2.5) 
 
Fetching lambci/lambda:ruby2.5 Docker container image...... 
2018-12-07 08:32:19 Mounting /.../hello_ruby/hello_world as /var/task:ro inside runtime container 
START RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 Version: $LATEST 
END RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 
REPORT RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72  Duration: 527.55 ms     Billed Duration: 600 ms Memory Size: 128 MB     Max Memory Used: 30 MB 
{"statusCode":200,"body":"{\"message\":\"Hello World!\",\"location\":\"himitsu-no-akko-chan\\n\"}"} 
アプリは自身のIPアドレスを取得して返却するもののようですね。

次にAPI Gateway経由で実行してみましょう。

aws-sam-cliにはローカルで発火できる疑似Eventを簡単に生成することができます。便利!

$ sam local generate-event apigateway aws-proxy > event_file_apigateway.json 
{ 
  "body": "eyJ0ZXN0IjoiYm9keSJ9", 
  "resource": "/{proxy+}", 
  "path": "/path/to/resource", 
  "httpMethod": "POST", 
  "isBase64Encoded": true, 
  "queryStringParameters": { 
    "foo": "bar" 
  }, 
  "pathParameters": { 
    "proxy": "/path/to/resource" 
  }, 
  "stageVariables": { 
    "baz": "qux" 
  }, 
  "headers": { 
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, sdch", 
    "Accept-Language": "en-US,en;q=0.8", 
    "Cache-Control": "max-age=0", 
    "CloudFront-Forwarded-Proto": "https", 
    "CloudFront-Is-Desktop-Viewer": "true", 
    "CloudFront-Is-Mobile-Viewer": "false", 
    "CloudFront-Is-SmartTV-Viewer": "false", 
    "CloudFront-Is-Tablet-Viewer": "false", 
    "CloudFront-Viewer-Country": "US", 
    "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Custom User Agent String", 
    "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 
    "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 
    "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 
    "X-Forwarded-Port": "443", 
    "X-Forwarded-Proto": "https" 
  }, 
  "requestContext": { 
    "accountId": "123456789012", 
    "resourceId": "123456", 
    "stage": "prod", 
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 
    "requestTime": "09/Apr/2015:12:34:56 +0000", 
    "requestTimeEpoch": 1428582896000, 
    "identity": { 
      "cognitoIdentityPoolId": null, 
      "accountId": null, 
      "cognitoIdentityId": null, 
      "caller": null, 
      "accessKey": null, 
      "sourceIp": "127.0.0.1", 
      "cognitoAuthenticationType": null, 
      "cognitoAuthenticationProvider": null, 
      "userArn": null, 
      "userAgent": "Custom User Agent String", 
      "user": null 
    }, 
    "path": "/prod/path/to/resource", 
    "resourcePath": "/{proxy+}", 
    "httpMethod": "POST", 
    "apiId": "1234567890", 
    "protocol": "HTTP/1.1" 
  } 
} 
ではAPI GatewayにEventを送ってローカル実行してみましょう。

$ sam local invoke HelloWorldFunction -e event_file_apigateway.json 
2018-12-07 08:38:13 Invoking app.lambda_handler (ruby2.5) 
 
Fetching lambci/lambda:ruby2.5 Docker container image...... 
2018-12-07 08:38:17 Mounting /.../hello_ruby/hello_world as /var/task:ro inside runtime container 
START RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 Version: $LATEST 
END RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 
REPORT RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72  Duration: 490.94 ms     Billed Duration: 500 ms Memory Size: 128 MB     Max Memory Used: 30 MB 
{"statusCode":200,"body":"{\"message\":\"Hello World!\",\"location\":\"higashino-keigo\\n\"}"} 
まあ、直接lambda動かしたのと差はないですね。


パッケージ&デプロイ

これもsamでちょちょいと。


その前に、パッケージするためのS3バケットが必要なので、ご準備ください。


パッケージ用にbundle isntall

$ bundle install --deployment --path hello_world/vendor/bundle 
gemもパッケージするために --deployment を付けてinstallします。


パッケージ

$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket <YOUR_BUCKET_NAME> --profile <YOUR_PROFILE_NAME> 
Uploading to xxxxxxxxxxxxxxxxxxx  643003 / 643003.0  (100.00%) 
Successfully packaged artifacts and wrote output template to file packaged.yaml. 
Execute the following command to deploy the packaged template 
aws cloudformation deploy --template-file /.../hello_ruby/packaged.yaml --stack-name <YOUR STACK NAME> 


デプロイ

$ sam deploy --template-file packaged.yaml --stack-name <YOUR_STACK_NAME> --capabilities CAPABILITY_IAM --profile <YOUR_PROFILE_NAME> 
 
Waiting for changeset to be created.. 
Waiting for stack create/update to complete 
Successfully created/updated stack - ServerlessFunctionHelloRuby 
CloudFormationのスタック名は任意でOKです。

Amazon Management Consoleで確認したらちゃんとできていました



API_Gateway_2.png


できたAPIをブラウザでアクセスしてみたらちゃんと動きましたー!

apigateway_lambda_hello.png


(バボちゃん4を意識して)S3 + Slack通知を追加


tempalte.yaml

使用するS3バケットとLambdaからの権限付与するのと、Bucket名とSlack Incoming WebhookのURLをアプリで扱えるように環境変数で渡します。


またBacklogからのWebhookはPOSTになるので、API GatewayのMethodも変更します。

AWSTemplateFormatVersion: '2010-09-09' 
Transform: AWS::Serverless-2016-10-31 
Description: > 
  babo_ruby 
  Sample SAM Template for babo_ruby 
 
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst 
Globals: 
  Function: 
    Timeout: 3 
Resources: 
  Bucket: 
    Type: AWS::S3::Bucket 
  BaboFunction: 
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction 
    Properties: 
      CodeUri: babo/ 
      Handler: app.lambda_handler 
      Runtime: ruby2.5 
      Policies: 
        - S3CrudPolicy: 
            BucketName: !Ref Bucket 
      Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object 
        Variables: 
          BUCKET_NAME: !Ref Bucket 
          SLACK_URL: https://hooks.slack.com/services/xxxxxxxxxxxxx 
      Events: 
        HelloWorld: 
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api 
          Properties: 
            Path: /webhook 
            Method: post 
Outputs: 
  HelloWorldApi: 
    Description: "API Gateway endpoint URL for Prod stage for Hello World function" 
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/webhook/" 
  HelloWorldFunction: 
    Description: "Hello World Lambda Function ARN" 
    Value: !GetAtt BaboFunction.Arn 
  HelloWorldFunctionIamRole: 
    Description: "Implicit IAM Role created for Hello World function" 
    Value: !GetAtt BaboFunction.Arn 
 


Gemfile

source "https://rubygems.org" 
 
gem "aws-sdk-s3" 
gem 'slack-notifier' 


app.rb

require 'aws-sdk-s3' 
require 'slack-notifier' 
require 'json' 
 
def lambda_handler(event:, context:) 
  begin 
    bucket_name = ENV['BUCKET_NAME'] 
    slack_url   = ENV['SLACK_URL'] 
 
    bl_body = JSON.parse(event['body']) 
    bl_project_id = bl_body['project']['id'] 
 
    s3 = Aws::S3::Resource.new 
    s3_obj = s3.bucket(bucket_name).object("#{bl_project_id}.conf") 
    s3_body = JSON.parse(s3_obj.get.body.read) 
 
  rescue => error 
    puts error 
    raise error 
  end 
 
  notifier = Slack::Notifier.new(slack_url, username: 'babo_tester', channel: s3_body['channel_name']) 
  res = notifier.ping("『#{bl_body['project']['name']}』で変化がありました") 
  return res 
end 


Backlogの設定


Webhookエンドポイントの

前述したパッケージ&デプロイします。

実行されたCloudFormation StackのOutputで表示れているAPI Gatewayのエンドポイントをコピペして、Backlogの『プロジェクト設定>インテグレーション>Webhook』のWebHook URLに登録して全てのイベントを通知するようにします。

BacklogプロジェクトのIDを確認して、{ProjectID番号}.confにして、作成されたS3バケットにアップロードします。(連携の仕組みは昔のポストを参照ください)


動作確認

Backlogでチケットを登録すると、Slackに通知が来ました!やったね!
Screen Shot 2018-12-08 at 18.51.44.png


まとめ

すでにちゃんと動いているものを別言語とか別サービスで作り直すのは無駄かなと思いつつ、新しい技術を覚えるのにはコストが低く、ただHello worldだけに終わらず、もう少し使える知見になりやすいなと考えています。

アドベントカレンダー担当日までに、バボちゃんのロジック部分を移行するまではできなかったので、今回はここまでです。

Fusicでは普段、Tech Blogで分野・大小様々な技術発信をしているので、バボちゃんのロジックを移行して、更に汎用性を持たせるところまでできたらそこでソース公開するポストでもを書こうと目論んでします。

もし使ってみたいなーと思っている方がいたらぜひウォッチしていただければ幸いです。

さて正月休みに気分転換でやるかな。

最後までお読みいただき、ありがとうございました。

それでは良いRuby Lifeを!

コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)