hiroaki's blog

技術系を中心に気になったこととかいろいろと。

AWS でサーバレスアーキテクチャを触ってみた感想

今年度になって業務とは関係ないところでサービスを試しに作る機会があったので、前々から気になっていたサーバレスアーキテクチャで構成してみた。 構築する上でのハマりポイントとか躓いた点がいくつかあったので、忘れないようにするためにも残しておこうと思う。

使用したサービス

一般的なサーバレスアーキテクチャ構成と同じ構成。

という組み合わせで構築してみた。

IAM ユーザーの作成

サービスを構築していく前に最初に IAM ユーザーを作成する必要がある。 作成したユーザーに対して使用するサービスのアクセス権を追加していく。
ルート権限のアカウントでも利用は可能だが、セキュリティ面や運用面を考慮するとルート以外の権限のユーザーを作成して対応することをオススメする。

IAM ユーザーの作成とアクセス権限の設定

今回のサーバレスアーキテクチャを操作するユーザーを作成する。そのユーザーに対して使用するサービスのアクセス権限を追加する。

  • IAM を開き左メニューからユーザーをクリック
  • 「ユーザーを追加」ボタンからユーザを作成
  • 作成したユーザーを選択し「アクセス権限の追加」ボタンから権限を追加
  • 「既存のポリシーを直接アタッチ」を選択し、リストの中から下記の権限を追加していく
    • AmazonDynamoDBFullAccess
    • AmazonAPIGatewayAdministrator
    • AWSLambdaFullAccess

f:id:hiro14aki:20171029013144p:plain

これで作成した IAM ユーザーにアクセス権限が追加された。

アクセスキー情報

次に作成した IAM ユーザーのアクセスキーを作成する。

  • 作成したユーザーをクリックし「認証情報」タブをクリック
  • 「アクセスキーの作成」ボタンをクリック

f:id:hiro14aki:20171029013215p:plain

アクセスキーが生成されると同時にシークレットアクセスキーも作成される。 シークレットアクセスキーはこのタイミングしかダウンロードできないようなので要注意。

DynamoDB

ここからようやくサーバレスアーキテクチャの環境を構築していく。 今回は単純に ID と name とステータスをもたせるだけの簡単なテーブル構成とした。

テーブルの作成

  • DynamoDB のコンソールを開き「テーブルの作成」をクリック
  • 左のメニューから「テーブル」を選択肢、作成されたテーブルをクリックする
  • 「項目」タブを開いて「項目の作成」からデータベースの情報を追加

編集モーダルが開くのでここから項目とデータを追加していく。

f:id:hiro14aki:20171029013320p:plain

デフォルトは Tree ビューでの項目追加だが、Text ビューモードに切り替えるとオブジェクトのフォーマットで記述できるので階層構造やデータタイプが理解しやすいかもしれない。

f:id:hiro14aki:20171029013326p:plain

DynamoDB の予約語

実装してから気づいたのだが、DynamoDB には予約語が存在している。
DynamoDB の予約語

これと同じ単語をテーブルの属性名に設定していると Query を発行するときにエラーとなる。後述するが、DynamoDB のアクセス時にステータス(status)を変更する際は下記のように予約語を置換してあげる必要がある。

Key : {
  "id" : event['body-json'].item.key
},
UpdateExpression: 'SET #st = :val',
ExpressionAttributeNames: {
    '#st': 'status'
},

Lambda Function

API Gateway からリクエストを受け付け、DynamoDB を起動するための Lambda Function を作成する。

ロールの作成

まずは Lambda から DynamoDB へアクセスするためのロールを作成する。Lambda からログを残す必要があるため、CloudWatch のログ権限も追加しておく必要がある。

f:id:hiro14aki:20171029013743p:plain

  • IAM の左のメニューから「ロール」を選択
  • ロールの作成で「Lambda」を選択
  • ポリシー一覧から「AmazonDynamoDBFullAccess」を選択
  • ロール名を入力してロールを作成

f:id:hiro14aki:20171029013809p:plain

これでロールが作成された。

Function の作成

次に Lambda Function を作成していく。

  • 「関数の作成」ボタンをクリック
  • ベースとなるテンプレートを選択する
    一から作成しても良いがベースとなる構成があったほうが楽なので
    ここでは「hello-world」を選択する。
  • 必要な情報を入力して関数を作成する
    • ロール : 前のステップで作成したロールを選択
    • Lambda Function のコード : 既にローカルなどで実装されていれば入力しても OK だが、とりあえずそのままでも OK。

f:id:hiro14aki:20171029013832p:plain

これでベースとなる Function が作成された。

