1. はじめに
1-1. この記事で行うこと
- LocalStackコンテナをセットアップし、動作確認
- Node.js環境コンテナ内にAWS CDKプロジェクトを作成
- AWS CDKプロジェクトをLocalStackへデプロイし、動作確認
1-2. この記事の対象者
- AWS CDKを本物のAWSを使わずに動かしたい方
- AWS CDKをすぐに捨てられる環境で色々と試してみたい方
1-3. 動作環境
このチュートリアルは、Bashなどのコマンドが利用でき、DockerとDocker Composeがインストール済みの環境で実行できます。
1-4. 各種のバージョン
2022年2月19日時点で動作確認済のバージョンは以下の通りです。
- docker
- Docker version 20.10.0, build 7287ab3
- docker-compose
- docker-compose version 1.26.0, build unknown
- localstack
- 0.14.0
- aws-cli
- aws-cli/1.22.58 Python/3.8.5 Linux/4.4.0-210-generic botocore/1.24.3
- aws-cdk
- 1.145.0
- aws-cdk-local
- 1.65.8
2. チュートリアル
このチュートリアルではLocalStackというローカルでAWSを擬似的に使用できるツールを用います。詳しい説明は以下の記事をご参考ください。
LocalStack を使って無料で AWS を学ぶ
このLocalStackのDockerコンテナ内に擬似的なAWS環境を立て、そこへAWS CDKをデプロイし、動作確認を行っていきます。
2-1. 最終的なファイル構成
いきなりですが、このチュートリアルが完了した状態のファイル構成です。
以下の「▶︎ 詳細」をクリックしてご確認ください。
$ tree -L 5 -I node_modules
.
├── localstack
│ ├── CHANGELOG.md
│ ├── Dockerfile
│ ├── LICENSE.txt
│ ├── MANIFEST.in
│ ├── Makefile
│ ├── README.md
│ ├── bin
│ │ └── 略
│ ├── doc
│ │ └── 略
│ ├── docker-compose.yml
│ ├── localstack
│ │ └── 略
│ ├── requirements.txt
│ ├── setup.py
│ └── tests
│ └── 略
├── node_workdir
│ ├── Dockerfile
│ ├── app
│ │ └── sample
│ │ ├── README.md
│ │ ├── bin
│ │ │ ├── sample.d.ts
│ │ │ ├── sample.js
│ │ │ └── sample.ts
│ │ ├── cdk.json
│ │ ├── cdk.out
│ │ │ ├── SampleStack.template.json
│ │ │ ├── cdk.out
│ │ │ ├── manifest.json
│ │ │ └── tree.json
│ │ ├── jest.config.js
│ │ ├── lib
│ │ │ ├── sample-stack.d.ts
│ │ │ ├── sample-stack.js
│ │ │ └── sample-stack.ts
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── test
│ │ │ ├── sample.test.d.ts
│ │ │ ├── sample.test.js
│ │ │ └── sample.test.ts
│ │ └── tsconfig.json
│ └── docker-compose.yml
└── s3.txt
2-2. LocalStackのセットアップ
2-2-1. LocalStackをダウンロードする
$ git clone https://github.com/localstack/localstack
Cloning into 'localstack'...
remote: Enumerating objects: 27765, done.
remote: Counting objects: 100% (2217/2217), done.
remote: Compressing objects: 100% (1063/1063), done.
remote: Total 27765 (delta 1355), reused 1793 (delta 1044), pack-reused 25548
Receiving objects: 100% (27765/27765), 12.74 MiB | 21.92 MiB/s, done.
Resolving deltas: 100% (20109/20109), done.
2-2-2. LocalStackコンテナを起動する
$ cd localstack
# デフォルトで存在するDockerネットワーク「bridge」では、他のコンテナからコンテナ名で名前解決できないため、
# 「network_mode: bridge」をコメントアウトし、Dockerネットワーク「localstack_default」が自動生成されるようにする
$ sed -i 's/network_mode: bridge/# network_mode: bridge/' docker-compose.yml
$ docker-compose up -d
Creating network "localstack_default" with the default driver
Pulling localstack (localstack/localstack:)...
latest: Pulling from localstack/localstack
# ・・・(中略)
Digest: sha256:0ab755bf60c1c1ce03aa5adb51e947b04bdac75607cbaea64460802ca3f5de75
Status: Downloaded newer image for localstack/localstack:latest
Creating localstack_main ... done
$ cd ..
2-2-3. LocalStackのステータスを確認する
$ curl http://localhost:4566
{"status": "running"}
https://github.com/localstack/localstack
2020-09-15: A major (breaking) change has been merged in PR #2905 - starting with releases after v0.11.5, all services are now exposed via the edge service (port 4566) only! Please update your client configurations to use this new endpoint.
(DeepLによる翻訳)
PR #2905 に主要な (壊すような) 変更がマージされました - v0.11.5 以降のリリースから、すべてのサービスがエッジサービス (ポート 4566) のみを介して公開されるようになりました! この新しいエンドポイントを使用するために、クライアントの設定を更新してください。
公式githubにport 4566のみで動いてると書いてあるので、curlのポートに:4566
を指定しています。
上手く動いていると{"status": "running"}
が返ってきます。
2-2-4. aws-cliをインストールする
$ pip install awscli
Collecting awscli
Downloading awscli-1.22.58-py3-none-any.whl (3.8 MB)
|████████████████████████████████| 3.8 MB 8.3 MB/s
# ・・・(中略)
Installing collected packages: pyasn1, rsa, jmespath, python-dateutil, botocore, s3transfer, docutils, awscli
Successfully installed awscli-1.22.58 botocore-1.24.3 docutils-0.15.2 jmespath-0.10.0 pyasn1-0.4.8 python-dateutil-2.8.2 rsa-4.7.2 s3transfer-0.5.1
LocalStackは基本的にはGUIで操作できないのでaws-cliを使用する必要があります。
2-2-5. LocalStack用のaws-cliのconfigureを設定する
$ aws configure set aws_access_key_id dummy --profile local
$ aws configure set aws_secret_access_key dummy --profile local
$ aws configure set region ap-northeast-1 --profile local
LocalStack用なのでaws_access_key_id
とaws_secret_access_key
は適当な値 (dummy
等) で大丈夫です。
2-3. LocalStackの動作確認
Amazon Simple Storage Service (以降、S3) へバケットを登録し、テキストファイルをアップロードできるかを確認します。
2-3-1. S3にバケットを登録する
$ aws s3 mb s3://test-s3 \
--endpoint-url http://localhost:4566 \
--profile local
make_bucket: test-s3
2-3-2. S3のバケット一覧を取得する
$ aws s3 ls \
--endpoint-url http://localhost:4566 \
--profile local
2022-02-19 06:50:52 test-s3
2-3-3. S3へファイルをコピーする
$ echo "test text" > s3.txt
$ aws s3 cp ./s3.txt s3://test-s3 \
--endpoint-url http://localhost:4566 \
--profile local
upload: ./s3.txt to s3://test-s3/s3.txt
2-3-4. 指定したS3のバケットのファイル一覧を取得する
$ aws s3 ls test-s3 \
--endpoint-url http://localhost:4566 \
--profile local
2022-02-19 06:53:10 10 s3.txt
2-4. Node.js環境のセットアップ
今回はAWS CDKをTypeScriptを使用して作成するので、Node.js環境が必要となります。
特別な理由はないですが、Node.js環境もDockerで準備していきます。
2-4-1. Node.js環境作業ディレクトリを作成する
$ mkdir node_workdir && cd node_workdir
$ mkdir app
2-4-2. Dockerfileを作成する
$ vi Dockerfile
以下のファイルの中身をコピペしてください。
FROM node:14-alpine
WORKDIR /app
CMD ["sh"]
2-4-3. docker-compose.ymlを作成する
$ vi docker-compose.yml
以下のファイルの中身をコピペしてください。
version: '3'
services:
app:
build: .
image: my-node-image
networks:
- localstack_default
volumes:
- ./app:/app
environment:
- LOCALSTACK_HOSTNAME=localstack_main
tty: true
networks:
localstack_default:
external: true
networks
に「2-2-2. LocalStackコンテナを起動する」で生成されたDockerネットワークをlocalstack_default
指定しています。
また、environment
のLOCALSTACK_HOSTNAME
に「2-2-2. LocalStackコンテナを起動する」で生成されたコンテナ名のlocalstack_main
を指定することで、後ほど出てくるcdklocal
コマンド実行時にLocalStackコンテナへ接続可能となります。
2-4-4. Node.js環境コンテナを起動する
$ docker-compose up -d
Building app
Step 1/3 : FROM node:14-alpine
14-alpine: Pulling from library/node
59bf1c3509f3: Pull complete
39aa0d19fbe1: Pull complete
f0b94cdce0f3: Pull complete
664688b94c15: Pull complete
Digest: sha256:9a2aa545388a135b496bd55cef2be920b96c4526c99c140170e05a8de3fce653
Status: Downloaded newer image for node:14-alpine
---> 755b96824e40
Step 2/3 : WORKDIR /app
---> Running in 4cbba13c1a78
Removing intermediate container 4cbba13c1a78
---> 852f95361e20
Step 3/3 : CMD ["sh"]
---> Running in ce69f75cc813
Removing intermediate container ce69f75cc813
---> 6776a5c22ebe
Successfully built 6776a5c22ebe
Successfully tagged my-node-image:latest
WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating node_workdir_app_1 ... done
コンテナの起動に成功するとCreating node_workdir_app_1 ... done
のように生成されたコンテナの名前が出力されます。
2-4-5. Node.js環境コンテナの中に入る
$ docker exec -it node_workdir_app_1 sh
/app #
先ほど生成されたコンテナの名前を指定してdocker exec
コマンドを実行します。
オプションに-it
を、コマンドにsh
を指定しているので、コンテナの中に入ることができます。
Dockerコンテナに入る方法については以下の記事をご参考ください。
docker exec -itって実際は何をしてるの?【90日目】
なお、「2-5. AWS CDKプロジェクトの作成〜デプロイ」は、このコンテナの中に入っている状態で進めていきます。
2-5. AWS CDKプロジェクトの作成〜デプロイ
aws-cdkでプロジェクトを作成し、aws-cdk-localでLocalStackへデプロイしていきます。
2-5-1. aws-cdkとaws-cdk-localをグローバルインストールする
/app # npm install -g aws-cdk@1
/usr/local/bin/cdk -> /usr/local/lib/node_modules/aws-cdk/bin/cdk
+ aws-cdk@1.145.0
added 217 packages from 231 contributors in 11.003s
/app # npm install -g aws-cdk-local
/usr/local/bin/cdklocal -> /usr/local/lib/node_modules/aws-cdk-local/bin/cdklocal
+ aws-cdk-local@1.65.8
added 2 packages from 1 contributor in 0.874s
補足ですが、下記のIssueを参考にし、npm install -g aws-cdk@1
とすることで、aws-cdk
のv2がインストールされないようにしています。
「[Error: The stack named CDKToolkit failed to deploy: CREATE_FAILED (Deployment failed) #57] (https://github.com/localstack/aws-cdk-local/issues/57)」
Localstack Free version doesn't support ECR so you must upgrade to Pro-version if you want to use aws-cdk@2
2-5-2. サンプルcdkプロジェクト用ディレクトリを作成し、移動する
/app # mkdir sample && cd sample
2-5-3. サンプルcdkプロジェクトを作成する
/app/sample # cdk init sample-app --language=typescript
Applying project template sample-app for typescript
# Welcome to your CDK TypeScript project
You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`SampleStack`)
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.
## Tutorial
See [this useful workshop](https://cdkworkshop.com/20-typescript.html) on working with the AWS CDK for Typescript projects.
## Useful commands
* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template
Initializing a new git repository...
/bin/sh: git: not found
Unable to initialize git repository for your project.
Executing npm install...
npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated uuid@3.3.2: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm WARN deprecated sane@4.1.0: some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added
npm WARN deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^2.1.2 (node_modules/jest-haste-map/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
npm WARN sample@0.1.0 No repository field.
npm WARN sample@0.1.0 No license field.
✅ All done!
*************************************************************************************************************************************************
*** Newer version of CDK is available [2.13.0] ***
*** Information about upgrading from version 1.x to version 2.x is available here: https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html ***
*** Upgrade recommended (npm install -g aws-cdk) ***
*************************************************************************************************************************************************
cdk init
コマンドで--language=typescript
を指定してTypeScriptでファイルが生成されるようにしています。
また、sample-app
と指定すると、簡単なサンプルを含んだプロジェクトが生成されます。出力の中に簡単なサンプルについての説明があります。
You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`SampleStack`)
which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.
(DeepLによる翻訳)
このプロジェクトの内容を探ってみてください。スタック (SampleStack
) のインスタンスを持つ CDK アプリのデモを行います。
これは、Amazon SNSのトピックにサブスクライブされているAmazon SQSキューを含んでいます。
生成された簡単なサンプルのソースコードを、ちょっとだけ見てみましょう。
/app/sample # vi ./lib/sample-stack.ts
import * as sns from '@aws-cdk/aws-sns';
import * as subs from '@aws-cdk/aws-sns-subscriptions';
import * as sqs from '@aws-cdk/aws-sqs';
import * as cdk from '@aws-cdk/core';
export class SampleStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const queue = new sqs.Queue(this, 'SampleQueue', {
visibilityTimeout: cdk.Duration.seconds(300)
});
const topic = new sns.Topic(this, 'SampleTopic');
topic.addSubscription(new subs.SqsSubscription(queue));
}
}
Amazon Simple Queue Service (以降、SQS) のキューとAmazon Simple Notification Service (以降、SNS)のトピックとサブスクリプションが定義されています。SNSトピックからメッセージを発行すると、サブスクリプションとして登録されているキューに登録されるようになっています。
2-5-4. TypeScriptファイルをJavaScriptファイルにトランスパイルする
/app/sample # npm run build
> sample@0.1.0 build /app/sample
> tsc
ブラウザやNode.js環境はJavaScriptしか実行できないため、TypeScriptファイルをJavaScriptファイルへ必ず変換しましょう。
2-5-5. デプロイ先のリージョンを環境変数として設定する
/app/sample # export AWS_DEFAULT_REGION=ap-northeast-1
2-5-6. 初めてデプロイする場合、デプロイ用のS3バケットなどを作成する
/app/sample # cdklocal bootstrap
⏳ Bootstrapping environment aws://000000000000/ap-northeast-1...
CDKToolkit: creating CloudFormation changeset...
✅ Environment aws://000000000000/ap-northeast-1 bootstrapped.
2-5-7. LocalStackへデプロイする
/app/sample # cdklocal deploy
✨ Synthesis time: 4.55s
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 │
├───┼─────────────────────────┼────────┼─────────────────┼─────────────────────────┼───────────────────────────┤
│ + │ ${SampleQueue.Arn} │ Allow │ sqs:SendMessage │ Service:sns.amazonaws.c │ "ArnEquals": { │
│ │ │ │ │ om │ "aws:SourceArn": "${Sam │
│ │ │ │ │ │ pleTopic}" │
│ │ │ │ │ │ } │
└───┴─────────────────────────┴────────┴─────────────────┴─────────────────────────┴───────────────────────────┘
(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)? y
SampleStack: deploying...
SampleStack: creating CloudFormation changeset...
✅ SampleStack
✨ Deployment time: 5.26s
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:000000000000:stack/SampleStack/8775e216
✨ Total time: 9.81s
途中でDo you wish to deploy these changes (y/n)?
と入力を求められるので、問題がなければy
を入力しましょう。
2-5-8. Node.js環境コンテナから抜け出す
/app/sample # exit
$
2-6. AWS CDKからLocalStackへデプロイされたリソースの動作確認
「リソースが生成されているか」と「SNSトピックからメッセージを発行し、サブスクリプションとして登録されているキューに登録されるか」を確認していきます。
2-6-1. SQSキューの一覧を取得する
$ aws sqs list-queues \
--endpoint-url http://localhost:4566 \
--profile local
{
"QueueUrls": [
"http://localhost:4566/000000000000/SampleStack-SampleQueue49AAAEFF-c0802178"
]
}
SQSキューがちゃんと生成されていますね。
ちなみに、QueueUrls
に表示されている値は各自の環境で異ります。
2-6-2. 指定したSQSキューの詳細を取得する
$ aws sqs get-queue-attributes \
--queue-url http://localhost:4566/000000000000/SampleStack-SampleQueue49AAAEFF-c0802178 \
--attribute-names All \
--endpoint-url http://localhost:4566 \
--profile local
{
"Attributes": {
"ApproximateNumberOfMessages": "0",
"ApproximateNumberOfMessagesDelayed": "0",
"ApproximateNumberOfMessagesNotVisible": "0",
"CreatedTimestamp": "1645254466.340205",
"DelaySeconds": "0",
"LastModifiedTimestamp": "1645254467.617937",
"MaximumMessageSize": "262144",
"MessageRetentionPeriod": "345600",
"QueueArn": "arn:aws:sqs:ap-northeast-1:000000000000:SampleStack-SampleQueue49AAAEFF-c0802178",
"Policy": "{\"Statement\": [{\"Action\": \"sqs:SendMessage\", \"Condition\": {\"ArnEquals\": {\"aws:SourceArn\": \"arn:aws:sns:ap-northeast-1:000000000000:SampleStack-SampleTopic5FE9B5DC-c2e948f9\"}}, \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"sns.amazonaws.com\"}, \"Resource\": \"arn:aws:sqs:ap-northeast-1:000000000000:SampleStack-SampleQueue49AAAEFF-c0802178\"}], \"Version\": \"2012-10-17\"}",
"ReceiveMessageWaitTimeSeconds": "0",
"VisibilityTimeout": "300"
}
}
--queue-url
に指定する値は「2-6-1. SQSキューの一覧を取得する」で取得した各自の値を指定してください。
今回はとりあえず出力結果のうち、次の一項目だけ確認します。ApproximateNumberOfMessages
(キューから取得可能なメッセージのおおよその数) が"0"
になっていることが確認できましたでしょうか。
2-6-3. SNSトピックの一覧を取得する
$ aws sns list-topics \
--endpoint-url http://localhost:4566 \
--profile local
{
"Topics": [
{
"TopicArn": "arn:aws:sns:ap-northeast-1:000000000000:SampleStack-SampleTopic5FE9B5DC-c2e948f9"
}
]
}
SNSトピックもちゃんと生成されていますね。
TopicArn
も同様に表示されている値は各自の環境で異ります。
2-6-4. 指定したSNSトピックへメッセージを発行する
$ aws sns publish \
--message test_message \
--topic-arn arn:aws:sns:ap-northeast-1:000000000000:SampleStack-SampleTopic5FE9B5DC-c2e948f9 \
--endpoint-url http://localhost:4566 \
--profile local
{
"MessageId": "47899304-fe28-4c3a-b7d0-d35a41c44202"
}
--topic-arn
に指定する値は「2-6-3. SNSトピックの一覧を取得する」で取得した各自の値を指定してください。
発行に成功するとMessageId
が入ったJSONが返ってきます。
2-6-5. 指定したSQSキューの詳細を取得し、ApproximateNumberOfMessagesを確認する
$ aws sqs get-queue-attributes \
--queue-url http://localhost:4566/000000000000/SampleStack-SampleQueue49AAAEFF-c0802178 \
--attribute-names All \
--endpoint-url http://localhost:4566 \
--profile local
{
"Attributes": {
"ApproximateNumberOfMessages": "1",
"ApproximateNumberOfMessagesDelayed": "0",
"ApproximateNumberOfMessagesNotVisible": "0",
"CreatedTimestamp": "1645254466.340205",
"DelaySeconds": "0",
"LastModifiedTimestamp": "1645254467.617937",
"MaximumMessageSize": "262144",
"MessageRetentionPeriod": "345600",
"QueueArn": "arn:aws:sqs:ap-northeast-1:000000000000:SampleStack-SampleQueue49AAAEFF-c0802178",
"Policy": "{\"Statement\": [{\"Action\": \"sqs:SendMessage\", \"Condition\": {\"ArnEquals\": {\"aws:SourceArn\": \"arn:aws:sns:ap-northeast-1:000000000000:SampleStack-SampleTopic5FE9B5DC-c2e948f9\"}}, \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"sns.amazonaws.com\"}, \"Resource\": \"arn:aws:sqs:ap-northeast-1:000000000000:SampleStack-SampleQueue49AAAEFF-c0802178\"}], \"Version\": \"2012-10-17\"}",
"ReceiveMessageWaitTimeSeconds": "0",
"VisibilityTimeout": "300"
}
}
「2-6-2. 指定したSQSキューの詳細を取得する」と同じコマンドを実行しましょう。
ApproximateNumberOfMessages
(キューから取得可能なメッセージのおおよその数) が"1"
になっていたら、SNSトピックから発行されたメッセージがSQSキューに登録される処理がうまくいった証拠です!