BioErrorLog Tech Blog

試行錯誤の記録

AWS ChaliceアプリケーションをCDKでデプロイする | cdk-chalice

ChaliceアプリケーションをCDKでデプロイする方法を記します。


はじめに

おはよう。@bioerrorlogです。

先日、AWS ChaliceをCDKでデプロイする方法を紹介する記事がAWSの公式ブログにポストされました

私はまだChaliceを触ったことがありませんでしたので、この記事を参考にChaliceアプリケーションをCDKでデプロイしてみます。


なお、本記事のコードはこちらにまとめています。

github.com


AWS Chaliceとは

f:id:BioErrorLog:20200207073536p:plain
画像はAWS Chalice GitHubより引用

Chaliceは、サーバレスアプリケーションのフレームワークです。

Chaliceを使用することで、API GatewayとLambdaを使用するアプリをすぐにデプロイすることができます。

以下、Chaliceについて参考になりそうな資料:


作業環境

作業はUbuntu上のCloud9で行いました。

各バージョンは以下の通りです。

$ lsb_release -d
Description:    Ubuntu 18.04.4 LTS

$ python --version
Python 3.7.5
# 後述のように、Python3.6以下だとエラーが発生するので注意

$ cdk --version
1.31.0 (build 8f3ac79)

$ chalice --version
chalice 1.13.1, python 3.7.5, linux 4.15.0-1065-aws

$ pip show boto3
Name: boto3
Version: 1.12.39


実行手順

前準備: Python3.7のインストール

まず、前準備としてPython3.7をインストールします。

Python3.6以前のバージョンだと、cdk synth時にcdk-chaliceパッケージで以下のようなエラーが発生してしまうためです*1

RuntimeError: Click will abort further execution because Python 3 was configured to use ASCII as encoding for the environment. Consult https://click.palletsprojects.com/python3/ for mitigation steps.

This system supports the C.UTF-8 locale which is recommended. You might be able to resolve your issue by exporting the following environment variables:

    export LC_ALL=C.UTF-8
    export LANG=C.UTF-8

そしてこちら、単純にエラーメッセージに示される解決策 export LC_ALL=C.UTF-8export LANG=C.UTF-8 を実行しても問題は解決しませんでした。


このエラーは、Python3.7をインストールすることで解消します。

以下、Python3.7のインストールと、それに伴い必要となるパッケージをインストールする手順です。

# Python3.7 インストール
sudo apt update
sudo apt install python3.7

# python3.7-venvのインストール
sudo apt install python3.7-venv


前準備は以上です。


Chaliceプロジェクト作成

それではまず、以下のコマンドでChaliceプロジェクトを作成していきます。

# 前準備: CDKのインストール
npm install -g aws-cdk

# ワークスペースの作成
mkdir users-service
cd users-service

# Python仮想環境の作成とアクティブ化
python3.7 -m venv .venv # Python3.7で実行する
source .venv/bin/activate

# Chaliceインストールとプロジェクトの作成
pip install chalice
chalice new-project web-api
cd web-api


作成したChaliceプロジェクトのディレクトリ構造は以下の通りです。

$ tree -a
web-api
├── app.py
├── .chalice
│   └── config.json
├── .gitignore
└── requirements.txt

1 directory, 4 files


あとは、とりあえず必要なツールをrequirements.txtに放り込んでインストールすれば、プロジェクトの準備はおしまいです。

echo "boto3" > requirements.txt
echo "chalice" >> requirements.txt
pip install -r requirements.txt


Chalice appコード

つぎは、Chaliceのコードを書いていきます。
Chaliceプロジェクトの中に作成されたapp.pyに、以下のPythonコードを書き込みます。

import os
import boto3
from chalice import Chalice

# アプリケーションオブジェクト作成
app = Chalice(app_name='web-api')

# DynamoDBの取得
dynamodb = boto3.resource('dynamodb')
dynamodb_table = dynamodb.Table(os.environ['DYNAMODB_TABLE_NAME'])