今回は GET リクエストと POST リクエストで処理を分けるため下記のようなコードを作成した。ランタイムは「Node.js 6.10」を使用している。

'use strict';

let AWS = require('aws-sdk');

AWS.config.update({
  accessKeyId: process.env.IAM_USER_API_KEY,
  secretAccessKey: process.env.IAM_USER_SECRET_KEY,
  region: "us-east-2"
});

let db = new AWS.DynamoDB.DocumentClient({});

exports.handler = (event, context, callback) => {
    let table = {
        TableName : 'test'
    };
    let query = "";
    
    switch(event.context['http-method']){
        case "GET":
            query = table;
            console.log("get method");
            db.scan(query, function(err, data) {
                callback(null, data);
            });
            break;
        case "POST":
            let param = {
              Key : {
                "id" : event['body-json'].item.key
              },
              UpdateExpression: 'SET #st = :val',
              ExpressionAttributeNames: {
                  '#st': 'status'
              },
              ExpressionAttributeValues: {
                  ":val": event['body-json'].item.val
              },
              ReturnValues:"UPDATED_NEW"
            }
            query = Object.assign({}, table, param);
            console.log("post method");
            db.update(query, function(err, data) {
                callback(null, data);
            });
            break;
    }
}

DynamoDB へのアクセス

IAM ユーザを作成した時に生成した「アクセスキー」と「シークレットアクセスキー」を使用する。直接コード内にキー情報を書くのはセキュリティ的にも良くないので、環境変数化して使用する。

AWS.config.update({
  accessKeyId: process.env.IAM_USER_API_KEY,
  secretAccessKey: process.env.IAM_USER_SECRET_KEY,
  region: "us-east-2"
});

環境変数は Lambda コンソールの「環境変数」メニューから作成できる。

Lambda Function のテスト

Lambda コンソールの上部の「テストイベントの作成」からテストを実行できる。「テストイベントの設定」からテストを作成していくが、サーバレスアーキテクチャのテストであれば「API Gateway AWS Proxy」が一番テストデータに近いのでそれを使用する。
テストの結果は Lambda コンソール上部に表示されるのでそれをベースにデバッグや修正を行っていく。

API Gateway

最後に Lambda Function を呼び出すための API を作成する。

API の作成

下記の手順で API を作成していく。

  • API Gateway のコンソールを開き「API の作成」をクリック
  • API 名と説明を記載して「API の作成」ボタンをクリック
    既に作成済みの API があればそれからクローンも可能。 今回は新規で作成したため「新しい API」を選択

これで API の枠が完成した。作成した API にはまだ何もアクションが設定されていないため下記の手順で追加していく。

  • 左メニューから「リソース」を選択
  • ページ上部の「アクション」タブから「メソッドの作成」をクリックする
  • 作成するアクションを選択してチェックマークをクリックする。ここでは GET メソッドを選択。

f:id:hiro14aki:20171029013428p:plain

すると GET アクションの初期セットアップ画面が表示される。

f:id:hiro14aki:20171029013548p:plain

  • 統合タイプにて「Lambda 関数」を選択
  • 「Lambda のリージョン」を選択して適切なリージョンを選ぶ
  • 上記手順で作成した Lambda Function をテキストボックスに入力して「保存」ボタンをクリック
  • 「Lambda 関数に権限を追加する」というモーダルが出てくるため「OK」ボタンをクリック

f:id:hiro14aki:20171029013608p:plain

内容に問題がなければ API Gateway が作成される。

f:id:hiro14aki:20171029013628p:plain

この状態で Lambda 側のトリガーを確認すると、API Gateway と統合されていることがわかる。

f:id:hiro14aki:20171029015436p:plain

マッピングパラメータ

API 作成後の初期状態だとリクエストしたパラメータは API Gateway から Lambda Function には引き継がれない設定となっている。Lambda 側で値を受け取って処理をさせたい場合は、API Gateway でパラメータをマッピングし、それを Lambda 側で受け取るようにする必要がある。

  • 「統合リクエスト」をクリック
  • 「本文マッピングテンプレート」をクリック
  • マッピングテンプレートの追加」をクリック
  • Content-Type を入力してチェックマークをクリックして作成

テンプレートの生成から「メソッドリクエストのパススルー」を選択すると、すべてのパラメータを受け渡すテンプレートが生成される。ここから使用したいパラメータを探して使用していくと設計がしやすいかもしれない。

API の公開

作成した API を公開する。

  • 「アクション」ボタンから「API のデプロイ」をクリックする
  • API のデプロイモーダルが開くのでデプロイされるステージを選択
    今回は「新しいステージ」を選択して公開する
  • 「デプロイ」ボタンをクリック

