😇

AWS CDKメモ

2022/07/30に公開

基本

cdk --version: 2.30
CDK:typescript
Lambda:Python3.9

cdk workshopをベースにして拡張する。

初期化

  • プロジェクトのディレクトリを作成し、initを実行する
    • ディレクトリ名がcdk-workshopならスタック名がCdkWorkshopになる
    • lib/cdk-workshop-stack.tsにcdkの本体を記述する
cdk init app --language typescript

cdk.StackかStackか

この2つの書き方はimportの仕方の違いだけで、同じ。記述が混ざっている場合があるので注意

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

export class CdkWorkshopStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

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

Lambda基本

lib/cdk-workshop-stack.ts
    const hello = new lambda.Function(this, 'HelloHandler', {
      runtime: lambda.Runtime.PYTHON_3_9,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'my_lambda.handler',
      environment: {'SNS_ARN': myTopic.topicArn,},
      retryAttempts: 0,
      timeout: cdk.Duration.seconds(60),
    });
  • ts内ではhelloでアクセス
  • LogicalIDは HelloHandlerにハッシュを追加したものになる
  • handler: my_lambda.handler の内容を、lambda/my_lambda.py に記載する
    • lambda/lib/ と同じ階層
  • ロールを指定しない場合は自動で作成されるが、自分で設定する場合は myRoleを作って role: myRoleで設定する
  • environment: {Key:Value} でLambdaの環境変数を設定する。ここでは myTopicをいうSNSトピックのArnにアクセスしている
  • リトライ0に
  • タイムアウトの設定 Durationを個別にインポートしてもよい

ポリシー追加

  • ポリシーをhelloのRoleに追加する
  • ポリシーを作るとき、hello function arnを参照するとCircular dependencyエラーになってしまう TODO
   const snsTopicPolicy = new iam.PolicyStatement({
     actions: ['sns:*'],
     // resources: [hello.functionArn],
     resources: ['*'],
   });

   hello.addToRolePolicy(snsTopicPolicy);

マネージドポリシーを追加するとき

    // hello.addToRoleManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"));
    // myRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"));

Roleを自分で作ってLambdaに設定する

    const myRole = new iam.Role(this, 'My Role', {
      assumedBy: new iam.ServicePrincipal('sns.amazonaws.com'),
    });

lambda.Functionのプロパティ設定に

handler: 'my_lambda.handler',
role: myRole,

のように追加する(handler: と同じ列)

タイムアウト。デフォルトは3秒?設定するならcdk.Durationを使う。

      timeout: cdk.Duration.seconds(3)

${accountID}とかpseudo parametersを使いたい

  • ScopedAwsをインポート
  • const { accountId, region } = new ScopedAws(this);で定義
  • ${region}:${accountId}などを使うときは、バッククオートで囲む
import { ScopedAws } from 'aws-cdk-lib';

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

    const { accountId, region } = new ScopedAws(this);
    
    const lambdaPolicy = new iam.PolicyStatement({
      actions: ['*'],
      resources: [`arn:aws:cloudformation:${region}:${accountId}:stack/Cdk*/*`],
    });

環境変数を.envファイルから読んでCDKで使う

  • ローカルの環境変数から読むこともできるし、.envから読むこともできる。ただし、ローカル環境変数の場合は、cdk synthで読んでくれない。
  • .gitignore に .env を追加することで、環境変数を git 管理外にする
  • dotenvをインストール npm install dotenv --save
  • .envファイルをプロジェクトのルートに作り、環境変数を記載する
.env
SNS_ARN=arn:aws:snsxxxxxxxxxxxxxxxxxxxxx

呼び出し方

lib/cdk-workshop-stack.ts
import * as dotenv from 'dotenv';

dotenv.config();

を記述してから

process.env.SNS_ARN 

で呼び出す。

SNSトピックを作成

lib/cdk-workshop-stack.ts

import * as sns from 'aws-cdk-lib/aws-sns';
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
    const myTopic = new sns.Topic(this, 'MyTopic');

emailサブスクライブ

ハードコードで

    myTopic.addSubscription(new subscriptions.EmailSubscription('your@mail'));

Contextを使う

  • cdk deploy, cdk synth, で --context email=your@mailを追加
    const email = this.node.tryGetContext('email');
    myTopic.addSubscription(new subscriptions.EmailSubscription(email));

既存のSNSトピックを使う

ハードコード

    const topic = sns.Topic.fromTopicArn(this, 'MyTopic', <topic-arn>);

ローカル環境変数を読む

(設定は別記)

    const myTopic = sns.Topic.fromTopicArn(this, 'MyTopic', process.env.SNS_ARN ?? '');
    const Email = process.env.MAIL ?? '';
    const Topic = new sns.CfnTopic(this, 'Topic', {
    subscription: [{
      endpoint: Email,
      protocol: 'email',
      }],
    });

CDKで、CloudFormationのParameter

CDKでは非推奨になっている。(Contextが推奨)

const emailAddress = new cdk.CfnParameter(this, 'email-param');

と書いておく。

デプロイするときは、
cdk deploy --parameters emailparam=aaaa@example.com のようにする。

複数のパラメータがあるときはコンマでつなぐ
--parameters emailparam=aaaa@example.com,other_pram=1

LogicalIDではハイフンが削除されてemail-param が emailparamになることに注意。

