【第1回】AWS CDK × Python インフラ自動化入門 — スタックの基本構造とLambdaリソースを実際に定義してみよう

テックツール

CloudFormationのYAMLを書くのに疲れて、「もうPythonで全部書けたりしないかな」と調べ始めたのがAWS CDKとの出会いでした。

このシリーズは全4回で、AWS CDK(Cloud Development Kit)をPythonで使うための基礎から実践的なパターンまでを扱います。今回の第1回は「そもそもCDKってどういうものか」と「実際にLambda関数をコードで定義するところ」まで。環境構築からデプロイの一連の流れを体験するのがゴールです。

  • 第1回(今回):スタックの基本構造とLambdaリソースを定義する
  • 第2回:DynamoDBとS3をスタックに追加してLambdaと繋げる
  • 第3回:EventBridgeとSQSでイベント駆動アーキテクチャを組む
  • 第4回:GitHub Actionsと連携してCI/CDパイプラインを作る

第2回以降のテーマは変わる可能性があります。あくまで今の構想ということで。

この記事でわかること

  • AWS CDKの基本概念(App、Stack、Construct)
  • L1/L2/L3レベルのConstructの違い
  • CDKの環境セットアップ手順
  • Pythonで実際にLambda関数を定義する書き方
  • cdk synth、bootstrap、deployの実行流れ

AWS CDKとは何か、ざっくり整理する

AWS CDKは、PythonやTypeScript、JavaなどのプログラミングでAWSのインフラをコードとして定義できるIaCフレームワークです。書いたコードは最終的にCloudFormationテンプレートに変換されてデプロイされます。つまり「CloudFormationのフロントエンド」みたいな立ち位置と理解しています。

YAMLで直接CloudFormationを書く方法と比べると、変数やループ、クラスの継承なんかが使えるので、繰り返しの定義が減ります。あと補完が効く。個人的にはこれが一番大きいかもしれません。

現在の主流はCDK v2です。v1は2023年6月にサポートが終了しているので、これから始めるならv2一択でよいかと思います。

CDKの主要な概念

コードを読み書きするうえで最低限おさえておきたい概念が3つあります。

  • App:CDKアプリのエントリーポイント。app.pyがそれにあたります。1つ以上のスタックを含みます。
  • Stack:CloudFormationスタックに1対1でマッピングされる単位。ここにリソースを定義していきます。
  • Construct:AWSリソースを表す部品。L1〜L3の3段階があって、抽象度が違います。

ConstructのL1/L2/L3はよく出てくる概念なので少し補足します。

  • L1(Cfn系):CloudFormationリソースと1対1対応。CfnFunctionのように名前に「Cfn」が付く。設定の自由度は最大だけどほぼYAMLと変わらない。
  • L2:AWSが用意した高レベルな抽象。lambda_.Functions3.Bucketなど。ベストプラクティスがデフォルト値として入っている。普段は主にこれを使う。
  • L3(Patterns):複数のL2を組み合わせたパターン。API Gateway + Lambdaのセットみたいなやつ。

最初はL2だけ使えれば十分です。詰まったときにL1に降りるイメージ。

環境セットアップ

CDKを使うには以下が必要です。

  • Node.js(CDK CLIがNode製のため)
  • AWS CLI(設定済みであること)
  • Python 3.8以上

CDK CLIはnpmでインストールします。

npm install -g aws-cdk
cdk --version

バージョンが表示されればOK。自分が最初にCDKを試したとき「Node.jsが必要」という点で少し躊躇しました。Pythonで書くのになぜ…という感覚ですが、ただCLI部分だけがNodeで、書くコード自体は完全にPythonなので気にしなくて大丈夫です。

プロジェクトを作成する

作業ディレクトリを作ってcdk initを実行します。

mkdir my-cdk-app && cd my-cdk-app
cdk init app --language python

実行すると仮想環境が自動で作成されるとは限りません。必要に応じて自分で仮想環境を作成して、有効化して依存パッケージをインストールします。

source .venv/bin/activate
pip install -r requirements.txt

生成されるディレクトリ構成はこんな感じです。

my-cdk-app/
├── app.py                   # エントリーポイント
├── cdk.json                 # CDKの設定ファイル
├── requirements.txt
├── my_cdk_app/
│   ├── __init__.py
│   └── my_cdk_app_stack.py  # スタックの定義
└── tests/

メインで触るのはapp.pymy_cdk_app/my_cdk_app_stack.pyの2ファイルです。

スタックの基本構造を読む

自動生成されたmy_cdk_app_stack.pyを見ると、こうなっています。

from aws_cdk import Stack
from constructs import Construct

class MyCdkAppStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # ここにリソースを定義していく

Stackクラスを継承して、__init__の中にリソースを追加していく形です。シンプル。**kwargsでスタックのプロパティ(envでリージョン・アカウントを指定するなど)を受け取れます。

app.pyの方はこうなっています。

import aws_cdk as cdk
from my_cdk_app.my_cdk_app_stack import MyCdkAppStack

app = cdk.App()
MyCdkAppStack(app, "MyCdkAppStack")
app.synth()

Appインスタンスを作って、スタックを渡して、synth()を呼ぶ。CDKアプリとは、1つ以上のCDKスタックのコレクションであり、スタックはAWSリソースとプロパティを定義するコンストラクトの集まりです。synth()がCloudFormationテンプレートを生成するトリガーになっています。