# 以下、エンドポイントの設定
# @app.routeに渡した情報がAPI Gatewayに設定される
@app.route('/users', methods=['POST'])
def create_user():
    user = app.current_request.json_body
    dynamodb_table.put_item(Item=user)
    return user

@app.route('/users/{username}', methods=['GET'])
def get_user(username):
    response = dynamodb_table.get_item(Key={'username': username})
    return response['Item']

@app.route('/users/{username}', methods=['DELETE'])
def delete_user(username):
    dynamodb_table.delete_item(Key={'username': username})


DynamoDBテーブルへcreate, get, deleteするAPIが定義されました。
デコレータ@app.routeに渡した情報が、API Gatewayに設定されるという仕組みです。


次は、このChaliceアプリケーションで利用されるAWSリソースをCDKでデプロイします。


CDKプロジェクトの作成

まずはCDKプロジェクトを作成し、下準備を済ませます。

# ワークスペースの作成
cd ..
mkdir infra
cd infra

# CDKプロジェクトの作成
cdk init --language python --generate-only

# デフォルトのinfraファイルは削除して、後から新しく作成する
rm -rf infra
# Construct libraryを作るわけではないのでsetup.pyは不要
rm setup.py

# 必要なツールをrequirements.txtに放り込んでインストール
echo "aws-cdk.aws-dynamodb" > requirements.txt
echo "aws-cdk.core" >> requirements.txt
echo "cdk-chalice==0.6.0" >> requirements.txt # 現時点でのlatest version: 0.7.0ではインストールに失敗した
pip install -r requirements.txt

# 新規のstacksパッケージを作成
mkdir stacks
touch stacks/__init__.py
touch stacks/web_api.py


これで環境の下準備が整ったので、CDKコードを書いていきます。


CDKコード

まずは、先ほど作成したstacks/web_api.pyにCDK Stackを定義していきます。

import os

from aws_cdk import (
    aws_dynamodb as dynamodb,
    aws_iam as iam,
    core as cdk
)
from cdk_chalice import Chalice