cdk synthをするとyamlではこのようになる。

Parameters:
  emailparam:
    Type: String

プロパティ追加できるdoc
プロパティ追加するとsynthはできるけど、deployでエラーになる。

    const emailAddress = new cdk.CfnParameter(this, 'email-param', {
      type: 'string',
      description: 'Some description',
      default: 'default@mail'
    });

こちらはOK

cdk deploy --parameters emailparam=aaaa@example.com
    const email = new CfnParameter(this, 'email-param');
    myTopic.addSubscription(new subscriptions.EmailSubscription(email.valueAsString));

Outputで変数確認

helloFnというLambda、emailというcontextがあるとする。

    new cdk.CfnOutput(this, 'Output_1', { value: helloFn.functionArn });
    new cdk.CfnOutput(this, 'Output_2', { value: email });
cdk synth --context email=aaaa@example.com

とすると、こういうOutputsが出る。

Outputs:
  Output1:
    Value:
      Fn::GetAtt:
        - HelloHandlerXXXXXXXX
        - Arn
  Output2:
    Value: aaaa@example.com

?? ''

(よくわかっていない)

    const myTopic = sns.Topic.fromTopicArn(this, 'MyTopic', process.env.SNS_ARN ?? '');

?? '' に関して。これがないとa

Argument of type string | undefined is not assignable to parameter of type string というエラーが出る。
stringが存在しないときstringのところにundefiendが渡されてしまってエラーになる。undefiend のとき空白としている。

Circular dependencyエラーを回避する

CDKでそのとき作る関数のarn を指定するとエラーになるので、これを回避する。
ポリシーを作るときに、その関数だけにresourcesを限定したいとき。

const helloFn = new lambda.Function(this, 'HelloHandler', {
  runtime: lambda.Runtime.PYTHON_3_9,
  code: lambda.Code.fromAsset('lambda'),
  handler: 'my_lambda.handler',
  environment: {'SNS_ARN': myTopic.topicArn,},
});

helloFn.role!.addToPrincipalPolicy(new iam.PolicyStatement({
  actions: [ 'sns:publish' ],
  resources: [ this.formatArn({
    service: 'lambda',
    resource: 'function',
    resourceName: 'HelloHandler',
  }) ],
}));

ここに載っていた
https://github.com/aws/aws-cdk/issues/11020

既存CDKを開始するとき

  • git clone などでダウンロード
  • プロジェクトのルートディレクトリに入る

初期化

npm install
cdk bootstrap

Misc memo

  • cdk deployを途中で止めるのは?Terminalで止めてもAWS上では続行されているので、AWS上で止めるかCLIでCfnの生成を止められる?
  • ディレクトリ上でスタックがすでに生成されているか調べるには??Stack名を探す?TODO
  • Cfnコンソールでスタックを消しても同じ??たぶん同じTODO

EventBridegeでLambdaを呼ぶ

  • my_lambda handlerを毎分呼ぶ
  • rule.addTarget(new targets.LambdaFunction(fn));で設定している
  • Roleは自動でCDKがやってくれる。
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as lambda from 'aws-cdk-lib/aws-lambda';


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

    const fn = new lambda.Function(this, 'HelloHandler', {
      runtime: lambda.Runtime.PYTHON_3_9,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'my_lambda.handler',
      environment: {'StackName': Test01Stack.name,},
    });

    const rule = new events.Rule(this, 'Schedule Rule', {
     schedule: events.Schedule.cron({ minute: '*', hour: '*' }),
    });
    rule.addTarget(new targets.LambdaFunction(fn));

  }
}

stack 名を変数として使う

const stack = cdk.Stack.of(this);

https://stackoverflow.com/questions/56790272/cdk-how-to-get-stack-name-in-construct

全てのリソースにtagを入れる

Tags.of(scope).addでOK。IAM Roleとかにも一括で設定できる。

import { Tags } from 'aws-cdk-lib';

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

    Tags.of(scope).add("my_key", "yes");

https://docs.aws.amazon.com/cdk/v2/guide/tagging.html

Tags.of(SCOPE).add() applies a new tag to the given construct and all of its children.

Tags.of(SCOPE).remove() removes a tag from the given construct and any of its children, including tags a child construct may have applied to itself.

一見意味不明なエラー

cdk synthとかcdk deploy--context xxx=xxx を忘れていたらこのエラー。一見何かわかりませんよね。

/node_modules/constructs/src/construct.ts:536
  return id.replace(PATH_SEP_REGEX, '--');
            ^
TypeError: Cannot read properties of undefined (reading 'replace')
    at sanitizeId (xxx/node_modules/constructs/src/construct.ts:536:13)
    at Node.tryFindChild (xxx/node_modules/constructs/src/construct.ts:114:27)
    at Topic.addSubscription (xxx/node_modules/aws-cdk-lib/aws-sns/lib/topic-base.js:1:1218)
    at new CdkApprovalMultiStepsStack (xxx/lib/cdk-approval-multi-steps-stack.ts:20:32)
    at Object.<anonymous> (xxx/bin/cdk-approval-multi-steps.ts:7:1)
    at Module._compile (node:internal/modules/cjs/loader:1112:14)
    at Module.m._compile (xxx/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1166:10)
    at Object.require.extensions.<computed> [as .ts] (xxx/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:988:32)

Subprocess exited with error 1

Discussion