Closed6

AWS CDK(Python)を試してみる

kun432kun432

概要

これまで使ったことのあるIaCは以下。

  • Ansible
  • Terraform

あくまでも個人的に思っているだけだけど、これらはインフラエンジニア的視点に立っていると思っていて、これに対して、

  • AWS CDK
  • AWS SAM
  • Serverless Framework

などは、アプリ開発者視点に立っているというふうに感じており、あまり触ったことがない。

とりあえずいくつか触ってみようと思うが、まずはAWS CDKから。

kun432kun432

前提

以下を参考にdevcontainerを作った。

https://zenn.dev/nmemoto/articles/using-aws-cdk-and-aws-sam-with-remote-containers

githubのテンプレートレポジトリにしている。

https://github.com/kun432/devcontainer-aws-cdk-sam

最初のサイトにworkspace周りの設定が含まれているが、ここはちょっと自分でハマってみたいので、一旦設定は入れてない。

とりあえず上記テンプレートレポジトリからレポジトリを作って、vscode devcontainerで作業することとする。足りなければ適宜編集していく。

なお、dockerはremote development で使用しているサーバ上で動かしている。Docker Destktopではないので注意。

kun432kun432

プロジェクト作成

プロジェクトディレクトリ作成。空ディレクトリじゃないとプロジェクトは作れないらしい。ちょっとレポジトリのディレクトリ構成はあとで考えないといけないかも。

$ mkdir cdk-workshop && cd cdk-workshop

プロジェクト作成

$ cdk init sample-app --language python

出力

Applying project template sample-app for python

# Welcome to your CDK Python project!

You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`cdk_workshop_stack`)
which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

This project is set up like a standard Python project.  The initialization process also creates
a virtualenv within this project, stored under the .venv directory.  To create the virtualenv
it assumes that there is a `python3` executable in your path with access to the `venv` package.
If for any reason the automatic creation of the virtualenv fails, you can create the virtualenv
manually once the init process completes.

To manually create a virtualenv on MacOS and Linux:

```
$ python3 -m venv .venv
```

After the init process completes and the virtualenv is created, you can use the following
step to activate your virtualenv.

```
$ source .venv/bin/activate
```

If you are a Windows platform, you would activate the virtualenv like this:

```
% .venv\Scripts\activate.bat
```

Once the virtualenv is activated, you can install the required dependencies.

```
$ pip install -r requirements.txt
```

At this point you can now synthesize the CloudFormation template for this code.

```
$ cdk synth
```

You can now begin exploring the source code, contained in the hello directory.
There is also a very trivial test included that can be run like this:

```
$ pytest
```

To add additional dependencies, for example other CDK libraries, just add to
your requirements.txt file and rerun the `pip install -r requirements.txt`
command.

