はじめに
CDKでスタックを作成するとき、インフラ系のサービス(VPC、S3、CloudWatch、・・・)と、アプリケーションやサーバと直接結びつくサービス(Lambda、EC2、・・・)とで、スタックを分けたいと思うことがありました。スタックのデプロイは時間がかかるので、スタックを分けることであまり変更のないスタックはそのままにしつつ、よく変更するアプリケーションやサーバのスタックだけ更新するといったことができるメリットがあります。
他にも無償の範囲で利用できるサービスと、プロビジョニングするだけで費用が発生するサービス(VPCエンドポイントやALBなど)で分けておくと、不必要なときはまとめてスタックを消しておくことでコスト削減につなげるといったことも可能になります。
すでに作成されているサービスを参照するときは、各サービスのfrom_lookup
関数を使えば検索できます。しかし、自分のアカウントIDやリージョンを予め指定する必要があるほか、CDKで作成したスタックのテンプレートを他アカウントで使用できないなどのデメリットがあります。できることならfrom_lookup
関数の使用を避ける設計をしたほうが保守性の高いシステムになるのではないかと、個人的には感じています。
ここでVPCのfrom_lookup
のリファレンスを読むと、次の記載があります。
This function only needs to be used to use VPCs not defined in your CDK application. If you are looking to share a VPC between stacks, you can pass the Vpc object between stacks and use it as normal.
この関数は、CDK アプリケーションで定義されていない VPC を使用する場合にのみ使用する必要があります。スタック間でVPCを共有したい場合は、スタック間でVpcオブジェクトを渡し、通常通り使用することができます。(DeepL翻訳)
公式的にも、CDKで作成したサービスを参照するときはfrom_lookup
を使用せず、スタック間でオブジェクトを渡せばいいとあります。
が、じゃあどうすればできるんだよ?というのを探すのに苦労したので、備忘録として残します。
結論
スタックごとに作成したサービスのオブジェクトをインスタンス変数として保持しておき、コンストラクタでスタックのオブジェクトごと渡すだけで参照できます。
この方法に限らず、作ったサービスのオブジェクトを何らかの方法で渡せばいいです。
実際の書き方
通常通り、Cloud9などのサーバでCDKの開発環境を初期化します。
mkdir sample_app
cd sample_app
cdk init app --language python
source .venv/bin/activate
python -m pip install -r requirements.txt
cdk synth
# リージョンで初めてCDKを使うときのみ以下を実行
cdk bootstrap
ここでは冒頭の例のようにインフラとサーバでスタックを分割し、インフラスタックでVPCを作成し、サーバスタックでEC2を配置してみます。まずはインフラスタックを作成します。
from aws_cdk import (
Stack,
aws_ec2,
)
from constructs import Construct
class InfraStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
self.vpc = aws_ec2.Vpc(self, "infra-stack-vpc",
cidr="10.0.0.0/16",
enable_dns_support=True,
enable_dns_hostnames=True,
max_azs=3,
nat_gateways=0,
subnet_configuration=[
aws_ec2.SubnetConfiguration(
name="infra-subnet",
cidr_mask=24,
subnet_type=aws_ec2.SubnetType.PRIVATE_ISOLATED,
),
],
)
ここでのポイントは、aws_ec2.Vpc
のオブジェクトをスタックのインスタンス変数として保持している点です。これにより実際にこのスタッククラスを初期化するapp.py
でaws_ec2.Vpc
オブジェクトを参照できるようになります。あとは後続のスタックにInfraStack
のインスタンスをコンストラクタで渡すだけです。
from aws_cdk import (
Stack,
aws_ec2,
)
from constructs import Construct
from sample_app.infra_stack import InfraStack
class ServerStack(Stack):
def __init__(self, scope: Construct, construct_id: str, infra: InfraStack, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
self.ec2 = aws_ec2.Instance(self, "test-instance",
vpc=infra.vpc,
instance_type=aws_ec2.InstanceType.of(
aws_ec2.InstanceClass.T2,
aws_ec2.InstanceSize.MICRO
),
machine_image=aws_ec2.MachineImage.latest_amazon_linux(
generation=aws_ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
),
)
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from sample_app.infra_stack import InfraStack
from sample_app.server_stack import ServerStack
app = cdk.App()
env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION'))
infra = InfraStack(app, "InfraStack",
env=env,
)
server = ServerStack(app, "ServerStack",
env=env,
infra=infra,
)
app.synth()
最後に、作ったスタックをデプロイします。デプロイに限らず、複数スタックの操作をするときは、コマンドの最後に--all
をつける必要があります。
cdk synth --all
cdk deploy --all
なお、--all
の代わりにスタック名を指定すると、個別のスタックごとに操作することもできます。
cdk destroy ServerStack
おわりに
CDKは個別関数レベルのリファレンスは結構整理されていて見やすいですが、使い方のサンプルが少ないのが難点です。