AWS Elasticsearch ServiceがVPC対応したので、lambda→ESをVPCで括ってみた

VPC Support for Amazon Elasticsearch Service Domains – Amazon Elasticsearch Service http://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-vpc.html

FireShot_Capture_065_-_Amazon_Elasticsearch__-_https___ap-northeast-1_console_aws_amazon_com_es_home.png

やること

  1. VPCの設定をする
  2. Elasticsearch Serviceを新規作成してVPC設定する
    (既存のドメインをVPC対応にする・VPCを切り替える方法はまだわからない)
  3. lambdaにVPCを設定する
  4. テスト

VPC設定

  1. プライペートとパブリックの2パターンサブネットを作成する
  2. パブリックサブネット内でNATゲートウェイを作成する
  3. NAT,インターネットゲートウェイをルートテーブルに割り当てる
  4. ESに設定するセキュリティグループを設定する

ES設定

  1. 新しいドメインの作成
  2. ネットワーク設定でVPC, サブネット(プライペート), セキュリティグループを設定する
  3. アクセスポリシーをVPC用に設定
    FireShot_Capture_066_-_Amazon_Elasticsearch__-_https___ap-northeast-1_console_aws_amazon_com_es_home.png

lambda設定

  1. ロールにVPCアクセス権限をアタッチする
  2. lambdaをプライペートサブネットに配置する

API Gateway設定

(疎通確認だけなら、lambda関数に繋がればよいかと)

疎通確認

ESはVPC内なので、外のネットワークからはアクセスできない
そのため、Kibana見れない状態…
VPC内にWindows立ち上げるとかEC2入れてcurlで叩いてみるとか

lambda内のログに出力するなりで確認

はまりどころ

lambdaにパプリックネットワークのサブネットを付けてしまっていた為、外に接続できなかった
原因をAPI Gatewayかと思い、メソッドを設定したものの解決せず時間がかかってしまった

もしかして

ESへのアクセスを制限したいだけであれば、lambdaをVPC内に設置してNAT Gateway経由でESにアクセスすればIP許可だけで十分だったのでは…?

続きを読む

API Gateway + AWS Lambda で%26が渡せなかった話

謎のInternal server error

https://example.com/v1/?n=[name]
みたいな感じに、API Gatewayでnパラメータを受けて、lambdaで処理する仕組みを作ったら、nパラメータに”a&z” (URL Encode: “a%26z”)みたいな”&”を含むデータを渡すと謎のInternal server error出てハマった。

{
message: "Internal server error"
}

ClowdWatchLogにはsignatureがどうのこうののエラーが。

  • ClowdWatchLog
Execution failed due to configuration error: 
The request signature we calculated does not match the signature you provided. 
Check your AWS Secret Access Key and signing method. 
Consult the service documentation for details.

原因

サポートに問い合わせたところ、以下の回答が。


The error is because the query string “a%26z” is decoded as “a&z” by CloudFront viz. the URL Encoded form. So, the API Gateway gets the query string as “a&z”. Now, when the invocation URL to lambda is being created it appends the Query String given in Integration Request of that resource. Now, the URL is
similar to “https://lambda..amazonaws.com/2015-03-31/functions//invocations?name=a%26z” but while calculating the signature [1] it treats as invalid because “/invocations?name=a%26z” is not a valid lambda invocation URL. The correct Lambda invocation URL is explained in this doc [2].

Moving forward, if you want to use a%26z to your backend then the correct way is to pass it like “a%2526z” In query string and have a body mapping template.

[1]. http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
[2]. http://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#w2ab1c67c12c49c11


知識のなさと英語力のなさでいまいち原因を理解できていないが(英語とAWS得意な誰か解説してください:pray:)、2重URLエンコードして %2526 にすればエラーにならないということは理解できた。Lambda内で一度URL Decodeしないといけないが。

解決

2重URLエンコードするのと同じようなものだが、結局、データをすべてBASE64でエンコードしてLambdaでDecodeすることにした。

疑問

既に多くの人がここではまっている気がするんだけど、ググっても全然情報ないのはなぜ?

続きを読む

Web ページから、AWS Lambda の関数に引数付きでアクセスする

次の AWS Lambda 関数に、API Geteway 経由でアクセスする方法です。
これは、3つの整数、aa,bb,cc を与えて合計 sum を計算するプログラムです。

sum_up.py
# -*- coding: utf-8 -*-
#
#   sum_up.py
#
#                   Oct/20/2017
#
# --------------------------------------------------------------------
import sys
import json

# --------------------------------------------------------------------
def sum_up_handler(event, context):
    sys.stderr.write("*** sum_up_handler *** start ***\n")
#
    aa = 0
    bb = 0
    cc = 0
#
    sys.stderr.write("Received event: " + json.dumps(event, indent=2) + "\n")
    if ('aa' in event):
        sys.stderr.write("aa = " + event['aa'] + "\n")
        aa = int(event['aa'])
#
    if ('bb' in event):
        sys.stderr.write("bb = " + event['bb'] + "\n")
        bb = int(event['bb'])
#
    if ('cc' in event):
        sys.stderr.write("cc = " + event['cc'] + "\n")
        cc = int(event['cc'])
#
    version = "Oct/19/2017 AM 16:42"
#
    sum = aa + bb + cc
#
    sys.stderr.write("version = " + version + "\n")
    sys.stderr.write("sum = " + str(sum) + "\n")
    sys.stderr.write("*** sum_up_handler *** end ***\n")
#
    rvalue = {}
    rvalue['aa'] = aa
    rvalue['bb'] = bb
    rvalue['cc'] = cc
    rvalue['sum'] = sum
    rvalue['version'] = version
    rvalue['language'] = 'Python'
#
    return rvalue
#
# --------------------------------------------------------------------

1) これを、AWS Lambda の関数にする方法は、

python の関数を AWS Lambda で使用する

2) API Gateway の設定は、

API Gateway から Lamda にアクセスする

3) API Gateway で cors を有効にします。

cors_oct1901.png

4) Web ページとスクリプトです。jQuery を使います。

cors.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />
<script src="jquery-3.2.1.min.js"></script>
<script src="cors.js"></script>
<title>AWS API Gateway</title>
</head>
<body>

<div id="answer">answer</div>


<hr />
<div id="outarea_aa">outarea_aa</div>
<div id="outarea_bb">outarea_bb</div>
<div id="outarea_cc">outarea_cc</div>
<div id="outarea_dd">outarea_dd</div>
<div id="outarea_ee">outarea_ee</div>
<div id="outarea_ff">outarea_ff</div>
<div id="outarea_gg">outarea_gg</div>
<div id="outarea_hh">outarea_hh</div>


<hr />
Version: Oct/19/2017 PM 15:19<br />

</body>
</html>

次のプログラムの rest_api_id は、変更して下さい。

