[ハンズオン] 初心者でもAWS SAMでサーバレスアーキテクチャを学ぼう！簡単なWebの仕組みを作ってみよう！

# [ハンズオン] 初心者でもAWS SAMでサーバレスアーキテクチャを学ぼう:bangbang:簡単なWebの仕組みを作ってみよう:bangbang:

AWS Summit2017に行ってきて、サーバレスアーキテクチャについて学んで来ました。
そこで、ハンズオンを通してサーバレスアーキテクチャを伝えて行きたいと思います。
また、今回のハンズオンでは、色々なAWSサービスを利用しているので、初心者でもわかりやすいような形で書いていけたらと思います。

## 用語説明

アイコンのダウンロードは公式サイトから。
https://aws.amazon.com/jp/architecture/icons/

### AWS Lambda

![Compute_AWSLambda.png](https://qiita-image-store.s3.amazonaws.com/0/52154/1b6500f3-21b1-af63-3153-ed3e382645d1.png)

> AWS Lambda を使用すれば、サーバーのプロビジョニングや管理なしでコードを実行できます。課金は実際に使用したコンピューティング時間に対してのみ発生し、コードが実行されていないときには料金も発生しません。Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。コードさえアップロードすれば、高可用性を実現しながらコードを実行およびスケーリングするために必要なことは、すべて Lambda により行われます。コードは、他の AWS サービスから自動的にトリガーするよう設定することも、ウェブやモバイルアプリケーションから直接呼び出すよう設定することもできます。
[AWS Lambdaについて](https://aws.amazon.com/jp/lambda/)

サーバレスアーキテクチャを構成するためには、不可欠なサービスです。
簡単にLambdaの特徴として、下記があります。

- サーバ管理が不要
- 継続的スケーリング
- 秒以下の単位で計測

Lambdaでは、「C#, Java8, Node.js v4.3, Node.js v6.10, Python v2.7, Python v3.6」をサポートしてます。
今回のハンズオンでは、Node.js v6.10を利用して構築していこうと思います。

### AWS S3

![Storage_AmazonS3.png](https://qiita-image-store.s3.amazonaws.com/0/52154/d9163e9e-3720-79cc-4783-9a8bccf1262f.png)

> Amazon Simple Storage Service (Amazon S3) なら、大規模なデータを、それがどのようなフォーマットのデータであっても、簡単かつ実用的に収集し、保存し、分析できます。S3 は、ウェブサイトやモバイルアプリケーション、社内アプリケーション、IoT センサーやデバイスからのデータなど、どこからの、どのような量のデータでも保存と取得が可能なオブジェクトストレージです。99.999999999% の耐久性を実現するよう設計されており、多くのお客様が、それぞれ何十億というオブジェクトとエクサバイト単位のデータを保存しています。メディアの保存と配信の手段として、ビッグデータ分析用の "データレイク" として、データのバックアップ先として、サーバーレスコンピューティングアプリケーションのストレージ層として活用できます。モバイルデバイスで撮影した写真や動画、モバイルデバイスなどのバックアップ、パソコンのバックアップ、自動生成されるログファイル、IoT センサーのストリーム、高解像度画像といったデータを保管しておき、さらにそのデータを機械学習のため、AWS のその他のサービスやサードパーティアプリケーションによる分析、トレンド分析、可視化やその他の処理で利用できるようにするには理想的な場所です。
[AWS S3について](https://aws.amazon.com/jp/lambda/)

S3では様々なデータを大容量ストレージに保存していきます。
簡単なイメージでは、DropBoxが安くて大容量に保存できるイメージです。
※ ただし、課金方法として、データ転送量に対してもかかるので、あまりにも大きいデータをIn/Outしすぎるとデータ転送量が高く付きます

また、S3ではEndpointの作成もできるので、S3だけで簡単なWebサービスを作成可能です。
（HTML、CSS、JavaScriptを配置することができる）

### AWS APIGateway

![ApplicationServices_AmazonAPIGateway.png](https://qiita-image-store.s3.amazonaws.com/0/52154/7c400db5-115d-46ed-c2cd-cef1e7813b49.png)


> 全面的に管理されたサービスである Amazon API Gateway を利用すれば、どのようなスケールであっても、開発者は簡単に API の配布、保守、監視、保護が行えます。Amazon Elastic Compute Cloud (Amazon EC2) で稼働しているアプリケーション、AWS Lambda で実行されているコード、または任意のウェブアプリケーションといった、お客様のバックエンドサービスからのデータ、ビジネスロジック、機能にアクセスするための API を作成します。[AWS APIGatewayについて](https://aws.amazon.com/jp/documentation/apigateway/)

APIを作成するときに必要な、EndpointやAPIに対して送るパラメータの設定やレスポンスの設定を細かく設定することができます。
基本的に、Lambdaに対してパラメータを送って返してきたものを受け取ってクライアントに返すように設定できます。
X-API-Keyの設定もでき、特定のリクエストしか通さないようにすることもできます。

### AWS CloudFormation


![ManagementTools_AWSCloudFormation.png](https://qiita-image-store.s3.amazonaws.com/0/52154/c51ba8d3-dd5a-5e29-acdb-ac08e729b20a.png)

> AWS CloudFormation は Amazon Web Services リソースのモデル化およびセットアップに役立つサービスです。リソース管理に割く時間を減らし、AWS で実行するアプリケーションにより注力できるようになります。使用するすべての AWS リソース (Amazon EC2 インスタンスや Amazon RDS DB インスタンスなど) を記述するテンプレートを作成すれば、AWS CloudFormation がお客さまに代わってこれらのリソースのプロビジョニングや設定を受け持ちます。AWS リソースを個別に作成、設計して、それぞれの依存関係を考える必要はありません。AWS CloudFormation がすべてを処理します。次のシナリオは AWS CloudFormation がどのように役立つかを示します。[AWS CloudFormationについて](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html)

CloudFormationでは、AWSサービスをマネジメントコンソールでポチポチしたものを、コード化（定義ファイル化）して、リソースを作成できます。
ちなみに、うちでは[Terraform](https://www.terraform.io/)を利用してAWSリソースを管理しているので、なかなｋCloudFormationを使う機会がないです。

### AWS DynamoDB

![Database_AmazonDynamoDB.png](https://qiita-image-store.s3.amazonaws.com/0/52154/ac793a66-5654-c63f-8006-1388bd222c18.png)

> Amazon DynamoDB は、整合性があり、10 ミリ秒未満のレイテンシーを必要とする、すべての規模のアプリケーションに対応した、高速かつフレキシブルな NoSQL データベースサービスです。完全マネージド型のクラウドデータベースで、ドキュメントとキー値のストアモデルの両方をサポートしています。その柔軟なデータモデル、信頼性の高いパフォーマンス、およびスループットキャパシティーの自動スケーリングは、モバイル、ウェブ、ゲーム、広告、IoT、その他多くの用途に最適です。[AWS DynamoDBについて](https://aws.amazon.com/jp/dynamodb/)

DynamoDBはNoSQL型のDBなので、高速に操作ができます。
DynamoDBの料金計算は少し複雑なので、興味があれば調べてみて下さい。
基本的には、占領するストレージ料金と読み込み時の料金、書き込み時の料金があります。
特徴としては、下記になります。

- 高速、安定したパフォーマンス
- 高いスケーラビリティ
- 完全マネージド型
- イベントドリブンプログラミング
- FGAC (きめ細かなアクセス制御)
- 柔軟性

### AWS IAM

![SecurityIdentityCompliance_IAM.png](https://qiita-image-store.s3.amazonaws.com/0/52154/4bf315cc-62de-6fbd-84b0-ac8e50ca44a2.png)

> AWS Identity and Access Management (IAM) は、ユーザーに対して AWS へのアクセスを安全に制御するための仕組みです。
認証・認可に基づくアクセス権の管理は、アクセス対象のシステムを安全に運用する上で必要不可欠です。認証によってアクセス元が誰かを確認し、認可によって特定の条件下におけるリソースへのアクセス権限を与えます。[AWS IAMについて](https://aws.amazon.com/jp/iam/)

IAMでは、マネジメントコンソールのログインやAWS CLIでの操作をするために必須となります。
基本的にはRootユーザではログインせずに、IAMユーザでログインすることをおすすめします。
IAMユーザでは、ユーザ毎にアクセス権限を付与できるので、その人のロールによって権限を付与することができます。

### AWS SAM

![General_SAMLtoken.png](https://qiita-image-store.s3.amazonaws.com/0/52154/ed42320f-d49f-e5f6-e162-fb577e1d7e58.png)

> The goal of AWS SAM is to define a standard application model for serverless applications.
This GitHub project is the starting point for AWS SAM, and contains general information, information about the model, and examples of common applications.[AWS SAMについて](https://github.com/awslabs/serverless-application-model)

SAMは、サーバレスアーキテクチャを定義するためのファイルを作成します。
下記でもう少し詳しい話をします。

### AWS CLI(SDK)

![SDKs_AWSCLI.png](https://qiita-image-store.s3.amazonaws.com/0/52154/622841ca-bbd6-d719-546a-9133ac9a1664.png)

> AWS コマンドラインインターフェイス (CLI) は、AWS サービスを管理するための統合ツールです。ダウンロードおよび設定用の単一のツールのみを使用して、コマンドラインから複数の AWS サービスを制御し、スクリプトを使用してこれらを自動化することができます。[AWS CLIについて](https://aws.amazon.com/jp/cli/)

AWSのリソースをコマンドラインから制御することができます。
マネジメントコンソールを触らないので、プログラムやサーバから直接やり取りをしてAWSを制御できます。
うちでも様々なところで使っていまして、例としては、EC2（開発環境）を深夜時間帯だけ自動でシャットダウンする仕組みを入れてます。

## AWS SAMについて（詳細説明）

AWS Serverless Application Model (SAM) 

- サーバーレスアプリケーションの管理フレームワーク
- CloudFormationテンプレートで管理
- Lambda、API Gateway、 DynamoDBのリソースおよび複数のイベントソースをサポート
- Apache 2.0ライセンス


設定可能なリソースタイプ

- AWS::Serverless::Function [Lambda]
- AWS::Serverless::Api [API Gateway]
- AWS::Serverless::SimpleTable [DynamoDB]

設定可能なイベントソースタイプ 

- S3
- SNS
- Kinesis
- DynamoDB
- Api
- Schedule
- CloudWatchEvent
- IoTRule
- AlexaSkill

定義文は基本的にはCloudFormationのものをSAM用にテンプレを切り替えて利用します。

```
Transform: AWS::Serverless-2016-10-31
```

これを入れることで、テンプレをSAM用に切り替えれます。

## AWS SAMを簡単に使ってみよう:bangbang:

### :one: 実行ができるIAMユーザを作ろう

IAMユーザ作成のリンクは[こちら](https://console.aws.amazon.com/iam/home?region=ap-northeast-1#/home)から

ユーザから「ユーザを追加」でユーザを追加します。
今回、必要なのは、「プログラムによるアクセス」を利用して、「アクセスキーID」と「シークレットアクセスキー」が必要なのですが、ブラウザ上で確認もしたいので、「AWS マネジメントコンソールへのアクセス」にもチェックをつけて下さい。

アクセス権限ですが、今回の場合だと「CloudFormation」と「S3」へのアクセスができればOKですが、一旦「AdministratorAccess」の権限を付与しておきます。

最後に確認できたらIAMユーザを作成して、出てくる「アクセスキーID」と「シークレットアクセスキー」をメモしておいて下さい。

:warning:ここで表示された「アクセスキーID」と「シークレットアクセスキー」はGithub等には上げないようにしましょう。コンソールからAWSのサービスを全て触られてしまいます。。:warning:

### :two: AWS CLIをインストールしよう

AWS CLIはpipからインストールします。

:no_entry_sign: pipがない人はこちら

```
curl -O https://bootstrap.pypa.io/get-pip.py
python get-pip.py --user
pip --version
```

pipが入っている人はこちらのコマンドを打って、AWS CLIをインストールしましょう。

```
pip install awscli --upgrade --user
aws --version
```

これでAWS CLIがインストールされたと思います。

### :three: S３バケットを作成しよう

マネジメントコンソールからポチポチで作成はできますが、せっかくなので、AWS CLIからコマンドを叩いて作ってみましょう。

```
aws s3 mb s3://bucket-name
```

ここで、bucket-nameは自分のS3のバケット名を指定します。
ちなみに、bucket-nameは全世界でユニークな名前じゃないと登録できないので、注意です。

他のAWS CLIでのS3コマンド一覧

```
// bucketの新規作成
aws s3 mb s3://bucket-name
// bucketの削除
aws s3 rb s3://bucket-name --force
// bucketの一覧取得
aws s3 ls
// bucketの内のファイル一覧取得
aws s3 ls s3://bucket-name
```

:four: AWS SAMを利用する準備をしよう

まず、AWS SAMはYAML形式で各AWSサービスを触る定義ファイルを作ります。

```
mkdir sam-test
cd sam-test
vim app-spec.yml
```

```app-spec.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: This is sam test and echo Hello World
Resources:
  SamTestHelloWorld:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10
      CodeUri: .
      Description: ''
      MemorySize: 256 
      Timeout: 60
      Environment:
        Variables:
          Test: hoge
          Test2: fuga
```

こちらがSAMを利用するための定義ファイルです。
この定義ファイルでは、Lambdaを作成するものなので、今度はLambdaを作成したらそこにソースを乗せたいので、ソースを書いておきます。

```
vim index.js
```

```index.js
'use strict'

exports.handler = function(event, context, callback) {
  console.log("env1 = " + process.env.Test1);
  console.log("env2 = " + process.env.Test2);  
  callback(null, "Hello World");
}
```

### :five: AWSサービスの作成＆コードをデプロイ

作ったソースコードはZIP化してS3に上げる準備をします。

```
zip app.zip index.js -x "*.DS_Store"
```

定義ファイルとソースコードができたら、次にAWSサービスを定義ファイルから作っていきます。

```
aws cloudformation package --template-file app-spec.yml --output-template-file output-app-spec.yml --s3-bucket <<your bucket>>
```

:warning: your bucketには自分が作成したbucketを指定します。:warning: 

Cloudformationを叩いて定義ファイルからサービスを作っていきます。

次に、作成したLambdaにソースコードをDeployしていきます。

```
aws cloudformation deploy --template-file output-app-spec.yml --stack-name <<cloudformation tag name>> --capabilities CAPABILITY_IAM
```

:warning: cloudformation tag nameはcloudformationのJob名なので、好きな名前でOKです:warning: 

### :six: 確認をしましょう

最後のコマンドまで打ったら、マネジメントコンソールにログインして、下記のサービスを見てみましょう。

- Cloudformation
    - きっと自分でつけたtag nameのJobが動いているはず
- S3
    - きっと自分が名付けたS3バケットがいるはず
- Lambda
    - きっと自分がさっき定義したLambdaがいて、ソースコードが見えるはず

Lambdaを実行してみましょう。
実行すると、きちんと「Hello World」がコンソール上で見えるはずです。

以上がAWS SAMのサンプルとなります。
次に、DynamoDBやAPIGatewayを使って、よりアプリケーションに近いものを作って行こうと思います。

## 簡単なWebサービスをAWS SAMを使って作ってみよう:bangbang:

### 構成

![AWS Icons.png](https://qiita-image-store.s3.amazonaws.com/0/52154/c0148dfd-30db-a648-64d9-4c03f2ba7e95.png)

### 作り方

:warning: AWS CLIやIAM, S3バケットはできていることを前提にしております。:warning:

#### :one: 定義ファイルを作成してみましょう

```app-spec.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Hands on AWS SAM!
Resources:
  # AWS Lambdaに対する記述
  GetFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.get
      Runtime: nodejs6.10
      Policies: AmazonDynamoDBReadOnlyAccess
      MemorySize: 256 
      Timeout: 60
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      # API Gatewayに対する記述
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: get
 
  # AWS Lambdaに対する記述
  PutFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.put
      Runtime: nodejs6.10
      Policies: AmazonDynamoDBFullAccess
      MemorySize: 256 
      Timeout: 60
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      # API Gatewayに対する記述
      Events:
        PutResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: put
 
  # AWS Lambdaに対する記述
  DeleteFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.delete
      Runtime: nodejs6.10
      Policies: AmazonDynamoDBFullAccess
      MemorySize: 256 
      Timeout: 60
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      # API Gatewayに対する記述
      Events:
        DeleteResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: delete
 
  # DynamoDBに対する記述
  Table:
    Type: AWS::Serverless::SimpleTable
```

各LambdaにポリシーやAPI Gatewayの定義が付いているところがミソです。

#### :two: ソースコードを書いてみよう

DynamoDBにアクセスするために各それぞれのモジュールを定義していきます。
また、ソースコードは一つのファイルで複数のLambdaに対応できるようにしております。

```index.js
// もちろんStrictModeで実行
'use strict';
 
// dynamoDBを使う準備
let doc = require('dynamodb-doc');
let dynamo = new doc.DynamoDB();
 
// table名は環境変数から取得する
const tableName = process.env.TABLE_NAME;
 
// 各メソッドで使用するため、レスポンス形式は共通化した
const createResponse = (statusCode, body) => {
  return {
    "statusCode": statusCode,
    "body": body
  }
};

// メソッド一覧がわかりやすく載っている
// http://qiita.com/inouet/items/b2246d2419daf1788f63
 
// Getメソッド
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#getItem-property
exports.get = (event, context, callback) => {
  var params = {
    "TableName": tableName,
    "Key": {
      "id": event.pathParameters.resourceId
    }
  };
  dynamo.getItem(params, (err, data) => {
    var response;
    if (err) {
      response = createResponse(500, err);
    } else {
      response = createResponse(200, data.Item.message);
    }
    callback(null, response);
  });
};
 
// Putメソッド
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#putItem-property
exports.put = (event, context, callback) => {
  var item = {
    "id": event.pathParameters.resourceId,
    "message": event.body
  };

  var params = {
    "TableName": tableName,
    "Item": item
  };
  dynamo.putItem(params, (err, data) => {
    var response;
    if (err) {
      response = createResponse(500, err);
    } else {
      response = createResponse(200, null);
    }
    callback(null, response);
  });
};
 
// Deleteメソッド
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#deleteItem-property
exports.delete = (event, context, callback) => {
  var params = {
    "TableName": tableName,
    "Key": {
      "id": event.pathParameters.resourceId
    }
  };
  dynamo.deleteItem(params, (err, data) => {
    var response;
    if (err) {
      response = createResponse(500, err);
    } else {
      response = createResponse(200, null);
    }
    callback(null, response);
  });
};
```

#### :three: 実行してみよう

ソースコードをまとめる

```
zip app.zip index.js -x "*.DS_Store"
```

定義ファイルを作成する

```
aws cloudformation package --template-file app-spec.yml --output-template-file output-app-spec.yml --s3-bucket <<your bucket>>
```

デプロイする

```
aws cloudformation deploy --template-file output-app-spec.yml --stack-name <<cloudformation tag name>> --capabilities CAPABILITY_IAM
```

#### :four: 確認してみよう

APIGatewayを作成すると、エンドポイントが作成されるので、CURLを叩いてデータの流れを追ってみましょう。

- PUT

```
curl -X PUT https://lcp5zapi.ap-northeast-1.amazonaws.com/Prod/resource/piyo -d 'hoge'
```

- GET

```
curl -H "Content-type: application/json" -X GET https://lcapi.ap-northeast-1.amazonaws.com/Prod/resource/piyo
```

- DELETE

```
curl -H "Content-type: application/json" -X DELETE https://lcp5pi.ap-northeast-1.amazonaws.com/Prod/resource/piyo
```

#### :five: 確認してみよう

下記は上記を実行しながらみたスクショです。

- S3（ソースが眠っている場所）

![スクリーンショット 2017-08-01 15.26.59.png](https://qiita-image-store.s3.amazonaws.com/0/52154/85a93a56-f5c9-64d1-7de4-8fd9b9c9a31b.png)

- CloudFormation（定義により、作成したプロセス一覧）

<img width="1033" alt="スクリーンショット 2017-07-31 20.26.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/52154/4d6a6c9a-6f57-1f1f-8567-94f5d45032ec.png">
<img width="1395" alt="スクリーンショット 2017-07-31 20.25.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/52154/fb218e77-a959-c46d-27df-415a66aed87c.png">

- Lambda（作られたFunction一覧）

<img width="1221" alt="スクリーンショット 2017-07-31 20.26.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/52154/6ae7e9a4-38fd-48ad-6a31-4e887d26f47d.png">

- APIGateway（Lambdaそれぞれの定義）

<img width="1217" alt="スクリーンショット 2017-07-31 20.27.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/52154/15a93fa5-d1d7-9dbc-0956-c15fe5c44370.png">

- DynamoDB（作られたDB）

<img width="936" alt="スクリーンショット 2017-07-31 20.27.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/52154/f83d3a8d-21fb-d959-f2fc-be05aa420ed3.png">

## まとめ

AWS SAMを使えば、簡単にサーバレスアーキテクチャを作成することができ、変更も容易です。
今まではサーバレスでアプリケーションを作成するときは、Lambdaを作成して、ソースを手動で上げてテストを繰り返して、APIGatewayをポチポチで作成して・・・の繰り返しだったので、コマンドで操作かつ一発で全部作成できるところが魅力的です。
また、マネジメントコンソールで作成すると、コード化出来ていないので、どういった設定をして作ったのかが見えずらかったり、作った本人も忘れてしまうといったことをコード化により防止できるのではないかと思います。

是非みなさんもつかってみてください。

## 参考サイト

- AWS SAMについて
    - https://www.slideshare.net/YoshidaShingo/getting-started-with-aws-sam
    - https://github.com/awslabs/serverless-application-model


- AWS DynamoDBについて
    - http://qiita.com/inouet/items/b2246d2419daf1788f63

- AWS CLIについて（Pythonの文字コード問題）
    - http://d.hatena.ne.jp/shu223/20111201/1328334689