【CDK】AWS CDKでALB-EC2-RDSの構成を作ってみた

記事タイトルとURLをコピーする

こんにちは、篠﨑です。

CDKを勉強する一環として、基本的なWeb構成をAWS CDKで作成してみました。

​ せっかくならブログにしてみようということで、書いてみます! ​​

開発環境

​ 今回はCloud9を利用します。

CDKとPythonのバージョンは以下の通りです。

  • CDK: 2.54.0 (build 9f41881)
  • Python: Python 3.8.15

構築物

​ VPC関連リソース/EC2/RDSの作成をします。

また、EC2へのログインはSSMを使用して行いますので、VPC Endpointを利用しています。

​ ​

手順

それでは、CDKを始める手順で書いていきます。

CDK Init

​ ​ それでは実際に始めます。

まずはディレクトリの作成や初期アプリの作成などを行います。

一行目はCloud9で開発する際に個人的によく使うものなので入れています。

特に必須ではないですので、不要な方は実行しなくても構いません。

npm install -g c9
mkdir web-app && cd web-app
cdk init app --language python

Activate

​ アプリケーションの初期設定が完了したら、仮想環境を設定していきます。

​ ​

source ./venv/bin/activate
pip install -r requirements.txt

​ ​

ディレクトリ構成

​ 少し手順とは横道にそれてしまいますが、初期の構成について少し触れておこうと思います。

ディレクトリの名前はもしかすると違う方もいるかもしれないですが、基本的に同じツリー構造ができているかと思います。

​ ​

.
├── app.py
├── cdk.json
├── README.md
├── requirements-dev.txt
├── requirements.txt
├── source.bat
├── tests
│   ├── __init__.py
│   └── unit
│       ├── __init__.py
│       └── test_web_app_stack.py
└── web_app
    ├── __init__.py
    └── web_app_stack.py

​ 簡単にですが、理解するために必要な構成要素の紹介をします。

​ ​

cdk.json

​ CDKの設定ファイルです。

構成要素の生成をどのように実行するかが定義してあります。

デフォルトでは、app.pyを実行するようになっています。

app.py

​ デフォルトでアプリケーションのルートとなるようなファイルです。

CDKアプリの全体を表します。

今回はこのファイルは特にいじりません。

​ ​ ​

web_app/web_app_stack.py

​ デフォルトでは、ここに構成要素となるようなリソースについて記載をします。

今回は、このファイルにVPC/EC2/RDS/ALBを書いていきます。

​ ​ それでは、具体的にどのように書くのか見ていきます。

​ ​

リソース作成

​ ​ 実際に書いていきます。

まずは、ファイルを開きましょう。

c9 web_app/web_app_stack.py 

​ ファイルを開くと、下のようなファイルが見えたかと思います。

(コメントアウトは消してます) ​

今回、実際に構築物を書く部分は以下にある「ここにコードを書きます」となっている部分です。

​ ​

from aws_cdk import (
    Duration,
    Stack,
)
from constructs import Construct
​
class WebAppStack(Stack):
​
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
​
        ##################################
        ######ここにコードを書きます######
        ##################################

​ それでは、先に完成物をこちらに載せます。

申し訳程度に下に解説をつけましたので、興味がありましたら、ご参照ください。

​ ​

from aws_cdk import (
    Duration,
    Stack,
    aws_iam as iam,
    aws_ec2 as ec2,
    aws_rds as rds,
    aws_elasticloadbalancingv2 as elb,
    aws_elasticloadbalancingv2_targets as tg,
)
from constructs import Construct
​
class WebAppStack(Stack):
​
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        ## set instance profile to use ssm
        instance_profile = iam.Role(self, "ec2_profile",
            assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
            description="for instance profile",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name("CloudWatchAgentServerPolicy"),
                iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMManagedInstanceCore"),
            ]
        )
        
        ## VPC and VPC endpoints for ssm
        vpc = ec2.Vpc(self, "web-vpc",
            cidr="10.100.0.0/16",
            flow_logs={
                "traffic_type": ec2.FlowLogTrafficType.ALL
            },
            vpc_name="web_vpc",
        )
        ec2.InterfaceVpcEndpoint(self, "ssm_endpoint",
            vpc=vpc,
            service=ec2.InterfaceVpcEndpointAwsService("ssm")
        )
        ec2.InterfaceVpcEndpoint(self, "ssmmessage_endpoint",
            vpc=vpc,
            service=ec2.InterfaceVpcEndpointAwsService("ssmmessages")
        )
        
        
        ## EC2
        
        ec2_instance = ec2.Instance(self, "Web-ec2",
            vpc=vpc,
            instance_type=ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
            machine_image=ec2.MachineImage.latest_amazon_linux(generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2),
            instance_name="web-instance",
            key_name="  YOUR_KEY_NAME",
            block_devices=[ec2.BlockDevice(
                device_name="/dev/xvda",
                volume=ec2.BlockDeviceVolume.ebs(10)
                )
            ],
            role=instance_profile,
        )
        
        
        ## RDS
        db_instance = rds.DatabaseInstance(self, "web-rds",
            engine=rds.DatabaseInstanceEngine.mysql(version=rds.MysqlEngineVersion.VER_8_0_28),
            vpc=vpc,
        )
        
        ## ALB
        alb = elb.ApplicationLoadBalancer(self, "alb",
            vpc=vpc,
            internet_facing=True,
        )
        listener = alb.add_listener("listener", port=80)
        listener.add_targets("target",
            port=80,
            targets=[tg.InstanceIdTarget(instance_id=ec2_instance.instance_id)],
            health_check=elb.HealthCheck(
                path="/index.html",
            )
        )
        
        ## Define Connections
        ec2_instance.connections.allow_from(alb, ec2.Port.tcp(80))
        db_instance.connections.allow_from(ec2_instance, ec2.Port.tcp(3306))