cors.js
// -----------------------------------------------------------------------
//
//  cors.js
//
//                      Oct/20/2017
//
// -----------------------------------------------------------------------
jQuery (function ()
{
    jQuery("#outarea_aa").text ("*** cors.js *** start *** PM 18:28")

    getPost()

    jQuery("#outarea_hh").text ("*** cors.js *** end ***")
})

// -----------------------------------------------------------------------
function getPost()
{
    const rest_api_id="bbc2fbufm6"

    const url="https://" + rest_api_id + ".execute-api.ap-northeast-1.amazonaws.com/test"

    const params = {'aa': '121','bb': '304','cc': '256'}

    const args = JSON.stringify(params)

    jQuery.post(url,args,function(res)
        {
        const sum = res.sum
        var str_out = ""
        str_out += "aa = " + params['aa'] + "<br />"
        str_out += "bb = " + params['bb'] + "<br />"
        str_out += "cc = " + params['cc'] + "<br />"
        str_out += "sum = " + sum + "<br />"
        jQuery("#answer").html (str_out)
        })
}

// -----------------------------------------------------------------------

続きを読む

DynamoDBの予約語一覧を無理やり動的に取得してみた

DynamoDBの予約語を対処するために一覧が欲しくなったのですが、いちいちプログラム内で定義したくなかったので、AWSの予約語一覧ページから一覧を取得するサービスを作りました。

前提

  1. AWSが公開しているDynamoDB予約語一覧のWebページ( http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html )をスクレイピングして予約語一覧を抽出します。
  2. 実装はAWS Lambda (Python3)、Webサービスとして動作させるためにAPI Gatewayを利用します。

結果

https://github.com/kojiisd/dynamodb-reserved-words

DynamoDB予約語一覧ページの確認

今回は以下のページをParseしたいと思います。

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ReservedWords.html

HTMLを見てみると、Parseしたい箇所はcodeタグで囲まれているようで、しかもこのページ内でcodeタグが出現するのは1度だけのようです。

スクリーンショット 2017-10-15 18.03.18.png

これであればすぐにパースできそうです。

HTMLの取得とParse

BeautifulSoupを利用すれば、簡単に実装ができます。BeautifulSoupのインストールは他のページでいくらでも紹介されているので、ここでは省略します。

import sys
import os


sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib'))
from bs4 import BeautifulSoup
import requests

URL = os.environ['TARGET_URL']

def run(event, context):

    response = requests.get(URL)
    soup = BeautifulSoup(response.content, "html.parser")

    result_tmp = soup.find("code")
    if result_tmp == None:
        return "No parsing target"

    result = result_tmp.string

    return result.split('n');

とりあえず申し訳程度にパースできない場合(AWSのページがcodeタグを使わなくなった時)を想定してハンドリングしています。

API Gatewayを定義してサービス化

コードが書けてしまえばAPI Gatewayを定義してサービス化するだけです。

設定は非常に単純なので割愛で。

スクリーンショット 2017-10-15 18.12.11_deco.png

アクセスしてみる

実際にアクセスしてみます。

