この記事は ハンズラボ AdventCalendar2021 1日目の記事です。
プログラム言語でAWSインフラを定義できるという AWS CDK を試してみました。言語はPython3を利用します。
インストール
Node、Python3、virtualenvが必須のため事前にセットアップ。
私の環境ではpyenvも導入しています。
$ node -v
v12.12.0
$ python -V
Python 3.8.6
$ virtualenv --version
virtualenv 20.10.0 from /Users/xxxx/.pyenv/versions/3.8.6/lib/python3.8/site-packages/virtualenv/__init__.py
早速CDKをインストールします。
$ npm install -g aws-cdk
# インストール確認
$ cdk --version
1.134.0 (build dd5e12d)
初期設定
今回の検証用ディレクトリを作成し、そこで初期設定を行います。
$ mkdir cdk_practice
$ cd cdk_practice
$ cdk init app --language python
source .venv/bin/activate
→ pip install -r requirements.txt
を実行しなさい、とメッセージが表示されますが私は pipenv を使いたいので小細工をします。
特にこだわりのない方はメッセージに従ってコマンドを実行することをおすすめします。
# デフォルトで作成された仮想環境を削除
$ rm -rf .venv
# pipenvで再セットアップ
$ export PIPENV_VENV_IN_PROJECT=true
$ pipenv --python 3
# ライブラリインストール
$ pipenv install
$ pipenv install --dev -r ./requirements-dev.txt
# もう使わないので削除
$ rm -rf requirements*.txt
# 仮想環境有効化
$ pipenv shell
コーディング
ドキュメントによるとCDKでは構築したいサービスに対応するコアモジュールをインストールする必要があるようです。命名規則は aws-cdk.SERVICE-NAME
とのことです。今回はS3バケットを作成したいので、以下のコマンドを実行しS3用モジュールをインストールします。
$ pipenv install aws-cdk.aws-s3
次はコードを見ていきます。主に修正を行うのは app.py
と cdk_practice/cdk_practice_stack.py
のようです。過去のバージョンでは setup.py
も存在していたようですが、現在のバージョンでは配置されないようです。
app.py
初期化直後の app.py
はこんな感じです。
#!/usr/bin/env python3
import os
from aws_cdk import core as cdk
# For consistency with TypeScript code, `cdk` is the preferred import name for
# the CDK's core module. The following line also imports it as `core` for use
# with examples from the CDK Developer's Guide, which are in the process of
# being updated to use `cdk`. You may delete this import if you don't need it.
from aws_cdk import core
from cdk_practice.cdk_practice_stack import CdkPracticeStack
app = core.App()
CdkPracticeStack(app, "CdkPracticeStack",
# If you don't specify 'env', this stack will be environment-agnostic.
# Account/Region-dependent features and context lookups will not work,
# but a single synthesized template can be deployed anywhere.
# Uncomment the next line to specialize this stack for the AWS Account
# and Region that are implied by the current CLI configuration.
#env=core.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')),
# Uncomment the next line if you know exactly what Account and Region you
# want to deploy the stack to. */
#env=core.Environment(account='123456789012', region='us-east-1'),
# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
)
app.synth()
これに以下の通り修正を加えました。
-
from aws_cdk import core as cdk
でimportするのが推奨というコメントに従いfrom aws_cdk import core
を削除 - CdkPracticeStackにenv追加
#!/usr/bin/env python3
import os
from aws_cdk import core as cdk
from cdk_practice.cdk_practice_stack import CdkPracticeStack
app = cdk.App()
CdkPracticeStack(app, "CdkPracticeStack",env=cdk.Environment(region='ap-northeast-1.'))
app.synth()
複数のスタックを管理したい場合はCdkPracticeStack
と同様の定義を追加していけば良さそうですね。
cdk_practice_stack.py
次は初期化直後の cdk_practice/cdk_practice_stack.py
です。
from aws_cdk import (
core as cdk
# aws_sqs as sqs,
)
# For consistency with other languages, `cdk` is the preferred import name for
# the CDK's core module. The following line also imports it as `core` for use
# with examples from the CDK Developer's Guide, which are in the process of
# being updated to use `cdk`. You may delete this import if you don't need it.
from aws_cdk import core
class CdkPracticeStack(cdk.Stack):
def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# The code that defines your stack goes here
# example resource
# queue = sqs.Queue(
# self, "CdkPracticeQueue",
# visibility_timeout=cdk.Duration.seconds(300),
# )
今回構築したいS3バケットの仕様は以下の通りです。
- バケット名重複防止のためバケット名にAWSアカウントIDを含ませる
- デフォルトキーを利用してサーバーサイドで暗号化する
- ファイルの有効期限を1年とする
- スタックが削除されたら合わせて削除する
コアモジュールの仕様書と睨めっこしながら書き上げたコードがこちらです。
from aws_cdk import (
core as cdk,
aws_s3 as s3,
)
class CdkPracticeStack(cdk.Stack):
def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# アカウントIDの取得
accountId = cdk.Aws.ACCOUNT_ID
# バケットの作成
bucket = s3.Bucket(self,
'cdkpracticebucket',
# サーバーサイド暗号化
encryption=s3.BucketEncryption.S3_MANAGED,
# バケット名にアカウントIDを付与
# f'hogehoge-{accountId}'だとNG
bucket_name= 'cdkpracticebucket-' + accountId
)
# 1年経過で削除するライフサイクルルールを追加
bucket.add_lifecycle_rule(
enabled=True,
expiration=cdk.Duration.days(365)
)
# スタック削除時に一緒に削除
bucket.apply_removal_policy(cdk.RemovalPolicy.DESTROY)
テンプレート生成
以下のコマンドを実行すると記述した定義をもとにCloudFormationテンプレート(yaml形式)が生成され、標準出力に表示されます。定義に誤りがある場合はエラーになります。
$ cdk synth
今回はこのようなテンプレートになりました。また/cdk.out
にも同様にテンプレートが出力されるようです。
Resources:
cdkpracticebucket59CD39AD:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
BucketName:
Fn::Join:
- ""
- - cdkpracticebucket-
- Ref: AWS::AccountId
LifecycleConfiguration:
Rules:
- ExpirationInDays: 365
Status: Enabled
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
Metadata:
aws:cdk:path: CdkPracticeStack/cdkpracticebucket/Resource
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
Modules: aws-cdk=1.134.0
デプロイ
テンプレートを見た感じ問題なさそうですのでデプロイしていきます。ここからはAWSアカウントに対するcredentialが必要ですので、別途設定してください。
$ cdk deploy
後片付け
一通り検証できたので後片付けをします。削除コマンドは cdk destroy
です。
$ cdk destroy
Are you sure you want to delete: CdkPracticeStack (y/n)? y
CdkPracticeStack: destroying...
✅ CdkPracticeStack: destroyed
apply_removal_policy
を指定していたのでバケットも消えています。指定しなかった場合、デフォルト設定がRETAIN
のため削除がスキップされます。
ハマりポイント
バケット名にアカウント名を含める際、CloudFormationでは擬似パラメーターを活用してこのように書くことが多いです。
!Sub hogehoge-${AWS::AccountId}
CDKでアカウントIDを取得したい場合は aws_cdk.core.Aws を見れば良いようです。
そこでPythonでも同じようにbucket_nameを指定してみたところエラー。
bucket_name= f'cdkpracticebucket-{accountId}'
色々と調査した結果、以下の記述だとテンプレートに変換することができました。
bucket_name= 'cdkpracticebucket-' + accountId
生成されたテンプレートでは、Fn::Join
を利用した形式に自動変換されていました。加算演算子を使った場合のみ、CDK内部で何らかの変換処理が行われているものと思われます。
BucketName:
Fn::Join:
- ""
- - cdkpracticebucket-
- Ref: AWS::AccountId
バケット名のような文字列のみ受け付けるパラメータに擬似パラメータを渡したい場合は工夫が必要のようです。
感想
上記のハマりなどもあり最初は苦労しました。しかし、一般のプログラム言語を利用していることからIDEの豊富な支援をそのまま受けることができ、補完機能なども多数扱えるためノウハウが溜まればCloudFormationより高速に構築できるようになる可能性を感じました。テストの実施もできるようですので、もう少し深掘りして検証を行ってみても良いかもしれません。