AWS CDKを使ってLamda in VPCを構築する

今回はAWS CDKを使ってLambdaをデプロイしたいと思います。 単一なLambdaをデプロイするところから始まり、最終的には VPCを作成し、その中にLambdaを配置したものをゴールとします。

参考:AWS CDK Intro Workshop

事前準備

CDK CLIのインストール

aws-cdkをグローバルインストールしましょう。

# aws-cdkをインストール
npm install -g aws-cdk

インストール後にcdkコマンドが通るかどうか確認します。 以下のコマンドでバージョンが表示されればOKです。

# バージョンの確認
cdk --version

初期化処理の実行

続いて初期化処理です。 まずはcdk-workshopというディレクトリを作成し、その配下に移動しましょう。

mkdir cdk-workshop && cd cdk-workshop

ここでcdk initコマンドを実行し、初期化を行います。 オプションでTypescriptを使用することを宣言します。

cdk init sample-app --language typescript

しばらく待つとcdk-workshop内に各種ディレクトリ・ファイルが作成されると思います。 以下のような構成になっているはずです。

  • cdk-workshop

    • bin
    • lib
    • node_modules
    • test
    • .gitignore
    • .npmignore
    • cdk.json
    • jest.config.js
    • package-lock.json
    • package.json
    • README.md
    • tsconfig.json

デフォルトではAWS SQSのキューとAWS SNSのトピックを作成するものとなっているので、ここを書き換えて行きます。

Lambdaの作成・デプロイ

Lambdaの作成

まずはLambdaの作成を行います。 cdk-workshopの配下にlambdaディレクトリを作成し、hello.jsファイルを作成します。

mkdir lambda && touch lambda/hello.js

hello.jsの中身は以下のようにしましょう。

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `Hello, CDK! You've hit ${event.path}\n`,
  };
};

スタックの更新

作成したhello.jsを使うようにスタックを更新します。 lib/cdk-workshop-stack.tsを以下のように編集しましょう。

import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';

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

    // Lambdaリソースを宣言
    const hello = new lambda.Function(this, 'HelloHandler', {
      // ランタイムの指定
      runtime: lambda.Runtime.NODEJS_14_X,
      // lambdaディレクトリを指定
      code: lambda.Code.fromAsset('lambda'),
      // hello.jsのhandler関数を指定
      handler: 'hello.handler'
    });
  }
}

Watchの実行

ここまでの手順で何か誤りがないか確認してみます。 以下のコマンドを実行しましょう。

npm run watch

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

Starting compilation in watch mode...
Found 0 errors. Watching for file changes.

npm run watchtscを用いてTypescriptのファイルをコンパイルし、エラーがあるかどうかを監視しています。 この状態でTSファイルを編集すると監視が走り、コンソールが更新されることが分かります。

Synthの実行(テンプレートの生成)

エラーがないことを確認したら、以下のコマンドでテンプレートを生成してみましょう。

cdk synth

しばらく待つと、cdk.outというディレクトリが生成されていると思います。 AWS CDKAWSインフラをコードで記載することができますが、最終的にその内容をAWSに反映させるためにCloud Formationを使います。

その際に用いるテンプレートを生成するのが今のcdk synthコマンドです。 cdk.out/CdkWorkshopStack.template.jsonを参照すると、Cloud Formationの形式に従ってテンプレートが生成されていることが分かります。

Bootstrapの準備

それでは早速デプロイ...といいたいところですが、まだ準備があります。 先ほど述べたように、デプロイ時にもCloud Formationを用いるのですが、 そのテンプレートなどを一時的にS3にアップロードする必要があります。

そのためにブートストラップスタック、というものを用意する必要があります。 「必要がある」とはいっても、実際には下記のコマンドをデプロイの前に実行するだけです。 ※これは「デプロイ先のリージョンごと」に「初回のみ」の手順になります。

cdk bootstrap

実行後S3のバケットを確認するとcdktoolkit-*****といった名前のものが作成されているかと思います。

デプロイ実行

いよいよデプロイです。 下記コマンドを実行しましょう。

cdk deploy

コンソール上に差分(今回は新規作成なので、全て新規)が表示され、確認を経た後でデプロイが行われます。 Cloud Formationのコンソールでもスタックが作成され、完了後にはLambdaが作成されました。

実行ロールを定義して、VPCに含める

今度は作成したLambdaに独自で定義した実行ロールを付けて、さらにVPCの中に配置します。 lib/cdk-workshop-stack.tsを以下のように修正しましょう。

import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';

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

    // VPCを宣言
    const vpc = new ec2.Vpc(this, 'WorkshopVPC', {
      // CIDR
      cidr: '10.1.0.0/16',
      // PublicとPrivateのサブネットを定義
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'WorkshopPublicSubnet',
          subnetType: ec2.SubnetType.PUBLIC
        },
        {
          cidrMask: 24,
          name: 'WorkshopPrivateSubnet',
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED
        }
      ]
    });

    // Lambdaの実行ロールを宣言
    const role = new iam.Role(this, 'WorkshopRole', {
      // ロール名
      roleName: 'workshop-role',
      // LambdaサービスからこのロールにAssumeRoleできるよう設定
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      // ポリシーの宣言
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          'service-role/AWSLambdaVPCAccessExecutionRole'
        ),
      ],
    });

    // Lambdaリソースを宣言
    const hello = new lambda.Function(this, 'HelloHandler', {
      // ランタイムの指定
      runtime: lambda.Runtime.NODEJS_14_X,
      // lambdaディレクトリを指定
      code: lambda.Code.fromAsset('lambda'),
      // hello.jsのhandler関数を指定
      handler: 'hello.handler',
      // VPC内に配置
      vpc: vpc,
      // 実行ロールの設定
      role: role,
    });
  }
}

単一のスタックの記述が長くなりました。 この辺りはCloud Formationのテンプレートを自身で書く場合と同様に、レイヤーごとなどでスタックを切り分けると管理がしやすくなります。

それでは再度デプロイしてみましょう。

cdk deploy

先ほどと同様の手順通りに進んでいけば、LambdaVPCの中に入り、実行ロールが変わっていることが確認できると思います。

まとめ

今回はCDKを使ってVPCの中にLambdaを配置し、独自に定義した実行ロールを割り当てるところまでの実装を行いました。 実際の運用を考える場合にはLambdaGitHubCodeCommitなどからpullしてきたものを用いるので、cdk deployを走らせる前にソースのプルを行う必要があります。

上記のように、実運用する場合には必要な要素がまだ欠けていますが、CDKに置き換えやすい箇所から順番に置き換えていくと、後々デプロイを自動化しやすくなるのではないかと思いました。

今回の内容が役立ちましたら幸いです。

SNSでシェアする