$ curl -X GET https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod
["", "ABORT", "ABSOLUTE", "ACTION", "ADD", "AFTER", "AGENT", "AGGREGATE", "ALL", "ALLOCATE", "ALTER", "ANALYZE", "AND", "ANY", "ARCHIVE", "ARE", "ARRAY", "AS", "ASC", "ASCII", "ASENSITIVE", "ASSERTION", "ASYMMETRIC", "AT", "ATOMIC", "ATTACH", "ATTRIBUTE", "AUTH", "AUTHORIZATION", "AUTHORIZE", "AUTO", "AVG", "BACK", "BACKUP", "BASE", "BATCH", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT", "BLOB", "BLOCK", "BOOLEAN", "BOTH", "BREADTH", "BUCKET", "BULK", "BY", "BYTE", "CALL", "CALLED", "CALLING", "CAPACITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", "CHECK", "CLASS", "CLOB", "CLOSE", "CLUSTER", "CLUSTERED", "CLUSTERING", "CLUSTERS", "COALESCE", "COLLATE", "COLLATION", "COLLECTION", "COLUMN", "COLUMNS", "COMBINE", "COMMENT", "COMMIT", "COMPACT", "COMPILE", "COMPRESS", "CONDITION", "CONFLICT", "CONNECT", "CONNECTION", "CONSISTENCY", "CONSISTENT", "CONSTRAINT", "CONSTRAINTS", "CONSTRUCTOR", "CONSUMED", "CONTINUE", "CONVERT", "COPY", "CORRESPONDING", "COUNT", "COUNTER", "CREATE", "CROSS", "CUBE", "CURRENT", "CURSOR", "CYCLE", "DATA", "DATABASE", "DATE", "DATETIME", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DEFINE", "DEFINED", "DEFINITION", "DELETE", "DELIMITED", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", "DETACH", "DETERMINISTIC", "DIAGNOSTICS", "DIRECTORIES", "DISABLE", "DISCONNECT", "DISTINCT", "DISTRIBUTE", "DO", "DOMAIN", "DOUBLE", "DROP", "DUMP", "DURATION", "DYNAMIC", "EACH", "ELEMENT", "ELSE", "ELSEIF", "EMPTY", "ENABLE", "END", "EQUAL", "EQUALS", "ERROR", "ESCAPE", "ESCAPED", "EVAL", "EVALUATE", "EXCEEDED", "EXCEPT", "EXCEPTION", "EXCEPTIONS", "EXCLUSIVE", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXPLAIN", "EXPLODE", "EXPORT", "EXPRESSION", "EXTENDED", "EXTERNAL", "EXTRACT", "FAIL", "FALSE", "FAMILY", "FETCH", "FIELDS", "FILE", "FILTER", "FILTERING", "FINAL", "FINISH", "FIRST", "FIXED", "FLATTERN", "FLOAT", "FOR", "FORCE", "FOREIGN", "FORMAT", "FORWARD", "FOUND", "FREE", "FROM", "FULL", "FUNCTION", "FUNCTIONS", "GENERAL", "GENERATE", "GET", "GLOB", "GLOBAL", "GO", "GOTO", "GRANT", "GREATER", "GROUP", "GROUPING", "HANDLER", "HASH", "HAVE", "HAVING", "HEAP", "HIDDEN", "HOLD", "HOUR", "IDENTIFIED", "IDENTITY", "IF", "IGNORE", "IMMEDIATE", "IMPORT", "IN", "INCLUDING", "INCLUSIVE", "INCREMENT", "INCREMENTAL", "INDEX", "INDEXED", "INDEXES", "INDICATOR", "INFINITE", "INITIALLY", "INLINE", "INNER", "INNTER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INSTEAD", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "INVALIDATE", "IS", "ISOLATION", "ITEM", "ITEMS", "ITERATE", "JOIN", "KEY", "KEYS", "LAG", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEAD", "LEADING", "LEAVE", "LEFT", "LENGTH", "LESS", "LEVEL", "LIKE", "LIMIT", "LIMITED", "LINES", "LIST", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATION", "LOCATOR", "LOCK", "LOCKS", "LOG", "LOGED", "LONG", "LOOP", "LOWER", "MAP", "MATCH", "MATERIALIZED", "MAX", "MAXLEN", "MEMBER", "MERGE", "METHOD", "METRICS", "MIN", "MINUS", "MINUTE", "MISSING", "MOD", "MODE", "MODIFIES", "MODIFY", "MODULE", "MONTH", "MULTI", "MULTISET", "NAME", "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NONE", "NOT", "NULL", "NULLIF", "NUMBER", "NUMERIC", "OBJECT", "OF", "OFFLINE", "OFFSET", "OLD", "ON", "ONLINE", "ONLY", "OPAQUE", "OPEN", "OPERATOR", "OPTION", "OR", "ORDER", "ORDINALITY", "OTHER", "OTHERS", "OUT", "OUTER", "OUTPUT", "OVER", "OVERLAPS", "OVERRIDE", "OWNER", "PAD", "PARALLEL", "PARAMETER", "PARAMETERS", "PARTIAL", "PARTITION", "PARTITIONED", "PARTITIONS", "PATH", "PERCENT", "PERCENTILE", "PERMISSION", "PERMISSIONS", "PIPE", "PIPELINED", "PLAN", "POOL", "POSITION", "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVATE", "PRIVILEGES", "PROCEDURE", "PROCESSED", "PROJECT", "PROJECTION", "PROPERTY", "PROVISIONING", "PUBLIC", "PUT", "QUERY", "QUIT", "QUORUM", "RAISE", "RANDOM", "RANGE", "RANK", "RAW", "READ", "READS", "REAL", "REBUILD", "RECORD", "RECURSIVE", "REDUCE", "REF", "REFERENCE", "REFERENCES", "REFERENCING", "REGEXP", "REGION", "REINDEX", "RELATIVE", "RELEASE", "REMAINDER", "RENAME", "REPEAT", "REPLACE", "REQUEST", "RESET", "RESIGNAL", "RESOURCE", "RESPONSE", "RESTORE", "RESTRICT", "RESULT", "RETURN", "RETURNING", "RETURNS", "REVERSE", "REVOKE", "RIGHT", "ROLE", "ROLES", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", "ROWS", "RULE", "RULES", "SAMPLE", "SATISFIES", "SAVE", "SAVEPOINT", "SCAN", "SCHEMA", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SEGMENT", "SEGMENTS", "SELECT", "SELF", "SEMI", "SENSITIVE", "SEPARATE", "SEQUENCE", "SERIALIZABLE", "SESSION", "SET", "SETS", "SHARD", "SHARE", "SHARED", "SHORT", "SHOW", "SIGNAL", "SIMILAR", "SIZE", "SKEWED", "SMALLINT", "SNAPSHOT", "SOME", "SOURCE", "SPACE", "SPACES", "SPARSE", "SPECIFIC", "SPECIFICTYPE", "SPLIT", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATE", "STATIC", "STATUS", "STORAGE", "STORE", "STORED", "STREAM", "STRING", "STRUCT", "STYLE", "SUB", "SUBMULTISET", "SUBPARTITION", "SUBSTRING", "SUBTYPE", "SUM", "SUPER", "SYMMETRIC", "SYNONYM", "SYSTEM", "TABLE", "TABLESAMPLE", "TEMP", "TEMPORARY", "TERMINATED", "TEXT", "THAN", "THEN", "THROUGHPUT", "TIME", "TIMESTAMP", "TIMEZONE", "TINYINT", "TO", "TOKEN", "TOTAL", "TOUCH", "TRAILING", "TRANSACTION", "TRANSFORM", "TRANSLATE", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", "TRUNCATE", "TTL", "TUPLE", "TYPE", "UNDER", "UNDO", "UNION", "UNIQUE", "UNIT", "UNKNOWN", "UNLOGGED", "UNNEST", "UNPROCESSED", "UNSIGNED", "UNTIL", "UPDATE", "UPPER", "URL", "USAGE", "USE", "USER", "USERS", "USING", "UUID", "VACUUM", "VALUE", "VALUED", "VALUES", "VARCHAR", "VARIABLE", "VARIANCE", "VARINT", "VARYING", "VIEW", "VIEWS", "VIRTUAL", "VOID", "WAIT", "WHEN", "WHENEVER", "WHERE", "WHILE", "WINDOW", "WITH", "WITHIN", "WITHOUT", "WORK", "WRAPPED", "WRITE", "YEAR", "ZONE "]

うまくできました。

まとめ

思いつきで作成してみましたが、AWSのページが閉鎖されたりHTML構造が変更されない限り、仕様変更などは気にせずに使えそうです。

あとBeautifulSoup初めて使いましたが、かなり便利。

続きを読む

Lambdaのポイントメモ

概要

Lambdaで開発する時に、「あれどうだっけな」ってなった時のメモです。

Lambda関数の種類

  • 同期呼び出し

    • ストリームベース

      • Kinesis Streams
      • DynamoDB Streams
    • それ以外
      • Cognito
      • Echo
      • Lex
      • API Gateway
  • 非同期呼び出し
    • S3
    • SNS
    • CloudFormation
    • CloudWatchLogs
    • CloudWatchEvents
    • SES
    • CodeCommit
    • Config

イベントソース

ストリームベースのイベントソース

  • 通常のイベントソースはイベントソースがLambda関数を呼び出す。
  • ストリームベースのイベントソースは、Lambdaがストリームに対してポーリングを行ない、変更や新しいイベントの存在を検出したらレコードを取得し、Lambda関数を実行する。

アクセス権限

アイデンティティベースのポリシー

  • IAMユーザー、IAMグループ、IAMロールに対してアタッチするポリシー

リソースベースのポリシー

  • Lambdaに対してアタッチされるポリシー
  • Lambdaにイベントソースをセットアップして、サービスまたはイベントソースに対して、Lambda関数を呼び出すアクセス権限を付与する。
  • ポリシーの付与には、AWS CLIかAWS SDKを用いて指定する。

クロスアカウントアクセス

  • Lambda関数を所有するアカウントとそれを呼び出すアプリケーションやイベントソースのアカウントが別でも呼び出せる。

テスト

  • ユニットテストはローカルで実行、これ以外はクラウド上で実行する。

    • ユニットテストは比較的手元で実行したい事が多いテストだから。
  • Lambda関数をローカルで実行するた為に必要なもの
    • ローカルで呼び出す為のエントリポイント
    • イベント
    • contextオブジェクト

エントリポイント

  • ローカル環境で実行する際の呼び出しをどの様に行うか。
  • 実行関数を作って、if文で制御して普通に実行する。

イベントデータ

contextオブジェクト

  • contextオブジェクトには実行中のLambda関数のランタイム情報が格納されている。
  • ハンドラ内部からcontextオブジェクトの各プロパティならびにいくつかのメソッドにアクセスが可能。
  • 極論、ランタイム情報へのアクセスが不要であれば、ローカル実行時は特にエミュレートしなくとも良い。
  • 言語毎に用意されているプロパティ名やメソッド名は異なる

