AWS Cloud Development Kit(CDK)(Python)を使ってVPC+Sagemaker+Lambda環境構築してみた(VPC編)

2020.08.18

どーもsutoです。

私は業務のなかでCloudFormationを使ってインフラ構築を自動化しているのですが、今回はAWS Cloud Development Kit(CDK)を使ってPythonコードでインフラ構築自動化をやってみたいと思います。

AWS CDKは、CloudFormationのテンプレートをTypescriptやPythonのようなプログラミング言語で記述でき、コマンド1つでデプロイ、破棄、スタック更新のDiffができるツールです。

はじめに

今回、自分のアカウントで機械学習検証用環境のインフラを構築し、いつでも環境のデプロイ/破棄ができるように自動化&コード管理したい、というモチベからAWS CDKを本格的に触ってみることにしました。

本検証の目標

  1. AWS CDKのインストールと初期設定、VPC構築(←本記事)
  2. Sagemakerのノートブックインスタンスを自動構築、VPCとの関連付け
  3. ノートブックインスタンスのスケジュールによる自動停止を行うLambdaの追加設定

でやっていきます。

2、3の内容は後ほど別の記事でまとめていきます。

AWS CDKのインストール

ローカル作業環境はMacでPython3インストール済みです。

まずnpmでインストールします

npm install -g aws-cdk
cdk --version

ローカル環境での下準備

CDKの新しいプロジェクト用のフォルダを作成し、移動します。

mkdir sage-nw && cd sage-nw

使用言語を指定してプロジェクトの雛形を作成します。

~ sage-nw % cdk init --language python
~ sage-nw % ls
README.md		app.py			cdk.json		requirements.txt	sage_nw			setup.py		source.bat

ディレクトリ内の仮想環境に入ります。

~ sage-nw % source .env/bin/activate
(.env) ~ sage-nw %

 setup.pyの「install_requires」部分を編集(aws-cdk.aws-ec2追記)して必要なリソースのモジュールもインストールします。

    install_requires=[
        "aws-cdk.core",
        "aws-cdk.aws-ec2",
    ],
pip install -r requirements.txt

app.pyを編集してデプロイするリージョンを指定しておくと便利です。

SageNwStack(app, "sage-nw", env={'region': 'ap-northeast-1'})

コードを記述

ここから「sage_nw」フォルダ内のsage_nw_stack.pyにAWSリソースの構築内容を記述して保存します。

今回のVPC構築内容は以下のとおりです。

  • CIDRが/16のVPCを1つ新規作成
  • CIDRが/24のパブリックサブネット2つ、プライベートサブネット2つを各AZに1つずつ配置されるように新規作成
  • セキュリティグループを1つ新規作成(sagemaker ノートブックにアタッチする用に作っておきます)
from aws_cdk import core
from aws_cdk import aws_ec2 as ec2

class SageNwStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

    # The code that defines your stack goes here
    # 変数の宣言
    vpc_cidr = '10.1.0.0/16'
    subnet_mask = 24
    
    # 新規VPC作成
    vpc = ec2.Vpc(
        self,
        id="Sage-vpc",
        cidr=vpc_cidr,
        nat_gateways=0,
        subnet_configuration=[
            ec2.SubnetConfiguration(
                cidr_mask=subnet_mask, name='public', subnet_type=ec2.SubnetType.PUBLIC,
            ),
            ec2.SubnetConfiguration(
                cidr_mask=subnet_mask, name='private', subnet_type=ec2.SubnetType.ISOLATED,
            ),
        ],
    )
    
    security_group = ec2.SecurityGroup(
        self,
        id='Sage-sg',
        vpc=vpc,
        security_group_name='test-sg'
    )
    security_group.add_ingress_rule(
        peer=ec2.Peer.ipv4(vpc_cidr),
        connection=ec2.Port.all_traffic(),
    )

デフォルト設定でVPCを構築すると、サブネットのCIDRが勝手に決められる、NATゲートウェイが作成されるなど少々都合が悪かったのでいろいろオプションを指定しています。

参考

スタックの確認とデプロイ

lsコマンドで本プロジェクトで作成されるスタック一覧を表示できます。

cdk ls

cdk synthコマンドでCloudFormationテンプレで記述した場合のコードを見ることができます。

