AWS CDK Python boto3でEC2の構築をしてみた

# はじめに

今回はAWS CDKでEC2の構築を行いたいと思います。AWS CDKはコードでリソースの操作ができるIacツールです。様々なプログラミング言語をサポートしているので、使い慣れた言語を利用することで学習コストを抑えることができます。今回はPython boto3を使って構築していきます。

コードでリソースの操作をするのは最初はとっつきにくいですが、一度構築してしまえば、何度も同じ環境をコマンド一つで再現することができます。

例えば、本番環境と検証環境で同じ環境を用意したいというニーズがあった場合。コンソールからポチポチ画面を操作して同じ環境を構築するのは骨の折れる作業です。また、人が作業する以上ミスが発生するリスクがあります。Iacを導入することで確実に同じ環境を構築することができミスも削減できます。


# 作成するリソースの構成図

サブネット１つにEC2を１台たてるシンプルな構成で構築していきたいと思います。
![boto3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622891/d8c0a979-96cf-83b0-a6e3-5e3d68d52145.png)



# 実装
それでは実際にコードを書いて実装してみます。以下のコードで構成図通りの環境を構築することができます。

```python:ec2.py
#boto3インポート
import boto3
#インスタンスの作成
ec2 = boto3.client('ec2')

#vpc作成
vpc = ec2.create_vpc(
    CidrBlock = '192.0.0.0/24',
    TagSpecifications=[
        {               
                        'ResourceType': 'vpc',
                       'Tags': [
                {
                    'Key': 'Name',
                    'Value': 'boto3-test-vpc'
                },
            ]
        }
    ]
    
)

vpcId = vpc['Vpc']['VpcId']
#サブネット作成
publicSubnet1 = ec2.create_subnet(
    AvailabilityZone = 'ap-northeast-1a',
    CidrBlock = '192.0.0.0/28',
    VpcId = vpcId
)
publicSubnet1Id = publicSubnet1['Subnet']['SubnetId']

#インターネットゲートウェイの作成
igw = ec2.create_internet_gateway(
    TagSpecifications=[
        {               
                        'ResourceType': 'internet-gateway',
                       'Tags': [
                {
                    'Key': 'Name',
                    'Value': 'boto3-test-igw'
                },
            ]
        }
    ]
)

igwId = igw['InternetGateway']['InternetGatewayId']

#インターネットゲートウェイをvpcにアタッチ
ec2.attach_internet_gateway(
    InternetGatewayId= igwId,
    VpcId =vpcId
)

#ルートテーブルの作成
routeTable = ec2.create_route_table(
    VpcId = vpcId,
    TagSpecifications=[
        {               
                        'ResourceType': 'route-table',
                       'Tags': [
                {
                    'Key': 'Name',
                    'Value': 'boto3-test-route-table'
                },
            ]
        }
    ]
)
routeTableId = routeTable['RouteTable']['RouteTableId']

route1 = ec2.create_route(
    DestinationCidrBlock= '0.0.0.0/0',
    GatewayId= igwId,
    RouteTableId= routeTableId
)
#サブネットとルートテーブルの紐づけ
ec2.associate_route_table(
    RouteTableId = routeTableId,
    SubnetId = publicSubnet1Id,
)

#セキュリティグループの作成
securityGroup = ec2.create_security_group(
    Description='test-boto3',
    GroupName='boto3-test',
    VpcId= vpcId,
)
sgId = securityGroup['GroupId']

#sshの設定
authorizeSsh =  ec2.authorize_security_group_ingress(
    GroupId = sgId,
      IpPermissions=[
        {
            'FromPort': 22,
            'IpProtocol': 'tcp',
            'IpRanges': [
                {
                    'CidrIp': 'myip',
                    'Description': 'SSH access from the office',
                },
            ],
            'ToPort': 22,
        },
    ],

)

#キーペアの作成
keyName = "boto3-test"

keyPair = ec2.create_key_pair(
    KeyName=keyName,
    KeyFormat="ppk"
)

#キーペアのファイルを作成する
keyData = keyPair['KeyMaterial']
#with命令でfileオブジェクトの生成、書き込み専用（ファイルが存在しなければ新規作成）
with open(keyName + ".ppk",mode='w') as f:
    f.write(keyData)

#ec2インスタンスの起動
instance1 = ec2.run_instances(
    ImageId="ami-05a56ce08feadf9c4",
    MinCount=1,
    MaxCount=1,
    InstanceType="t2.micro",
    KeyName=keyName,
    SecurityGroupIds=[sgId],
    SubnetId=publicSubnet1Id,
    TagSpecifications=[
        {
            'ResourceType': 'instance',
            'Tags': [
                {
                    'Key': "Name",
                    'Value': "boto3"
                },
            ]
        }
    ]
)
```