VPC

  • Lambda関数からVPC内にあるパブリックなIPを持っていないAWSリソースにアクセスする事が可能。

    • VPC内のみの接続にしてあるRDSの接続も可能
    • インターネットからアクセスがサポートされていないElastiCacheの様なサービスでも呼び出し可能

アクセス制御

  • EC2やRDSのLambda制御を行いたい場合は、セキュリティグループで行う事ができる。

レイテンシ

  • VPC内のリソースへのアクセスはレイテンシ増の原因となりやすい為、アクセスは最低限にするべき。

    • ENIの作成に時間がかかる。

ENIのレイテンシ

  • ENI は新規のリクエストや久しぶりのリクエストが発生した場合自動的にLambdaによって作成される。
  • 初期化処理に時間がかかる。(10~30秒)
  • 2回目以降は普通のレイテンシ

RDSとの接続の場合

  1. RDSにLambdaから直接レコードを書き込むのではなく、一旦DynamoDBにレコードを書き込み、その時点でレスポンスしてします。
  2. その後、DynamoDB ストリームとLambdaを利用して、非同期にRDSへとデータを反映する。

環境変数

  • 関数からアクセス可能な環境変数を定義する事が可能。

    • コードの変更なく動的に設定した値を渡せる。
    • 無駄なハードコーディングが不要になる。
  • 環境変数へのアクセスは言語毎に変わる。

環境変数の利用制限

  • 合計サイズが4KB
  • 文字で始める
  • 英数字とアンダースコアのみ
  • AWS Lambdaが予約する特定のキーセットは利用不可

Lambda関数自体が利用する環境変数

  • 予約語のうち、更新可能なものと更新不可のものがある。

環境変数の暗号化

  • キーと値はAWS KMS で自動的に暗号化されて保存される。

    • 独自のサービスキーを利用することも可能。

      • 別途IAMロールに対してポリシーが必要。

エラーハンドリング

  • 処理が異常終了した場合の振る舞いは、イベントソースのタイプと呼び出しタイプによって異なる。

    • 関数の実行がタイムアウト
    • 関数の呼び出しがスロットリング
    • 関数の処理でエラーor例外が発生

ストリームベースのイベントソースの場合

  • Amazon Kinesis StreamsもしくはDynamoDB Streamsがイベントソースの場合
  • レコードの有効期限が切れるまで再試行され続ける。
    • その間、エラーの発生したシャードからの読み取りはブロックされ、そのレコードのバッチの有効期限が切れるか処理が成功するまで、新しいレコードの読み込みは行われない。
    • レコードの処理順序が保証される。

ストリームベース以外のイベントソースの場合

同期呼び出しの場合

  • 呼び出し元にエラーが返される。
  • 呼び出し元はそのエラーを受け取り、必要に応じてリトライの処理を実装する。

非同期呼び出しの場合

  • 2回自動的にリトライされる。
  • 3回処理が失敗した場合、デッドレターキューが構成されていれば、そのイベントはデッドレターキューとして指定されているSQSのキューもしくはSNSのトピックに送信される。
    • これにより、障害発生時のイベントを取りこぼす事がなくなる
  • デッドレターキューが構成されていなければイベントは破棄される。

デッドレターキュー

  • デッドレターキューは、関数を非同期で呼び出す場合において、2回のリトライ、つまり3回の試行にも関わらず処理が正常終了しなかった場合にそのイベントを指定されたSQSもしくはSNSへと送る仕組み。

    • コードのロジックエラー
    • スロットリング
  • Lambdaでデッドレターキューを設定する前に、キューもしくはトピックを作成しておく必要がある。

終わり

今後も仕様が変わる度にしっかりキャッチアップしていきたいですね。

続きを読む

Serverless FrameworkでAWS Lamda関数を作成する

概要

Serverless Frameworkとは、Lambda、API Gateway、DynamoDBなどを作成、管理、デプロイできるツールです。
Frameworkと付いていますが、ツールです。
この記事では、python3でLambda関数を作成します。

環境

  • CentOS7.2
  • serverless 1.23.0
  • node v6.11.3
  • npm 3.10.10
  • OpenSSL 1.0.2k-fips 26 Jan 2017

npmのインストール

以下の記事を参照
npmのインストール手順

Serverless Frameworkのインストール

slsというディレクトリを作成し、そこで作業を行います。

$ mkdir sls
$ cd sls
$ npm init
$ npm install --save serverless

serverlessコマンドのパスを通します

$ npm bin serverless
$ echo 'export PATH="$HOME/sls/node_modules/.bin/:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile

インストール確認

$ serverless -v
1.23.0

aws credential登録

以下のコマンドで、AWSのキーを登録します。

$ serverless config credentials --provider aws --key XXXXXXXXXXXXEXAMPLE --secret XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEXAMPLEKEY
Serverless: Setting up AWS...
Serverless: Saving your AWS profile in "~/.aws/credentials"...
Serverless: Success! Your AWS access keys were stored under the "default" profile.

Lambda関数の作成

以下のコマンドで、Lambda関数を作成します。

$ serverless create -t aws-python3 -p sample-app

Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/home/vagrant/sls/sample-app"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  ___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.23.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"

オプションの説明ですが、
-pは、Lambda関数名のprefixとなります。

また、-tで実装する言語を選びます。
以下のいずれかを選択します。

  • -t

    • aws-nodejs
    • aws-python
    • aws-python3
    • aws-java-maven
    • aws-java-gradle
    • aws-scala-sbt
    • aws-csharp
    • openwhisk-nodejs

すると、以下のファイルが生成されます。
handler.pyは、Lambda関数のテンプレート、serverless.ymlは設定ファイルになります。

$ ll sample-app/
total 8
-rw-rw-r--. 1 vagrant vagrant  497 Oct 10 04:42 handler.py
-rw-rw-r--. 1 vagrant vagrant 2758 Oct 10 04:42 serverless.yml

関数の情報を設定

serverless.ymlに関数の設定情報が書かれているので、環境合わせて編集します。

serverless.yml
provider:
  name: aws
  runtime: python3.6

# you can overwrite defaults here
- #  stage: dev
-#  region: us-east-1
+  stage: production
+  region: ap-northeast-1

# *snip*

# 関数名などの定義
functions:
-  hello:
-    handler: handler.hello
+  sample-func:
+    handler: handler.main

serverless.ymlで、関数のメソッド名を変更したので、handler.pyも以下のように編集します。

handler.py
-def hello(event, context):
+def main(event, context):

deploy

以下のコマンドでデプロイをします。

$ cd sample-app
$ serverless deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (389 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...
Service Information
service: sample-app
stage: production
region: ap-northeast-1
stack: sample-app-production
api keys:
  None
endpoints:
  None
functions:
  sample-func: sample-app-production-sample-func

すると、sample-app-production-sample-funcという名前のLambda関数が作成されます。

Lambda関数の実行

deployが出来たら、以下のコマンドで、Lambda関数を実行することができます。

$ serverless invoke -f sample-func

{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {}}"
}

