LoginSignup
3
0

【AWS CDK入門】AWS CDKでAPI Gateway + Lambdaを構築してみた

Posted at

はじめに

AWS CDKってドキュメント見てもよくわからん...ってなったので、実際に動かしてみた。
すると、CDKすげええええぇってなったので、その感動を共有したくて記事を書いてみた。

対象とする読者

  • AWS CDKとはなんぞやって人
  • AWS CDKをお金をかけずに動かしてみたい人

AWS CDK のこれだけ知っておけばOK

AWSのIaC (コードでインフラを管理)といえば、CloudFormation!
→ しかし、以下の課題が・・・

  • YAML/JSONなので、ループや条件分岐などを用いたリソース定義が書けない
  • 大規模なインフラだと、一から十まで書くの大変

これらを解決するのが、AWS CDK!
→ CloudFormation用のテンプレートをプログラム言語から生成しちゃおう

  • TypeScriptやPythonなどでリソースを定義するのでループなど自由自在
  • ライブラリでリソースが抽象化されているので記述量が大幅減
    (抽象化 = REST APIを作るといったパターンが用意されている)

AWS CDK を触ってみよう

何はともあれ、触ってみよう

やったこと

  • AWS CDKの実行環境構築
  • CDKが用意しているsample-app(SNS + SQS)の構築
  • API Gateway + Lambdaの環境構築

筆者の環境

  • macOS
  • AWSアカウント、IAMユーザー作成済み
  • Node.jsインストール済み

1. sample-app を読み解きそのままデプロイ

CDKが用意しているsample-appをそのままデプロイする。配備される資源は以下の通り。

1.1 事前準備

cdkコマンドが実行できるように、アクセスキーIDとシークレットアクセスキーを取得する。

  1. IAMユーザでAWSマネジメントコンソールにログイン
  2. IAMサービスに移動し、「自分の認証情報」を開く
  3. 「アクセスキーを作成」から、アクセスキーIDとシークレットアクセスキーを作成
  4. 以下内容のファイルを作成し、~/.aws/credentialsとして配置
~/.aws/credentials
[default]
aws_access_key_id = 【アクセスキーID】
aws_secret_access_key = 【シークレットアクセスキー】

[default]の部分は必要に応じて、profileを設定して使い分ける必要がある。

1.2 aws-cdkのインストール

$ npm install -g aws-cdk

1.3 プロジェクト作成 (cdk init)

以下を実行して、プロジェクトを作成する。

$ mkdir cdk-workshop && cd cdk-workshop
$ cdk init sample-app --language typescript

上記を実行すると、いくつかのディレクトリとファイルが作成される。後述するcdk synthコマンドでテンプレートを作成する際に実行されるコマンドは、cdk.jsonの以下の行である。

cdk.json
"app": "npx ts-node --prefer-ts-exts bin/cdk-workshop.ts",

したがって、まずはbin/cdk-workshop.tsを確認してみる。

bin/cdk-workshop.ts
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { CdkWorkshopStack } from '../lib/cdk-workshop-stack';

const app = new cdk.App();
new CdkWorkshopStack(app, 'CdkWorkshopStack');

これを見ると、デプロイするスタックの内容はlib/cdk-workshop-stack.tsに定義されていることがわかる。そこで、次にlib/cdk-workshop-stack.tsを見てみる。

lib/cdk-workshop-stack.ts
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import { Construct } from 'constructs';

export class CdkWorkshopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const queue = new sqs.Queue(this, 'CdkWorkshopQueue', {
      visibilityTimeout: Duration.seconds(300)
    });

    const topic = new sns.Topic(this, 'CdkWorkshopTopic');

    topic.addSubscription(new subs.SqsSubscription(queue));
  }
}

これを見ると、デプロイ時に以下が実施されるようだ。

  • 可視性タイムアウト300秒の「CdkWorkshopQueue」というキューの作成
  • 「CdkWorkshopTopic」というSNSトピックの作成
  • 「CdkWorkshopTopic」のサブスクリプションとして「CdkWorkshopQueue」の追加

1.4 CloudFormationテンプレート作成 (cdk synth)

次に以下コマンドを実行して、CloudFormaitonテンプレートを作成する。

$ cdk synth

すると、以下のファイルが作成される。

cdk.out/
├── CdkWorkshopStack.assets.json
├── CdkWorkshopStack.template.json  <- CloudFormationテンプレート
├── cdk.out
├── manifest.json
└── tree.json

1.5 ブートストラップの実行 (cdk bootstrap)

cdkのデプロイに必要な、S3バケット、IAMロール、SSMパラメータ等を作成する。
※cdkによるデプロイの初回のみ行う。

$ cdk bootstrap

1.6 デプロイ (cdk deploy)

最後に以下コマンドを実行し、資源をデプロイする。

$ cdk deploy

以下のようにIAMポリシーの変更内容について確認されるので、「y」と入力する。