Import

​ 今回は、VPC/EC2/RDS/ALBの構成にしましたので、aws_ec2, aws_rds, aws_elasticloadbalancingv2, aws_elasticloadbalancingv2_targets, aws_iamのモジュールを利用しました。

​ 実際どれを使うかなどは、ドキュメントをご参照ください。

https://docs.aws.amazon.com/cdk/api/v1/python/modules.html ​ ​ ​

VPC & VPC Endpoint

​ VPCは今回は特にVPC FlowLogsを入れました。

vpc_nameを入れない限り、デフォルトでNameタグがついて決まってしまいますので、気になる方は付けるようにしてください。

​ cidr以外はオプションですので、つけなくても作成可能ですが、VPC FlowLogsは作成されません。

​ また、VPCを作成した場合、デフォルトでサブネットがパブリック (Internet Gatewayと疎通するサブネット)とプライベート (NAT Gateway経由でInternet Gatewayへ外へ出れるサブネット)がAZそれぞれに一つずつ作成され、合計四つ作成されます。

ちなみにCidr Blockは/18でした。

​ (今回、サブネットにNameタグをつけるのを忘れましたので、デフォルトのNameタグが付与されます。) ​

        vpc = ec2.Vpc(self, "web-vpc",
            cidr="10.100.0.0/16",
            flow_logs={
                "traffic_type": ec2.FlowLogTrafficType.ALL
            },
            vpc_name="web_vpc",
        )
        ec2.InterfaceVpcEndpoint(self, "ssm_endpoint",
            vpc=vpc,
            service=ec2.InterfaceVpcEndpointAwsService("ssm")
        )
        ec2.InterfaceVpcEndpoint(self, "ssmmessage_endpoint",
            vpc=vpc,
            service=ec2.InterfaceVpcEndpointAwsService("ssmmessages")
        )
        

EC2

​ EC2はAMIやInstanceTypeなどの設定が可能です。

個人的に、InstanceTypeの設定にわざわざec2のモジュールから引っ張ってこないといけないのかとちょっと面倒だと感じたりはしました。。

また、AMIのlatest_amazon_linuxで、generationを設定しない場合、最新バージョンのAmazon Linuxになってしまいます (Amazon Linux2の前の世代になってしまう)ので、ご注意ください。

        ec2_instance = ec2.Instance(self, "Web-ec2",
            vpc=vpc,
            instance_type=ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
            machine_image=ec2.MachineImage.latest_amazon_linux(generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2),
            instance_name="web-instance",
            key_name="YOUR_KEY_NAME",
            block_devices=[ec2.BlockDevice(
                device_name="/dev/xvda",
                volume=ec2.BlockDeviceVolume.ebs(10)
                )
            ],
            role=instance_profile,
        )

RDS

​ ​ マスターユーザーやパスワードはSecrets Managerに格納されます。

今回指定しなかったDBInstanceClassはdb.m5.large、ストレージは100GiBでした。

これらについても設定は可能です。

​ ​

        db_instance = rds.DatabaseInstance(self, "web-rds",
            engine=rds.DatabaseInstanceEngine.mysql(version=rds.MysqlEngineVersion.VER_8_0_28),
            vpc=vpc,
        )

ALB

​ ALB、リスナー、ターゲットと三つに分けて作成します。

サブネットは特に指定していませんが、Internet Facingの場合はデフォルトでパブリックサブネットに配置されます。

​ ​

        alb = elb.ApplicationLoadBalancer(self, "alb",
            vpc=vpc,
            internet_facing=True,
        )
        listener = alb.add_listener("listener", port=80)
        listener.add_targets("target",
            port=80,
            targets=[tg.InstanceIdTarget(instance_id=ec2_instance.instance_id)],
            health_check=elb.HealthCheck(
                path="/index.html",
            )
        )

Security Group

​ 今回、EC2へのSSHはSSMから行いますので、SSHのポートは空けていません。

Web三層構造ということで、ALB-EC2とEC2-RDSの疎通が取れるようにしました。

また、デフォルトでSecurity Groupのアウトバウンドはすべて許可されていますので、ご注意ください。

​ ​

        ec2_instance.connections.allow_from(alb, ec2.Port.tcp(80))
        db_instance.connections.allow_from(ec2_instance, ec2.Port.tcp(3306))

​ 以上で、今回スクリプトを書いた部分となります。

​ ​

Deploy

​ ​ はじめにcdk bootstrapというコマンドを打ちます。

デプロイにはCloudFormationを使用しますので、AWSアカウントとデプロイ先のリージョンにデプロイ先のスタックが必要なため、先にbootstrapの実行を行います。

​ ​

cdk bootstrap

​ 続いてデプロイします。

​ ​

cdk deploy

​ これで完了となります。

篠﨑 勇輔(書いた記事を見る)

クラウドインテグレーション部 SRE2課

入社4年目