パラメータを付ける場合は、-dオプションで指定します。
戻り値にinputの項目が増えているのが確認できます。

$ serverless invoke -f sample-func -d '{"key":"value"}'

{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {"key": "value"}}"
}

以下のようなjsonファイルを作成して、パラメータを渡すこともできます。

event.json
{
  "key" : "value"
}

-pオプションでjsonファイルを指定して実行

$ serverless invoke -f sample-func -p event.json
{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {"key": "value"}}"
}

Lambda関数の削除

関数の削除は以下のコマンドで行います。
関連するS3のファイルもすべて消してくれます。
AWS Console上で手動でLambda関数を削除すると、S3のファイルなどが残ってしまいます。

関数のあるディレクトリに移動でして実行します。

$ cd sample-function
$ serverless remove -v

-sオプションで、特定のstageのみを削除することもできます。

$ serverless remove -v -s dev

その他

以下のモジュールで、擬似的にローカルでApi Gateway、DynamoDBを使うことができます。

$ npm install aws-sdk
# 擬似的Api Gateway
$ npm install --save-dev serverless-offline
# 擬似的DynamoDB
$ npm install --save-dev serverless-dynamodb-local

参考

続きを読む

API Gatewayのログ出力を有効化する

前置き

Chaliceを使ってlambda関数の開発をしている為、簡単にCloudwatchのログが見れる。
が、lambdaで処理した後のレスポンスが取得出来ていない…

対応方法

IAMロールを新規作成・設定する

  1. IAMのダッシュボードへ移動する
  2. ロールを選択
  3. ロールの作成
  4. AWSサービス:API Gatewayを選択するスクリーンショット 2017-10-10 11.01.46.png

  5. AmazonAPIGatewayPushToCloudWatchLogs を選択する(これしか表示されていない、はず)

  6. ロール名、説明を入力する (ロール名はapiwateway-logs- とかにすればわかりやすそう)

  7. 作成したロールを選択して、ARNを記録するスクリーンショット_2017-10-10_11_06_21.png

API-GatewayにIAMロールを設定する

  1. API Gatewayのダッシュボードへ移動する
  2. 設定を選択
  3. CloudWatchログのロールARNに、上で記録したARNを記入して保存スクリーンショット_2017-10-10_11_06_49.png

ログを記録したいAPIを設定する

  1. APIを選択する
  2. ステージを選択する
  3. 設定タブにCloudWatch設定が表示されているので、チェックを入れて保存するスクリーンショット_2017-10-10_11_08_53.png

Cloudwatchのログを確認する

  1. API-Gateway-Execution-Logs_/ でログが出力されているスクリーンショット_2017-10-10_11_31_14.png

注意

Cloudwatchログの有効期間を設定して、ログ死しないように
ステージ毎に異なるロググループになってしまう(検索で対応は出来る)

結果

API Gatewayのログ出力は出来るようになったが、これだけではまだエラーレスポンスは取得できていない。
lambda関数の設定と、API Gatewayの設定も必要…

続きを読む

いまからはじめるJavaでAWS Lambda(ラムダ) 前編

いまからはじめるJavaでAWS Lambda(ラムダ)

AWS Lambda関数をJava8で記述し、AWS上 Lambda Functionとしてアップロードし、それをJavaクライアントから呼び出す例をステップ・バイ・ステップで紹介します。

想定読者

  • Javaが書けて、はじめてAWS Lambdaをつかう人
  • いままではnode.jsでLambda関数を作っていたが、わけあってJavaでつくってみようとおもう人(=私のような)

記事構成

TL;DR 前編・後編で書きます

  • 【前編】 JavaでLambda関数(クラウド側)と、クライアント(ローカル側)をお手軽に作る←本稿
  • 【後編】 Lambda関数(クラウド側)の同期型、非同期型の呼び出しタイプ(Invocation Type)と、Javaクライアント側の同期、非同期呼び出し、API Gatewayでの公開

AWS Lambda(ラムダ)とは

  • 自前でサーバーを作ったり、管理したりする必要が無く、コードをAWS Lambdaにあげるだけで各種リクエスト(イベント)の処理が可能な素敵なサービスです。

 http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html
 https://aws.amazon.com/jp/lambda/faqs/

  • AlexaのスキルもAWS Lambdaで作成することができます。

JavaとAWS Lambda

  • JavaでAWS Lambdaの処理(Lambda関数)を記述することが可能
  • Javaクライアントから、直接Lambda関数を呼び出すことも可能
  • Javaに限らないが、API Gatewayというサービスと連携させると、Lambda関数にendpointをつくって公開することができ、Web APIのようにGETやPOSTといったHTTP(S)経由でも呼び出すことが可能

目次

以下のステップを1つずつ説明しつつ実施していきます

  1. AWS Lambda関数をJavaでコーディングする
  2. アップロード用のjarファイルを作る
  3. jarファイルをAWS Lambdaに登録して実行可能(呼び出し可能)にする
  4. AWSコンソール上でテストする
  5. ローカルJavaから、AWS Lambda関数を呼び出しする

1.AWS Lambda関数をコーディングする

Java用ライブラリ読み込み

aws-lambda-java-coreライブラリを追加します。mavenを使う場合は、以下の記述を追加します

maven
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-core</artifactId>
    <version>1.1.0</version>
</dependency>

Lambda関数の中身をコーディングする

ここでは、関数を呼び出すと結果が返るリクエストレスポンス型(RequestResponse)のLambda関数をつくります。

今回はaws-lambda-java-coreライブラリが用意しているRequestHandlerインタフェースを実装して、POJOを入出力できるLambda関数を実装します。

以下は、姓(lastName)と名(firstName)を入力すると、フルネームを出力してくれるLambda関数のサンプルです。

MyLambda.java
package lambda.cloud;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import lambda.cloud.MyLambda.Input;
import lambda.cloud.MyLambda.Output;

public class MyLambda implements RequestHandler<Input, Output> {

  @Override
  public Output handleRequest(Input in, Context context) {

    final Output out = new Output();
    out.in = in;
    out.fullName = in.firstName + "_" + in.lastName;

    return out;
  }

  public static class Input {
    public String firstName;
    public String lastName;
  }

  public static class Output {
    public Input in;
    public String fullName;

  }

}

