AWS CDK (Python3) でS3 Bucketを1個作って感じたこと

# 0. はじめに
AWS CDK (Python3) に慣れ、特徴や良さなどを掴むため、簡単にS3 Bucketを作ってみました。

# 1. Cloud9にAWS CDKをインストールして設定
今回、Cloud9でAWS CDKを試しています。
Cloud9にAWS CDK (Python3) をインストールして設定する方法は「[Cloud9にAWS CDK (Python3) をインストールする方法](https://qiita.com/nasuvitz/items/25938c3598168c9d5fd3)」をご参照いただければと思います。

上記リンク先の手順で設定が済んだ環境を前提に、S3 Bucketを作っていきます。

# 2. S3用ライブラリをインストール
Cloud9でターミナルを開き、アプリのディレクトリに移動した後、`sudo pip install aws-cdk.aws-s3` で、S3用のライブラリをインストールします。

```console
(master) $ sudo pip install aws-cdk.aws-s3
Collecting aws-cdk.aws-s3
  Downloading https://files.pythonhosted.org/packages/60/94/c31c9532ffec488c6cceee54fd5ec9b60b723bd236b027bf5a709eb76214/aws_cdk.aws_s3-1.0.0-py3-none-any.whl (203kB)
    100% |████████████████████████████████| 204kB 4.5MB/s 
Requirement already satisfied: publication>=0.0.3 in /usr/local/lib/python3.6/site-packages (from aws-cdk.aws-s3)
Collecting aws-cdk.aws-events>=1.0.0,~=1.0 (from aws-cdk.aws-s3)
  Downloading https://files.pythonhosted.org/packages/db/d2/6ccf3b54e59edfdcf43ddb0e4af9f94c66dbbf12a9ad6351392abc8e7203/aws_cdk.aws_events-1.0.0-py3-none-any.whl (90kB)
    100% |████████████████████████████████| 92kB 7.5MB/s 
Collecting aws-cdk.aws-iam>=1.0.0,~=1.0 (from aws-cdk.aws-s3)
  Downloading https://files.pythonhosted.org/packages/ce/af/313eb2dd733acec35a6e66d3c27d5e9dc1354690c458647fb3b4d7b1ed3e/aws_cdk.aws_iam-1.0.0-py3-none-any.whl (175kB)
    100% |████████████████████████████████| 184kB 5.3MB/s 
Requirement already satisfied: aws-cdk.core>=1.0.0,~=1.0 in /usr/local/lib/python3.6/site-packages (from aws-cdk.aws-s3)
Requirement already satisfied: jsii~=0.14.0 in /usr/local/lib/python3.6/site-packages (from aws-cdk.aws-s3)
Collecting aws-cdk.aws-kms>=1.0.0,~=1.0 (from aws-cdk.aws-s3)
  Downloading https://files.pythonhosted.org/packages/79/d8/c583e44b065981f79c25892628363b480cf2d4f1b4af9f6e33bd0c26f2f0/aws_cdk.aws_kms-1.0.0-py3-none-any.whl (51kB)
    100% |████████████████████████████████| 51kB 7.0MB/s 
Collecting aws-cdk.region-info>=1.0.0,~=1.0 (from aws-cdk.aws-iam>=1.0.0,~=1.0->aws-cdk.aws-s3)
  Downloading https://files.pythonhosted.org/packages/13/6c/a3d42568a17f902e3c737691a197e91a45d58baecf362318ed01b850132a/aws_cdk.region_info-1.0.0-py3-none-any.whl (42kB)
    100% |████████████████████████████████| 51kB 8.3MB/s 
Requirement already satisfied: aws-cdk.cx-api>=1.0.0,~=1.0 in /usr/local/lib/python3.6/site-packages (from aws-cdk.core>=1.0.0,~=1.0->aws-cdk.aws-s3)
Requirement already satisfied: python-dateutil in /usr/local/lib/python3.6/site-packages (from jsii~=0.14.0->aws-cdk.aws-s3)
Requirement already satisfied: cattrs in /usr/local/lib/python3.6/site-packages (from jsii~=0.14.0->aws-cdk.aws-s3)
Requirement already satisfied: importlib-resources; python_version < "3.7" in /usr/local/lib/python3.6/site-packages (from jsii~=0.14.0->aws-cdk.aws-s3)
Requirement already satisfied: attrs>=18.2 in /usr/local/lib/python3.6/site-packages (from jsii~=0.14.0->aws-cdk.aws-s3)
Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.6/site-packages (from jsii~=0.14.0->aws-cdk.aws-s3)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.6/site-packages (from python-dateutil->jsii~=0.14.0->aws-cdk.aws-s3)
Installing collected packages: aws-cdk.region-info, aws-cdk.aws-iam, aws-cdk.aws-events, aws-cdk.aws-kms, aws-cdk.aws-s3
Successfully installed aws-cdk.aws-events-1.0.0 aws-cdk.aws-iam-1.0.0 aws-cdk.aws-kms-1.0.0 aws-cdk.aws-s3-1.0.0 aws-cdk.region-info-1.0.0
```

# 3. s3_stack.pyの作成
アプリのディレクトリの中身は、次のようになっています。

```console
(master) $ ls -a
.  ..  app.py  cdk  cdk.json  cdk.out  .env  .git  README.md  requirements.txt  setup.py
```

この中でcdkという名前のディレクトリがありますので、cdkに移動して、この下にs3_stack.pyを作ります。
今回は動作確認なので、値をべた書きしています。

今回のs3_stack.pyのコードは次の通りです。

```python:s3_stack.py
from aws_cdk import core
from aws_cdk import (
    aws_s3 as s3
)

class S3Stack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        
        # Code
        bucket = s3.Bucket(self, "MyFirstBucket",
            bucket_name = "aws-cdk-test-bucket-20190715",
            block_public_access = 
                s3.BlockPublicAccess(
                    block_public_acls=True,
                    block_public_policy=True,
                    ignore_public_acls=True,
                    restrict_public_buckets=True
                ),
            versioned = False,
            removal_policy = core.RemovalPolicy.DESTROY
        )
```

S3のBucketを作るため、AWS CDKのs3用ライブラリを使用します。
[APIリファレンス](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_s3/Bucket.html)が公開されているため、このAPIリファレンスを見ながら組み上げていきます。

[Bucket](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_s3/Bucket.html)の場合、block_public_accessの[BlockPublicAccess](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_s3/BlockPublicAccess.html)や、removal_policyの[RemovalPolicy](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.core/RemovalPolicy.html#aws_cdk.core.RemovalPolicy)のようにオブジェクトやその属性を渡すものについては、そのクラスのドキュメントを読みながら書いていきます。

# 4. app.pyの変更
続いてapp.pyを書き換えます。

```python:app.py
#!/usr/bin/env python3

from aws_cdk import core
from cdk.s3_stack import S3Stack

app = core.App()

S3Stack(app, "S3Stack")

app.synth()
```

# 5. スタックをデプロイする
`cdk ls` で確認します。

```console
(master) $ cdk ls
S3Stack
```

この時、cdk.outの下には `S3Stack.template.json` という名前でCloudFormationテンプレートが出力されています。

```json:S3Stack.template.json
{
  "Resources": {
    "MyFirstBucketB8884501": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": "aws-cdk-test-bucket-20190715",
        "PublicAccessBlockConfiguration": {
          "BlockPublicAcls": true,
          "BlockPublicPolicy": true,
          "IgnorePublicAcls": true,
          "RestrictPublicBuckets": true
        }
      },
      "UpdateReplacePolicy": "Delete",
      "DeletionPolicy": "Delete",
      "Metadata": {
        "aws:cdk:path": "S3Stack/MyFirstBucket/Resource"
      }
    }
  }
}
```

`cdk deploy` コマンドでデプロイを実行します。
実行すると、CloudFormationの変更セットが作成された後、CloudFormationのテンプレートがデプロイされていきます。

```console
(master) $ cdk deploy                                                                   
S3Stack: deploying...
S3Stack: creating CloudFormation changeset...
 0/3 | 09:15:28 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501) 
 0/3 | 09:15:29 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata 
 0/3 | 09:15:30 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501) Resource creation Initiated
 0/3 | 09:15:31 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated
 1/3 | 09:15:31 | CREATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata 
 2/3 | 09:15:51 | CREATE_COMPLETE      | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501) 
 3/3 | 09:15:53 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | S3Stack 

   S3Stack

Stack ARN:
(略)
```

CloudFormationのスタックの画面で、ちゃんとデプロイされていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/a5ca8889-2325-3068-ae87-e53db7a21e9f.png)

# 6. デプロイしたスタックを消す
動作確認が終わったので、スタックを消します。
`cdk destroy` で消すことができます。

```console
(master) $ cdk destroy
Are you sure you want to delete: S3Stack (y/n)? y
S3Stack: destroying...

   S3Stack: destroyed
```

CloudFormationの画面でも、スタックが消えたことが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/df6d90e0-894d-2d2f-36b2-2525f30119e7.png)

