AWS CDK実行環境とRDSを作成してみた

# ご挨拶
最近CDKを使用してWordPress用RDSを作成する機会があったので記事として残しておきます。
# 参考資料
[Working with the AWS CDK in Python](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-python.html)
[AWS CDK Python Reference](https://docs.aws.amazon.com/cdk/api/v2/python/index.html)
# 環境
CDK実行環境：Cloud9
CDKバージョン：2.22.0 (build 1db4b16)
言語：Python
OS：Ubuntu Server 18.04 LTS
インスタンスタイプ：t3.small
# 環境作成
```text:Cloud9
mkdir rds-setup
cd rds-setup
cdk init app --language python
source .venv/bin/activate
python -m pip install -r requirements.txt
```
上記のコマンドを実行すると「rds_setup」というディレクトリに「rds_setup_stack.py」というファイルが作成されるのでこれを編集していきます。
# 作成したコード
```python:rds_setup_stack.py
from aws_cdk import (
    Stack,
    aws_ec2 as ec2,
    aws_rds as rds,
    CfnOutput
)
from constructs import Construct

class RdsSetupStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here
        
        engine = rds.DatabaseInstanceEngine.mysql(
            version=rds.MysqlEngineVersion.VER_8_0_28
            )
        
        instance_type = ec2.InstanceType.of(
            instance_class=ec2.InstanceClass.BURSTABLE2,
            instance_size=ec2.InstanceSize.SMALL
            )
        
        default_vpc = ec2.Vpc.from_lookup(
            self, 
            "default_vpc", 
            is_default=True
            )
        
        security_groups = ec2.SecurityGroup(
            self, 
            "rds_sg",
            vpc=default_vpc,
            security_group_name="rds_sg"
            )
        
        security_groups.add_ingress_rule(
            peer=ec2.Peer.ipv4("172.31.0.0/16"),
            connection=ec2.Port.tcp(3306),
            )
        
        subnet_group = rds.SubnetGroup(
            self, 
            "private_subnet_group",
            description="for private subnets",
            vpc=default_vpc,
            subnet_group_name="private_subnet_group",
            vpc_subnets=ec2.SubnetType.PRIVATE_ISOLATED
            )
        
        db_instance = rds.DatabaseInstance(
            self,
            "db_instance",
            storage_encrypted=True,
            engine=engine,
            database_name="wordpress",
            instance_type=instance_type,
            vpc=default_vpc,
            auto_minor_version_upgrade=False,
            instance_identifier="wordpress",
            publicly_accessible=False,
            security_groups=[security_groups],
            subnet_group=subnet_group
            )
        
        db_endpoint = CfnOutput(self, "db_endpoint", value=db_instance.instance_endpoint.hostname, export_name="endpoint")
        db_secret = CfnOutput(self, "db_secret", value=db_instance.secret.secret_full_arn, export_name="secret")
```
## コード説明
### データーベースエンジンバージョン指定
```python:rds_setup_stack.py
        engine = rds.DatabaseInstanceEngine.mysql(
            version=rds.MysqlEngineVersion.VER_8_0_28
            )
```
[DatabaseInstanceEngine](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_rds/DatabaseInstanceEngine.html)
上記のメソッドでMySQLの8.0.28を使用するように指定します
### インスタンスタイプ指定
```python:rds_setup_stack.py
        instance_type = ec2.InstanceType.of(
            instance_class=ec2.InstanceClass.BURSTABLE2,
            instance_size=ec2.InstanceSize.SMALL
            )
```
[InstanceType](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_ec2/InstanceType.html#aws_cdk.aws_ec2.InstanceType)
上記のメソッドでインスタンスタイプを指定します
### VPC指定
```python:rds_setup_stack.py
        default_vpc = ec2.Vpc.from_lookup(
            self, 
            "default_vpc", 
            is_default=True
            )
```
[Vpc](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_ec2/Vpc.html)
上記のメソッドでデフォルトのVPCを指定します
注意点としてはapp.pyのenv引数にアカウントIDとリージョンを指定しないとエラーが発生します (アカウントIDは環境によって変更してください)
```text:エラー内容
jsii.errors.JSIIError: Cannot retrieve value from context provider vpc-provider since account/region are not specified at the stack level. Configure "env" with an account and region when you define your stack.See https://docs.aws.amazon.com/cdk/latest/guide/environments.html for more details.
```
以下のように記述すると正常に動きます
```python:app.py
#!/usr/bin/env python3
import os

import aws_cdk as cdk

from rds_setup.rds_setup_stack import RdsSetupStack


app = cdk.App()
RdsSetupStack(app, "RdsSetupStack",
    env=cdk.Environment(account='アカウントID', region='ap-northeast-1')
    )

app.synth()

```
### セキュリティグループ作成
```python:rds_setup_stack.py
        security_groups = ec2.SecurityGroup(
            self, 
            "rds_sg",
            vpc=default_vpc,
            security_group_name="rds_sg"
            )
        
        security_groups.add_ingress_rule(
            peer=ec2.Peer.ipv4("172.31.0.0/16"),
            connection=ec2.Port.tcp(3306),
            )
```
[SecurityGroup](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_ec2/SecurityGroup.html)
上記のメソッドでRDSに設定するセキュリティグループを作成します
ec2.Peer.ipv4()はVPCのCIDRにすることでVPC内からはアクセスが可能になります
ここにセキュリティグループIDを追加する場合はec2.Peer.security_group_id()で設定可能です
### サブネットグループ作成
```python:rds_setup_stack.py
        subnet_group = rds.SubnetGroup(
            self, 
            "private_subnet_group",
            description="for private subnets",
            vpc=default_vpc,
            subnet_group_name="private_subnet_group",
            vpc_subnets=ec2.SubnetType.PRIVATE_ISOLATED
            )
```
[SubnetGroup](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_rds/SubnetGroup.html)
上記のメソッドでサブネットグループを作成します
ec2.SubnetType.PRIVATE_ISOLATEDでプライベートサブネットが選択されます
### RDSインスタンス作成
```python:rds_setup_stack.py
        rds.DatabaseInstance(
            self,
            "db_instance",
            storage_encrypted=True,
            engine=engine,
            database_name="wordpress",
            instance_type=instance_type,
            vpc=default_vpc,
            auto_minor_version_upgrade=False,
            instance_identifier="wordpress",
            publicly_accessible=False,
            security_groups=[security_groups],
            subnet_group=subnet_group
            )
```
[DatabaseInstance](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_rds/DatabaseInstance.html)
上記のメソッドでRDSインスタンスを作成します
注意点としてはstorage_encrypted=Trueにしている時は使用できないインスタンスタイプがあること、パスワードとユーザー名がAWS Secrets Managerに保存されることくらいだと思います
[暗号化できないインスタンスタイプ](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Overview.Encryption.html#Overview.Encryption.Availability)
AWS Secrets Managerに保存したくない場合は[CfnDBInstance](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_rds/CfnDBInstance.html)を使用して作成することで可能になります
### エンドポイント、シークレットARNの出力
```python:rds_setup_stack.py
        db_endpoint = CfnOutput(self, "db_endpoint", value=db_instance.instance_endpoint.hostname, export_name="endpoint")
        db_secret = CfnOutput(self, "db_secret", value=db_instance.secret.secret_full_arn, export_name="secret")
```
[CfnOutput](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk/CfnOutput.html)
上記のメソッドでパラメータを出力しています
こうすることで他のスタックから参照することが可能になります
他のスタックから参照する場合は以下のように記載すると使用できます
```python:例
db_endpoint = Fn.import_value("endpoint")
```

# デプロイ
以下のコマンドを順番に実行するとRDSインスタンスが作成されます
```text:Cloud9
cdk bootstrap
cdk synth
cdk deploy
```
cdk bootstrapを実行するとCDKを使用するのに必要なIAMロールなどが作成されます
cdk synthを実行するとCloudFormationテンプレートが確認できます
cdk deployを実行するとリソースが作成されます。またスタック名を引数に設定することで設定したスタックだけをデプロイすることが可能です
# 後片付け
以下のコマンドを実行するとスタックが削除されます
```text:Cloud9
cdk destroy
```
# 感想
CDKの場合リソースが自動で作成されていることがあるのでそこだけは注意する必要があると感じました。
がっつりCDKを使用したのは初めてでしたが今後もコードで管理したいといった要望が増えると思うので今後も学習を続けたいと思いました。