AWS Cloud Development Kit(CDK)でURL短縮サービスを作ってみた

サーバーレスなURL短縮サービスを Python 版 AWS CDK で作るウェビナーを見つけたので、動かしてみました。

最終的には下図のようなサーバーレスなインフラを構築します。

※図はウェビナーから引用

構築された URL 短縮サービスを触ってみます。

targetUrl に URL を渡し、短縮 URL を生成します。

$ curl https://go.example.info?targetUrl=https://aws.amazon.com/cdk/
Created URL: https://go.example.info/4692efec

短縮 URL でアクセスすると、元の URL にリダイレクトされます。

$ curl -I https://go.example.info/4692efec
HTTP/2 301
date: Wed, 09 Oct 2019 10:03:35 GMT
content-type: application/json
content-length: 0
location: https://aws.amazon.com/cdk/
x-amzn-requestid: 939ce6c8-ddcf-48b7-a758-e244bd42d12c
x-amz-apigw-id: BSiCsGQ-liAFgmA=
x-amzn-trace-id: Root=1-5d9db077-c89a4731411fa428e4c1b245;Sampled=0

301 ステータスと location から期待通りリダイレクトされていることがわかりますね。

本ブログでは、ウェビナーに従い、ミニマムな CDK アプリケーションから始めて、インクリメンタルに機能拡張・デプロイし、短縮URLサービスを構築します。

チュートリアルの流れ

  1. AWS CDK のインストール
  2. CDK アプリケーションの作成
  3. パッケージのインストール
  4. DynamoDB のモデリング
  5. Lambda 関数の作成
  6. Lambda の権限設定
  7. API Gateway の構築
  8. DNS 設定
  9. 後片付け

1. AWS CDK のインストール

まずは AWS CDK をインストールします。

$ npm install -g aws-cdk
...
$ cdk --version
1.12.0 (build 923055e)

利用リージョンでまだ一度も AWS CDK を利用したことがない場合、$ cdk bootstrap で初期化します。

CDKToolkit という CloudFormation スタックが作成され、CDK の実行に必要な S3 バケットなどが作成されます。

この処理をスキップすると、以下のようなエラーが発生します。

$ cdk deploy
...
 ❌  cdk-app-name failed: Error: This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap aws://unknown-account/unknown-region")
This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap aws://unknown-account/unknown-region")

2. CDK アプリケーションの作成

url-shortener という名前の新規アプリケーションを作成します。

[~]$ mkdir url-shortener
[~]$ cd url-shortener
[~/url-shortener]$ cdk init --language python

ファイル構成を確認します。

[~/url-shortener]$ tree
.
├── README.md
├── app.py
├── cdk.json
├── requirements.txt
├── setup.py
└── url_shortener
    ├── __init__.py
    └── url_shortener_stack.py

1 directory, 7 files

主要ファイルに絞って解説します。

ファイル名 概要
app.py CDKで呼び出されるメインプログラムです。
requirements.txt setup.pyを参照するようになっているため、直接は編集しません。
setup.py 依存パッケージを管理します。
url_shortener/url_shorteneer_stack.py Constructs/Stacksを定義します。今回はこのファイルを主に編集します。

CDK 初期化直後の app.pyurl_shortener_stack.py を確認します。

app.py

アプリケーションを定義している app.py を確認します。

#!/usr/bin/env python3

from aws_cdk import core

from url_shortener.url_shortener_stack import UrlShortenerStack # UrlShortenerStack スタックをインポート


app = core.App()                        # CDK App の作成
UrlShortenerStack(app, "url-shortener") # App と Stack の紐付け

app.synth()

url_shortener_stack.py

スタックを定義している url_shortener_stack.py を確認します。 初期状態では Construct は何も定義されていません。

#!/usr/bin/env python3

from aws_cdk import core

from url_shortener.url_shortener_stack import UrlShortenerStack


