[AWS CDK] AWS CDK Intro Workshop for Java #reinvent
はじめに
今年のre:Inventで個人的にとても楽しかったセッションのデモがAWS CDKです。 すでにドキュメント AWS Cloud Development Kit - Docsが公開されていますが、まだDeveloper Preview版であるが故に以下の文言が強調して記述されています。
Do not use this version of the AWS CDK in production.
まだまだ破壊的変更が入るから、本番でまだ使うんじゃねえぞという開発側の強い意思を感じます。
個人的にはこれは 今後来ると思う、いや来てくれるはず、むしろ絶対来てほしい、という気持ちなので、是が非でも応援していきたい所存。お願い開発チーム!
CloudFormationはCloudのインフラリソースをjsonやyamlに記述し、コードで管理することができます。そのためInfrastracture as a Codeを体現しているサービスと言えます。しかし個人的に、 yamlやjsonをコードということに若干の違和感が・・・。プログラミング言語であるHTMLと言われているようななんとなくモヤモヤするようなものがありました。
しかし、AWS CDKのコンセプトは Infrastracture is Code です。紛れもない プログラミング言語 でインフラのリソースが記述、制御できます。素晴らしすぎる。CloudFormationをどうしても使いこなせなかった自分にインフラの制御・管理ができるようになるワンチャン到来です。
今回はAWS CDK Intro Workshop こちらを参考にして、AWS CDKの機能の一部を実際に利用してみます。
これを使えば、インフラ側のエンジニアの人たちとスムーズにやりとりを行うためのプログラマ側の良きツールとして活躍してくれるのでは、という淡い期待を胸にWorkshopを覗いてみましょう。
AWS CDKはCloudFormationテンプレートを生成する
AWS CDKは各種プログラミング言語でAWSのインフラリソースの構成を記述し、制御することができます。中間生成物としてCloudFormationテンプレートを出力するため、万が一ツールがオワコン化しても記述して生成したリソースはそのまま引き継ぐことが可能です。中間生成物としてCloudFormationテンプレートを出力することは、AWS CDKを知らない人でもCloudFormationテンプレートを読んで理解することができるという利点があります。
またAWS CDKをプログラマが利用するモチベーションとして、既存のプログラミング言語で記述ができるため、プログラミング言語の様々な制御構文や便利なライブラリを利用することが可能ということが大きいかと思います。CloudFormationテンプレートでの制御も可能ではありますが、プログラミング言語と比べるとどうしても可読性が落ちてしまうではないかというのが個人的な感想です。 *1
Workshopを試す
Workshopの内容はTypeScriptで構成されています。そのままWorkshopを試すのも良いですが、折角なので別の言語で試してみましょう。ということでJavaを選択します。大きな作業の流れは以下のとおりです。
- サンプルアプリケーションプロジェクトを作成する
- Tsサンプルアプリケーションと同じコードに変更する
- Compile & Deploy
- 不要なコードを削除
- Compile & Diffを実行して不要なResourceを削除をプレビュー
- Deployして不要なResourceを削除
- 必要なResourceをコードに追加
- Compile & DeployでResourceを作成
- Invoke
ではやっていきます。
執筆時点での環境
このツールは開発中のためすごい勢いでアップデートされています。現時点での自分の環境は以下のとおりです。
- Apache Maven 3.6.0
- cdk 0.18.1 (build 9f7af21)
Create Sample Application Project
AWS Intro Workshop > New Project > cdk init
手始めにプロジェクトの雛形を作成しましょう。language
を指定するとともに、アプリケーションかライブラリを指定します。今回は実行可能な単独のアプリケーションとして構成したいので app
を選びます。 *2
Workshopの手順に沿ってはじめにディレクトリを作成し、移動しておくことをおすすめします。
$ mkdir hello-java-cdk2 $ cd hello-java-cdk2/ $ cdk init app --language=java --app Applying project template app for java Initializing a new git repository... Executing mvn package... Welcome to your CDK Java project! It is a Maven-based project, so you can open this directory with any Maven-compatible Java IDE, and you should be able to build and run tests from your IDE. You should explore the contents of this template. It demonstrates a CDK app with two instances of a stack (`HelloStack`) which also uses a user-defined construct (`HelloConstruct`). The `cdk.json` file tells the CDK Toolkit how to execute your app. It uses a script called `app.sh` to do that. Note that this script expects a local file called `.classpath.txt` to exist. This file is automatically created by `mvn package`. # Useful commands * `mvn package` compile and run tests * `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!
mavenプロジェクトとしてJavaアプリケーションのサンプルアプリケーションが構成されます。
サンプルアプリケーションの簡単な概要の説明もありますね。それぞれの言語で微妙に異なるようで、Javaの場合 HelloStack
というStackクラスが1つ(さらにそこに関連するユーザー独自のConstructクラスの HelloConstruct
)。HelloStack
クラスから生成されるインスタンスが2つあるようです。
First Compile
Javaの場合はソースを変更した際にはCompileが必要です。Compileをスキップした場合コードの変更が反映されないのでご注意ください。
mavenプロジェクトで構成されているためCompileにはmavenを利用します。
$ mvn compile [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.myorg:hello-java-cdk2 >---------------------- [INFO] Building hello-java-cdk2 0.1 [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-dependency-plugin:2.8:build-classpath (build-classpath) @ hello-java-cdk2 --- ...
jarファイルを生成する package
を実行するとCompileと同時にテストも実行されます。
プロジェクト構造
構造をざっと確認します。
$ tree src/main/ src/main/ └── java └── com └── myorg ├── HelloApp.java ├── HelloConstruct.java ├── HelloConstructProps.java └── HelloStack.java
- HelloApp.java アプリケーションのエントリポイントです。mainメソッドが定義されています。 同じStackクラスを2つインスタンス化しています。
- HelloConstruct.java S3 Bucketをfor文を使って生成するコードが記述されています。
- HelloConstructProps.java 上記の
HelloConstruct
で利用するプロパティビルダーのコードです。 - HelloStack.java Stackのコードになります。SNS, SQSのリソースの作成、HelloConstructのインスタンス化などが記述されています。
Stackのコードを作り直す
Javaのサンプルアプリケーションは、SNS, SQS以外にS3 Bucketをいくつか生成するコードになっています。そのため、このままDeployすると不要なS3 Bucketまで作成されてしまいます。TypeScriptのサンプルアプリケーションに合わせてコードを削ります。
生成するStackは一つだけ
public class HelloApp { public static void main(final String argv[]) { App app = new App(); new HelloStack(app, "hello-cdk-1"); // new HelloStack(app, "hello-cdk-2"); app.run(); } }
同じStackクラスを2つ生成する必要がないので削除。
S3 Bucketは不要なので削除
S3 Bucketを生成しているのはHelloConstruct
クラスです。こちらをインスタンス化している箇所をHelloStack
から削除します。
public class HelloStack extends Stack { public HelloStack(final App parent, final String name) { this(parent, name, null); } public HelloStack(final App parent, final String name, final StackProps props) { super(parent, name, props); Queue queue = new Queue(this, "MyFirstQueue", QueueProps.builder() .withVisibilityTimeoutSec(300) .build()); Topic topic = new Topic(this, "MyFirstTopic", TopicProps.builder() .withDisplayName("My First Topic Yeah") .build()); topic.subscribeQueue(queue); // HelloConstruct hello = new HelloConstruct(this, "Buckets", HelloConstructProps.builder() // .withBucketCount(5) // .build()); // // User user = new User(this, "MyUser", UserProps.builder().build()); // hello.grantRead(user); } }
ひとまずこれで最小限まで削りました。この段階でCloudFormationテンプレートを出力してみます。
まずはCompileから。
$ mvn compile [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.myorg:hello-java-cdk2 >---------------------- [INFO] Building hello-java-cdk2 0.1 [INFO] --------------------------------[ jar ]--------------------------------- Downloading from central: https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/maven-metadata.xml Downloaded from central: https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/maven-metadata.xml (461 B at 269 B/s) [INFO] ...
続いてテンプレートの出力。テンプレートは $ cdk synth
で標準出力されます。
$ cdk synth --profile YOUR_PROFILE Resources: MyFirstQueueXXXXXXXX: Type: AWS::SQS::Queue Properties: VisibilityTimeout: 300 Metadata: aws:cdk:path: hello-cdk-1/MyFirstQueue/Resource MyFirstQueuePolicyXXXXXXXX: Type: AWS::SQS::QueuePolicy Properties: PolicyDocument: Statement: - Action: sqs:SendMessage Condition: ArnEquals: aws:SourceArn: Ref: MyFirstTopicXXXXXXXX Effect: Allow Principal: Service: sns.amazonaws.com Resource: Fn::GetAtt: - MyFirstQueueXXXXXXXX - Arn Version: "2012-10-17" Queues: - Ref: MyFirstQueueXXXXXXXX Metadata: aws:cdk:path: hello-cdk-1/MyFirstQueue/Policy/Resource MyFirstTopicXXXXXXXX: Type: AWS::SNS::Topic Properties: DisplayName: My First Topic Yeah Metadata: aws:cdk:path: hello-cdk-1/MyFirstTopic/Resource MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX: Type: AWS::SNS::Subscription Properties: Endpoint: Fn::GetAtt: - MyFirstQueueXXXXXXXX - Arn Protocol: sqs TopicArn: Ref: MyFirstTopicXXXXXXXX Metadata: aws:cdk:path: hello-cdk-1/MyFirstTopic/MyFirstQueueSubscription/Resource CDKMetadata: Type: AWS::CDK::Metadata Properties: Modules: "@aws-cdk/assets=0.18.1,@aws-cdk/aws-autoscaling-api=0.18.1,@aws-cdk/aw\ s-cloudwatch=0.18.1,@aws-cdk/aws-codepipeline-api=0.18.1,@aws-cdk/aws-e\ c2=0.18.1,@aws-cdk/aws-events=0.18.1,@aws-cdk/aws-iam=0.18.1,@aws-cdk/a\ ws-kms=0.18.1,@aws-cdk/aws-lambda=0.18.1,@aws-cdk/aws-logs=0.18.1,@aws-\ cdk/aws-s3=0.18.1,@aws-cdk/aws-s3-notifications=0.18.1,@aws-cdk/aws-sns\ =0.18.1,@aws-cdk/aws-sqs=0.18.1,@aws-cdk/aws-stepfunctions=0.18.1,@aws-\ cdk/cdk=0.18.1,@aws-cdk/cx-api=0.18.1"
Profileの指定にも対応しているのでいつもどおり --profile
で指定します。Switch Roleにも対応していますので特に問題ありません。
Diff
AWS環境へ生成・破棄・変更されるResourceの一覧を確認します。
$ cdk diff --profile YOUR_PROFILE Resources [+] AWS::SQS::Queue MyFirstQueue MyFirstQueueXXXXXXXX [+] AWS::SQS::QueuePolicy MyFirstQueue/Policy MyFirstQueuePolicyXXXXXXXX [+] AWS::SNS::Topic MyFirstTopic MyFirstTopicXXXXXXXX [+] AWS::SNS::Subscription MyFirstTopic/MyFirstQueueSubscription MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX
Deploy
この状態でDeployを実行します。実行されるCloudFormationは先程標準出力でプレビューしたテンプレートがそのまま実行されます。
$ cdk deploy --profile YOUR_PROFILE hello-cdk-1: deploying... hello-cdk-1: creating CloudFormation changeset... 0/6 | 11:26:31 PM | CREATE_IN_PROGRESS | AWS::SNS::Topic | MyFirstTopic (MyFirstTopicXXXXXXXX) 0/6 | 11:26:32 PM | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata 0/6 | 11:26:32 PM | CREATE_IN_PROGRESS | AWS::SNS::Topic | MyFirstTopic (MyFirstTopicXXXXXXXX) Resource creation Initiated 0/6 | 11:26:32 PM | CREATE_IN_PROGRESS | AWS::SQS::Queue | MyFirstQueue (MyFirstQueueXXXXXXXX) 0/6 | 11:26:32 PM | CREATE_IN_PROGRESS | AWS::SQS::Queue | MyFirstQueue (MyFirstQueueXXXXXXXX) Resource creation Initiated 1/6 | 11:26:33 PM | CREATE_COMPLETE | AWS::SQS::Queue | MyFirstQueue (MyFirstQueueXXXXXXXX) 1/6 | 11:26:34 PM | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated 2/6 | 11:26:34 PM | CREATE_COMPLETE | AWS::CDK::Metadata | CDKMetadata 3/6 | 11:26:43 PM | CREATE_COMPLETE | AWS::SNS::Topic | MyFirstTopic (MyFirstTopicXXXXXXXX) 3/6 | 11:26:45 PM | CREATE_IN_PROGRESS | AWS::SNS::Subscription | MyFirstTopic/MyFirstQueueSubscription (MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX) 3/6 | 11:26:45 PM | CREATE_IN_PROGRESS | AWS::SQS::QueuePolicy | MyFirstQueue/Policy (MyFirstQueuePolicyXXXXXXXX) 3/6 | 11:26:46 PM | CREATE_IN_PROGRESS | AWS::SQS::QueuePolicy | MyFirstQueue/Policy (MyFirstQueuePolicyXXXXXXXX) Resource creation Initiated 3/6 | 11:26:46 PM | CREATE_IN_PROGRESS | AWS::SNS::Subscription | MyFirstTopic/MyFirstQueueSubscription (MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX) Resource creation Initiated 4/6 | 11:26:46 PM | CREATE_COMPLETE | AWS::SQS::QueuePolicy | MyFirstQueue/Policy (MyFirstQueuePolicyXXXXXXXX) 5/6 | 11:26:46 PM | CREATE_COMPLETE | AWS::SNS::Subscription | MyFirstTopic/MyFirstQueueSubscription (MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX) 6/6 | 11:26:48 PM | CREATE_COMPLETE | AWS::CloudFormation::Stack | hello-cdk-1 ✅ hello-cdk-1 Stack ARN: arn:aws:cloudformation:us-east-1:XXXXXXXXXXXXXXXX:stack/hello-cdk-1/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXX
CloudFormationで実行結果を確認すると、実行されたテンプレートが確認できます。
テストコードを削る
まずはじめにテストコードを削っておきます。Javaのmavenプロジェクトの場合テストコードを記述できるのがとても良いのですが、このあとの作業で少々面倒になるので一旦削ります(全方面土下座)
public class HelloStackTest { private final static ObjectMapper JSON = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true); @Test public void testStack() throws IOException { // App app = new App(); // HelloStack stack = new HelloStack(app, "test"); // // // synthesize the stack to a CloudFormation template and compare against // // a checked-in JSON file. // JsonNode actual = JSON.valueToTree(app.synthesizeStack(stack.getName()).getTemplate()); // JsonNode expected = JSON.readTree(getClass().getResource("expected.cfn.json")); // assertEquals(expected, actual); } }
テストコードは mvn package の際に実行されます。
このテストは生成されるCloudFormationが正しいかを実際のCloudFormationのJSONテンプレートに基づいてテストしているため、最終的な形になるまではメンテナンスがとてつもなく手間がかかります。最終的な形が決まってからテストを記述する他ないかと思います(現時点では)
ここまでで準備完了。
Hello CDK Workshop
では、コードを修正してResourceを変更していきましょう。目指すはAPI Gatewayを経由してLambda Functionを実行する構成です。先程までDeployしているアプリケーションとは全く異なるアプリケーションです。
Remove Resource
ではまずは今まであったSNS TopicとSQSのQueueを消します。
public class HelloStack extends Stack { public HelloStack(final App parent, final String name) { this(parent, name, null); } public HelloStack(final App parent, final String name, final StackProps props) { super(parent, name, props); } }
Compile & Diff
$ mvn compile [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.myorg:hello-java-cdk2 >---------------------- [INFO] Building hello-java-cdk2 0.1 [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-dependency-plugin:2.8:build-classpath (build-classpath) @ hello-java-cdk2 --- ...
$ cdk diff --profile YOUR_PROFILE Resources [-] AWS::SQS::Queue MyFirstQueue MyFirstQueueXXXXXXXX destroy [-] AWS::SQS::QueuePolicy MyFirstQueue/Policy MyFirstQueuePolicyXXXXXXXX destroy [-] AWS::SNS::Topic MyFirstTopic MyFirstTopicXXXXXXXX destroy [-] AWS::SNS::Subscription MyFirstTopic/MyFirstQueueSubscription MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX destroy
SNS, SQSが削除対象です。
Deploy
$ cdk deploy --profile YOUR_PROFILE hello-cdk-1: deploying... hello-cdk-1: creating CloudFormation changeset... 0/6 | 11:44:48 PM | UPDATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata 1/6 | 11:44:50 PM | UPDATE_COMPLETE | AWS::CDK::Metadata | CDKMetadata 1/6 | 11:44:52 PM | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack | hello-cdk-1 1/6 | 11:44:53 PM | DELETE_IN_PROGRESS | AWS::SNS::Subscription | MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX 1/6 | 11:44:53 PM | DELETE_IN_PROGRESS | AWS::SQS::QueuePolicy | MyFirstQueuePolicyXXXXXXXX 2/6 | 11:44:54 PM | DELETE_COMPLETE | AWS::SQS::QueuePolicy | MyFirstQueuePolicyXXXXXXXX 3/6 | 11:44:54 PM | DELETE_COMPLETE | AWS::SNS::Subscription | MyFirstTopicMyFirstQueueSubscriptionXXXXXXXX 3/6 | 11:44:55 PM | DELETE_IN_PROGRESS | AWS::SQS::Queue | MyFirstQueueXXXXXXXX 3/6 | 11:44:55 PM | DELETE_IN_PROGRESS | AWS::SNS::Topic | MyFirstTopicXXXXXXXX 4/6 | 11:44:56 PM | DELETE_COMPLETE | AWS::SNS::Topic | MyFirstTopicXXXXXXXX 4/6 Currently in progress: hello-cdk-1, MyFirstQueueXXXXXXXX ✅ hello-cdk-1 Stack ARN: arn:aws:cloudformation:us-east-1:XXXXXXXXXXXXXXXX:stack/hello-cdk-1/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXX
Deployを実行すると先程プレビューで表示していたリソースが差分として削除されているのがわかります。
Hello Lambda
Lambdaのリソースを追加します。手順を確認してみるとFunctionハンドラを別ファイルで用意し、Stack内で定義するLambda Functionリソースを生成する際に別ファイルで定義したFunctionハンドラを文字列で指定してるようです。
依存関係を追加
手順では $ npm install @aws-cdk/aws-lambda
でライブラリのインストールを実行しています。JavaのMavenプロジェクトなので pom.xml
に依存関係を追加します。<dependencies>
のセクションを確認します。
<dependencies> <!-- AWS Cloud Development Kit --> <dependency> <groupId>software.amazon.awscdk</groupId> <artifactId>cdk</artifactId> <version>0.18.1</version> </dependency> <!-- Respective AWS Construct Libraries --> <dependency> <groupId>software.amazon.awscdk</groupId> <artifactId>iam</artifactId> <version>0.18.1</version> </dependency> <dependency> <groupId>software.amazon.awscdk</groupId> <artifactId>s3</artifactId> <version>0.18.1</version> </dependency> <dependency> <groupId>software.amazon.awscdk</groupId> <artifactId>sns</artifactId> <version>0.18.1</version> </dependency> <dependency> <groupId>software.amazon.awscdk</groupId> <artifactId>sqs</artifactId> <version>0.18.1</version> </dependency> ... </dependencies>
Construct Libraryのセクションがありました。Lambdaを追加します。
<dependency> <groupId>software.amazon.awscdk</groupId> <artifactId>lambda</artifactId> <version>0.18.1</version> </dependency>
これを追加。Import Changesで変更を適用。
実はこの手順は不要でLambdaのConstruct LibraryはSNSをインポートと同時に依存関係に含まれているためLibrary自体は存在します。そのため、実はこの手順自体はスキップしてしまってもLambda Functionのリソース定義は出来てしまいます(あとで気づいた)
Add Lambda Function
Lambda Functionのリソースを追加します。
まずはLambda Handlerのコードを作成します。JavaでLambdaを記述する際には RequestHandler
や RequestStreamHandler
インタフェースを実装したクラスを作成します。
今回はAPI GatewayからJSONをパラメータとして入力されるようなので RequestStreamHandler
を利用します。com.myorg
パッケージ以下に lambda
パッケージを追加し、その中に RequestStreamHandler
をimplementsした Hello
クラスを作成しましょう。
public class Hello implements RequestStreamHandler { @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { String path = JsonPath.read(inputStream, "$.path"); String body = String.format("Hello, CDK! You've hit %s \n", path); // https://aws.amazon.com/jp/blogs/compute/error-handling-patterns-in-amazon-api-gateway-and-aws-lambda/ Map<String, Object> payload = new HashMap<>(); payload.put("Content-type", "text/plain"); payload.put("httpStatus", 200); payload.put("requestId", context.getAwsRequestId()); payload.put("body", body); String message = new ObjectMapper().writeValueAsString(payload); // write response OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8"); writer.write(message); writer.flush(); writer.close(); } }
こちら を参考にTutorialと同等のStatusCodeとContent-Type等を定義したレスポンスをOutputStreamに書き込みます。これでLambdaの方はOK。
続いてHelloStack
クラスにLambdaリソースを定義するコードを追加します。
public class HelloStack extends Stack { public HelloStack(final App parent, final String name) { this(parent, name, null); } public HelloStack(final App parent, final String name, final StackProps props) { super(parent, name, props); FunctionProps functionProps = FunctionProps.builder() .withRuntime(Runtime.JAVA8) .withCode(Code.asset(System.getProperty("user.dir") + "/target/hello-java-cdk.zip")) .withHandler("com.myorg.lambda.Hello") .withFunctionName("hello-lambda") .withTimeout(10) .build(); Function function = new Function(this, "HelloHandler", functionProps); } }
FunctionProps
が少々厄介だったので解説します。
メソッド名 | 役割 |
---|---|
.withRuntime |
LambdaのRuntimeを指定。Javaは Runtime.JAVA8 を指定。 |
.withCode |
Lambdaへアップロードするzipファイルかもしくはzipしたいディレクトリ, AmazonS3のパスなどを指定する。directory , file 指定は deprecatedで利用できないのでそれらを利用したい場合は、 Code.asset を利用する。(後述) |
.withHandler |
Handlerとなるクラスを指定。 Hello::requestHandler とメソッド名も指定できるが省略可能。 |
.withFunctionName |
新たに追加するLambdaの名称を指定。 |
.withTimeout |
Timeoutの時間を秒で指定します。Bootstrapに時間がかかりそうなので一応10秒に延長しました。 |
全て指定が必須です。
.withCode() の指定について
.withCode
の指定は注意が必要です。Code.asset(System.getProperty("user.dir") + "/target/hello-java-cdk.zip")
について解説します。 Code.asset
を使い、ローカルで生成される成果物を指定しそれをLambdaへのUpload対象物として扱います。今回は生成したzipファイル(これも一工夫必要)を指定していますが、他にディレクトリを指定することができます。
また Code.bucket
を利用してS3バケットを指定でき、 Code.inline
で4kB以下のコードならば文字列で指定できます。ケースによって使い分けます。
Dependency libraryをjarの中に全部含める
テンプレートのmavenプロジェクトではpom.xmlに追加した依存ライブラリがjarに含まれず、Lambdaの実行時に NoClassDefFoundError がスローされます。以下のPluginを追加
<!-- jar with dependency library --> <plugin> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin>
生成されるjarの名称が hello-java-cdk2-0.1-jar-with-dependencies.jar
に変更されます。
mavenで生成物のjarファイルを変換する
上記のCode.asset
で指定できるのは zipファイル と ディレクトリ のみです。つまりjarファイルは指定ができません。jarファイルを指定すると以下のようになります。
$ cdk deploy --profile YOUR_PROFILE Exception in thread "main" software.amazon.jsii.JsiiException: Asset must be a .zip file or a directory (/XXXXXXXX/Develop/cdk/hello-java-cdk2/target/hello-java-cdk2-0.1-jar-with-dependencies.jar) Error: Asset must be a .zip file or a directory ...
さてどうしよう。
とボヤいていたら強者からアドヴァイスが(ありがたや
普通にassemblyでいいのではhttps://t.co/VQPiXA58zD
— Amazon Prime Videoにある魔法使いの嫁を見て (@wreulicke) December 25, 2018
copy-dependenciesして
自分でzipする方法があるhttps://t.co/4yhyorIMoN— Amazon Prime Videoにある魔法使いの嫁を見て (@wreulicke) December 25, 2018
実際やるならこういうのを使ったほうが良さそう。非公開アカウントからリネームしろやとのこと。
とりあえず動かすために雑に対応。 *3renameプラグインを使いました。
<plugin> <groupId>com.coderplus.maven.plugins</groupId> <artifactId>copy-rename-maven-plugin</artifactId> <version>1.0</version> <executions> <execution> <id>copy-and-rename-file</id> <phase>package</phase> <goals> <goal>rename</goal> </goals> <configuration> <sourceFile>${project.build.directory}/hello-java-cdk2-0.1-jar-with-dependencies.jar</sourceFile> <destinationFile>${project.build.directory}/hello-java-cdk.zip</destinationFile> </configuration> </execution> </executions> </plugin>
pom.xmlに追加。<phase>
はjarが作成される package
のタイミングを指定。<sourceFile>
に生成されるjarファイル名を直値で指定。 <destinationFile>
にzipにリネームしたファイル名を直値で指定。
直値で指定しているため、柔軟性にかけますがひとまずこれで。
Compile + package
変更を適用とLambdaへアップロードするZipファイルを生成ためPackageを実行します。Classファイルが更新されZipファイルが target
ディレクトリ以下に生成されます。
$ mvn package [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.myorg:hello-java-cdk2 >---------------------- [INFO] Building hello-java-cdk2 0.1 [INFO] --------------------------------[ jar ]--------------------------------- Downloading from central: https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/maven-metadata.xml Downloaded from central: https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/maven-metadata.xml (461 B at 494 B/s) [INFO]
成功したら、diffを確認。
$ cdk diff --profile YOUR_PROFILE Parameters [+] Parameter HelloHandler/Code/S3Bucket HelloHandlerCodeS3BucketXXXXXXXX: {"Type":"String","Description":"S3 bucket for asset \"hello-cdk-1/HelloHandler/Code\""} [+] Parameter HelloHandler/Code/S3VersionKey HelloHandlerCodeS3VersionKeyXXXXXXXX: {"Type":"String","Description":"S3 key for asset version \"hello-cdk-1/HelloHandler/Code\""} Resources [+] AWS::IAM::Role HelloHandler/ServiceRole HelloHandlerServiceRole11EF7C63 [+] AWS::Lambda::Function HelloHandler HelloHandlerXXXXXXXX
Lambda FunctionとIAMのRoleが追加、さらにパラメータがいくつか追加されています。
Deploy
$ cdk deploy --profile YOUR_PROFILE hello-cdk-1: deploying... ❌ hello-cdk-1 failed: Error: This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap xxxxxxxxxxxx/us-east-1") This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap xxxxxxxxxxxx/us-east-1
あら失敗しました。 cdk bootstrap
を実行せよとのこと。
$ cdk bootstrap xxxxxxxxxxxx/us-east-1 --profile YOUR_PROFILE ⏳ Bootstrapping environment xxxxxxxxxxxx/us-east-1... CDKToolkit: creating CloudFormation changeset... 0/2 | 1:00:32 AM | CREATE_IN_PROGRESS | AWS::S3::Bucket | StagingBucket 0/2 | 1:00:32 AM | CREATE_IN_PROGRESS | AWS::S3::Bucket | StagingBucket Resource creation Initiated 1/2 | 1:00:53 AM | CREATE_COMPLETE | AWS::S3::Bucket | StagingBucket 2/2 | 1:00:55 AM | CREATE_COMPLETE | AWS::CloudFormation::Stack | CDKToolkit ✅ Environment xxxxxxxxxxxx/us-east-1 bootstrapped.
いくつかのS3 Bucketを作成している様子。LambdaにアップロードするAssetsバケットを作成しています。
再度Deployを実行します。
$ cdk deploy --profile YOUR_PROFILE hello-cdk-1: deploying... Updated: (zip) hello-cdk-1: creating CloudFormation changeset... 0/4 | 1:03:40 AM | CREATE_IN_PROGRESS | AWS::IAM::Role | HelloHandler/ServiceRole (HelloHandlerServiceRolexxxxxxxx) 0/4 | 1:03:40 AM | CREATE_IN_PROGRESS | AWS::IAM::Role | HelloHandler/ServiceRole (HelloHandlerServiceRolexxxxxxxx) Resource creation Initiated 0/4 | 1:03:41 AM | UPDATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata 1/4 | 1:03:43 AM | UPDATE_COMPLETE | AWS::CDK::Metadata | CDKMetadata 2/4 | 1:03:51 AM | CREATE_COMPLETE | AWS::IAM::Role | HelloHandler/ServiceRole (HelloHandlerServiceRolexxxxxxxx) 2/4 | 1:03:54 AM | CREATE_IN_PROGRESS | AWS::Lambda::Function | HelloHandler (HelloHandlerxxxxxxxx) 2/4 | 1:03:55 AM | CREATE_IN_PROGRESS | AWS::Lambda::Function | HelloHandler (HelloHandlerxxxxxxxx) Resource creation Initiated 3/4 | 1:03:55 AM | CREATE_COMPLETE | AWS::Lambda::Function | HelloHandler (HelloHandlerxxxxxxxx) 3/4 | 1:03:57 AM | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack | hello-cdk-1 4/4 | 1:03:58 AM | UPDATE_COMPLETE | AWS::CloudFormation::Stack | hello-cdk-1 ✅ hello-cdk-1 Stack ARN: arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/hello-cdk-1/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
Deployできました。AWSコンソールで確認してみます。
実行して確認
Lambdaを実行してみましょう。API Gatewayから呼び出される前提なのでTestのパラメータテンプレートからAmazon API Gateway AWS Proxy を選択し入力パラメータとしてセットします。
実行結果。
Conguraturation
API Gatewayのセットアップ
pom.xmlにいつものように依存関係を追加して・・・。
あれ?
Maven Repository - software.amazon.awscdk
mavenリポジトリにapigatewayが存在しませんでした。このセクションは現状Javaでできなさそう。ということで残念ながらここで終了!
まとめ
aws-cdkのJavaプロジェクトによるチュートリアルを実行してみました。TypeScriptに比べてかなりいろいろと準備する必要があって苦労しました。JavaでCDKを実行するためには
- pom.xml に依存関係を追加する
- Dependency LibraryをJar内に含めるために maven-assembly-plugin を追加する
- jarファイルのままだとDeployできないのでrenameするか自前でzipにするassemblyの設定を書くかが必要です。
- Assetを使うため
$ cdk bootstrap
を実行 - Deploy前にかならず
package
を実行 package
する際にはテストが必ず実行されるので変更が多い開発中は外しておいたほうが良さそう
といった注意事項が必要でした。思った以上にハマりポイントが多かった印象。
これらを鑑みて個人的には
- Lambda書くならTypeScriptのほうがいい
- AWS CDKいじるならTypeScriptのほうがいい
ということでAWS CDKはTypeScriptをやっていこうと思います!