IAM Statement Changes
┌───┬─────────────────────────────┬────────┬─────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
│   │ Resource                    │ Effect │ Action                      │ Principal                   │ Condition                      │
├───┼─────────────────────────────┼────────┼─────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
│ + │ ${CdkWorkshopQueue.Arn}     │ Allow  │ sqs:SendMessage             │ Service:sns.amazonaws.com   │ "ArnEquals": {                 │
│   │                             │        │                             │                             │   "aws:SourceArn": "${CdkWorks │
│   │                             │        │                             │                             │ hopTopic}"                     │
│   │                             │        │                             │                             │ }                              │
└───┴─────────────────────────────┴────────┴─────────────────────────────┴─────────────────────────────┴────────────────────────────────┘
(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)? 

AWS CDK のここがすごい!!
SNSからSQSキューへ通知するには、SQSキューのアクセスポリシーにそれを許可するIAMポリシーが必要であるが、CDKが自動で作成してくれていた!!

2. API Gateway+Lambda環境を構築

2.1 スタック変更

以下のような環境を構築する。

ただし、そのままだとAPIを誰でも叩けてしまうので、APIキーと使用量プランによる利用制限をかける。

スタックの内容を以下のように変更する。

lib/cdk-workshop-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Runtime, Code } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';

export class CdkWorkshopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const helloLambda = new NodejsFunction(this, 'HelloHandler', {
      runtime: Runtime.NODEJS_18_X,
      entry: 'src/hello.ts',
      handler: 'handler'
    });
    const helloApi = new LambdaRestApi(this, 'hello-endpoint', {
      handler: helloLambda,
      defaultMethodOptions: { apiKeyRequired: true },
      proxy: false
    });
    const helloEndPoint = helloApi.root.addResource('hello');
    helloEndPoint.addMethod('GET');

    const apiKey = helloApi.addApiKey('ApiKey', {
      apiKeyName: 'hello-api-key'
    });
    const plan = helloApi.addUsagePlan('UsagePlan', {
      name: 'hello-api-usageplan'
    });
    plan.addApiKey(apiKey);
    plan.addApiStage({ stage: helloApi.deploymentStage });
  }
}

Lambda関数にデプロイするソースコードは以下の通り。

src/hello.ts
import {
  APIGatewayEventRequestContextV2,
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2
} from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEventV2,
  context: APIGatewayEventRequestContextV2
): Promise<APIGatewayProxyResultV2> => {
  console.log({ event, context });
  const responseBody = {
    message: 'Hello World!'
  };
  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(responseBody)
  };
};

2.2 cdk synth & cdk deploy

→ デプロイすると、先ほど構築したSNS+SQS環境が削除され、新しくAPI Gateway+Lambda環境が構築される。

cdk synthコマンドを実行すると Error: spawnSync docker ENOENT が出る場合
$ cdk synth
Error: spawnSync docker ENOENT
    at Object.spawnSync (node:internal/child_process:1117:20)
    at spawnSync (node:child_process:876:24)
    at dockerExec (/Path/to/directory/cdk-workshop/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:1:3585)
    at Function.fromBuild (/Path/to/directory/cdk-workshop/node_modules/aws-cdk-lib/core/lib/bundling.js:1:4718)
    at new Bundling (/Path/to/directory/cdk-workshop/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js:1:3776)
    at Function.bundle (/Path/to/directory/cdk-workshop/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js:1:960)
    at new NodejsFunction (/Path/to/directory/cdk-workshop/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/function.js:1:1729)
    at new CdkWorkshopStack (/Path/to/directory/cdk-workshop/lib/cdk-workshop-stack.ts:11:25)
    at Object.<anonymous> (/Path/to/directory/cdk-workshop/bin/cdk-workshop.ts:6:1)
    at Module._compile (node:internal/modules/cjs/loader:1356:14) {
  errno: -2,
  code: 'ENOENT',
  syscall: 'spawnSync docker',
  path: 'docker',
  spawnargs: [
    'build',
    '-t',
    'cdk-805ab1d42744d346e7ee81b097a37bd764cf7a67af41bcccaad6ccee384363e2',
    '--platform',
    'linux/amd64',
    '--build-arg',
    'IMAGE=public.ecr.aws/sam/build-nodejs18.x',
    '--build-arg',
    'ESBUILD_VERSION=0',
    '/Path/to/directory/cdk-workshop/node_modules/aws-cdk-lib/aws-lambda-nodejs/lib'
  ]
}

NodejsFunctionでは、esbuildを用いてTypeScriptをビルドする。
macOSでは手動でインストールする方が良いらしく、以下を実施すると解消した。
参考:[備忘録] Error: spawnSync docker ENOENTの解消

$ npm i -D esbuild

2.3 APIのテスト

URLをコンソールから確認

APIキーをコンソールから確認

APIキーをヘッダーに渡して以下を実行

$ curl -X GET -H "x-api-key: APIキー" https://xxxx.execute-api.us-east-1.amazonaws.com/prod/hello
{"message":"Hello World!"} 

ちなみに誤ったAPIキーを指定すると以下のようになる

$ curl -X GET -H "x-api-key: 誤ったAPIキー" https://xxxx.execute-api.us-east-1.amazonaws.com/prod/hello
{"message":"Forbidden"} 

まとめ・所感

  • AWS CDKはすごい!!ちょっとした検証を行うとき、ささっと環境構築できそう!!
  • リソースの定義・連携の記述方法は、慣れないとちょっと難しい。
    → 結局公式のドキュメントが一番わかりやすいかも?

参考文献

以下の記事を参考にさせていただきました。ありがとうございます。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0