app = core.App()
UrlShortenerStack(app, "url-shortener")

app.synth()

以降では

  • Construct
  • Stack

を追加・編集して機能拡張します。

3. パッケージのインストール

アプリケーションに必要なパッケージをインストールします。

requirements.txt の中身は -e . となっていて、インストールするパッケージは同じ階層にある setup.py を参照するようになっています。

setup.pyinstall_requires にインストールするパッケージを追加します。

...
    install_requires=[
        "aws-cdk.core",
        "aws-cdk.aws-dynamodb",
        "aws-cdk.aws-events",
        "aws-cdk.aws-events-targets",
        "aws-cdk.aws-lambda",
        "aws-cdk.aws-s3",
        "aws-cdk.aws-ec2",
        "aws-cdk.aws-ecs-patterns",
        "aws-cdk.aws-certificatemanager",
        "aws-cdk.aws-apigateway",
        "aws-cdk.aws-cloudwatch",
        "cdk-watchful",
        "boto3"
    ],
...

※ Tech Talk のデモ全体で利用するモジュールを含めています。

次に venv で仮想環境を作成し、パッケージをインストールします。

[~/url-shortener]$ python3 -m venv .env
[~/url-shortener]$ source .env/bin/activate
(.env) [~/url-shortener]$ pip install -r requirements.txt

4. DynamoDB のモデリング

URL と短縮 URL のマッピングを行う DynamoDB のモデルを定義します。

  • 論理テーブル名 : mapping-table
  • パーティションキー : id(STRING 型)

キーが短縮 URL、バリューが元の URL というシンプルなデータモデルです。

url_shortener/url_shortener_stack.py を更新します。

from aws_cdk import core
from aws_cdk import aws_dynamodb

class UrlShortenerStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        table = aws_dynamodb.Table(self, "mapping-table",
                                   partition_key=aws_dynamodb.Attribute(
                                       name="id",
                                       type=aws_dynamodb.AttributeType.STRING))

$ cdk deploy コマンドでデプロイします。

$ cdk deploy
url-shortener: deploying...
url-shortener: creating CloudFormation changeset...
 0/3 | 18:23:41 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata   | CDKMetadata
 0/3 | 18:23:41 | CREATE_IN_PROGRESS   | AWS::DynamoDB::Table | mapping-table (mappingtable5416B587)
 0/3 | 18:23:41 | CREATE_IN_PROGRESS   | AWS::DynamoDB::Table | mapping-table (mappingtable5416B587) Resource creation Initiated
 0/3 | 18:23:42 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata   | CDKMetadata Resource creation Initiated
 1/3 | 18:23:42 | CREATE_COMPLETE      | AWS::CDK::Metadata   | CDKMetadata
 2/3 | 18:24:12 | CREATE_COMPLETE      | AWS::DynamoDB::Table | mapping-table (mappingtable5416B587)
 3/3 | 18:24:13 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | url-shortener

 ✅  url-shortener

Stack ARN:
arn:aws:cloudformation:eu-central-1:12345:stack/url-shortener/xxx

コンソールの DynamoDB を確認すると、新規テーブルが作成されています。

また、CloudFormation を確認すると、 url-shortener というスタックが作成されています。 以降では、このスタックを差分更新します。

5 :Lambda 関数の作成

短縮 URL サービスは API Gateway と Lambda でサーバーレスに構築します。 URL の登録とリダイレクトを行う Lambda 関数を作成します。

まず、Lambda 関数用ディレクトリ・ファイルを作成します

[~/url-shortener]$ mkdir lambda
[~/url-shortener]$ touch lambda/handler.py

Lambda 関数lambda/handler.py では以下の関数を定義します。

  • create_short_url 関数 : クエリーストリング targetUrl から短縮 URL を生成し、DynamoDB に登録
  • read_short_url 関数 : 短縮 URL から本来の URL を DynamoDB から取得し、HTTP ステータス 301 でリダイレクト