以下のように、handleRequestメソッドを実装するだけです。引数 Input はリクエスト、戻り値 Output がレスポンスを示します。

 public Output handleRequest(Input in, Context context) {

2. アップロード用のjarファイルを作る

次にLambda関数としてAWS Lambdaで使えるようにするためにはコードをjarファイル(またはzip)にワンパッケージ化してAWS Lambdaにアップロードする必要があります。

このjarファイルは、いまつくったコードの他、依存しているライブラリなどをひとまとめに統合しておく必要があります。

ひとまとめに統合したjarファイル(つまりfat jar)をつくるためにmaven pom.xmlのplugins以下にmaven-shade-pluginを追加しておきます。

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
            <createDependencyReducedPom>false</createDependencyReducedPom>
        </configuration>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
            </execution>
        </executions>
    </plugin>

pom.xml

pom.xml全体は、以下のようになります

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>lambda.cloud</groupId>
    <artifactId>mylambda</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <name>AWS lambda example</name>
    <description>example of AWS lambda
    </description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <createDependencyReducedPom>false</createDependencyReducedPom>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

</project>

ソースコードダウンロード

上記サンプルのフルソースコードは以下にあります
https://github.com/riversun/aws_lambda_example_basic_client.git

Eclipseでcloneする場合
File>Import>Git>Projects from Git>Clone URI
https://github.com/riversun/aws_lambda_example_basic_client.git を指定
import as general projectを選択してインポート
– インポート後に、プロジェクト上を右クリックしたメニューでConfigure>Convert to Maven projectを実行

これでEclipse上でMavenプロジェクトとなるので、以後取り扱いが楽になります。

mavenをつかってアップロード用のjarを作成する

  • (1)コマンドラインでjarを作る場合

pom.xmlのあるディレクトリで以下のコマンドを実行します

mvn packgage
  • (2)Eclipse上でjarを作る場合

 Step 1. 右クリックメニューでRun As>Maven buildを選択する
 

 Step 2. Edit configurationsダイアログで、Goalspackage shade:shade と入力しRun
 

(1)(2)いずれの場合でも、/target以下にすべての依存コード・ライブラリを含んだ mylambda-1.0.0.jarが生成されます。

これがAWS Lambdaアップロード用のjarファイルとなります。

3. jarファイルをアップロードしてAWS Lambdaに登録する

AWSコンソールからLambda関数を登録する手順をみていきます

(1)AWS Lambdaを開く
Lambdaサービスを開きます。
img01.png

(2)新しいLambda関数を作る
まだLambda関数をつくっていない場合は以下のような画面になります。

関数の作成(Create function)をクリックします。

img02.png

(3)Lambda関数を[一から作成]する

以下のように設計図(blue print)一覧から選択する画面が出ますが、一から作成(Author from scratch)を選択します。

img03.png

(4)基本的情報画面で名前、ロールを設定する

ここでは、myFunctionという名前のLambda関数をつくっていきます

  • 名前(Name)は「myFunction
  • ロール(Role)は「テンプレートから新しいロールを作成(Create new role from template)
  • ロール名(Role name)は「myRole

入力できたら、関数の作成(Create function)をクリックします

img04.png

(5) jarファイルをアップロードする

img06.png

STEP 1
まず、画面上部にある、ARNを確認しておきます
画面に表示されている、arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction部分をメモしておきます

ご存知ARN(Amazon Resource Name)はAWS リソースを一意に識別するためのもので、Lambda関数実行時にその特定のために使います。

STEP 2
ランタイム(runtime)からJava8を選択する

STEP 3
ハンドラ(Handler)にlambda.cloud.MyLambdaと入力する
ハンドラ名は、パッケージ名.クラス名::メソッド名 のフォーマットで記述します。
さきほど作ったコードにあわせて、パッケージ名が、lambda.cloud、クラス名がMyLambdalambda.cloud.MyLambdaを入力しています。この例では、メソッド名は省略しても動作します。

STEP 4
アップロード(upload)をクリックしてさきほど作ったmylambda-1.0.0.jarをアップロードします。

はい、ここまでで、さきほど自作したLambda関数がAWS Lambdaに登録されました。

4. AWSコンソール上でテストする

アップロードが終わったら、コンソール上からテストしてみます。

(1)テスト用イベントの準備

Lambda関数は 何らかのイベントをトリガーにして起動する という考え方があり、たとえば、S3バケットにオブジェクトが作成されたというイベントをトリガーとして、Lambda関数を発火させる、という使い方があります。

そこで、Lambda関数を実行するための入力のことをイベントと呼びます。

ここでは、イベントをコンソール上から発火させ、Lambda関数の動作を確認します。

画面上部にある、テストイベントの設定(Configure test events)を選択します。

img07.png

すると以下のように、テストイベントの設定(Configure test events)画面が開きますので、ここでテスト用のイベントを作成します。

image08b.jpg

イベント名(Event name)MyEventとして、その下にあるエディットボックスはLambda関数をリクエストするときの本文です。

さきほどつくったPOJOで入出力するLambda関数は、実際の呼び出しではPOJOが自動的にJSONにマップされ、JSONで入出力されます。

そこで以下のようにJSON形式でイベントの本文を入力します

{
  "firstName": "john",
  "lastName": "doe"
}

入力したら、画面下にある作成を押します。

(2)テスト用イベントの実行

いま作成したMyEventを早速実行します。画面右上のテストを押します。

img09.png

ちなみに、「ハンドラーで指定されたファイル名がデプロイパッケージのファイル名と一致しないため、Lambda 関数 「myFunction」はインラインで編集できません。」というメッセージが表示されても気にしなくてOkです。
Lambdaコンソールでは、Java、C# などのコンパイル済み言語のインラインエディタは提供されていません。

(3)実行結果を確認する

テストを押してしばらくすると、結果画面が表示されます。

成功のときは、実行結果: 成功(Execution result:succeeded)と表示されます。その下の▼詳細(details)を押して展開すると結果の詳細を確認できます。

img10.png

Lambda関数の出力も、さきほどのPOJO Outputクラスが以下のようなJSONに変換されます。

入出力用のPOJO
public static class Input {
    public String firstName;
    public String lastName;
  }

  public static class Output {
    public Input in;
    public String fullName;

  }
実行結果
{
  "in": {
    "firstName": "john",
    "lastName": "doe"
  },
  "fullName": "john_doe"
}

5. ローカルJavaから、AWS Lambda関数を呼び出す

さきほど作ったLambda関数 myFunction をJavaプログラムから呼び出します。

ライブラリを読み込む

JavaからAWS Lambda関数をたたく場合には、aws-java-sdk-lambdaライブラリを追加します。mavenを使う場合は、以下を追加します

maven
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-lambda</artifactId>
    <version>1.11.210</version>
</dependency>

Javaクライアント

コードは以下のようになります。(各種設定値はダミーです)

Javaクライアント
public class ExampleLambdaClient {

  public static void main(String[] args) {
    ExampleLambdaClient client = new ExampleLambdaClient();
    client.invokeLambdaFunction();

  }

  private void invokeLambdaFunction() {

    final String AWS_ACCESS_KEY_ID = "ANMNRR35KPTR7PLB3C7D";
    final String AWS_SECRET_ACCESS_KEY = "UKA6EsKY25LJQBEpUvXyYkj8aWKEDnynEZigVPhz";

    AWSCredentials credentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);

    // ARN
    String functionName = "arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction";

    String inputJSON = "{"firstName":"john","lastName": "doe"}";

    InvokeRequest lmbRequest = new InvokeRequest()
        .withFunctionName(functionName)
        .withPayload(inputJSON);

    lmbRequest.setInvocationType(InvocationType.RequestResponse);

    AWSLambda lambda = AWSLambdaClientBuilder.standard()
        .withRegion(Regions.AP_NORTHEAST_1)
        .withCredentials(new AWSStaticCredentialsProvider(credentials)).build();

    InvokeResult lmbResult = lambda.invoke(lmbRequest);

    String resultJSON = new String(lmbResult.getPayload().array(), Charset.forName("UTF-8"));

    System.out.println(resultJSON);

  }
}

