やーまんぶろぐ

気が向いた時にだけ書くブログ

Serverless Web Application Workshop をCLIでやってみた Lab2: Beta Sign-up Mailing List

サーバーレスアーキテクチャ Advent Calendar 2016 の13日目の記事です。

「Serverless Web Application Workshop をCLIでやってみた Lab 1: Static Website Hosting」 の続きになります。
yamano3201.hatenablog.jp

大枠はこちらで紹介されています。
qiita.com

コンソールでの手順はREADMEに記載されています。
github.com

(できるだけ)CLIに置き換えてみたものをメモしていきます。
作業はMacで、リージョンはバージニアを想定して書いてます。

ワークショップは全部で4つあります。

  • Lab 1: Static Website Hosting
  • Lab 2: Beta Sign-up Mailing List
  • Lab 3: Administrative Interface
  • Lab 4: Product Update Blog

今回はLab 2: Beta Sign-up Mailing Listについて書いていきます。

やること

ベータサービスにユーザがサインアップするメーリングリストを作成。認証のいらないユーザなので、Cognito identity poolとIAM policyを使って、DynamoDBにメールアドレスが書き込まれ、DyanmoDB StreamsからのLambda連携で確認メールをSESを使って配信する。一般ユーザがサインアップ後、確認メールが自身のメールアドレスに届く事を確認する。

送信元メールアドレス検証

  • XXX@XXXXXX.XXXにはメールが受け取れる自分のアドレスを設定しましょう。
  • 登録した送信元メールアドレスに検証要求メールが届くので、リンクをクリックして検証しましょう
$ export EMAIL_ADDRESS=XXX@XXXXXX.XXX
$ aws ses verify-email-identity --email-address ${EMAIL_ADDRESS}

LambdaとSESにアクセス可能なIAMロールの作成

  • LambdaとSESにアクセスできるIAMロールを作成しておきます。
  • ロールを作成してからポリシーをアタッチします。
  • 後半のLambda作成まで出番はありません。
$ export ASSUME_ROLE=assume_role.json
$ cat << EOF > ${ASSUME_ROLE}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
$ aws iam create-role --role-name LambdaSESRole --assume-role-policy-document file://${ASSUME_ROLE}
$ aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonSESFullAccess --role-name LambdaSESRole
$ aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole --role-name LambdaSESRole

Emailの値を取得するためのAmazon DynamoDBテーブルを作成

  • DynamoDBテーブルを作成します。
  • 後で使うのでTableArnをレスポンスから取得しておきます。
$ aws dynamodb create-table --table-name Wildrydes_Emails --attribute-definitions AttributeName=Email,AttributeType=S --key-schema AttributeName=Email,KeyType=HASH --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 > dynamodb.json

$ export TABLE_ARN=$(cat dynamodb.json | jq -r '.TableDescription.TableArn')

認証のいらないユーザがアクセス可能なAmazon Cognito IDプールの作成

$ aws cognito-identity create-identity-pool --identity-pool-name wildrydes --allow-unauthenticated-identities > cognito.json
export COGNITO=$(cat cognito.json | jq -r '.IdentityPoolId')

認証のいらないユーザ用のIAMロールの作成とポリシーの付与

  • 手順通りコンソールからだと自動で作成されてセットされるので圧倒的にラクできます。無理やりCLIからIAMロール作成してみました。
  • Unauth Role(認証のいらないユーザ用のロール)につけるポリシーで、DynamoDBにPUTアクションを許可する設定を追加します。
  • READMEの手順で作るとAuth Roleにのみつくので注意しましょう。ここでの手順はUnauth Roleのポリシーも変更する手順としているので順番にコピペすれば大丈夫です。
  • 直接は使わないけどAuth_Roleも作成しておきます。おそらく作成してアタッチしないとダメな気がする。
$ export COGNITO_AUTH_ROLE=cognito_auth_role.json
$ cat << EOF > ${COGNITO_AUTH_ROLE}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "${COGNITO}"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}
EOF

$ aws iam create-role --role-name Cognito_wildrydesAuth_Role --assume-role-policy-document file://${COGNITO_AUTH_ROLE}
$ export COGNITO_POLICY=cognito_policy.json
$ cat << EOF > ${COGNITO_POLICY}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "dynamodb:PutItem",
      "Resource": "${TABLE_ARN}"
    }
  ]
}
EOF
$ aws iam put-role-policy --role-name Cognito_wildrydesAuth_Role --policy-name oneClick_Cognito_wildrydesAuth_Role --policy-document file://${COGNITO_POLICY}
  • 認証のいらないユーザ用のIAMロールの作成とポリシーの付与