Resources:
  SagevpcFE72F9E6:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.1.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:

        - Key: Name
          Value: sage-nw/Sage-vpc
        Metadata:
            aws:cdk:path: sage-nw/Sage-vpc/Resource
    SagevpcpublicSubnet1Subnet94477DD3:
        Type: AWS::EC2::Subnet
        Properties:
            CidrBlock: 10.1.0.0/24
            VpcId:
                Ref: SagevpcFE72F9E6
            AvailabilityZone:
                Fn::Select:
          - 0
          - Fn::GetAZs: ""
            MapPublicIpOnLaunch: true
            Tags:
                - Key: aws-cdk:subnet-name
          Value: public
                      - Key: aws-cdk:subnet-type
          Value: Public
                            - Key: Name
          Value: sage-nw/Sage-vpc/publicSubnet1
              Metadata:
                        aws:cdk:path: sage-nw/Sage-vpc/publicSubnet1/Subnet
    SagevpcpublicSubnet1RouteTableC299A029:
              Type: AWS::EC2::RouteTable
              Properties:
                        VpcId:
                                  Ref: SagevpcFE72F9E6
                        Tags:
                                  - Key: Name
          Value: sage-nw/Sage-vpc/publicSubnet1
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/publicSubnet1/RouteTable
    SagevpcpublicSubnet1RouteTableAssociation6D1A442B:
                Type: AWS::EC2::SubnetRouteTableAssociation
                Properties:
                            RouteTableId:
                                        Ref: SagevpcpublicSubnet1RouteTableC299A029
                            SubnetId:
                                        Ref: SagevpcpublicSubnet1Subnet94477DD3
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/publicSubnet1/RouteTableAssociation
    SagevpcpublicSubnet1DefaultRoute07A07172:
                Type: AWS::EC2::Route
                Properties:
                            RouteTableId:
                                        Ref: SagevpcpublicSubnet1RouteTableC299A029
                            DestinationCidrBlock: 0.0.0.0/0
                            GatewayId:
                                        Ref: SagevpcIGW9475FA95
                DependsOn:
                            - SagevpcVPCGW7B030D8E
                  Metadata:
                                aws:cdk:path: sage-nw/Sage-vpc/publicSubnet1/DefaultRoute
    SagevpcpublicSubnet2Subnet2C67E71A:
                  Type: AWS::EC2::Subnet
                  Properties:
                                CidrBlock: 10.1.1.0/24
                                VpcId:
                                        Ref: SagevpcFE72F9E6
                                AvailabilityZone:
                                        Fn::Select:
          - 1
          - Fn::GetAZs: ""
            MapPublicIpOnLaunch: true
            Tags:
                - Key: aws-cdk:subnet-name
          Value: public
                      - Key: aws-cdk:subnet-type
          Value: Public
                            - Key: Name
          Value: sage-nw/Sage-vpc/publicSubnet2
              Metadata:
                        aws:cdk:path: sage-nw/Sage-vpc/publicSubnet2/Subnet
    SagevpcpublicSubnet2RouteTable8690CC41:
              Type: AWS::EC2::RouteTable
              Properties:
                        VpcId:
                                  Ref: SagevpcFE72F9E6
                        Tags:
                                  - Key: Name
          Value: sage-nw/Sage-vpc/publicSubnet2
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/publicSubnet2/RouteTable
    SagevpcpublicSubnet2RouteTableAssociation3C69FE0F:
                Type: AWS::EC2::SubnetRouteTableAssociation
                Properties:
                            RouteTableId:
                                        Ref: SagevpcpublicSubnet2RouteTable8690CC41
                            SubnetId:
                                        Ref: SagevpcpublicSubnet2Subnet2C67E71A
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/publicSubnet2/RouteTableAssociation
    SagevpcpublicSubnet2DefaultRouteE00663C9:
                Type: AWS::EC2::Route
                Properties:
                            RouteTableId:
                                        Ref: SagevpcpublicSubnet2RouteTable8690CC41
                            DestinationCidrBlock: 0.0.0.0/0
                            GatewayId:
                                        Ref: SagevpcIGW9475FA95
                DependsOn:
                            - SagevpcVPCGW7B030D8E
                  Metadata:
                                aws:cdk:path: sage-nw/Sage-vpc/publicSubnet2/DefaultRoute
    SagevpcprivateSubnet1SubnetB83A6331:
                  Type: AWS::EC2::Subnet
                  Properties:
                                CidrBlock: 10.1.2.0/24
                                VpcId:
                                        Ref: SagevpcFE72F9E6
                                AvailabilityZone:
                                        Fn::Select:
          - 0
          - Fn::GetAZs: ""
            MapPublicIpOnLaunch: false
            Tags:
                - Key: aws-cdk:subnet-name
          Value: private
                      - Key: aws-cdk:subnet-type
          Value: Isolated
                            - Key: Name
          Value: sage-nw/Sage-vpc/privateSubnet1
              Metadata:
                        aws:cdk:path: sage-nw/Sage-vpc/privateSubnet1/Subnet
    SagevpcprivateSubnet1RouteTable1F39D14D:
              Type: AWS::EC2::RouteTable
              Properties:
                        VpcId:
                                  Ref: SagevpcFE72F9E6
                        Tags:
                                  - Key: Name
          Value: sage-nw/Sage-vpc/privateSubnet1
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/privateSubnet1/RouteTable
    SagevpcprivateSubnet1RouteTableAssociation385BE067:
                Type: AWS::EC2::SubnetRouteTableAssociation
                Properties:
                            RouteTableId:
                                        Ref: SagevpcprivateSubnet1RouteTable1F39D14D
                            SubnetId:
                                        Ref: SagevpcprivateSubnet1SubnetB83A6331
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/privateSubnet1/RouteTableAssociation
    SagevpcprivateSubnet2Subnet6652904B:
                Type: AWS::EC2::Subnet
                Properties:
                            CidrBlock: 10.1.3.0/24
                            VpcId:
                                        Ref: SagevpcFE72F9E6
                            AvailabilityZone:
                                        Fn::Select:
          - 1
          - Fn::GetAZs: ""
            MapPublicIpOnLaunch: false
            Tags:
                - Key: aws-cdk:subnet-name
          Value: private
                      - Key: aws-cdk:subnet-type
          Value: Isolated
                            - Key: Name
          Value: sage-nw/Sage-vpc/privateSubnet2
              Metadata:
                        aws:cdk:path: sage-nw/Sage-vpc/privateSubnet2/Subnet
    SagevpcprivateSubnet2RouteTableB1CE60A7:
              Type: AWS::EC2::RouteTable
              Properties:
                        VpcId:
                                  Ref: SagevpcFE72F9E6
                        Tags:
                                  - Key: Name
          Value: sage-nw/Sage-vpc/privateSubnet2
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/privateSubnet2/RouteTable
    SagevpcprivateSubnet2RouteTableAssociationD4FFD8F0:
                Type: AWS::EC2::SubnetRouteTableAssociation
                Properties:
                            RouteTableId:
                                        Ref: SagevpcprivateSubnet2RouteTableB1CE60A7
                            SubnetId:
                                        Ref: SagevpcprivateSubnet2Subnet6652904B
                Metadata:
                            aws:cdk:path: sage-nw/Sage-vpc/privateSubnet2/RouteTableAssociation
    SagevpcIGW9475FA95:
                Type: AWS::EC2::InternetGateway
                Properties:
                            Tags:
                                        - Key: Name
          Value: sage-nw/Sage-vpc
                  Metadata:
                                aws:cdk:path: sage-nw/Sage-vpc/IGW
    SagevpcVPCGW7B030D8E:
                  Type: AWS::EC2::VPCGatewayAttachment
                  Properties:
                                VpcId:
                                              Ref: SagevpcFE72F9E6
                                InternetGatewayId:
                                              Ref: SagevpcIGW9475FA95
                  Metadata:
                                aws:cdk:path: sage-nw/Sage-vpc/VPCGW
    SagesgCC54FEB9:
                  Type: AWS::EC2::SecurityGroup
                  Properties:
                                GroupDescription: sage-nw/Sage-sg
                                GroupName: test-sg
                                SecurityGroupEgress:
                                              - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
                                    SecurityGroupIngress:
                                                    - CidrIp: 10.1.0.0/16
          Description: from 10.1.0.0/16:ALL TRAFFIC
          IpProtocol: "-1"
                                        VpcId:
                                                          Ref: SagevpcFE72F9E6
                      Metadata:
                                        aws:cdk:path: sage-nw/Sage-sg/Resource
    CDKMetadata:
                      Type: AWS::CDK::Metadata
                      Properties:
                                        Modules: aws-cdk=1.54.0,@aws-cdk/assets=1.59.0,@aws-cdk/aws-cloudwatch=1.59.0,@aws-cdk/aws-ec2=1.59.0,@aws-cdk/aws-events=1.59.0,@aws-cdk/aws-iam=1.59.0,@aws-cdk/aws-kms=1.59.0,@aws-cdk/aws-logs=1.59.0,@aws-cdk/aws-s3=1.59.0,@aws-cdk/aws-s3-assets=1.59.0,@aws-cdk/aws-ssm=1.59.0,@aws-cdk/cloud-assembly-schema=1.54.0,@aws-cdk/core=1.54.0,@aws-cdk/cx-api=1.54.0,@aws-cdk/region-info=1.59.0,jsii-runtime=Python/3.8.2

CloudFormationでCDK実行環境を作成します。(AWS アカウント、リージョン単位で一度だけ実行するコマンドです。(リージョンで1度実行したことがあれば、2回目以降は実行する必要はありません)以下の例では--profileでスイッチロール先である自分のアカウントを指定しています。

cdk bootstrap --profile suto

確認が終わったら以下コマンドでスタック作成してみます。

(.env) ~ sage-nw % cdk deploy sage-nw --profile suto

無事スタックCOMPLETEしました。

まとめ

AWSCDKとCloudFormationのコードの長さを比較してみると、記述の短さは一目瞭然でCDKでスタックを作成した方が簡単であることがわかります。

今回は入門編という内容でVPC構築のみでした。次回の記事でSagemakerのノートブックインスタンスを構築するコードを追加してみようと思います。

<次回記事>

参考