# 7. やってみて感じたこと
簡単にS3バケットを1つ作ってみただけではありますが、これだけでも見えてきたことがあります。

## Terraform moduleみたいなのをPythonで書ける
今回は、Pythonのコードにバケット名などの値をべた書きしましたが、Pythonなのでこの値を外部から渡せます。
言い換えればPythonのクラスとオブジェクトを利用して、従来CloudFormationテンプレート内でべた書きしていたコードを、抽象化しつつ再利用できると言えます。
クラスが定義できていけば、app.pyの記述が簡単になっていきますので、CloudFormationを素で書くよりも楽になりそうです。
この考え方はTerraformのmoduleでも同じだと思いますが、AWS CDKではTerraformのmoduleに相当するものをPythonで書けて使えると考えれば、AWS CDKの方がTerraformよりも融通が効きそうなので、開発がかなり捗るイメージがします。

## 守りたいものを守らせる
block_public_accessのような、会社/チーム/プロジェクトのポリシー上、変えられると困るものについては、クラス内に決め打ちで定義しておき、必要な属性しか渡さないようにすれば、自動的(無意識)にガバナンスを守らせながら開発を進めることができそうです。

## if文やfor文の活用
これまでのような考え方があって、さらにif文やfor文を組み込めますから、AWS CDKを使った開発はCloudFormationやTerraformよりも自由度が高くなると感じたところです。

## 変更管理
内部的にはCloudFormationの変更セットも作っていますから、コードの変更管理に加え、CloudFormationとしての変更管理もできます。

## サポート
何よりも上記をAWSが公式に出してきているのですから、サポートも頼れます。

# 8. 最後に
真剣にAWS CDKに取り組めば、真のInfrastructure as Codeが見えてくる気がしました。
もう少し色々なものを作りながら、もっと深くAWS CDKを学んでみようと感じました。

# 9. 参考文献
[Getting Started With the AWS CDK](https://docs.aws.amazon.com/ja_jp/cdk/latest/guide/getting_started.html)
[AWS CDK Python Reference](https://docs.aws.amazon.com/cdk/api/latest/python/)