LambdaリソースをCDKで定義する

では実際にLambda関数を定義してみます。まずLambdaのハンドラーコードを用意します。

# lambda/handler.py
import json

def handler(event, context):
    print(f"Received event: {json.dumps(event)}")
    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Hello from CDK!"})
    }

次にスタック側でLambda関数を定義します。

from aws_cdk import (
    Stack,
    Duration,
    aws_lambda as lambda_,
)
from constructs import Construct

class MyCdkAppStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        my_function = lambda_.Function(
            self,
            "MyFunction",
            runtime=lambda_.Runtime.PYTHON_3_12,
            handler="handler.handler",
            code=lambda_.Code.from_asset("lambda"),  # ここ注意:lambdaディレクトリへのパス
            timeout=Duration.seconds(30),
            memory_size=256,
            environment={
                "ENV_NAME": "dev",
            },
        )

主要なパラメータをひとつずつ見てみます。

  • runtime:実行環境の指定。CDK v2はすべてのAWSサービスの安定したコンストラクトをaws-cdk-libという単一パッケージに含んでいます。ランタイムはlambda_.Runtime.PYTHON_3_12のように指定します。
  • handler"ファイル名.関数名"の形式。lambda/handler.pyhandler関数なので"handler.handler"
  • code:ハンドラーコードの場所。Code.from_asset()でローカルのディレクトリを指定するのが基本です。
  • timeout:デフォルトは3秒。Durationクラスを使って指定します。
  • environment:環境変数を辞書で渡せます。

正直、最初はhandlerの指定形式でちょっとハマりました。lambda/handler.pyに関数が書いてある場合、handlerに渡すのは"lambda/handler.handler"ではなく"handler.handler"です。codeで指定したディレクトリからの相対パスになるので。

また、このコンストラクトをデプロイすると、AWS::Lambda::Functionリソースが作成または更新されます。更新時、CloudFormationは内部でUpdateFunctionConfigurationUpdateFunctionCodeのLambda APIを順番に呼び出します。この仕組みを知っておくと、デプロイ中の挙動が少し理解しやすくなります。

cdk synthでCloudFormationテンプレートを確認する

コードを書いたら、まずcdk synthを実行してみます。

cdk synth

cdk synthはCDKアプリを合成し、各スタックのCloudFormationテンプレートを含むクラウドアセンブリを作成します。このクラウドアセンブリには、アプリをAWS環境にデプロイするために必要なすべてのファイルが含まれています。実際にAWSへのデプロイは行われません。「自分のコードが何を作ろうとしているか」を確認するコマンドです。

ターミナルにもYAMLが出力されるので、Lambda関数の定義が正しく含まれているか目視チェックできます。デプロイ前に必ずやっておくのがおすすめです。

bootstrapしてからdeployする

初回デプロイの前にcdk bootstrapが必要です。

cdk bootstrap

cdk bootstrapは、CDKを使用してデプロイを行うにあたり必要になるリソースをデプロイするためのコマンドです。実行すると、デフォルトでCDKToolkitという名前のCloudFormationスタックが対象アカウントにデプロイされます。S3やIAMロールなどCDKのデプロイ基盤となるリソースがここに含まれています。同一アカウント・リージョンで一度やれば以後は不要です。

bootstrapが済んだらデプロイします。

cdk deploy

途中でIAMの変更についての確認プロンプトが出ます。yを入力して進めます。しばらく待つとCloudFormationスタックが作成されて、Lambda関数がデプロイされます。

マネジメントコンソールのLambdaページを開くと、定義したとおりの関数が作られているはずです。ここでコードを書いてデプロイまで確認できると、「CDKって本当にちゃんと動くんだ」という実感が得られます。自分は最初にこれを見たとき、ちょっとテンションが上がりました。

リソースを削除するには

作ったリソースを削除したいときはcdk destroyを実行します。

cdk destroy

CloudFormationスタックごと削除されます。ただし、デフォルトだとS3バケットなど一部のリソースはデータ保護のために削除されずに残ることがあります。このあたりは第2回でDynamoDB・S3を追加するときにRemovalPolicyの設定と一緒に扱う予定です。

※この記事にはプロモーションが含まれます

ちなみに、お名前.com レンタルサーバー(WordPressに特化した高速レンタルサーバー。月額990円〜、独自ドメイン実質0円)も気になっています。お名前.com レンタルサーバー

まとめ

今回はAWS CDKの全体像とPythonでLambda関数をスタックに定義するところまで扱いました。

  • CDKはCloudFormationをプログラミング言語で書くためのフレームワーク
  • App、Stack、Constructが3つの主要な概念で、L2レベルのConstructを普段は使う
  • 環境構築はcdk initから始まり、synthbootstrapdeployの流れでデプロイ可能
  • Lambda関数はlambda_.Functionコンストラクトで定義し、ハンドラーのパスはcodeで指定したディレクトリからの相対パス

次回は、このスタックにDynamoDBとS3を追加してLambdaと連携させる方法を見ていきます。

📚 シリーズ「AWS CDK × Python インフラ自動化入門」(第1回 / 全4回)

→ 次回の記事: 公開後にリンクが追加されます

参考になったらクリックしてもらえると嬉しいです!

Blogmura NetDev Ranking
タイトルとURLをコピーしました