AWSCredentials credentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);
  • アクセスキーID(AWS_ACCESS_KEY_ID)とシークレットアクセスキー(AWS_SECRET_ACCESS_KEY)をつかってcredentialsをつくります。

InvokeRequest lmbRequest = new InvokeRequest()
        .withFunctionName(functionName)
        .withPayload(inputJSON);
  • リクエストを生成します。#withFunctionNameでは関数名を指定します。ARN(arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction)を指定するか、関数名(myFunction)を指定します。ここではARNを指定しました

  • #withPayloadでリクエストの本文(JSON)を指定します。
    ここではJSON文字列 “{“firstName”:”john”,”lastName”: “doe”}”;を指定してます。


lmbRequest.setInvocationType(InvocationType.RequestResponse);
  • 呼び出しタイプ(invocation type)を指定します
    ここでは InvocationType.RequestResponse を指定しています。これでRequestResponse型の呼び出しタイプになります。RequestResponseにしておくと、処理結果を受け取ることができます。

InvokeResult lmbResult = lambda.invoke(lmbRequest);
String resultJSON = new String(lmbResult.getPayload().array(), Charset.forName("UTF-8"));
  • Lambda関数を呼び出して、結果(JSON)を受け取ります。

Javaクライアントのソースコード

クライアント側のフルソースコードは以下においてあります
https://github.com/riversun/aws_lambda_example_basic_client.git

まとめ

  • Javaでも比較的簡単にAWS lambdaの関数を記述できました
  • Javaのクライアントから直接 Lambda関数を呼び出せました

サンプルはEclipse(Oxygen)で作成しましたが、特段プラグインなど必要ありません
(AWS SDK Plugin for Eclipseも不要。あれば、もっと手軽になりますが)

  • 後編では、Lambda関数およびJavaクライアント側の同期/非同期についてや、API Gatewayについて書きます。

おまけ

  • 2009年頃ですがコードだけ書いたら後はおまかせ、課金は使った分だけというサービスの元祖「Google App Engine (GAE)」が登場したときは衝撃をうけました。独特の制約が多かったものの、とてもお世話になりました。

  • 今度はその元祖!?が、Google Cloud Functionsを投入しています。今現在はβで実績はこれからだとおもいますが、今後はそちらも選択肢に入ってきそうです。

続きを読む

CloudFront に SSL 証明書を導入する際のポイントまとめ

CloudFront に SSL を入れる際に気をつける項目のまとめです。

2017年9月時点では、特に S3 との連携で大きな制約があり、将来的には改善されていくのではと思います。現在の状況を確認してください。

SNI か専用 IP か

まず、ブラウザと CloudFront 間の通信で、SNI (Server Name Indication) が使えるかどうか検討します。

ガラケーやWindowsXP の IE は SNI に対応していません。これらの古い環境でサイトが見えなくても構わないかどうかを確認します。

SNI 非対応のブラウザに対応するには、専用 IP を使う必要があります。専用 IP は $600/月かかります。

専用 IP 独自 SSL の料金体系はシンプルです。SSL 証明書ごとの専用 IP アドレスに伴う追加コストのため、CloudFront ディストリビューションに関連付けられた各独自 SSL 証明書ごとに、毎月 600 USD をお支払いいただきます。

SNI は無料です。

SNI 独自 SSL 証明書管理には、前払い料金や最低月額料金は不要です。お客様にお支払いいただくのは、Amazon CloudFront のデータ転送と HTTPS リクエストに対する通常料金のみです。

証明書の取得について

どのドメインでどんな証明書が必要か、証明書は有料か無料か、を確認します。

証明書の取得方法には、以下の選択肢があります。

  1. 代理店などから購入する (有料)
  2. ACM (AWS Certificate Manager) を使う (無料、ただし EC2 にはインストール不可)
  3. Let’s Encrypt を使う (無料、ただし Amazon Linux 未対応)
  4. etc.

CloudFront – オリジン間を HTTPS にする

通常は、ビューア – CloudFront 間だけでなく、 CloudFront – オリジン間も通信を暗号化します。(表向き暗号化している風を装って、裏を平文で流して良いのかは微妙な問題です…)

CloudFront とオリジンの両方で、証明書が必要になる可能性があります。

オリジンが EC2 の場合、 ELB を入れるか検討する

ACM で取得した証明書を EC2 にインストールすることはできません。

https://aws.amazon.com/jp/certificate-manager/faqs/

Q: ACM により提供された証明書を使用できるのは、AWS のどのサービスですか?

ACM は AWS の以下のサービスと連携できます。
• Elastic Load Balancing – Elastic Load Balancing のドキュメントを参照してください。
• Amazon CloudFront – CloudFront のドキュメントを参照してください。
• Amazon API Gateway – API Gateway のドキュメントを参照してください。
• AWS Elastic Beanstalk – AWS Elastic Beanstalk のドキュメントを参照してください。

https://aws.amazon.com/jp/certificate-manager/faqs/

Q: Amazon EC2 インスタンスや自分のサーバーに証明書を使用できますか?

いいえ。現在のところ、ACM により提供される証明書は、AWS の特定のサービスのみで使用できます。

EC2 で ACM を使いたい場合は、CloudFront – ELB – EC2 の構成にします。証明書が無料になる代わりに、ELB の費用が発生します。

S3 に証明書を入れる必要はない

  • Amazon S3 は SSL/TLS 証明書を提供するため、その必要はありません。

この日本語訳はいまいち何を言っているのかわかりませんが、、

  • Amazon S3 provides the SSL/TLS certificate, so you don’t have to.

「S3 が証明書を提供するので、あなたが(提供する)必要はない」という意味です。

今のところ、S3 に独自の証明書を入れる手段はありません。オリジンに S3 の CNAME を指定することもできません。

http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html

以下の形式を使用してバケットを指定しないでください。

  • Amazon S3 のパススタイル (s3.amazonaws.com/bucket-name)
  • Amazon S3 の CNAME (ある場合)

CloudFront – S3 間の HTTPS には大きな制約がある

S3 をウェブサイトエンドポイントとしてオリジンに設定すると、HTTPS が使えなくなります。

Amazon S3 バケットがウェブサイトエンドポイントとして構成されている場合、オリジンとの通信に HTTPS を使用するように CloudFront を構成することはできません。Amazon S3 はその構成で HTTPS 接続をサポートしていないためです。