import json
import os
import uuid
import logging

import boto3

LOG = logging.getLogger()
LOG.setLevel(logging.INFO)


def main(event, context):
    LOG.info("EVENT: " + json.dumps(event))

    query_string_params = event["queryStringParameters"]
    if query_string_params is not None:
        target_url = query_string_params['targetUrl']
        if target_url is not None:
            return create_short_url(event)

    path_parameters = event['pathParameters']
    if path_parameters is not None:
        if path_parameters['proxy'] is not None:
            return read_short_url(event)

    return {
        'statusCode': 200,
        'body': 'usage: ?targetUrl=URL'
    }


def create_short_url(event):
    # Pull out the DynamoDB table name from environment
    table_name = os.environ.get('TABLE_NAME')

    # Parse targetUrl
    target_url = event["queryStringParameters"]['targetUrl']

    # Create a unique id (take first 8 chars)
    id = str(uuid.uuid4())[0:8]

    # Create item in DynamoDB
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(table_name)
    table.put_item(Item={
        'id': id,
        'target_url': target_url
    })

    # Create the redirect URL
    url = "https://" \
        + event["requestContext"]["domainName"] \
        + event["requestContext"]["path"] \
        + id

    return {
        'statusCode': 200,
        'headers': {'Content-Type': 'text/plain'},
        'body': "Created URL: %s" % url
    }

def read_short_url(event):
    # Parse redirect ID from path
    id = event['pathParameters']['proxy']

    # Pull out the DynamoDB table name from the environment
    table_name = os.environ.get('TABLE_NAME')

    # Load redirect target from DynamoDB
    ddb = boto3.resource('dynamodb')
    table = ddb.Table(table_name)
    response = table.get_item(Key={'id': id})
    LOG.debug("RESPONSE: " + json.dumps(response))

    item = response.get('Item', None)
    if item is None:
        return {
            'statusCode': 400,
            'headers': {'Content-Type': 'text/plain'},
            'body': 'No redirect found for ' + id
        }

    # Respond with a redirect
    return {
        'statusCode': 301,
        'headers': {
            'Location': item.get('target_url')
        }
    }

デプロイ対象に Lambda 関数を含めるよう、url_shortener/url_shortener_stack.py を更新します。

from aws_cdk import core
from aws_cdk import aws_dynamodb, aws_lambda

class UrlShortenerStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        table = aws_dynamodb.Table(self, "mapping-table",
                                   partition_key=aws_dynamodb.Attribute(
                                       name="id",
                                       type=aws_dynamodb.AttributeType.STRING))

        handler = aws_lambda.Function(self, "backend",
                                      runtime=aws_lambda.Runtime.PYTHON_3_7,
                                      handler="handler.main",
                                      code=aws_lambda.AssetCode(path="./lambda"))

aws_lambda.Functioncode 引数で Lambda 関数のソースパスを指定します。

aws_cdk.aws_lambda.AssetCode で Lambda 関数のソースコードのパスを指定します。

デモでは aws_cdk.aws_lambda.Code が利用されていますが、現在は非推奨のため、使わないようにしてください。

$ cdk deploy でデプロイします。

6:Lambda の権限設定

Lambda 関数が DynamoDB を読み書きできるように IAM 設定します。

DynamoDB Table のハイレベル API の grantReadWriteData(grantee) メソッドを利用すると、必要なポリシーを付与できます。JSON で複雑なポリシーを定義する必要はありません。

メソッド名からどのような権限が自明であり、可読性にも優れています。

grantReadWriteData(grantee) 以外にも

  • grantFullAccess(grantee)
  • grantReadData

など、ユースケースごとに権限が用意されています。

次に、書き込み先の DynamoDB テーブルを 環境変数 TABLE_NAME から取得するようにします。

この変数はプロビジョニング時に解決されます。

