echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Serverless Framework】Serverless Frameworkのダッシュボード上でCI/CDを実現する方法

はじめに

AWSにしろAzureにしろ、クラウドベースの開発でよく「CI/CD」(継続的インティグレーション/継続的デリバリー)が取り入れられていると思います。

そして、Git連携(=gitの特定リポジトリ/ブランチにpushしたら、連動してデプロイが実施される)を導入するケースも多いです。

実際にAWSなら、「AWS CodeBuild」「AWS CodePipeline」のようなCI/CD支援サービスも提供されています。

今回のブログは、そのCI/CD&Git連携を、Serverless Frameworkのダッシュボードのみで実現する方法です。

www.serverless.com

前提

  • Serverless Frameworkの1.48.0以降のバージョンが必要なので、必要に応じて事前にバージョンアップをしてください。(最新版なら問題なし)
  • 公式ページ(https://serverless.com)から、サインアップを済ませておいてください。(右上の「Sign-Up-Free」から)
  • 事前にGit連携対象のGitHubリポジトリ&ブランチを作成しておいてください。(ブランチは「master」でもOK)

初期設定

初回ログイン時、まずは作成するアプリの設定を行います。
下記3種類から選びます。(今回は「Serverless Framework Existing Project」前提で進めます)

項目 説明 備考
Node.js Rest API Node.jsのRest APIプロジェクトの新規作成
Python Rest API PythonRest APIプロジェクトの新規作成
Serverless Framework Existing Project 既存のServerless Frameworkプロジェクトから選択 新規作成せず、既存のプロジェクトを使用する場合はこちら。(まだAWSにデプロイしていなくてもOK)

f:id:Makky12:20200530195714p:plain

また、初回のみ「GitHub連携」及び「AWSアカウント連携」の設定を行う必要があります。

それぞれ、ボタンをクリックするとGitHub/AWSのログインページが表示されますので、GitHubのレポジトリ連携/AWSサインインを行えば、自動でダッシュボード画面に戻ります。

f:id:Makky12:20200531190750p:plain

アプリを登録する

まず、CI/CD導入対象のアプリを登録する必要があります。 まずダッシュボードにサインインします。

公式ページ右上の「Sign-in」からサインインすると、自分のダッシュボードが表示されます。

※下記CLIコマンドでサインイン画面を開くことができます。(もちろん自分でブラウザを開いても問題なし)

> serverless login

※その他、一度サインインすれば、CLIから下記コマンドでダッシュボードを開くことができます。

> serverless dashboard

ダッシュボードが表示されたら、[apps] - [add app]から、必要項目を設定して、[add app]ボタンをクリックすると、アプリを登録できます。(設定できる項目は以下の通り)

項目 説明 備考
name アプリの名前
profile デプロイに使用するprofile ※1

※1:profileは右上の[アカウント名]-[org settings]-[DEPLOYMENT PROFILES]から設定できます。(細かくは別記事で書きます)
とりあえずは「default」でOK

f:id:Makky12:20200530195849p:plain

その後、登録した[apps]から登録したアプリ名をクリックすると、「Deploy Your Service」項目が表示されますが、まずは先に下記の「デプロイステージの登録」を行います。

デプロイステージの登録

[app settings]-[stage]を選択すると、デプロイ先のステージを登録できます。 デフォルトで「default」が登録されていますが、「dev」「stg」など、明確にステージ名が決まっている場合、ここで事前に登録しておきます。

なお、設定項目は下記2つです。

項目 説明 備考
stage name ステージ名
deployment profile デプロイに使用するプロファイル

f:id:Makky12:20200530200628p:plain

CD/CDの選択

ここまで設定したら、登録した[apps]から登録したアプリ名をクリック後、下記いずれかの方法でCI/CDの設定を行います。

  • 「Deploy Your Service」画面で「Deploy Your GitHub」をクリックする※
  • [ci/cd settings]タブをクリックする

※ちなみに「Deploy From the CLI」を選択すると、CLIから登録したアプリをデプロイする方法が表示されます。

この方法ではGitHubレポジトリ・ブランチ連携デプロイは出来ませんが、登録したアプリのモニタリング&デプロイ履歴の確認などは出来ます。(この辺も別記事で書きます)

※「Deploy From the CLI」の場合、ログイン後、下記コマンドで登録したアプリと表示中のプロジェクトの紐づけを行うことができます。

> serverless --org (アカウント名) --app (登録したアプリ名)
# 例  
> serverless --org makky12 --app my-app  

設定できる項目は、以下の通りです。

項目 説明 備考
connection 連携するGitHubレポジトリ名 GitHubリポジトリを未設定の場合、まずGitHub側でリポジトリ設定が必要(ここで設定可能)
preview deploys テスト&デプロイ対象のブランチ(?) ※1
branch deploys デプロイトリガ元のブランチ&デプロイステージ
region デプロイ先のAWSリージョン ここからは「advanced settings」の項目※2
service デプロイ対象のサービス名 ※3
runtime デプロイ対象のプログラムランタイム 「node.js」「python」など※3
trigger directions デプロイトリガ元のブランチの中で、実際にトリガを起動するフォルダ・ファイル 「Always trigger a deployment」にチェックを入れると、該当ブランチをpushしたらデプロイが実行される。
チェックを入れなかった場合、指定したフォルダ・ファイルの変更がpushされた場合のみデプロイが実行される。
preview deploys プレビュー対象のステージ(?) [app settings]-[stage]で設定したステージ、または「use branch name as stage」を選択。
後者を選んだ場合、ブランチ名がそのままステージ名になる。
また「Destroy stage and resources when branch is deleted」で「ブランチを削除したら、該当ステージ&リソースを削除するかどうか」を設定できる。※1

※1:てか、なんで同じ名前なの...(誤記ではなく、本当に同じ名前)
※2:「advanced settings」とあるけど、リージョンってかなり重要なような...
※3:初期設定で「Serverless Framework(Existing Project)」を選択した場合、設定済&指定不可

f:id:Makky12:20200531191131p:plainf:id:Makky12:20200531191142p:plain

実行

ここまで設定したら、実際に設定したレポジトリ・ブランチに該当プロジェクトをpushしてみます。

設定に問題なければ、pushに連動してデプロイが実行されるはずです。 ※左上の「ci/cd」の隣に青い●マークがつき、クリックすると「実行中」みたいな感じで円がグルグル回っているはずです。

f:id:Makky12:20200531191320p:plain

デプロイ終了すると、結果に応じたアイコンが表示されるので、クリックするとデプロイのレポート・及びログを確認できます。

f:id:Makky12:20200531191451p:plain f:id:Makky12:20200530200424p:plain

あとは実際に動かしてみて、正しくデプロイされている&変更が反映されている事を確認してください。

デプロイ失敗理由(僕の場合)

  • serverless.ymlの変数値の設定に必要なファイルがpushされていなかった
  • node_modulesフォルダがpushされていなかった

しょうもない理由といえばしょうもない理由なんですが、ログを追って原因が分かった時、「あ、そうか、CI/CDだとそうなんだ」と思いました。

というのは、(前者はともかく後者は)今の業務で開発しているプロジェクトだと、node_modulesフォルダ一式はGitの管理対象外にしてたからです。(そうそう自分で直接変更なんてしないし、事前にnpm installすればOKなので)

でもCI/CDだと、当然Gitにpushしないと動かないので(Lambda実行時に「●●のモジュールがない」とエラーが出る)、Gitの管理対象にしないといけないんだ、と分かりました。

とはいえ、ここはうまい方法があるかもしれないので(それこそCodeBuildの「buildspec」ファイルみたいな機能を使うとか)、そのあたりも調べて、別記事で書きたいです。

まとめ

というわけで、Serverless FrameworkのダッシュボードだけでCI/CDを実現する方法でした。

AWS CodeBuildやAWS CodePipeline、及び各種CI/CD用ツールはありますが、Serverless Frameworkのダッシュボードだけでも、簡単なCI/CDを実現できますので、有効に活用していきたいですね。

また、今回記載できなかった

  • プロファイル設定
  • ビルドコマンド設定
  • アプリのモニタリング&各種通知設定

なんかについても、後日別記事で書きたいと思います。

それでは、かなり久々な記事でしたが、今回はこの辺で。

【Serverless Framework】Lambda Destinations機能をServerless Frameworkで実装する

はじめに

昨年11月末にAWSから「Lambda Destinations(Lambda非同期呼び出しの宛先指定)」機能が発表されました。

そしてこの機能について、3月中旬に「Serverless Frameworkが正式サポート」したと公式ブログで発表がありました。
そこで、早速この機能をServerless Frameworkで使ってみました。

serverless.com

前提:「Lambda Destinations(Lambda非同期呼び出しの宛先指定)」って?

概要としては、以下の機能になります。

  • Lambda実行結果に従って、次のアクション(=実行リソース)を指定できる。
    • 正常終了(Success)/異常終了(Failure)で個別に指定可能
  • 指定可能なアクション(=実行リソース)は、以下の通り
    • Simple Queue Service(SQS)
    • Simple Notification Service(SNS)
    • Lambda
    • Event Bridge
  • 非同期実行のLambda(=各種イベントで実行されるLambda)のみ設定可能
    • API Gateway(≒Rest API)経由で実行されるような、同期実行のLambdaでは実行不可(※)

API Gateway経由Lambdaでも、非同期で実施することは可能です。

https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2019/11/25/lambda-destinations1.png

【参考】

実践:Serverless Frameworkで実装してみる

※前提として、前掲のServerless Framework公式ブログに記載の通り、Serverless Frameworkのバージョンを1.66.0以上にしてください。

serverless.ymlの「functions」セクションで、以下のようなLambda関数を定義します。 (今回はSuccess/Failure共に、アクションはLambda関数にしています)

functions:  
  Challange:  
    handler: challange.index  
    events:  
      # API Gatewayは通常は同期実行(Lambda Destination対象外)だけど、テスト用に実装
      - http:  
          path: /challange  
          method: get  
      ## 実際はこんな感じで、何かのイベントで実行するように実装する(元リソースの定義は省略)  
      - schedule: rate(10 minutes)  
    # 「onSuccess」/「onFailure」に成功時/失敗時の定義名を記載する
    destinations:  
      onSuccess: Success  
      onFailure: Failure  
  
  # 成功時のリソースの定義を記載(Lambdaの場合はハンドラを定義)  
  Success:  
    handler: succeeded.index  
  
  # 失敗時のリソースの定義を記載(Lambdaの場合はハンドラを定義)
  Failure:  
    handler: failed.index  

定義としては、以上です。

なお上記関数ですが、下記の内容となります。(シンプルすぎるので内容は省略)

  • challange.index:onSuccess検証時は何もせずにreturnするだけ、onFailure検証時は何かErrorをthrowする。
  • succeeded.index/failed.index:console.log()でログを1行書き出すだけ

invokeでテスト実行する

上記serverless.ymlをデプロイしたら、実際にテストをしてみます。
※デプロイ成功していれば、「Challange」Lambda内に下記の通り「非同期呼び出し」ソースが二つ追加されます。
f:id:Makky12:20200418175220p:plain

デプロイを確認したら「invoke」コマンドでテストを行います。

ここで気を付けるのは、invoke」コマンドを非同期(=イベント)で呼び出すには「--type」オプションに「Event」オプションを指定しないといけない点です。

これを忘れると「--type=RequestResponse」(=同期呼び出し)として実行されるため、Lambda Destinationは正常に機能しません。
(堀家さん、ありがとうございます。)

# test.jsonはchallange.index実行時のevent引数の内容をそのまま指定  
# 「--type Event」を忘れないこと!
> slss invoke --function Challange --path test.json --type Event

正常に実行されると、特に何も表示されずにコマンドが終了しますので、CloudWatch Logを確認します。

CloudWatch Logを確認すると、成功時/エラー発生時共に正しくアクション先Lambdaが実施され、ログが書き出されていることが分かります。

  • 上が成功時、下がエラー発生時
  • 画像は1つにまとめてますが、実際はどちらもchallange.index、及びsucceeded.index(またはfailed.index)のログです。
  • エラー発生時にchallange.indexのログが3つあるのは、2回リトライ処理を実施しているためです。

f:id:Makky12:20200418185809p:plain
f:id:Makky12:20200418185830p:plain

なお、Serverless Framework公式の下記動画でも、Serverless FrameworkによるLambda Destinationsの実装が詳しく紹介されています。 www.youtube.com

まとめ

以上がServerless FrameworkでLambda Destinationsを実装する方法です。

Lambda Destinationsですが、成功時/失敗時の処理を個別に切り出せるので、下記のようなメリットがあると思います。

  • 成功時&エラー時の処理を、同一ソースに書かなくてよい。
  • 上記により、ソースの可視性&保守性の向上につながる
  • リトライ処理を考慮した成功時&エラー時の処理を実装できる

こういう便利な機能は、どんどん活用していきたいですね。

告知

4/24(金)にオンラインで開催される「Infra Study Meetup #1 ~Infrastructure as Code~」に「Infrastructure as Codeを導入して良かった点」という内容でオンライン登壇させて頂きますので、よろしくお願いいたします。(てか4/18(土)時点で、参加者2000人超えそうな勢いなんだけど...)

forkwell.connpass.com

それでは、今回はこの辺で。

【Serverless Framework】メモリ不足でデプロイができない場合の対処方法

概要

Serverless Frameworkでパッケージングやデプロイを行う際に、メモリ不足で失敗することがあります。その場合の対象方法です。

serverless.com

現象

先日、業務で開発したサーバーレスSPAをServerless Frameworkでデプロイしようとしたら、パッケージング時に下記エラーが発生し、デプロイできませんでした。

Ineffective mark-compacts near heap limit Allocation failed - Javascript heap out of memory

アプリやServerless Frameworkの設定を見直したり、「VS Codeが原因かな?」と思い下記記事の内容を色々試しましたが、現象改善しませんでした。

その後、コマンドプロンプトでも同様の現象が発生したので、単純にエラーメッセージの通り、Node.jsのメモリが足りないのが原因と分かりました。
※PCのメモリは十分な容量があったので、そちらはではないと考えました。(てか、そっちならほぼ詰みだった)

対応方法

対応方法ですが、Node.jsのメモリを増やしてあげれば解決します。

下記の通り、環境変数「NODE_OPTIONS」の「max_old_space_size」パラメータにメモリサイズ(MB)を指定することで、Node.jsのメモリを設定可能です。

# Node.jsのメモリサイズを指定する。(MBで)
# 下記だと、メモリサイズを4GBにする。
> set NODE_OPTIONS=--max_old_space_size=4096

なお「node」コマンドならば「max_old_space_size」パラメータの値をコマンド毎に指定できますが、Serverless Frameworkの「serverless(sls/slss)」コマンドではダメでした。(少なくとも僕の環境では)
(余談ですが、「serverless」コマンドのエイリアス、「sls」以外に「slss」でもOKなんですね。最近知りました。これでPowerShellでもエイリアスが使えます。(「Select-String」のエイリアス「sls」とかぶるから、「sls」はPowerShellでは使えない))

  • Node.jsのデフォルトのメモリサイズは1GBです。
  • あまり増やすと、今度は別アプリの動作などに影響がある場合があるので、注意してください。

上記コマンドを実施した後、先述のサーバーレスSPAをServerless Frameworkでデプロイしたところ、問題なくデプロイできました。

なお、実際のNode.jsのメモリサイズを確認する方法については、下記動画で詳しく説明されています。

まとめ

以上、Serverless Frameworkでメモリ不足が発生した場合の対象方法でした。

もちろん、この方法では対処出来ない場合もあるので、その場合はアプリ構成を考えたり(肥大化が原因の可能性がある)、場合によってはPC環境のスペックアップも考える必要があると思います。

ただ、暫定的な対応には使えると思うので、一度試すのもありかと思います。

告知

4/7(火)にオンラインで行われた「VS Code Meetup #4」にて、「開発チーム管理で役立ったVSCode拡張機能」という内容でオンライン登壇をさせて頂きました。
また、その時の登壇資料も公開していますので、よろしければご参照ください。

vscode.connpass.com

www.slideshare.net

その他、4/24(金)に同じくオンラインで開催される「Infra Study Meetup #1 ~Infrastructure as Code~」にも「Infrastructure as Codeを導入して良かった点」という内容でオンライン登壇させて頂くことになりましたので、よろしくお願いいたします。(てか4/12(日)時点で、参加者1400人超えてる...)

forkwell.connpass.com

それでは、今回はこのへんで。

【Serverless】Serverless Meetup Tokyo #16で発表した感想

経緯

先日、サーバーレスの勉強会イベントである「Serverless Meetup Tokyo #16」がオンラインで開催されました。
serverless.connpass.com

そしてこのイベントで、「AWS x Serverless Framework x QuickSightでIoTデータ可視化」という内容で、発表を行いました。(発表資料はslideshareで公開してます。)

www.slideshare.net

本日は、その「Serverless Meetup Tokyo #16」で登壇した感想についての話です。
※「Serverless Meetup Tokyo #16」の配信内容は、下記「Serverless Community(JP)」の公式ページ内のYoutubeリンクから閲覧できます。

【参考】Serverless Community(JP) について

Serverless Community(JP) はサーバーレスアーキテクチャを用いたアプリケーションの構築における経験の共有を目的とした、コミュニティです。

ServerlessConf Tokyo および Serverless Meetups in Japan の実行組織でもあります。
serverless.connpass.com

開催までの話

  • MS Ignite Tour Osakaで、株式会社Serverless Operationsの堀家さん(Serverless Frameworkのすごい人)のセッションに参加したことがきっかけで、「名古屋でサーバーレスイベントやりましょう」という話になる。
  • では、まず東京のイベント(サーバーレス + IoT)に参加することに
  • 今回ではAzureネタでの発表がほとんどらしいので、 AWSで何か発表しませんか?というお話を頂く
  • じゃあAWS関連で発表しよう&東京のサーバーレス勉強会の雰囲気を肌で感じよう!
  • 翌日は仕事休んで、ゆっくり東京観光でも...

と、思ってたんですけどねえ。

オンライン開催に変更

...まあ、新型コロナウィルスの影響もあり、オンライン開催に変更されました。
(最初は現地開催したいよね...という話もありましたが、最終的にはオンラインになりました)

残念ですが、こればっかりは仕方がないですね。

ちなみに当日ですが、

  • 参加者の方にはYouTubeのURLを連絡し、Youtubeで見てもらう
  • 発表者はZoomでチャット・画面共有などを実施し、いろいろ調整する

という内容でした。(ちなみに、発表者の間で、事前に配信テストなんかも行ってました。)

おかげで(?)当日は大きなトラブルにも見舞われず、イベント自体は(手前味噌ながら)大成功だった、と思っています。

実際にオンラインイベントで発表を行った感想&アドバイス

「聞いている人たちの感想&リアクションがない」ので、不安になる。

オンラインではリアルタイムに聞いている人のリアクションが分からないので「うまくいってるのか?」と、いろいろ不安になります。
(PowerPointを開くと複数ウィンドウがPowerPointで埋まるので、YouTubeとかTwitterを見られないんです)

これに関しては、イベント終了後のお話で「Power BI 王子」こと清水 優吾さんが「一例として、以下の方法がある」とおっしゃっており、実際おすすめです。

使用するアプリの操作が分からないので、何をしていいかわからない

これに関しては、本番でやらかしてしまわないよう、配信に使用するアプリ(今回ならZoom)の基本的な使い方は、あらかじめ押さえておくことをお勧めします。

特に下記あたりの機能は、あらかじめ確認しておいた方が良いです。

  • マイクのミュート/ミュート解除
  • カメラのON/OFF
  • チャット機能
  • 画面共有/共有の解除

オンライン会議の作法が分からない

これに関しては、やればわかる(し、多分経験者が教えてくれる)ので、あまり神経質にならなくてもよいとは思います。
少なくとも、下記の点あたりを押させておけばよいと思います。

  • 他の発表者の方が発表中の時は、マイクはミュートにする。
  • ガタガタしそうな場合(席を外す、物を探す...等)、マイクをミュートにする。
  • マイクのミュート/ミュート解除の勘違いに気を付ける。
    • うっかり何気ないつぶやきや、家族の会話がダダもれになったり...(経験者)
  • 事前に家族に伝えておく。
    • うっかり家族が入ってきたり、完全に油断した格好だったりすると...
  • カメラをONにし続ける場合、何気ない動作もオンラインで映ってたりするので、気を付ける

まあでも「物は試し」というか「経験して慣れる」のが一番だと思います。

参加者(=見る&聴く側の方)へのお願い

YouTubeなど、配信アプリのチャット&Twitterへの(ハッシュタグ付き)ツイートを、バンバン書き込んでください。

  • リアクションが見えるだけで、マジで不安が和らぎます。
  • コメント&ツイートが多いと「反響があった」という点で、本当に嬉しいです。

ディスりはNG、でも意見は大歓迎

  • 発表者も、その内容&関連項目について100%把握してる...とは限りません。
  • むしろ、詳しい方の意見・見解を頂けると、すごく参考&勉強になります。

感想

ただでさえ参加者が多いイベントで、初めての登壇(=発表)がオンラインということで、めちゃくちゃ緊張しましたが、「やってよかった!」と本当に思ってます。

ただ聴く側ではなく、発表を行うことで、いろいろ経験になりますし、何よりアプトプットを行うことで「多くのインプットが身につく」という恩恵もあります。(これは本当にそう思いました)

最近では「DevRel(Developer Relations)」という考えもありますが、自分がアプトプットを行うことで、下記のようなメリットもありますので、本当に登壇・発表を行うことはお勧めだと思いました。

  • 「自分」という技術者のアピール&PR(自分を知ってもらう)
  • 自分を知ってもらうことで、多くの方々と知り合う&関係を持つことができる
  • 知見を共有する&得ることができ、様々なソリューションに繋がる...など

機会があれば、また積極的にチャレンジしたいと思っています。

告知

上記「Serverless Meetup」イベントですが、「Serverless Meetup Tokyo #16」内でも私が発表した通り、名古屋でも開催します!

※名古屋では初めての「Serverless Meetup」イベントになります!
内容がそれなりに固まり次第、connpass上で公開します。

※【3/16 更新】公開しました!

serverless.connpass.com

(個人的には)クラウド&サーバーレスは、まだまだ名古屋ではそこまで浸透していない(オンプレがバリバリ現役)と思っているので、これを機会に名古屋でもクラウド&サーバーレス、及びそのコミュニティを広めたいと思っています。

みなさま、機会が合えば、ぜひよろしくお願いします!

  • コロナウィルス(COVID-19)の影響により、connpassページの内容(開催日・開催方法など)が変更になる場合があります。ご了承ください。
  • 変更が発生した場合、connpassページ、およびServerless Community(JP)公式Twitterアカウント(@serverless, ハッシュタグ#serverlessnagoya)より、随時連絡させて頂きます。

それでは、今回はこの辺で。

【PowerApps】PowerAppsアプリで撮影した画像をS3バケットにアップロードする【Serverless Framework】

はじめに

Micorsoft の製品の一つ、Power Platform
中でもPower AppsやPower Automateは、ノーコーディング(またはローコーディング)でかなり本格的なアプリが簡単に作れる、画期的な製品です。

また簡単なアプリならコーディングなしでサクッと作れてしまうので、ITエンジニアにも便利だと思います。

そこでお試しに、(趣味と実用を兼ねて)「カメラで撮影した画像を、AWSのS3バケットに保存する」アプリを作成しました。

なおPower Platformについては、「Power BI 王子」こと清水優吾さんが、素晴らしい資料を公開してますので、ご参照ください。

www.slideshare.net

使うもの

  • Power Apps
    • 上で説明した通りです。
  • カスタムコネクタ
    • Power Platform製品の「コネクタ」(=他のアプリとの連携用の部品)でサポートされていないアプリ・サービスとも連携を可能にする機能。
    • 主に、地先APIを始め、外部Web APIのコールなど
  • AWS Lambda & AWS S3
    • S3にファイルを保存する関係で使います。
  • Serverless Framework
    • もちろん今回も使います。

【注意】カスタムコネクタについて

カスタムコネクタですが、Power Appsで使用する場合「Premium限定機能」となります。(ダイアモンドのアイコンが表示されている)
そのため、試用は可能ですが、一定期間が過ぎると使用不可能(またはサブスクリプションの購入が必要)なので、注意してください。

※あくまで「Power Platformの色々な機能のお試し」として読んでください。

Power Apps側の作成

f:id:Makky12:20200301173858p:plain f:id:Makky12:20200301173941p:plain

Power Apps側の機能ですが、こんな感じになってます。(「Kintai-Upload」は、なんとなく付けただけで、意味はないです)

  • 「撮影」画面で、カメラ機能を使って撮影する。(撮影すると自動で「確認」画面に遷移する)
  • 「確認」画面で「送信する」をクリックすると、S3バケットに撮影した画像をアップロードする。
  • 「やりなおす」をクリックすると、「確認」画面に戻る

で、上記機能の実装方法なんですが、変に説明をするより、マイクロソフトの吉田大貴さんのこの動画を見た方が早いですので、そちらを参照ください。
マジでこの動画を参考にすれば、画像をS3にアップロードする直前までできてしまいます。

www.youtube.com

注意点としては、以下の通りです。

# JSON([画像のバイナリデータ], JSONFormat.IncludeBinaryData) とすると、バイナリデータをBase64形式の文字列に変換できる。    
# [画像のバイナリデータ]には「Image1.Image」など、「画像の表示部品の名前.Image」を指定する。
JSON(Image1.Image, JSONFormat.IncludeBinaryData)

AWS側の設定について

今回、カスタムコネクタ作成にはOpen API(swagger)の定義を利用します。
またOpen API定義はAPI Gatewayからエクスポートできるので、先にAWS側のリソースを作成する必要があります。

というわけで、今回もServerless Frameworkの出番です。

...といっても、Serverless Frameworkは過去記事でもかなり扱ってるので、今回はserverless.ymlの内容だけ。

service: kintai-upload
  
provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: ap-northeast-1
  stackName: ${self:service}
  apiName: api-${self:service}
  profile: default
  
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:ListBucket"
        - "s3:PutObject"
        - "s3:GetObject"
      Resource:
        - arn:aws:s3:::${self:service}/*  
  
  environment:
    TZ: Asia/Tokyo
  
functions:
  CreateImage:
    handler: createImage.index
    events:
      - http:
          path: /create
          method: post
          request:
            parameters:
              body:
                imagebinary: false
    environment:
      BUCKET_NAME: ${self:service}
  
  SendSms:
    handler: sendSms.index
    events:
      - s3:
          bucket: ${self:service}
          event: s3:ObjectCreated:*
    environment:
      BUCKET_NAME: ${self:service}

ざっくりまとめると、下記の流れです。(「SendSms」Lambda自体は、今回の記事では未使用)

  • api-kintai-uploadという名前のAPI Gatewayを作って、
  • そこに「CraeteImage」Lambdaを実行するエンドポイントを作成して、
  • 「kintai-upload」という名前のバケットを作成して、そこに「createObject」トリガを定義して
  • 上記トリガで「SendSms」Lambdaを実行する

なお「resourceセクションへのS3バケットの定義(てか、そもそもresourceセクション自体)がないけど」と思ったかもしれませんが、「function」セクションの「events」で「s3」を定義するだけで、下記設定を自動で行ってくれますので、「resources」セクションの定義は不要です。

  • events.s3.bucket に指定した名前のバケット作成
  • events.s3.event に定義したイベントトリガの作成

カスタムコントロールの作成

で、肝心のカスタムコネクタの作成ですが、これはPower Appのホーム画面から[データ]-[カスタムコネクタ]を選択し、右上の「カスタムコネクタの新規作成」をクリックすればOKです。

なお、作成方法がいくつかありますが、今回は「OpenAPIファイルをインポート」で実行します。 f:id:Makky12:20200307085858p:plain

OpenAPIファイルの取得

OpenAPIファイルですが、API Gatewayの場合、該当のAPIを選択した後、下記手順で取得できます。

  • 左のリストから「ステージ」を選択  
  • 「ステージ」で該当ステージを選択後、「エクスポート」タブを選択
  • 「次の形式を選択」で「Swagger」を選択後、ファイル形式に「JSON」を選択する。
    • OpenAPI3、及びYAML形式はまだサポートしてません。

f:id:Makky12:20200307091646p:plain

ファイルをDLしたら、「カスタムコネクタの作成」の「OpenAPIファイルをインポートします」で、DLしたファイルを選択します。
また「コネクタ名」に、一意の名前を入力します。
f:id:Makky12:20200307091806p:plain

で、あとは「全般」「セキュリティ」「定義」などを設定しますが、ここからは「swaggerエディタ」をONにすれば直接OpenAPIファイルの内容を編集できますので、そちらで編集します。
f:id:Makky12:20200307091905p:plain

今回は。こんな感じで定義しました。

swagger: '2.0'
info: {version: '2020-02-29T10:45:32Z', title: api-kintai-upload-to-cloud}
host: *******.amazonaws.com
basePath: /dev
schemes: []
consumes: []
produces: []
paths:
  /create:
    post:
      responses:
        '200': {description: OK}
        '400': {description: Request Error}
        '500': {description: Internal Error}
        default: {description: Something Error}
      parameters:
      - name: imagebinary
        in: body
        description: kintai file binary
        required: true
        schema: {type: string}
      operationId: Kintai-upload-to-cloud-makky12
      description: Kintai-upload-to-cloud用のカスタムコネクタ
      summary: Kintai-upload-to-cloud用のカスタムコネクタ
definitions: {}
responses: {}
securityDefinitions: {}
security: []
tags: []

ちなみに、テストをする場合「テスト」タブで必要な値を入力すればOKです。
(今回の場合、「imagebinary」にbase64文字列に変換した画像のバイナリデータを入力する) f:id:Makky12:20200304201223p:plain

Power Appsに組み込む

で、作成したカスタムコネクタをPower Appsに組み込む方法ですが、これは左のツリーの[データソース]-[コネクタ]から、先程作成したカスタムコネクタを選択すればOKです。

そして、実際にAPIにリクエストを送るには、ボタンの「onSelect」イベントなどで、下記のように設定すればOKです。

# 「api-kintai-upload-to-cloud」は、カスタムコネクタ名。    
# 「Kintaiuploadtocloudmakky12」は、OpenAPIの「operationId」の値。(「定義」タブの「操作ID」で設定可能)  
# imageBase64は、base64文字列に変換した画像のバイナリデータ
'api-kintai-upload-to-cloud'.Kintaiuploadtocloudmakky12(imageBase64);  

S3バケットに保存する。

ここまでこれば、あとはLambda側の処理です。

Lambda側では、特に特別なことはしていません。
よく見かける、画像をs3バケットに保存する処理だと思います。

強いて言えば、注意するのは下記2点です。

  • JSON()関数でbase64文字列にした場合、先頭と末尾に「"」がついているので、「"」を除去する
  • その他、「data:image/png;base64,」という文字列も付与されるので、これを取り除く
// @ts-check
'use strict';

const AWS = require("aws-sdk");
const S3 = new AWS.S3();
const moment = require("moment");

module.exports.index = async event => {
  
  let statusCode = 200;
  let message = "";
  
  try {
    console.info(`[even] ${JSON.stringify(event)})`);
    console.info(`[event body] ${event.body})`);  
  
    const bodyReplaced = event.body.replace(/\"/g, "").replace("data:image/png;base64,", "");
    console.info(`[event replaced] ${bodyReplaced})`)  
  
    const timeStamp = moment().format("YYYYMMDDHHmmssSSS");
    const params = {
      Bucket: process.env.BUCKET_NAME,
      Body: Buffer.from(bodyReplaced, 'base64'),
      Key: `kintai_${timeStamp}.png`,
      ContentType: "image/png"
    };
  
    const data = await S3.putObject(params).promise();
    // console.info(`[S3 data] ${JSON.stringify(data)}`);
  } catch (error) {
    statusCode = 500;
    message = error.message;
    console.error(`[error Message] ${message}`);
  }
  
  return {
    statusCode: statusCode,
    body: JSON.stringify({ message: message }),
  };
};
  

結果

「送信する」ボタンをクリックすると、S3バケットに画像ファイル(*.png)が保存されます。 f:id:Makky12:20200307193524p:plain

そして、ダウンロードして画像ビューワーで見ると、問題なく表示できます。 f:id:Makky12:20200307193617p:plain

まとめ

以上、カスタムコネクタを使用して、画像をS3バケットにアップロードする方法でした。

カスタムコネクタはプレミアム限定機能ですが、こういう感じで、自作APIなどの操作をローコーディングで行えます。
また、自作APIでなくても、プレミアム限定でなくても使用できるAPIもありますので(Twitterとか)、非常に便利です。

Power AppsやPower Automateなど、Power Platform製品、なかなか便利なので、今後も使っていきたいです。

それではまた。

【Serverless Framework】201個以上のリソースをデプロイする方法

はじめに

Infrastructure as Code(IaC)の代表格の1つである、AWS CloudFormation。
僕も業務でよく使うのですが、CloudFormationで1度にデプロイできるリソースの数(=1スタックのリソース数)は、「最大200個」と決められています。

なので当然、Serverless Frameworkでデプロイする際も、1度にデプロイできるリソースの数は200個までです。

でも、プロダクトが大きくなると「200個では足りない!」というケースもあると思います。
今回は、その場合にスタックをネストして、201個以上のリソースをデプロイする方法(+α)についてです。

CloudFormationの制限とスタックのネスト

「CloudFormationで1度にデプロイできるリソース数は、200個が上限」については、公式ページでも明言されています。
(一部ブログを見る限り、昔は明言されてなかった?)
AWS CloudFormation の制限

200個というのも、決して少なくはないのですが、プロダクト(≒リソース)が肥大化してくると、オーバーするケースもあります。(特にロール・ポリシーやアラートが色々増えると)

その解決方法の一つとして、公式サイトでも紹介されているのが「スタックのネスト」で、下記の考え方になります。

  • serverless.yml内で、serverless.ymlのデプロイ先スタック(=スタックA)とは別のスタック(スタックB)を別途定義する。
  • 200個を超えるリソースは、スタックBに紐づくリソースとして定義する。
  • 実際にserverless.ymlをデプロイすると200個を超えるリソースが作成されるが、スタックBに紐づけたリソースはスタックBで「上限200個」に影響するので、スタックAでの「上限200個」には影響しない。
    • もちろん、スタックBのリソースが200個を超えたらNG。
    • スタックB自身は(serverless.ymlに定義されるので)、スタックAの「上限200個」に影響する。

【参考】ネストされたスタックの操作

そこで今回は、Serverless Frameworkで「スタックのネスト」で対応する方法について扱いたいと思います。
※純粋なCloudFormationテンプレート、及びServerless Frameworkの「resources」セクションで扱う方法については、調査完了次第ブログにします。

Serverless Framework公式ページ

「serverless-plugin-split-stacks」という超便利プラグイン

...と、なんかもったいぶって書き方をしましたが、実はServerless Frameworkでスタックをネストする」のは、めちゃくちゃ簡単です。

やり方ですが、下記2つを実行するだけです。

  • 「serverless-plugin-split-stacks」というプラグインをインストールする(下記)
  • serverless.ymlの「plugins」セクションに「serverless-plugin-split-stacks」を追加する。

github.com

plugins:  
  - serverless-plugin-split-stacks  # この定義を追加するだけ
  - ...

これだけ。本当にこれだけ。CloudFormationテンプレートファイルのような「定義ファイルの分割」も一切不要です。(てか、マジで便利すぎ。このプラグイン)

上記作業さえ行っておけば、「deploy」コマンドなどを実施した際に、「serverless-plugin-split-stacks」がリソース数を計算し、200を超えたら自動で定義ファイルを分割してくれますので、こちらの作業は一切不要です。

なお「serverless-plugin-split-stacks」のカスタマイズについては、下記ブログで詳しくまとめられています。

qiita.com

いや、マジで便利すぎます。このプラグイン

デプロイを分割する

今回は「1回のデプロイで作業を完結させる」ために、スタックのネスト方法を記載しましたが、あえて「デプロイ自体を複数回行い、スタック自体を分ける」のも一つの手です。

確かに「何回もデプロイをやるなんて面倒」と思うかもしれませんが、あえて分けることで、以下のようなメリットもあります。

  • デプロイするリソースを分割できる
    • 1ファイルにまとめてしまうと、やたらデプロイに時間がかかるリソースがある場合(CloudFrontなど)、毎回すごい時間がかかってしまう。
    • 「修正(=デプロイ)頻度」「依存関係」などでファイルを分割し、必要なリソースだけデプロイすることで、そういった無駄なデプロイ時間をなくすことができる。
  • 「出力」の参照ができる
    • CloudFormationのスタックには「出力」という参照用項目がある。
      • Serverless Frameworkでは「resources」セクションの「Outputs」で定義できます。
    • 「出力」に値を設定しておけば、後でデプロイする側は、先にデプロイされた側の「出力」の値を参照できる。(ARNなど)

ただし、やはりデプロイを複数回にすると、どうしても管理の煩わしさの問題は発生すると思うので、そこは検討する必要があると思います。

【参考】: serverlessフレームワークで複数スタックに分割して、リスクを軽減する方法 - Qiita

まとめ

  • CloudFormationでは、1スタック(=1デプロイ)200リソースが上限である。
  • スタックのネストを行えば、1デプロイで200個を超えるリソースもデプロイできる。
  • Serverless Frameworkでは、「serverless-plugin-split-stacks」プラグインを使うと、驚くほど簡単にスタックのネストが行える
  • 場合によっては、あえてデプロイ(=スタック)自体を分けるのも一つの手

「リソース管理の煩雑さからの解放」が「冪等性(※)」と並ぶIaCの大きな魅力なので、「スタックのネスト」「スタックの分割」など、いろいろな方法で、極力リソース管理の煩わしさから解放され、注力すべき作業に注力したいですね。

てかそれがAWSやAzureなど、クラウドの大きな魅力の一つでもありますから。

※冪等性(べきとうせい):「誰が何回作業を行っても、必ず毎回同じ結果になる」性質のこと。毎回同じ品質を提供できるというメリットがある。

告知

私事ですが、今週木曜日の27日に開催される「Serverless Meetup Tokyo #16」イベント(下記)にて、スピーカーをさせて頂きます。(オンライン開催です)

serverless.connpass.com

コロナウィルスの影響で、オンライン開催に変更されましたが、発表自体は予定通り行いますので、よろしくお願いいたします。
※当日は、YouTubeでのライブ配信も行われます。

発表では、下記について触れる予定です

  • AWS
  • Infrastructure as Code(IaC)&Serverless Framework(これがメイン...にしたい)
  • AWS Athena&AWS QuickSight

それでは、今回はこの辺で。

【AWS】DynamoDBのキーとソートとフィルタについて

きっかけ

先日、とあるサーバーレスアプリを作っていた際、DynamoDBについて下記のツイートをしました。

そうしたら、下記の突っ込みリプを頂きました。

プライマリーキーではなくパーティションキーなのです
このキーの値によってデータが配置されるノードが決まります。同一ノード内ではある程度検索ができる(レンジキーやローカルインデックスではソートや絞り込みができる)わけですねー
ホットキーがタブーなのも特定のノードに処理が偏るからですね

というか確かに、DynamoDBのキーの扱いについて、認識がイマイチあいまいだなあ...と思う部分があったので、この機会にちょっと調べてみました、

主要なキー群(プライマリインデックス)

DynamoDBの主要なキーには、下記の3種類があります。

  • パーティションキー
    • DynamoDBのデータをどのパーティションに配置するかを決定するキー。
    • キーの形式は「ハッシュ型(HASH)」
    • 使用可能な検索は「完全一致」のみ
    • パーティションキーは必須
  • ソートキー
    • パーティションキーが同一の複数データについて、ソートや範囲検索を行うためのキー
    • キーの形式は「レンジ型(RANGE)」
    • 使用可能な検索は「完全一致」「前方一致(BeginWith)」及び「範囲検索」(値の大小など)
    • ソートキーは任意(なくてもよい)
    • ソートキーだけでの検索は不可(必ずパーティションキーとセット)
      • これが結構面倒だったりする
  • プライマリキー

※DynamoDBの「パーティション」の概念、及び「なぜパーティションキーが"完全一致"固定なのか」などについては、下記公式ページが参考になります。

パーティションとデータ分散(AWS公式ページ)】 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html

設計するときに注意すること

上記「パーティションキー」と「ソートキー」を踏まえ、DynamoDBテーブルを設計する際は、下記の点に注意する必要があります。

  • パーティションキー」には、テーブル内の情報の主属性を一意に特定できる値を設定する。
  • 「ソートキー」には、テーブル内の同一主属性のデータについて、並び替え、または範囲指定するような値を指定する

※例:会員制Webサイトのアクセスログを記録するテーブルの場合

てか、DynamoDBはパーティションキーの設計が本当に肝になるので、開発時は慎重に検討する必要があります。

その他のキー群(セカンダリインデックス)

プライマリインデックス(主に「パーティションキー」と「ソートキー」)は上記の通りですが、場合によっては下記のようなケースが出てきます。

  • パーティションキー&ソートキー以外の組み合わせでも検索をかけたい
  • ソートキー1個ではソート条件が足りない

※例えば、先述の「会員制Webサイトのアクセスログを記録するテーブル」の場合、下記のケースが出てくると思います。

  • ある会員がアクセスしたサイト内ページ(「購買履歴」「会員情報閲覧」など)で検索をかけたい
  • アクセスしたデバイスでも並び替えを行いたい

そんな「プライマリインデックスだけじゃ足りない」時に必要になるのが「セカンダリインデックス」です。

セカンダリインデックスには下記の2種類があり、どちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。

そしてどちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。

では何が違うかというと、以下の点が異なります。

  • グローバルセカンダリインデックス
  • ローカルセカンダリインデックス
    • プライマリとは別で作成できる、パーティションキーとソートキーのペア
      • ソートキーは必須(というか、ソートキーを増やすのが目的)
    • 「プライマリと異なるのはソートキーのみ」という点(=パーティションキーは同じ)
    • ローカルセカンダリインデックスを作成することで、プライマリのソートキーとは別の項目でのソートも可能

具体的な例

例えば下記「GameScores」テーブルがあったとして、この構成だと例えば「各ゲーム別のハイスコアラーの確認(昔のゲームでデモプレイ後に出てくるアレ)」ができません。
パーティションキーは「UserId」、ソートキーは「GameTitle」
f:id:Makky12:20200214202943p:plain

その場合、下記構成のグローバルセカンダリインデックスを作成すれば、それが実現できます。

f:id:Makky12:20200214203012p:plain

また「あるプレーヤーのハイスコアを出したプレイ履歴」を閲覧したい場合は、下記構成のローカルセカンダリインデックスを作成すればOKです。

【参考】
※どちらもAWS公式サイト(上記「GameScores」テーブル画像も、下記サイトのものです)

セカンダリインデックスを作成する際の注意

  • 処理速度の低下
    • セカンダリインデックスを作成すると、プライマリ以外にもセカンダリにも処理を行うため、スループットが低下する(特に書き込み)。
    • RDBで言えば「別途コピーテーブルを作成し、そちらにも各種処理を行う」イメージに近い
    • ストレージ容量も追加で必要になる
  • 個数の制限
    • セカンダリインデックスは、20個までという制限がある。(さすがに20個作成することはなかなかないけど)
    • 大量にセカンダリインデックスが必要な場合は、そもそもテーブル設計を見直す必要がある。(RDBでいう「正規化」)

query()メソッドとsort()メソッド

DynamoDBで複数データの取得によく使われるのが、以下の2つのメソッドです。

  • query() メソッド
  • scan() メソッド

どちらも「DynamoDBからデータを取り出す」点は同じですが、処理内容に下記の違いがあります。

  • query():「KeyConditionExpression」の条件でデータをフィルタリング→条件に合致したデータを取り出す→返却
  • scan():全データを取り出す→「FilterExpression」の条件でデータをフィルタリング→条件に合致したデータを返却

つまり、scan()はフィルタリングの有無に関わらずまず全データを取り出すため、下記のことが言えます。(もちろんデータ量次第ですが)

  • query()メソッドより、処理に時間がかかる
  • query()メソッドより、課金額が多くなる
    • DynamoDBの読み込み時の課金対象は「読み込まれたデータ(上で言う「取り出されたデータ」)」なので

なので、極力下記の方針にした方が良いです。
※公式サイトや各種ブログにも書かれている通りです。

  • DynamoDBからのデータの取り出しは、極力query()メソッドを使う
    • そのために、インデックス(プライマリ/セカンダリ)の構成よく考える
    • セカンダリインデックスが多量に必要になる場合、まずテーブル設計を見直す
  • 決め打ちで1データのみ取得する場合、getItem()メソッドを使う
  • scan()メソッドは、全データの取得が必須な場合のみ使う
    • インデックス構成では賄いきれない
    • 皇族処理の関係上、全データの取得が必須な場合... など

まとめ

  • プライマリキー(パーティションキーとソートキー)の構成を、よく検討する
    • DynamoDBは、本当にこれが重要
  • 場合によってはセカンダリキーの使用も検討する
    • うまく使えば、データの扱いが便利になる。(ただし多用は避ける)
    • セカンダリキーが多量に必要な場合、まずテーブル設計を見直す
  • データ取得は極力query()やgetItem()メソッドで行う。scan()を多用しない。

と言ったとこでしょうか。

てか、これ書きながら自分でも「業務で使ってるあのテーブル、設計ガバガバじゃねえか!」とか思い返してたりするんですけどね...

というか、(DynamoDBに限らず)時間をおいて見返すと「設計がむちゃくちゃ」とか「1から設計し直したい...」と思うものが、本当に多いです。
まだまだスキルアップしないとですね。

告知

私事ですが、下記「Serverless Meetup Tokyo #16」イベントにて、スピーカーをさせて頂くことになりました。
※開催日時:2020/2/27(木) 19:00~

serverless.connpass.com

なお当日は

  • スピーカー・登壇者側は人生初
  • 大トリ
  • 当日は業務終了後に名古屋から直行予定

と、あわただしい感じですが、皆さま、どうぞよろしくお願いします。
※発表では、下記について触れる予定です

  • AWS
  • Infrastructure as Code(IaC)&Serverless Framework
  • Athena&QuickSight

それではまた。