ウェブサイトエンドポイントは、

http://example-bucket.s3-website-ap-northeast-1.amazonaws.com/

のような URL での配信方法です。「静的ウェブサイトホスティング」の設定です。この URL をオリジンに設定すると、CloudFront – S3 間で HTTPS が使えません。

ウェブサイトエンドポイントは、以下の機能で必要です。

  • /foo/bar//foo/bar/index.html を返す
  • 独自のエラー画面を表示する
  • リクエストを全てリダイレクトする (例えば、www なしドメインを www ありにリダイレクトする)

ウェブサイトエンドポイントを使わず、S3オリジンで設定すると、サイトのトップ / (Default Root Object) 以外、index.html が見えるようにはなりません。

CloudFrontではDefault Root Objectという設定項目でIndex Documentを指定することができますが、これはRootへのアクセス時(例:http://example.com/) に対してのみ有効であり、サブディレクトリ(例:http://example.com/foo/) に対しては有効になりません。そのため、階層構造のあるWEBサイトをホストする際には不向きだと思われます。

ウェブサイトエンドポイントを使う場合は、裏が暗号化されていなくてもいいのか、バレなければいいのか、を検討しなければならなくなります。。ちなみに、オリジンが S3 であることは Server レスポンスヘッダで判別できます。S3 がウェブサイトエンドポイントかどうかは、/foo/bar/ の挙動で判別できます。

また、ウェブサイトエンドポイントにすると、Origin Access Identity を使うことができなくなります。バケット名を推測して、S3 に直接アクセスされる可能性があります。

ワイルドカード証明書の取得を検討する

CloudFront にもオリジンにも、ワイルドカード証明書が使えます。例えば、以下のような組み合わせが考えられます。

  • CloudFront *.example.com → Origin *.example.com

    • 1つのワイルドカード証明書を使う
  • CloudFront www.example.com → Origin www.example.com
    • 1つの非ワイルドカードの証明書を使う
    • CloudFront から Host ヘッダを転送する場合のみ、オリジンの証明書で Host ヘッダのドメイン名が使える
  • CloudFront www.example.com → Origin *.example.com
    • 表は高い非ワイルドカード証明書を使い、裏は安いワイルドカード証明書で済ませる
  • CloudFront www.example.com → Origin origin.example.com
    • 表と裏と、別々に非ワイルドカード証明書を購入する
    • または、証明書に別名 (SAN) を設定する
  • CloudFront www.example.com → Origin origin.example.jp
    • オリジンは .example.com でなくてもよい。DNS の自由が効かない場合、裏は Route53 で独自のドメインを取得すると便利

詳細は以下のドキュメントを参照してください。

カスタムオリジンを使用する場合、オリジンの SSL/TLS 証明書の共通名フィールドにドメイン名が含まれ、さらにサブジェクト代替名フィールドにもドメイン名がいくつか含まれることがあります。 (CloudFront は証明書ドメイン名にワイルドカード文字を使用できます)。

証明書のドメイン名のうち 1 つは、オリジンドメイン名に指定したドメイン名と一致する必要があります。ドメイン名が一致しない場合、CloudFront は、ビューワーに HTTP ステータスコード 502 (Bad Gateway) を返します。

内部の証明書を安くあげる

エンドユーザの目に触れないオリジン用の証明書にまでコストをかける必要があるかどうかを検討します。安価な証明書でも十分かもしれません。

オリジンが ELB なら ACM が使えます。

DNS を操作する権限があるか確認する

表側の DNS を操作する権限がない場合、オリジンに Route53 で取得した独自ドメインを設定すると、自由度が上がります。

オリジンに独自のドメインを使う場合は、その証明書が必要です。

リリース前の確認方法を検討する

リリース前に CloudFront に別のドメインを割り当てて確認する場合は、ワイルドカード証明書があったほうが便利です。

例えば https://staging.example.com/ のような URL を CloudFront に設定します。

Naked ドメインを使うか確認する

https://example.com/ のような Naked ドメイン (Zone Apex) を使うかどうか確認します。

Route53 を使わずに、Naked ドメインを CloudFront や ELB や S3 に割り当てることはできません。CloudFront を通さず、例えば EC2 で直接リクエストを受ける必要が出てきます。

Naked ドメインへのリクエストを EC2 で受ける場合

Naked ドメインへのリクエストを EC2 で直接受ける場合は、ACM が使えません。ACM で取得した証明書は EC2 にインストールできないからです。

証明書の購入が必要になる可能性があります。

Naked ドメインの DNS に Route53 を使っている場合

Route53 でエイリアスを使うと、CloudFront に Naked ドメイン (Zone Apex) を設定できます。

この場合は、Naked ドメインの証明書取得に ACM が使えます。

ワイルドカード証明書を使う場合、ワイルドカードに Naked ドメインは含まれないので、*.example.com に別名で example.com を設定します。

既存の証明書がある場合は SAN を確認する


既に証明書があって、足りない証明書を購入する場合。

既存の証明書の別名 (SAN) が何になっているのかを確認します。特に Naked ドメインなど、必要なドメインが SAN に設定されているかもしれません。

証明書をインポートするリージョンに注意する

CloudFront の証明書は、バージニア北部リージョンに入れる必要があります。

ACM で証明書を取得する場合にも、バージニア北部リージョンを選ぶ必要があります。

ビューワーと CloudFront との間で HTTPS を必須にするには、証明書をリクエストまたはインポートする前に AWS リージョンを 米国東部(バージニア北部) に変更する必要があります。

CloudFront とオリジンとの間で HTTPS を必須にする場合、オリジンとして ELB ロードバランサーを使用しているなら、任意のリージョンで証明書をリクエストまたはインポートできます。

例えば、オリジンが東京の ELB だとしたら、東京とバージニア北部リージョンの両方の ACM で証明書を取得 (またはインポート) します。

ACM の本人確認について

ACM で証明書を取得する場合は、本人確認 (取得意思の確認) メールが、証明書のドメイン宛てに送信されます。

Whois 情報の更新状態をチェックする

本人確認メールは、公開されている Whois の連絡先アドレスにも送信されます。

Whois に正しいアドレスが公開されている場合は、そのアドレスでメールを受け取るのが簡単です。

SES で ACM の認証を通す

ドキュメントの通りにやれば、ACM の確認メールを SES で受信するのは、さほど難しくありません。

SES の受信設定を行い、DNS に MX レコードを設定、受信したメールを SNS トピックで受け取るようにして、そのトピックに任意のメールアドレスをサブスクライブします。

ルールセットがアクティブになっているかどうかに注意する

SES のメール受信設定には、「ルールセット」というバージョン管理の仕組みがあります。

現行のルールセットをコピーし、編集し、アクティブなルールセットを入れ替える、という手順で設定を変更します。Inactive Rule Sets に新しいルールセットを足しても何も起こりません。

続きを読む