from aws_cdk import core
from aws_cdk import aws_dynamodb, aws_lambda

class UrlShortenerStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        table = aws_dynamodb.Table(self, "mapping-table",
                                   partition_key=aws_dynamodb.Attribute(
                                       name="id",
                                       type=aws_dynamodb.AttributeType.STRING))

        handler = aws_lambda.Function(self, "backend",
                                      runtime=aws_lambda.Runtime.PYTHON_3_7,
                                      handler="handler.main",
                                      code=aws_lambda.AssetCode(path="./lambda"))

        table.grant_read_write_data(handler)
        handler.add_environment('TABLE_NAME', table.table_name)

更新差分を確認

$ cdk diff コマンドで dry run します。

[~/url-shortener]$ cdk diff
Stack url-shortener
The url-shortener stack uses assets, which are currently not accounted for in the diff output! See https://github.com/aws/aws-cdk/issues/395
IAM Statement Changes
┌───┬────────────────────────────────┬────────┬────────────────────────────────┬─────────────────────────────────┬───────────┐
│   │ Resource                       │ Effect │ Action                         │ Principal                       │ Condition │
├───┼────────────────────────────────┼────────┼────────────────────────────────┼─────────────────────────────────┼───────────┤
│ + │ ${mapping-table.Arn}           │ Allow  │ dynamodb:BatchGetItem          │ AWS:${backend/ServiceRole}      │           │
│   │                                │        │ dynamodb:BatchWriteItem        │                                 │           │
│   │                                │        │ dynamodb:DeleteItem            │                                 │           │
│   │                                │        │ dynamodb:GetItem               │                                 │           │
│   │                                │        │ dynamodb:GetRecords            │                                 │           │
│   │                                │        │ dynamodb:GetShardIterator      │                                 │           │
│   │                                │        │ dynamodb:PutItem               │                                 │           │
│   │                                │        │ dynamodb:Query                 │                                 │           │
│   │                                │        │ dynamodb:Scan                  │                                 │           │
│   │                                │        │ dynamodb:UpdateItem            │                                 │           │
└───┴────────────────────────────────┴────────┴────────────────────────────────┴─────────────────────────────────┴───────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[+] AWS::IAM::Policy backend/ServiceRole/DefaultPolicy backendServiceRoleDefaultPolicy78BAA8F9
[~] AWS::Lambda::Function backend backendCBA98286
 ├─ [+] Environment
 │   └─ {"Variables":{"TABLE_NAME":{"Ref":"mappingtable5416B587"}}}
 └─ [~] DependsOn
     └─ @@ -1,3 +1,4 @@
        [ ] [
        [+]   "backendServiceRoleDefaultPolicy78BAA8F9",
        [ ]   "backendServiceRole77A15DC8"
        [ ] ]

[~/url-shortener]$

問題がなければデプロイします。

$ cde deploy

7 :API Gateway の構築

次に、Lambda 関数を呼び出す API Gateway を定義します。

aws_apigateway.LambdaRestApi メソッドで、 Lambda をバックエンドとした API Gateway を作成します。

from aws_cdk import core
from aws_cdk import aws_dynamodb, aws_lambda, aws_apigateway

class UrlShortenerStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        table = aws_dynamodb.Table(self, "mapping-table",
                                   partition_key=aws_dynamodb.Attribute(
                                       name="id",
                                       type=aws_dynamodb.AttributeType.STRING))

        handler = aws_lambda.Function(self, "backend",
                                      runtime=aws_lambda.Runtime.PYTHON_3_7,
                                      handler="handler.main",
                                      code=aws_lambda.AssetCode(path="./lambda"))

        table.grant_read_write_data(handler)
        handler.add_environment('TABLE_NAME', table.table_name)

        # define the API endpoint and associate the handler
        api = aws_apigateway.LambdaRestApi(self, "UrlShortenerApi", handler=handler)

デプロイします。

$ cdk deploy
...

 ✅  url-shortener