$ export COGNITO_UNAUTH_ROLE=cognito_unauth_role.json
$ cat << EOF > ${COGNITO_UNAUTH_ROLE}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "${COGNITO}"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "unauthenticated"
        }
      }
    }
  ]
}
EOF
$ aws iam create-role --role-name Cognito_wildrydesUnauth_Role --assume-role-policy-document file://${COGNITO_UNAUTH_ROLE}
$ aws iam put-role-policy --role-name Cognito_wildrydesUnauth_Role --policy-name oneClick_Cognito_wildrydesUnauth_Role --policy-document file://${COGNITO_POLICY}

作成したAWS Cognito IDプールに作成したロールをセット

  • けっきょくCLIからAuthenticated role, Unauthenticated roleのアタッチがわかりませんでした。。
  • コンソールからアタッチしてください。。

ワークショップ用に用意されたlab2のファイルをS3にsync

  • アプリコードは用意されたものを使います。
  • regionとIDプールを今回のものに書き換えてからsyncします。
$ sed -i -e "s/region: \'\'/region: \'us-east-1\'/" lab2/scripts/config.js
$ sed -i -e "s/identityPoolId: \'\'/identityPoolId: \'${COGNITO}\'/" lab2/scripts/config.js
$ aws s3 sync lab2/ s3://${BUCKET_NAME}

CloudFront経由でアクセスしてSIGN UP

  • Lab1で作成したCloudFront経由でアクセスしましょう。
  • 画面の下のほうにあるSIGN UP画面からSIGN UPしましょう。
$ open https://$(cat cloudfront.json | jq -r '.Distribution.DomainName')

f:id:yamano3201:20161213004224p:plain

  • 最初に検証したメールアドレスを入力、エンターして、[SIGN UP]の文字が[THANK YOU]に変化することを確認しましょう。
  • これでDynamoDBにPUTされたことを確認しましょう。
  • PCのSafari, Chromeの場合はSUBMITボタンが押せなかったのでenterを押しましょう。
  • iPhoneSafariの場合はSUBMITボタンを押せました。

SESを使ってメールを送信するLambdaを作成

  • 最初に作ったLambdaSESRoleのARNを使います。
  • ロジックはREADMEに書かれているコードをコピーします。変更後のDynamoDBテーブルの値宛にメールを送信するコードになっています。
  • 環境変数で最初に検証したメールアドレスを渡しています。
  • コードはzip化して渡します。
$ export LAMBDA_SES_ROLE=$(aws iam get-role --role-name LambdaSESRole | jq -r  .'Role.Arn')
$ cat << EOF > index.js
var AWS = require('aws-sdk');
var ses = new AWS.SES({apiVersion: '2010-12-01'});

exports.handler = (event, context, callback) => {
  event.Records.forEach((record) => {
    var params = {
      Source: process.env.EMAIL_ADDRESS,
      Destination: {
        ToAddresses: [record.dynamodb.NewImage.Email.S]
      },
      Message: {
        Body: {
          Html: {
            Data: '<html><h1>Thank you!</h1><p>Wild Rydes is coming soon! Stay tuned for more info about unicorns near you!</p></html>'
          },
          Text: {
            Data: 'Wild Rydes is coming soon! Stay tuned for more info about unicorns near you!'
          }
        },
        Subject: {
          Data: 'Wild Rydes Limited Private Beta Confirmation'
        }
      }
    };

    ses.sendEmail(params, (err, data) => {
      if (err) context.fail(err);
      else     context.succeed(null);
    });
  });
};
EOF
$ export EMAIL_FUNCTION=email_function
$ zip -r ${EMAIL_FUNCTION}.zip index.js
$ aws lambda create-function --region us-east-1 --function-name ConfirmationEmail --role ${LAMBDA_SES_ROLE} --runtime nodejs4.3 --handler index.handler --zip-file fileb://${EMAIL_FUNCTION}.zip --environment Variables={EMAIL_ADDRESS=${EMAIL_ADDRESS}}

DynamoDBのトリガーを作成

  • Stream ARNを取得して使用します。
  • Lambdaコマンドからストリームをイベントソースとして設定します。
  • イベントソースにはAmazon KinesisストリームかAmazon DynamoDBストリームしか指定できません。
$ export STREAM_ARN=$(aws dynamodbstreams list-streams | jq -r '.Streams | map(select(.TableName == "Wildrydes_Emails"))' | jq -r '.[].StreamArn')
$ aws lambda create-event-source-mapping --event-source-arn ${STREAM_ARN} --function-name ConfirmationEmail  --enabled --batch-size 1 --starting-position LATEST

電子メールが送信されることを確認

  • 再度CloudFront経由でアクセスしてSIGN UPしましょう。
  • 手順の例では最初に検証したアドレスに+1の文字列を付与して、XXX+1@XXXXXX.XXX と入力してenterを押しています。
  • SIGN UPしたアドレス宛に、最初に検証したメールから送信されていたら完成です。

最後に

毎回思いますけどLambdaのデバッグが大変ですね。Lambda開発のベストプラクティスも調べて実践してみたいです。

残りもやっていきたいと思います。