class WebApi(cdk.Stack):

    def __init__(self, scope: cdk.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        
        # DynamoDBの作成
        partition_key = dynamodb.Attribute(name='username',
                                           type=dynamodb.AttributeType.STRING)
        self.dynamodb_table = dynamodb.Table(
            self, 'UsersTable', partition_key=partition_key,
            removal_policy=cdk.RemovalPolicy.DESTROY)
        cdk.CfnOutput(self, 'UsersTableName', value=self.dynamodb_table.table_name)


        # LambdaがDynamoDBにアクセスするためのIAMロールを作成
        lambda_service_principal = iam.ServicePrincipal('lambda.amazonaws.com')
        self.api_handler_iam_role = iam.Role(self, 'ApiHandlerLambdaRole',
                                             assumed_by=lambda_service_principal)
        self.dynamodb_table.grant_read_write_data(self.api_handler_iam_role)


        # web_api_source_dirはChaliceアプリケーションソースコードへのパス
        # ソースコードはChaliceによってパッケージングされ、
        # SAMテンプレートの作成とLambdaデプロイのためのZIP化が行われる
        web_api_source_dir = os.path.join(os.path.dirname(__file__), os.pardir,
                                          os.pardir, 'web-api')
        chalice_stage_config = self._create_chalice_stage_config()
        
        # Chaliceの作成
        self.chalice = Chalice(
            self, 'WebApi', source_dir=web_api_source_dir,
            stage_config=chalice_stage_config)

    # Chalice Stagesを定義
    def _create_chalice_stage_config(self):
        chalice_stage_config = {
            'api_gateway_stage': 'v1',
            'lambda_functions': {
                'api_handler': {
                    'manage_iam_role': False,
                    'iam_role_arn': self.api_handler_iam_role.role_arn,
                    'environment_variables': {
                        'DYNAMODB_TABLE_NAME': self.dynamodb_table.table_name
                    },
                    'lambda_memory_size': 128,
                    'lambda_timeout': 10
                }
            }
        }

        return chalice_stage_config


これで、DynamoDBとChaliceアプリケーションを作成するCDK Stack WebApiが定義されました。

次は、CDK StackをもとにCDKアプリケーションをinfra/app.pyに定義します。

import os
from aws_cdk import core as cdk
from stacks.web_api import WebApi

app = cdk.App()

# Stackの環境を定義
# 'CDK_DEFAULT_ACCOUNT'と'CDK_DEFAULT_REGION'からは現在のデフォルトが読み込まれる
dev_env = cdk.Environment(
    account=os.environ['CDK_DEFAULT_ACCOUNT'],
    region=os.environ['CDK_DEFAULT_REGION'])

# 本番環境はアカウントidとregionを指定
prod_eu_west_1_env = cdk.Environment(account='123456789012', region='eu-west-1')
prod_us_east_1_env = cdk.Environment(account='123456789012', region='us-east-1')

# Stackを定義
WebApi(app, 'WebApiDev', env=dev_env)
WebApi(app, 'WebApiProdEuWest1', env=prod_eu_west_1_env)
WebApi(app, 'WebApiProdUsEast1', env=prod_us_east_1_env)

app.synth()

これでCDKコードは完成です。

あとは、このCDKコードをデプロイしていきます。


CDKデプロイ

さっそく、CDKをデプロイしていきます。

# CDKテンプレート生成
cdk synth
# CDKデプロイ
cdk deploy WebApiDev

デプロイが成功すると、次のようなOutputsが返ってきます。

Outputs:
WebApiDev.UsersTableName = WebApiDev-UsersTable9725E9C8-1HVYW061T3POO
WebApiDev.APIHandlerArn = arn:aws:lambda:ap-northeast-1:123456789012:function:WebApiDev-APIHandler-10LR6FLOB1SGV
WebApiDev.APIHandlerName = WebApiDev-APIHandler-10LR6FLOB1SGV
WebApiDev.RestAPIId = 0oexyzw39c
WebApiDev.EndpointURL = https://0oexyzw39c.execute-api.ap-northeast-1.amazonaws.com/v1/


では、Outputsに表示されたEndpointURL + /usersに対してPOSTし、動作を確認してみます。

curl \
    -H "Content-Type: application/json" \
    -X POST \
    -d '{"username":"bioerrorlog", "email":"bioerrorlog@example.com"}' \
    https://0oexyzw39c.execute-api.ap-northeast-1.amazonaws.com/v1/users


次のようにレスポンスが帰ってくれば、アプリケーションのデプロイは成功です。

{"username":"bioerrorlog","email":"bioerrorlog@example.com"}


DynamoDBにも、ちゃんとデータが格納されていることが確認できました。

f:id:BioErrorLog:20200412182449p:plain


リソースの削除

作成したリソースを削除するのは、ごく簡単です。

以下のコマンドを実行します。

cdk destroy WebApiDev

成功すれば、今回CDKで作成したリソースが削除されます。


おわりに

今回は、CDKを用いてChaliceアプリケーションをデプロイしました。

Python3.6以下ではうまく機能しないなど躓きどころはありましたが、使いこなせればサーバレスアプリケーションの作成にかかる手間が削減できそうです。

とはいうものの、私はまだSAMやServerless Frameworkなどメジャーなサーバレスフレームワークすらまだよく知らないので、そちらの方も手を出していこうと思っています。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

Deploying AWS Chalice application using AWS Cloud Development Kit | AWS Developer Blog

GitHub - alexpulver/aws-cdk-sam-chalice: Example project for working with AWS CDK, AWS SAM and AWS Chalice

GitHub - aws/chalice: Python Serverless Microframework for AWS

GitHub - alexpulver/cdk-chalice: AWS CDK construct for AWS Chalice

*1:このエラーについては、別記事にもまとめています。