これで作成した API が公開された。ステージエディターを開くと API を実行する URL が表示される。

使用量プランの作成

API を使用する上での使用量を設定することができる。システム構成やサービスレベル、利用料に合わせて API のスロットリングやクォータを設定することができる。

  • 左メニューから「使用量プラン」を選択
  • 使用量プランの作成画面にて名前やスロットリング、クォータの設定を入力して「次へ」を選択
  • 関連付けられた API ステージが表示されるので「API ステージの追加」ボタンをクリック
  • 対象の API とステージを紐付ける

API キーの追加

このまま API を公開するとどこからでも誰でも叩ける API となってしまう。公開サービスなら問題ないが、限定公開するような仕組みの場合、誰でも叩けてしまうとセキュリティ的にも利用料金的にもよろしくない。これを防ぐために、API キーを追加してキーを持っているクライアントからしか API を実行できないようにする。

  • 「メソッドリクエスト」をクリック
  • API キーの必要性」の横の編集ボタンをクリックし、パラメータを「true」に変更する
  • 左メニューから「API キー」を選択
  • 「アクション」ボタンから「API キーの作成」を選択
  • API キーの作成画面が表示されるので、名前を入力して「保存」ボタンをクリック
    API キーはカスタムでも問題ないが、ここでは自動生成で作成する
  • 「使用量プランに追加」ボタンをクリックし、作成した使用量プランに紐付ける

作成した API キーは「表示」リンクをクリックすることで表示される。API キーを使用して API を実行するには API キーを x-api-key という HTTP ヘッダーに含めてリクエストを送信する必要がある。

$ curl -H 'x-api-key:xxxxxxxxxx' https://xxxxxxxxxx.execute-api.us-east-2.amazonaws.com/test

作成してみた感想

サーバの構築が不要なのは楽

サーバレスアーキテクチャの利点ではあるが、自分でサーバリソースを用意しなくともこのようにバックエンドのシステムが構築できてしまうのは魅力的である。AWS なのでもちろんだが、サーバレスアーキテクチャの構築作業のすべてが AWS コンソール上でできてしまうのは非常に便利だと感じた。

簡単なサービスやログの取得の仕組みであれば EC2 を用意するよりもこちらの構成で組んだほうが楽かもしれない。

元インフラエンジニアとして気になったこと

ただ、自分もそうなのだが、今まで自前でサーバを構築してきた人にとっては AWS コンソールで全ての作業をすることに少し抵抗感があるかもしれない。当然 AWS でのお作法に則った上で環境を構築していくことになるのだが、これに慣れていないと「あれってどこで確認できるんだっけ?」「ここってどういう仕組みになってるんだっけ?」といった疑問が次から次へとでてきて構築に手間がかかってしまう。

EC2 であれば自分でサーバを構築していったり直接ログを確認したりできるが、サーバレスアーキテクチャの場合、このあたりも含めて AWS コンソールに包括されてしまうため、より一層 AWS に慣れた上で使っていく必要があると感じた。とりあえず触り続けて慣れてくれば問題はないと思うのだが、操作感に慣れるまでは少し時間はかかりそうだと感じた。

テスト環境が欲しい

AWS 上で構築していくため、当然動作テストも AWS 上で行う必要がある。コンソール上からテストを実施する方法も提供はされているものの、画面からの操作なので多少不便なところはあるし、ターミナルのようにログをリアルタイムで表示しながらリクエストを飛ばして確認するというやり方は難しい。

ローカルに同様の環境を作成して問題がなければ AWS へデプロイ、という手法が使えればこのあたりのストレスはある程度解消されるはずだが、現状ではこのような環境は自分で構築していくしかない。

aws-sam-local という Docker を利用してサーバレスアーキテクチャ構成をローカルに構築できるツールが AWS からオフィシャルに提供されてはいるものの、

  • DynamoDB が含まれていない
  • API Gatewayマッピングパラメータの機能がない
  • ネットワーク構成も Docker に依存するため外部リクエストができない (やり方はあるのかもしれないが見つけられなかった)

という状況なので、完全にローカルにクローンできるわけではなさそう。

まとめ

まだ痒いところに手が届かない感はあるが、そのあたりの代替は可能だしサーバレスアーキテクチャでの構成も使用するサービスによっては非常に使いやすい。当然ではあるが、構成ありきで環境構築していくのではなく、サービスの用途や使用する機能によって、自前でサーバーを構築するのかサーバレスアーキテクチャにするかはうまく見極めながら使い分けていくのがよいと思う。