## Useful commands

 * `cdk ls`          list all stacks in the app
 * `cdk synth`       emits the synthesized CloudFormation template
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk docs`        open CDK documentation

Enjoy!

Please run 'python3 -m venv .venv'!
Executing Creating virtualenv...
✅ All done!

プロジェクトのディレクトリ構造。

$ tree -a -L 2 
.
├── app.py
├── cdk.json
├── cdk_workshop
│   ├── cdk_workshop_stack.py
│   └── __init__.py
├── .gitignore
├── README.md
├── requirements-dev.txt
├── requirements.txt
├── source.bat
├── tests
│   ├── __init__.py
│   └── unit
└── .venv
    ├── bin
    ├── include
    ├── lib
    ├── lib64 -> lib
    └── pyvenv.cfg

8 directories, 11 files

venvで仮想環境が作られていることがわかる。

ではactivateする。ちょっとworkshopの構成が古いようで以下のように行う必要があった。

$ source .venv/bin/activate

仮想環境に入った。

(.venv) vscode ➜ /workspaces/aws-cdk-sample/cdk-workshop (main) $ 

Pythonモジュールをインストールする

$ pip install -r requirements.txt

app.pyがエントリーポイントになる。

app.py
#!/usr/bin/env python3

import aws_cdk as cdk

from cdk_workshop.cdk_workshop_stack import CdkWorkshopStack


app = cdk.App()
CdkWorkshopStack(app, "cdk-workshop")

app.synth()

3行目でインポートされているのがメインとなるコードのファイル。

ということで、cdk_workshop/cdk_workshop_stack.pyを開いてみる。

cdk_workshop/cdk_workshop_stack.py
from constructs import Construct
from aws_cdk import (
    Duration,
    Stack,
    aws_iam as iam,
    aws_sqs as sqs,
    aws_sns as sns,
    aws_sns_subscriptions as subs,
)


class CdkWorkshopStack(Stack):

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

        queue = sqs.Queue(
            self, "CdkWorkshopQueue",
            visibility_timeout=Duration.seconds(300),
        )

        topic = sns.Topic(
            self, "CdkWorkshopTopic"
        )

        topic.add_subscription(subs.SqsSubscription(queue))

ここで、IAM、SQS、SNSが設定されているのがわかる。これを元にCloudFormationテンプレートが生成(synth)される。

デプロイ

ということでcdk synthする。

$ cdk synth

CloudFormationのテンプレートが出力される。

Resources:
  CdkWorkshopQueue50D9D426:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 300
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
    Metadata:
      aws:cdk:path: cdk-workshop/CdkWorkshopQueue/Resource
  CdkWorkshopQueuePolicyAF2494A5:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sqs:SendMessage
            Condition:
              ArnEquals:
                aws:SourceArn:
                  Ref: CdkWorkshopTopicD368A42F
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Resource:
              Fn::GetAtt:
                - CdkWorkshopQueue50D9D426
                - Arn
        Version: "2012-10-17"
      Queues:
        - Ref: CdkWorkshopQueue50D9D426
    Metadata:
      aws:cdk:path: cdk-workshop/CdkWorkshopQueue/Policy/Resource
  CdkWorkshopQueuecdkworkshopCdkWorkshopTopicA7BCA841EC3B13D1:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn:
        Ref: CdkWorkshopTopicD368A42F
      Endpoint:
        Fn::GetAtt:
          - CdkWorkshopQueue50D9D426
          - Arn
    DependsOn:
      - CdkWorkshopQueuePolicyAF2494A5
    Metadata:
      aws:cdk:path: cdk-workshop/CdkWorkshopQueue/cdkworkshopCdkWorkshopTopicA7BCA841/Resource
  CdkWorkshopTopicD368A42F:
    Type: AWS::SNS::Topic
    Metadata:
      aws:cdk:path: cdk-workshop/CdkWorkshopTopic/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/1WNQQrCMBBFz9J9MlYFdd8L1Na9tGnEsTVpMwlSQu5uk4DgZv7/jwdzgPMFyqL7EBfDyCfswbe2EyPb0N3TQuCvTjrJqofKJd1aTyjWH8wzMFKb37qehMHZolbR+Ns3PaOINJUQYm0kaWdE+lFpNWA0A6tX+9Rqd4R9CafiRYjcOGXxLaHJ+QVjDGCovgAAAA==
    Metadata:
      aws:cdk:path: cdk-workshop/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - af-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.

上記の実体はcdk.outディレクトリ内に出力されているcdk-workshop.template.jsonがそれ。他にもいろいろあるみたい。

$ tree -a cdk.out
cdk.out
├── cdk.out
├── cdk-workshop.assets.json
├── cdk-workshop.template.json
├── manifest.json
└── tree.json

0 directories, 5 files

初めてデプロイするときはbootstrapが必要になる。AWSアカウントIDとリージョンを指定してcdk bootstrapを実行する。アカウントIDとリージョンはaws sts get-caller-identityとかaws configure get regionで確認すればいちいちマネージメントコンソール開かなくて済む。

$ cdk bootstrap aws://XXXXXXXXXXXX/ap-northeast-1

こんな感じでリソースが作成される。

⏳  Bootstrapping environment aws://XXXXXXXXXXXX/ap-northeast-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
CDKToolkit: creating CloudFormation changeset...
[█████████▋················································] (2/12)

4:19:45 PM | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | CDKToolkit
4:19:51 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role          | LookupRole
4:19:51 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role          | CloudFormationExecutionRole
4:19:51 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role          | ImagePublishingRole
4:19:51 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role          | FilePublishingRole
4:19:52 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket         | StagingBucket

以下のように表示されればOK。

 ✅  Environment aws://XXXXXXXXXXXX/ap-northeast-1 bootstrapped.

マネージメントコンソールでCloudFormationを見ると以下のようにスタックが作成されているのがわかる。

ではアプリケーションおよびインフラのデプロイを行いたいところだけど、その前にapp.pyを修正する。

app.py
(snip)
app = cdk.App()
CdkWorkshopStack(app, "cdk-workshop",
    env=cdk.Environment(account="XXXXXXXXXXXX", region="ap-northeast-1"),  # 追加
)
(snip)

これが正しいのかどうかわからないけど、これをせずにcdk deployするとus-east-1を見に行くようで、bootstrapされてないからリソース作れないとか言われる。

上記の修正を行ったらデプロイする。

$ cdk deploy

以下のように表示されたらyですすめる。これはSNSがSQSにアクセスできるようにアクセス権を修正するらしい。

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬─────────────────────┬────────┬─────────────────────┬─────────────────────┬──────────────────────┐
│   │ Resource            │ Effect │ Action              │ Principal           │ Condition            │
├───┼─────────────────────┼────────┼─────────────────────┼─────────────────────┼──────────────────────┤
│ + │ ${CdkWorkshopQueue. │ Allow  │ sqs:SendMessage     │ Service:sns.amazona │ "ArnEquals": {       │
│   │ Arn}                │        │                     │ ws.com              │   "aws:SourceArn": " │
│   │                     │        │                     │                     │ ${CdkWorkshopTopic}" │
│   │                     │        │                     │                     │ }                    │
└───┴─────────────────────┴────────┴─────────────────────┴─────────────────────┴──────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

リソースのデプロイが行われる。

cdk-workshop: creating CloudFormation changeset...
[███████████████████▎······································] (2/6)

4:38:15 PM | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | cdk-workshop
4:38:20 PM | CREATE_IN_PROGRESS   | AWS::SQS::Queue        | CdkWorkshopQueue

以下のように表示されればOK。

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cdk-workshop/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

✨  Total time: 93.12s

プロジェクト名と同じスタックが作成されているのがわかる。

ということでアプリを作っていくのだけど、続きはまた今度。

リソースの削除

一旦削除しておく。削除はcdk destroy

$ cdk destroy

確認が行われるのでyで。

Are you sure you want to delete: cdk-workshop (y/n)? 

リソースが削除される。

cdk-workshop: destroying... [1/1]
4:57:36 PM | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | cdk-workshop
4:57:41 PM | DELETE_IN_PROGRESS   | AWS::SNS::Topic        | CdkWorkshopTopic
4:57:41 PM | DELETE_IN_PROGRESS   | AWS::SQS::Queue        | CdkWorkshopQueue

以下のように表示されればOK。

 ✅  cdk-workshop: destroyed

CloudFormationを見てみると、cdk deployで作成されたスタックが削除されていることがわかる。cdk bootstrapのほうは残っていて、こちらはプロジェクトとは別にCDKの利用のためのスタックであることがわかる。

kun432kun432

続き

一旦cdk destroyしたので再度deployから。

$ cd cdk-workshop/
$ source .venv/bin/activate

通常はデプロイしたままで終わるはずなので、普段の作業開始のときは↑のようにすればよいと思う。

ではデプロイ。

$ cdk deploy

リソースの追加とデプロイ

ということで今回は実際にサンプルアプリとそれに必要なリソースを足していく。

https://cdkworkshop.com/ja/30-python/30-hello-cdk.html

サンプルのboilerplateではIAM/SQS/SNSの設定が含まれていたがこれらは不要なので削除する。

変更前

cdk-workshop/cdk_workshop/cdk_workshop_stack.py
from constructs import Construct
from aws_cdk import (
    Duration,
    Stack,
    aws_iam as iam,
    aws_sqs as sqs,
    aws_sns as sns,
    aws_sns_subscriptions as subs,
)


class CdkWorkshopStack(Stack):

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

        queue = sqs.Queue(
            self, "CdkWorkshopQueue",
            visibility_timeout=Duration.seconds(300),
        )

        topic = sns.Topic(
            self, "CdkWorkshopTopic"
        )

        topic.add_subscription(subs.SqsSubscription(queue))

変更後

cdk-workshop/cdk_workshop/cdk_workshop_stack.py
from constructs import Construct
from aws_cdk import (
    Stack,
)


class CdkWorkshopStack(Stack):

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

変更後のコードを適用するとリソースにどういう変更が行われるかを確認する。cdk diffを実行する。

$ cdk diff

出力

Stack cdk-workshop
IAM Statement Changes
┌───┬────────────────────────────┬────────┬─────────────────┬────────────────────────────┬─────────────────────────────┐
│   │ Resource                   │ Effect │ Action          │ Principal                  │ Condition                   │
├───┼────────────────────────────┼────────┼─────────────────┼────────────────────────────┼─────────────────────────────┤
│ - │ ${CdkWorkshopQueue.Arn}    │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com  │ "ArnEquals": {              │
│   │                            │        │                 │                            │   "aws:SourceArn": "${CdkWo │
│   │                            │        │                 │                            │ rkshopTopic}"               │
│   │                            │        │                 │                            │ }                           │
└───┴────────────────────────────┴────────┴─────────────────┴────────────────────────────┴─────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::SQS::Queue CdkWorkshopQueue CdkWorkshopQueue50D9D426 destroy
[-] AWS::SQS::QueuePolicy CdkWorkshopQueue/Policy CdkWorkshopQueuePolicyAF2494A5 destroy
[-] AWS::SNS::Subscription CdkWorkshopQueue/cdkworkshopCdkWorkshopTopicA7BCA841 CdkWorkshopQueuecdkworkshopCdkWorkshopTopicA7BCA841EC3B13D1 destroy
[-] AWS::SNS::Topic CdkWorkshopTopic CdkWorkshopTopicD368A42F destroy


コードから削除したリソースが削除対象となっていることがわかる。ではデプロイして削除する。

$ cdk deploy

出力


✨  Synthesis time: 3.15s

cdk-workshop: building assets...

[0%] start: Building c1f0976937c982e5b398d640697ca4de5a97c24183431f0d2c4ac590314544a4:XXXXXXXXXXXX-ap-northeast-1
c1f0976937c982e5b398d640697ca4de5a97c24183431f0d2c4ac590314544a4:XXXXXXXXXXXX-ap-northeast-1
[100%] success: Built c1f0976937c982e5b398d640697ca4de5a97c24183431f0d2c4ac590314544a4:XXXXXXXXXXXX-ap-northeast-1

cdk-workshop: assets built

cdk-workshop: deploying... [1/1]
[0%] start: Publishing c1f0976937c982e5b398d640697ca4de5a97c24183431f0d2c4ac590314544a4:XXXXXXXXXXXX-ap-northeast-1
[100%] success: Published c1f0976937c982e5b398d640697ca4de5a97c24183431f0d2c4ac590314544a4:XXXXXXXXXXXX-ap-northeast-1
cdk-workshop: creating CloudFormation changeset...
[█████████████████████████████████▏························] (4/7)

12:25:21 AM | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack | cdk-workshop
12:25:25 AM | DELETE_IN_PROGRESS   | AWS::SQS::Queue    | CdkWorkshopQueue50D9D426

CloudFormationのスタックが更新されて、リソースが順に削除されていくのがわかる。以下のように表示されれば削除完了。

 ✅  cdk-workshop

✨  Deployment time: 85s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cdk-workshop/7bff4550-ed35-11ed-9ebb-0ad2deb1c099

✨  Total time: 88.15s

ここからLambdaとAPI Gatewayのコードを書いていく。

まずLambda。

プロジェクトのルートディレクトリにlambda用のディレクトリを作成する。

$ mkdir lambda

Lambdaで実行されるコードを用意する。

lambda/hello.py
import json

def handler(event, context):
    print("request: {}".format(json.dumps(event)))
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "text/plain"
        },
        "body": "Hello, CDK! You have hist {}\n".format(event["path"])
    }

次にCDKでLambdaをデプロイするコードを書いていく。CDKでは各サービスがモジュールとなっているのでこれを呼び出す必要がある。AWS Construct Libraryというらしい。

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-construct-library.html

cdk_workshop/cdk_workshop_stack.pyに追加していく。

変更前

cdk-workshop/cdk_workshop/cdk_workshop_stack.py
from constructs import Construct
from aws_cdk import (
    Stack,
)


class CdkWorkshopStack(Stack):

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

ステップ・バイ・ステップでやっていく。Lambdaモジュールをimportする。

cdk-workshop/cdk_workshop/cdk_workshop_stack.py
from constructs import Construct
from aws_cdk import (
    Stack,
    aws_lambda as _lambda,     # 追加
)

(snip)

Lambdaの定義を追加

cdk-workshop/cdk_workshop/cdk_workshop_stack.py
(snip)

class CdkWorkshopStack(Stack):

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

        # 以下を追加
        my_lambda = _lambda.Function(
            self, 'HelloHandler',
            runtime=_lambda.Runtime.PYTHON_3_10,
            code=_lambda.Code.from_asset('lambda'),
            handler="hello.handler",
        )

変更内容を見てみる。

$ cdk diff
Stack cdk-workshop
IAM Statement Changes
┌───┬─────────────────────────────────┬────────┬────────────────┬──────────────────────────────┬───────────┐
│   │ Resource                        │ Effect │ Action         │ Principal                    │ Condition │
├───┼─────────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤
│ + │ ${HelloHandler/ServiceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:lambda.amazonaws.com │           │
└───┴─────────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬─────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                    │ Managed Policy ARN                                                             │
├───┼─────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴─────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[+] AWS::IAM::Role HelloHandler/ServiceRole HelloHandlerServiceRole11EF7C63 
[+] AWS::Lambda::Function HelloHandler HelloHandler2E4FBA4D 

ではデプロイする。

$ cdk deploy

デプロイが完了したらコンソールを見てみるとLambda関数が作成されているのがわかる。関数の名前は"プロジェクト名-ID"にランダムな文字列が付与されている模様。

"API Gateway AWS Proxy"のテストを実行して問題なければOK。

ではLamdbaのコードを変更して反映してみる。

cdk-workshop/lambda/hello.py
import json

def handler(event, context):
    print("request: {}".format(json.dumps(event)))
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "text/plain"
        },
        "body": "Good Afternoon, CDK! You have hist {}\n".format(event["path"])     # 変更
    }

cdk deploy --hotswap / cdk watch

通常であればcdk deployで変更することになるが、lambdaのコード変更だけで、CloudFormationスタックを更新してデプロイ・Lambdaのコードを更新してデプロイになると時間がかかる。たぶん、CloudFormationスタックが見ているのはLambda関数のデプロイだけで中身のコードはS3バケットにあるものを読み込んでいるだけ、つまりCloudFormationとは別で管理されているということになるということなのだろうか?

こういう場合はcdk deploy --hotswapを使うと、デプロイを高速化できるらしい。やってみる。

$ cdk deploy --hotswap
✨  Synthesis time: 3.15s

⚠️ The --hotswap and --hotswap-fallback flags deliberately introduce CloudFormation drift to speed up deployments
⚠️ They should only be used for development - never use them for your production Stacks!

cdk-workshop: building assets...

(snip)

✨ hotswapping resources:
   ✨ Lambda Function 'cdk-workshop-HelloHandler2E4FBA4D-sBfVPDHNOFp1'
✨ Lambda Function 'cdk-workshop-HelloHandler2E4FBA4D-sBfVPDHNOFp1' hotswapped!

 ✅  cdk-workshop

✨  Deployment time: 3.69s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cdk-workshop/7bff4550-ed35-11ed-9ebb-0ad2deb1c099

✨  Total time: 6.84s

実行時間が先程と比べると非常に短いのがわかる。Lambdaのテストで確認したらちゃんと変更されていた。

ただし、実行時のメッセージにも出ている通り、やはりこれはCloudFormationスタックの設定と乖離が出てしまう様子(TerraformでいうところのOutside of Terraform)なので、開発時にサクサク確認したいみたいな場合にやるもので、本番環境とかではやるべきではないということらしい。

⚠️ The --hotswap and --hotswap-fallback flags deliberately introduce CloudFormation drift to speed up deployments
⚠️ They should only be used for development - never use them for your production Stacks!

ちなみにcdk deploy --hotswapができるかどうかはリソースによるみたいで、--hotswapできない場合は通常のcdk deployにフォールバックしてくれるらしい。

さらにこれをすすめるためにcdk watchというのがある。こちらはコードの変更を監視して、変更されたら即反映するというもの。デフォルトだと--hotswapが有効になるようなので、開発時はサクサク進めたいという場合に便利な様子。

$ cdk watch

変更を検知するとこんな感じのログが流れて、デプロイが行われる。

Detected change to 'lambda/hello.py' (type: change). Triggering 'cdk deploy'

変更監視対象とするファイル等の設定はcdk.jsonで行えば良い様子。

cdk.json
{
  "app": "python3 app.py",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "requirements*.txt",
      "source.bat",
      "**/__init__.py",
      "python/__pycache__",
      "tests"
    ]
  },
(snip)

とりあえずは理解したのだけど、これドリフトが発生することになるのはどうやって解消するんだろう?最終的にcdk deployすればいいということなのかな?やってみる。

$ cdk deploy

ふむ、CloudFormationのスタックも更新されて反映された様子。

(snip)
cdk-workshop: creating CloudFormation changeset...

 ✅  cdk-workshop

✨  Deployment time: 23.13s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cdk-workshop/7bff4550-ed35-11ed-9ebb-0ad2deb1c099

✨  Total time: 26.27s

再度デプロイしてみたけど変更なしになっているので、これでいいみたい。Terraformとか触ってた身からすると差分が起きるのはちょっと躊躇する部分もあるけど、こういうものなのかもしれない。

 ✅  cdk-workshop (no changes)

次にAPI Gatewayを追加する。Lambdaのときと同じようにコードを追加していく。

cdk-workshop/cdk_workshop/cdk_workshop_stack.py
from constructs import Construct
from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_apigateway as apigw,     # 追加
)


class CdkWorkshopStack(Stack):

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

        my_lambda = _lambda.Function(
            self, 'HelloHandler',
            runtime=_lambda.Runtime.PYTHON_3_10,
            code=_lambda.Code.from_asset('lambda'),
            handler="hello.handler",
        )

        # 以下を追加
        apigw.LambdaRestApi(
            self, 'Endpoint',
            handler=my_lambda
        )

なるほど、他のリソースのパラメータで使う場合はこういうふうに受け渡すのね。あと、aws_apigatewayv2というリソースもあるけど、こちらは書き方が違うっぽくてパッと見でわからなかったので、またそのうち調べるということで。

デプロイ・・・ってかdiff取るの忘れたな。このあたりは毎回変更内容を出力してくれてかならず最終確認してくれるTerraformとは違うので意しないといけないなー。

$ cdk deploy

で出力内容にこういうのが含まれている。

Outputs:
cdk-workshop.Endpoint8024A810 = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/

curlでアクセスしてみるとAPI Gateway経由でLambdaが応答しているのがわかる。

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amaz
onaws.com/prod/
Good Evening, CDK! You have hist /

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/hogehoge
Good Evening, CDK! You have hist /hogehoge

このあともう少し複雑な感じになるのだけど、一旦ここまで。

リソースは削除しておく。

$ cdk destroy

まとめ

あくまでも個人的な感想。

TerraformやAnsibleあたりと比べると、

  • コードの量は少なくて済みそう
  • 宣言型だといろいろ面倒なロジック的な処理も当然書きやすい

というのがメリットかなと思う。反面、

  • 少ないコードで書けてしまうのでいろいろ隠蔽されて見えないことが多い
    • Terraformだと何かを追加する場合、関連するリソースとかも見る必要が出てくる。
    • これが逆にAWSリソースへの理解が逆に深まるところがあると感じる。
    • 初学者にはそのほうがいいかもしれない、特にインフラエンジニアならAnsibleとか触っていれば入りやすい、とか、ふと思った。
  • 今回Pythonで書いたけど、TypeScriptのほうが情報が多そうな気がする
    • なーんとなく個人的に勝手に感じてるのはTypeScriptのほうがメインの開発ラインなのかなーと。しらんけど。
    • 個人的には今ならPythonで書きたい、LLM周りはPython多いし、だいぶ慣れてきたこともあるし。
  • CloudFormation全然慣れない
    • 状態管理をマネージドに任せれるのはいいんだけど。Terraformのtfstateも結構しんどいし。
    • どうしても一歩引いてしまう自分がいる・・・

あたりはちょっと気になるところではある。

とりあえずここまで。気が向いたら続きをやる。

kun432kun432

そういえば、AWSアカウントIDをapp.pyにベタ書きしたのだけど、

cdk-workshop/app.py
(snip)
app = cdk.App()
CdkWorkshopStack(app, "cdk-workshop",
    env=cdk.Environment(account="XXXXXXXXXXXX", region="ap-northeast-1"),
)
(snip)

ここはenvfileとかを使ったほうがよい。

requirements.txt
(snip)
python-dotenv
$ pip install -r requirements.txt

.envファイルを用意する。

cdk-workshop/.env
AWS_ACCOUNT_ID=XXXXXXXXXXXX
AWS_REGION=ap-northeast-1

.envを読み込むconfig.py

cdk-workshop/config.py
from dotenv import load_dotenv
import os

load_dotenv()

AWS_ACCOUNT_ID = os.getenv('AWS_ACCOUNT_ID')
AWS_REGION = os.getenv('AWS_REGION')

config.pyをapp.pyでインポートする

cdk-workshop/app.py
#!/usr/bin/env python3

import aws_cdk as cdk

from cdk_workshop.cdk_workshop_stack import CdkWorkshopStack

from config import *     # 追加

app = cdk.App()
CdkWorkshopStack(app, "cdk-workshop",
    env=cdk.Environment(account=AWS_ACCOUNT_ID, region=AWS_REGION),     # 変更
)

app.synth()

ちなみに.gitignoreには既に追加されていた。

このスクラップは2023/10/13にクローズされました