それではコードの解説をしていきます。
```python:ec2.py
# boto3インポート  
import boto3  
```
まずはライブラリをインポートします。


```python:ec2.py
# インスタンスの作成  
ec2 = boto3.client('ec2') 
```
EC2サービスへのクライアントインターフェースを作成しています。これにより、EC2インスタンスに関連する操作をプログラムから行うことができます。

## VPCの作成


```python:ec2.py
# vpc作成  
vpc = ec2.create_vpc(  
    CidrBlock = '192.0.0.0/24',  
    TagSpecifications=[  
        {  
            'ResourceType': 'vpc',  
            'Tags': [  
                {  
                    'Key': 'Name',  
                    'Value': 'boto3-test-vpc'  
                },  
            ]  
        }  
    ]  
)  
```
新しいVPC（Virtual Private Cloud）を作成しています。CIDRブロックとして192.0.0.0/24をしていします。このVPCはNameタグをboto3-test-vpcとして設定します。


```python:ec2.py
vpcId = vpc['Vpc']['VpcId'] 
```
boto3のclientは戻り値としてdict型を返します。上記のコードでVPC IDを取得しています。

## サブネットの作成
```python:ec2.py
# サブネット作成  
publicSubnet1 = ec2.create_subnet(  
    AvailabilityZone = 'ap-northeast-1a',  
    CidrBlock = '192.0.0.0/28',  
    VpcId = vpcId  
)  
```
作成したVPC内に新しいサブネットを作成しています。このサブネットはアベイラビリティーゾーンap-northeast-1a(東京リージョンのAZ)に配置され、CIDRブロックとしては192.0.0.0/28が割り当てられています。先ほど取得したVPCIDを指定します。

```python:ec2.py
publicSubnet1Id = publicSubnet1['Subnet']['SubnetId'] 
```
作成したサブネットからサブネットIDを取得しています。

## インターネットゲートウェイの作成
```python:ec2.py
# インターネットゲートウェイの作成  
igw = ec2.create_internet_gateway(  
    TagSpecifications=[  
        {  
            'ResourceType': 'internet-gateway',  
            'Tags': [  
                {  
                    'Key': 'Name',  
                    'Value': 'boto3-test-igw'  
                },  
            ]  
        }  
    ]  
)  
```
インターネットゲートウェイを作成し、Nameタグでboto3-test-igwと命名しています。
```python:ec2.py
igwId = igw['InternetGateway']['InternetGatewayId'] 
```
作成したインターネットゲートウェイからIDを取得しています。
```python:ec2.py
# インターネットゲートウェイをvpcにアタッチ  
ec2.attach_internet_gateway(  
    InternetGatewayId= igwId,  
    VpcId =vpcId  
)  
```
VPCに対してインターネットゲートウェイをアタッチ（接続）しています。これにより、VPC内のインスタンスがインターネットと通信できるようになります。

## ルートテーブルの作成
```python:ec2.py
# ルートテーブルの作成  
routeTable = ec2.create_route_table(  
    VpcId = vpcId,  
    TagSpecifications=[  
        {  
            'ResourceType': 'route-table',  
            'Tags': [  
                {  
                    'Key': 'Name',  
                    'Value': 'boto3-test-route-table'  
                },  
            ]  
        }  
    ]  
)  
```
新しいルートテーブルを作成し、Nameタグでboto3-test-route-tableと命名しています。

```python:ec2.py
routeTableId = routeTable['RouteTable']['RouteTableId']  
```
作成したルートテーブルからIDを取得しています。

