#とある業務で「id変換表を参照して URL をリダイレクト」させる必要があったので、サーバレスで実現できるか検証をした際の備忘録
1. 要件
-
サイトA:https://example-A.com をクローズする。ただし、サイトAの一部のページはサイトB:https://example-B.com へ移植して、サイトAクローズ後も閲覧できるようにする(ここではページの移植作業については割愛)。
-
サイトAの移植対象ページのURLは、サイトAクローズ後も生かしておく。そのURLへアクセスすると、サイトBの移植後のURLへリダイレクトさせる。
-
サイトAの移植対象ページのURLの例
https://example-A.com/before/demo.php?id=100002
-
サイトBの移植先ページのURLの例
https://example-B.com/after/10100002
-
移植先のサイトBの id は移植元のサイトAの id と異なるものになってしまうため、id 変換表を用意する。
-
id変換表の例
before after
100000 10100000
100001 10100001
100002 10100002
100003 10100003
100004 10100004
100005 10100005
2. システム概要
2.1 id変換表作成
-
CSV形式のデータを DynamoDB へインポートする。ただし、CSV形式のデータは、そのままのフォーマットでは DynamoDB へインポートできないため、インポート用のフォーマットに整形する必要がある。
-
S3, AWS Data Pipeline を使う。
-
DynamoDB インポート形式のファイルを S3 バケットへアップロードする。Data Pipeline で S3 バケットファイルのデータを DynamoDB のテーブルへインポートする。
2.2 リダイレクト設定
-
Route53, ALB, Lambda(ALBのターゲット), DynamoDB を使う。
-
リダイレクト構成案1
-
今回は、この方法で構築した。
-
リダイレクト用ALBを用意する。
-
リダイレクト開始は、Route 53 で サイトA の Aエイリアスレコードを サイトA の ALB から リダイレクト用 ALB へ変更する。
-
リダイレクト構成案2
-
サイトAのALBを使用する。
-
リダイレクト開始は、サイトA の ALB のターゲットグループを、リダイレクト用Lambdaを設定したターゲットグループへ変更する。
3. システム構築
3.1 id変換表を作成
-
DynamoDB テーブルを作成する
-
CSVファイルを作成する
-
作業PCで次のような csv ファイルを作成する。
id-mapping.csvbefore,after s,s 100000,10100000 100001,10100001 100002,10100002 100003,10100003 100004,10100004 100005,10100005
- CSVファイルを DynamoDB インポート用のフォーマットへに整形する
- 下記サイトの python スクリプトをそのまま利用する。
【参考】CSVを整形してDynamoDBにインポートできるようにするPythonスクリプト
$ python3 csv2json.py
CSVのパスを入力:id-mpping.csv
$ cat id-mapping.json
{"before": {"s": "100000"}, "after": {"s": "10100000"}}
{"before": {"s": "100001"}, "after": {"s": "10100001"}}
{"before": {"s": "100002"}, "after": {"s": "10100002"}}
{"before": {"s": "100003"}, "after": {"s": "10100003"}}
{"before": {"s": "100004"}, "after": {"s": "10100004"}}
{"before": {"s": "100005"}, "after": {"s": "10100005"}}
-
整形された id-mapping.json ファイルを置く S3バケットを作成する
-
S3バケット名を 「dynamodb-datapipeline-************ 」 とする ( "************" はユニークな文字列)。
-
S3バケット 「dynamodb-datapipeline-************」 に「csv」というフォルダを作成する。
-
Data Pipeline を作成する
-
Name: import dynamodb from s3
Source: Import DynamoDB backup data from S3 -
Input S3 folder: s3://dynamodb-datapipeline-************/csv/
Target DynamoDB table name: id-mapping
Region of the DynamoDB table: ap-northeast-1 -
IAM roles: default
【参考】AWS Data Pipeline の IAM ロール -
Data Pipeline の裏で、EMRクラスターとEC2が作成されている。それらのプロビジョニングが完了するまで、Data Pipeline のステータスは「WAITING_FOR_RUNNER」になっている。
-
EMR クラスターのプロビジョニングが終わると、Data Pipeline の DynamoDB インポート処理が実行される。完了すると、ステータスは「FINISHED」となる。
3.2 リダイレクト設定 - リダイレクト構成案1 のシステムを構築する
-
リダイレクト用 ALB 作成
-
ALB 名を 「redirect」 とする。
-
Lambda にアタッチする IAM ロールを作成する
-
次のポリシーをアタッチする(今回は検証なので、FullAccessにしている)。
AmazonDynamoDBFullAccess
AWSLambdaBasicExecutionRole -
ロール名を 「lambda_redirect」 とする。
-
Lambda作成
-
トリガーの設定
Application Load Balancer: redirect
リスナー: HTTPS:443
ホスト: example-A.com
パス: * (ワイルドカード)
-
トリガーの設定が完了すると、ALB redirect のリスナーとターゲットグループに Lambda の設定が自動的に反映される。
-
ターゲットグループ: lambda-25bxlPn0KCbRK4JVO3Ii のヘルスチェック設定
-
Lambda関数作成(これがこのシステムの肝)
-
実際のコード
import boto3
from botocore.exceptions import ClientError
from boto3.dynamodb.conditions import Key
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info(event)
for key, value in event["queryStringParameters"].items():
if key == "id":
table = boto3.resource("dynamodb").Table("id-mapping")
item = table.get_item(Key={"before":value})
if ("Item" in item):
after_id = table.get_item(Key={"before":value})["Item"]["after"]
location = "https://example-B.com/after/" + after_id
response = {
"statusCode": 302,
"isBase64Encoded": False,
"headers": {
"Location": location
}
}
return response
else:
response = {
"statusCode": 302,
"isBase64Encoded": False,
"headers": {
"Location": "https://example-B.com"
}
}
return response
else:
response = {
"statusCode": 302,
"isBase64Encoded": False,
"headers": {
"Location": "https://example-B.com"
}
}
return response
if ( event["path"] == "/healthcheck" ):
response = {
"statusCode": 200,
"isBase64Encoded": False,
"headers": {
"Content-Type": "text/html; charset=utf-8"
}
}
return response
else:
response = {
"statusCode": 302,
"isBase64Encoded": False,
"headers": {
"Location": "https://example-B.com"
}
}
return response
-
コードの概要
-
「https://example-A.com/before/demo.php?id=100002」 というリクエストを Lambda が受け取ったら、queryStringParameters にクエリ文字列 「id=100002」が格納される。
【参考】ALBがAWS Lambdaに必要な変更を行った後の変換済みリクエストの例 -
id=100002 の値 「100002」 をキーとして、DynamoDB から 「10100002」を取り出して、リダイレクト用のURL 「https://example-B.com/after/10100002」 を生成して、リダイレクトする。
-
id の値が DynamoDB テーブルにない場合は、「https://example-B.com」 へリダイレクトする。
-
クエリ文字列がない場合は、 「https://example-B.com」 へリダイレクトする。
-
「https://example-A.com/healthcheck」 リクエストは、ステータスコード 200 を返す。
-
「/healthcheck」パス以外のすべてのリクエストは、「https://example-B.com」 へリダイレクトする。
-
テスト
-
クエリ文字列「id=100002」のテストを作成する
↓
↓
テスト実行
Location: https://exapmpe-B.com/after/10100002 -
クエリ文字列「id=100010」(DynamoDBに存在しない)のテストを作成する
↓
Location: https://exapmpe-B.com -
「/test1/test2」のテスト
↓
Location: https://exapmpe-B.com -
テストが問題なければ、リダイレクト時のステータスコードを 302 から 301 に変更して、
Route 53 で サイトA の Aエイリアスレコードを サイトA の ALB から リダイレクト用 ALB へ変更して、リダイレクトを開始する。