Outputs:
url-shortener.UrlShortenerApiEndpoint23405F0E = https://XXX.execute-api.eu-central-1.amazonaws.com/prod/

Stack ARN:
arn:aws:cloudformation:eu-central-1:1234:stack/url-shortener/5ea22370-e851-11e9-ab5a-025adfcb1154

デプロイが完了すると、最後に API Gateway のエンドポイントが出力されます。 このエンドポイントに対して、短縮 URL サービスを試してみましょう。

GET パラメーター targetUrl に短縮したい URL を渡します。

$ URL=https://XXX.execute-api.eu-central-1.amazonaws.com/prod/
$ curl $URL?targetUrl=https://aws.amazon.com/cdk/
Created URL: https://XXX.execute-api.eu-central-1.amazonaws.com/prod/d4e23681

生成された短縮 URL を GET します。

$ curl -I  https://XXX.execute-api.eu-central-1.amazonaws.com/prod/d4e23681
HTTP/2 301
content-type: application/json
content-length: 0
location: https://aws.amazon.com/cdk/
date: Wed, 09 Oct 2019 09:47:52 GMT
x-amzn-requestid: e44cbbf8-048e-4b1e-b061-6aa886c65e64
x-amz-apigw-id: BSfvSFyVliAFWOA=
x-amzn-trace-id: Root=1-5d9dacc8-786515e42454443a25f9e8aa;Sampled=0
x-cache: Miss from cloudfront
via: 1.1 6eea7c9b83576b73ff12f8e9ff2ad318.cloudfront.net (CloudFront)
x-amz-cf-pop: TXL51
x-amz-cf-id: H9_Jb03zbCpGtrb0GwS0gThJaFlgTOzZb0iisD-LuOx_FcrSg711yA==

レスポンスヘッダーの Location から、短縮元の URL にリダイレクトされていることがわかります。

8 :DNS 設定

最後に、API Gateway をカスタムドメイン名、かつ、HTTPS で公開するため、Amazon Route 53 と AWS Certificate Manager(ACM) の 設定を行います。

ホストゾーンを複数のアプリケーションで共有することを想定し、別スタックで定義します。

実運用では、このようなスタックはライブラリー化し、他の CDK プロジェクトからも利用できるようにしたいところですが、今回は簡略化して単に別ディレクトリで管理します。

デモでは 架空企業"Walters co." 向けにサイト構築しているため、waltersco_common という名前のディレクトリを作成します。

$ mkdir waltersco_common
$ touch waltersco_common/__init__.py

スタックにドメイン情報を含めます。

import os
from aws_cdk.core import Stack, Construct # , Environment
from aws_cdk import aws_apigateway, aws_route53, aws_route53_targets, aws_certificatemanager

# we need default values here since aws-cdk-examples build synthesizes the app
ZONE_NAME = os.environ.get('WALTERSCO_ZONE_NAME', 'example.info')
ZONE_ID = os.environ.get('WALTERSCO_ZONE_ID', 'XXXX')
ZONE_CERT = os.environ.get('WALTERSCO_ZONE_CERT', 'arn:aws:acm:eu-central-1:1234:certificate/1-2-3-4')

class WaltersCoStack(Stack):
    """
    A base CDK stack class for all stacks defined in our fun little company.
    """

    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

    def map_waltersco_subdomain(self, subdomain: str, api: aws_apigateway.RestApi) -> str:
        """
        Maps a sub-domain of waltersco.co to an API gateway
        :param subdomain: The sub-domain (e.g. "www")
        :param api: The API gateway endpoint
        :return: The base url (e.g. "https://www.waltersco.co")
        """
        domain_name = subdomain + '.' + ZONE_NAME
        url = 'https://' + domain_name

        cert = aws_certificatemanager.Certificate.from_certificate_arn(self, 'DomainCertificate', ZONE_CERT)
        hosted_zone = aws_route53.HostedZone.from_hosted_zone_attributes(self, 'HostedZone',
                                                                         hosted_zone_id=ZONE_ID,
                                                                         zone_name=ZONE_NAME)

        # add the domain name to the api and the A record to our hosted zone
        domain = api.add_domain_name('Domain', certificate=cert, domain_name=domain_name)

        aws_route53.ARecord(
            self, 'UrlShortenerDomain',
            record_name=subdomain,
            zone=hosted_zone,
            target=aws_route53.RecordTarget.from_alias(aws_route53_targets.ApiGatewayDomain(domain)))

        return url