```python:ec2.py
route1 = ec2.create_route(  
    DestinationCidrBlock= '0.0.0.0/0',  
    GatewayId= igwId,  
    RouteTableId= routeTableId  
)  
```
インターネットへのルート（全てのトラフィックを0.0.0.0/0でキャッチしてインターネットゲートウェイへと流すためのルール）をルートテーブルに追加しています。

```python:ec2.py
# サブネットとルートテーブルの紐づけ  
ec2.associate_route_table(  
    RouteTableId = routeTableId,  
    SubnetId = publicSubnet1Id,  
)  
```
ルートテーブルをサブネットに関連付けています。これによりサブネット内のインスタンスが作成したルートテーブルを利用してインターネットに出ることが可能になります。

## セキュリティグループの作成
```python:ec2.py
# セキュリティグループの作成  
securityGroup = ec2.create_security_group(  
    Description='test-boto3',  
    GroupName='boto3-test',  
    VpcId= vpcId,  
)  
```
新しいセキュリティグループを作成しています。インスタンスに適用されるファイアウォールです。

```python:ec2.py
sgId = securityGroup['GroupId']  
```
作成したセキュリティグループからIDを取得します。
```python:ec2.py
# sshの設定  
authorizeSsh =  ec2.authorize_security_group_ingress(  
    GroupId = sgId,  
    IpPermissions=[  
        {  
            'FromPort': 22,  
            'IpProtocol': 'tcp',  
            'IpRanges': [  
                {  
                    'CidrIp': 'myip',  
                    'Description': 'SSH access from the office',  
                },  
            ],  
            'ToPort': 22,  
        },  
    ],  
)  
```
セキュリティグループのインバウンドルールを設定しており、指定のIPアドレス(myipからのSSH(ポート22)接続を許可しています。ここは自身の環境のIPアドレスを設定してください。

## キーペアの作成
```python:ec2.py
# キーペアの作成  
keyName = "boto3-test"  

keyPair = ec2.create_key_pair(  
    KeyName=keyName,  
    KeyFormat="ppk"  
)  
```
キーペアを作成します。フォーマットはppk（PuTTY用のフォーマット）をしていします。これによりプライベートキーが生成され、セキュリティで保護された方法でインスタンスに接続する際に使用します。

```python:ec2.py
keyData = keyPair['KeyMaterial']  
```
生成したキーペアから実際のキー情報を取得しています。
```python:ec2.py
# キーペアのファイルを作成する  
# with命令でfileオブジェクトの生成、書き込み専用（ファイルが存在しなければ新規作成）  
with open(keyName + ".ppk", mode='w') as f:  
    f.write(keyData)  
```
プライベートキーをファイルとして保存しています。このファイルはSSHログインに使用します。ファイル名は先ほど設定したキーペア名に.ppk拡張子をつけたものです。

## EC2インスタンスの起動
```python:ec2.py
# ec2インスタンスの起動  
instance1 = ec2.run_instances(  
    ImageId="ami-05a56ce08feadf9c4",  
    MinCount=1,  
    MaxCount=1,  
    InstanceType="t2.micro",  
    KeyName=keyName,  
    SecurityGroupIds=[sgId],  
    SubnetId=publicSubnet1Id,  
    TagSpecifications=[  
        {  
            'ResourceType': 'instance',  
            'Tags': [  
                {  
                    'Key': "Name",  
                    'Value': "boto3"  
                },  
            ]  
        }  
    ]  
)  
```
最後に、EC2インスタンスを起動します。使用するAMIはAmazon Linux2023を指定しています。、インスタンスタイプ、キーペア、セキュリティグループ、サブネットを指定し、最低1つ、最高1つのインスタンスを起動するように設定します。インスタンスにはboto3という名前タグを設定しました。

# おわりに
今回はEC2インスタンス１台を起動するという簡単なものでしたがカスタマイズしていくことで独自のニーズに合わせたインフラの構築が可能です。ご活用いただけたら幸いです。

===

ナレッジコミュニケーションでは「Musubite」というエンジニア同士のカジュアルトークサービスを利用しています。
生成 AI 技術を使ったプロジェクトに携わるメンバーと直接話せるサービスですので興味がある方は是非利用を検討してください！

https://recruiting.musubite-job.com/features/company/knowledge-communication

今後もクラウド AI の最新情報を皆さんに届けていきたいと思います。お楽しみに。