__all__ = ["WaltersCoStack"]

URL 短縮サービスのスタックもこのスタックを継承し、go のサブドメインで API Gateway を公開するようにします。

from aws_cdk import core
from aws_cdk import aws_dynamodb, aws_lambda, aws_apigateway

from waltersco_common import WaltersCoStack

class UrlShortenerStack(WaltersCoStack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        table = aws_dynamodb.Table(self, "mapping-table",
                                   partition_key=aws_dynamodb.Attribute(
                                       name="id",
                                       type=aws_dynamodb.AttributeType.STRING))

        handler = aws_lambda.Function(self, "backend",
                                      runtime=aws_lambda.Runtime.PYTHON_3_7,
                                      handler="handler.main",
                                      code=aws_lambda.AssetCode(path="./lambda"))

        table.grant_read_write_data(handler)
        handler.add_environment('TABLE_NAME', table.table_name)

        # define the API endpoint and associate the handler
        api = aws_apigateway.LambdaRestApi(self, "UrlShortenerApi", handler=handler)

        self.map_waltersco_subdomain('go', api)

デプロイします。

[~/url-shortener]$ cdk deploy
url-shortener: deploying...
url-shortener: creating CloudFormation changeset...
 0/5 | 11:52:16 | CREATE_IN_PROGRESS   | AWS::ApiGateway::DomainName      | UrlShortenerApi/Domain (UrlShortenerApiDomain85D0CE65)
 0/5 | 11:52:16 | UPDATE_IN_PROGRESS   | AWS::CDK::Metadata               | CDKMetadata
 1/5 | 11:52:17 | UPDATE_COMPLETE      | AWS::CDK::Metadata               | CDKMetadata
 1/5 | 11:52:18 | CREATE_IN_PROGRESS   | AWS::ApiGateway::DomainName      | UrlShortenerApi/Domain (UrlShortenerApiDomain85D0CE65) Resource creation Initiated
 2/5 | 11:52:18 | CREATE_COMPLETE      | AWS::ApiGateway::DomainName      | UrlShortenerApi/Domain (UrlShortenerApiDomain85D0CE65)
 2/5 | 11:52:20 | CREATE_IN_PROGRESS   | AWS::ApiGateway::BasePathMapping | UrlShortenerApi/Domain/Map:--=>urlshortenerUrlShortenerApi41983DFF (UrlShortenerApiDomainMapurlshortenerUrlShortenerApi41983DFF17DD9E30)
 2/5 | 11:52:20 | CREATE_IN_PROGRESS   | AWS::Route53::RecordSet          | UrlShortenerDomain (UrlShortenerDomain9F16453F)
 2/5 | 11:52:20 | CREATE_IN_PROGRESS   | AWS::ApiGateway::BasePathMapping | UrlShortenerApi/Domain/Map:--=>urlshortenerUrlShortenerApi41983DFF (UrlShortenerApiDomainMapurlshortenerUrlShortenerApi41983DFF17DD9E30) Resource creation Initiated
 3/5 | 11:52:20 | CREATE_COMPLETE      | AWS::ApiGateway::BasePathMapping | UrlShortenerApi/Domain/Map:--=>urlshortenerUrlShortenerApi41983DFF (UrlShortenerApiDomainMapurlshortenerUrlShortenerApi41983DFF17DD9E30)
 3/5 | 11:52:21 | CREATE_IN_PROGRESS   | AWS::Route53::RecordSet          | UrlShortenerDomain (UrlShortenerDomain9F16453F) Resource creation Initiated
 4/5 | 11:52:53 | CREATE_COMPLETE      | AWS::Route53::RecordSet          | UrlShortenerDomain (UrlShortenerDomain9F16453F)

 ✅  url-shortener

Outputs:
url-shortener.UrlShortenerApiEndpoint23405F0E = https://0m59dyay05.execute-api.eu-central-1.amazonaws.com/prod/

Stack ARN:
arn:aws:cloudformation:eu-central-1:1234:stack/url-shortener/c3b74840-ea76-11e9-8004-0ac843449136

DNS の確認

Route 53 の ホストゾーンにレコードが追加されていることを確認します。

コマンドラインからも DNS レコードを確認します。

$ dig go.example.info

; <<>> DiG 9.10.6 <<>> go.example.info
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34240
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;go.example.info.	IN	A

;; ANSWER SECTION:
go.example.info. 60	IN	A	3.123.86.188
go.example.info. 60	IN	A	3.120.53.211

;; Query time: 77 msec
;; SERVER: 192.168.178.1#53(192.168.178.1)
;; WHEN: Wed Oct 09 12:02:02 CEST 2019
;; MSG SIZE  rcvd: 88

このカスタムドメインに対して、短縮URLサービスを試します。

targetUrl に URL を渡し、短縮 URL を生成します。

$ curl https://go.example.info?targetUrl=https://aws.amazon.com/cdk/
Created URL: https://go.example.info/4692efec

短縮 URL でアクセスします。

$ curl -I https://go.example.info/4692efec
HTTP/2 301
date: Wed, 09 Oct 2019 10:03:35 GMT
content-type: application/json
content-length: 0
location: https://aws.amazon.com/cdk/
x-amzn-requestid: 939ce6c8-ddcf-48b7-a758-e244bd42d12c
x-amz-apigw-id: BSiCsGQ-liAFgmA=
x-amzn-trace-id: Root=1-5d9db077-c89a4731411fa428e4c1b245;Sampled=0

301 ステータスと location から期待通りリダイレクトされていることがわかりますね。

9 後片付け

最後に、作成した AWS リソースをスを cdk destroy で削除しましょう。

$ cdk destroy
Are you sure you want to delete: url-shortener (y/n)? y
url-shortener: destroying...
   0 | 12:04:28 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack       | url-shortener User Initiated
...
 ✅  url-shortener: destroyed

裏では、CloudFormation のスタックが削除されます。

DynamoDB は CloudFormation の DeletionPolicyRetain にして作成されているため、スタックを削除しても、テーブルは削除されません。

手動で削除してください。

最後に

Python 版 AWS CDK で短縮 URL サービスを作りました。

CDK アプリケーションの開発スタイル・サイクルの雰囲気が何となく伝わったのではないかと思います。

繰り返しとなりますが、本ブログは次の AWS Online Tech Talks が元になっています。

このウェビナーは

  • 前半:(DynamoDB/Lambda/API Gateway/Route 53/ACM)アプリケーションの構築
  • 後半:(Fargate/CloudWatch)負荷ツール、テスト、監視

の2本立てで、最終的には下図のようなシステムを構築し、ブログではその前半に焦点をあてています。

※図はウェビナーから引用

CDK を試してみたいアプリケーションエンジニアは、ドキュメントを読むよりも、この動画を見たほうがとっつきやすいのではないかと思います。

CDK に興味を持たれた方は、CDK Workshop を参考に手を動かすと、理解が進むと思います。

なお、ウェビナーに付随するソースコードは GitHub で公開されています。

aws-cdk-examples/python/url-shortener at master · aws-samples/aws-cdk-examples · GitHub

それでは。