インフラCI/CDを試してみる(AWS CloudDevelopmentKit + CircleCI)

##概要
Infrastructure as Codeの勉強会にてAWS Cloud Development Kitを利用して、CircleCI上でCI/CDを行ってみた際の手順＋所感メモです。
CDKについては、[こちらの公式チュートリアル](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html)を参照ください。
環境：AWS_CDK==1.28.0 python==3.7.5 npm==5.8.0
詳しくは[こちらのgithub](https://github.com/lottotto/infrastructure-as-code)で。
所感を3行でまとめると以下の通りです。

- 実際にCI/CDすることができた。インフラ部分のデプロイはジョブの実行コードはこれで大きく変わらないと思う。
- CI/CDといってもテストをしていないので、CDだけである。Python版はテストがまだできない。
- ブランチ戦略を適切に取れば、ステージングの環境も同時に作ることができそう。デプロイ戦略になりうる気がする。  



## 作成するアーキテクチャ
#### 図
今回はこのようなアーキテクチャを作ろうと思います。デフォルトでかなりの値が設定されています。
![AWS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142319/a34f5509-ccd3-58b8-05ce-20975c5e1a66.png)

#### 実際のコード
```python:app.py
#!/usr/bin/env python3

from aws_cdk import core

from src.ec2_stack import EC2Stack



app = core.App()
EC2Stack(app, "cdk-ec2")

app.synth()
```
```python:ec2_stack.py

from aws_cdk import (
    core,
    aws_ec2 as ec2
)

class EC2Stack(core.Stack):

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

        cidr = '10.0.0.0/16'
        vpc = ec2.Vpc(
            self,
            id = 'cdk-test-vpc',
            cidr=cidr,
            max_azs=1)
    
        security_group = ec2.SecurityGroup(
            self,
            id='cdk-sg',
            vpc=vpc,
            security_group_name='cdk-sg',
        )

        security_group.add_ingress_rule(
            peer=ec2.Peer.ipv4(cidr),
            connection=ec2.Port.tcp(22),
        )
        security_group.add_ingress_rule(
            peer=ec2.Peer.ipv4('0.0.0.0/0'),
            connection=ec2.Port.tcp(80),
        )

        image_id = ec2.AmazonLinuxImage(generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2).get_image(self).image_id

        ec2.CfnInstance(
            self,
            id = 'cdk-instancd',
            availability_zone="ap-northeast-1a",
            image_id=image_id,
            instance_type="t2.micro",
            security_group_ids=[security_group.security_group_id],
            subnet_id=vpc.private_subnets[0].subnet_id,
            tags=[{
                "key":"Name",
                "value":"cdk-instance"
            }]
        )

```

## circleCIの設定
circleCIで実行する環境を整えます。
#### Docker Image
```docker:Dockerfile
FROM python:3.7.5-slim

# install npm
RUN apt-get update \ 
&& apt-get install  -y --no-install-recommends \
    npm=5.8.0+ds6-4 \
&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/*

# install aws cdk
RUN npm install -g aws-cdk@1.28.0
```
#### Context
最初はworkflowのAWS Permissionを利用しようと思ったのですが、認証情報がコンテナの中の~/.aws/credentialsに記述されるみたいです。.circleci/config.ymlで`ls -la`等実行してちゃんと書かれているかを確認したところ、自分のDocker Imageを利用している状況では~/.aws/というディレクトリは作られませんでした。なので、contextにAWSの認証情報を記述することにしました。
#### config.yml
```yaml:.circleci/config.yml
version: 2.1
jobs:
  deploy: 
    docker: 
      - image: danish9966/aws-cdk:1.28.0
    steps:
      - checkout
      - run: 
          command: pip install -r requirements.txt
          working_directory: code
      - run: 
          command: cdk deploy --require-approval never
          working_directory: code

workflows:
  cdk-deploy:
    jobs:
      - deploy:
          context: AWS_CREDENTIALS
          filters:
            branches:
              only:
                - master
                - develop
```

##　やってみる
できた。大体3分くらいでdeploy完了。
<img width="880" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142319/8f96c913-3a87-8845-9a3d-fd924d5819e1.png">
AWSのEC2ダッシュボード上にも確認。ちゃんとできている。
<img width="616" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142319/fb764def-e3a2-e4a6-5b23-55d13b53132c.png">

CI/CDなので、ec2のインスタンスタイプをmicroからsmallに変更してみる。
<img width="888" alt="スクリーンショット 2020-03-18 15.36.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142319/8c1a8b07-68cd-52e7-2d4e-3c27ded7d7b1.png">

## 所感
ちゃんとcircleCI上からAWSリソースをデプロイできて良かったです。
しかしこれでインフラのCI/CDできた！と思っても、CIは何もしてないと気付いてしまいました。。。

そこでInfrastructure as Code のテストについて考えてみます。
### Infrastructure as Code のテストについて
公式のブログによると、Snapshot tests,Fine-grained assertions,Validation testsの3種類のテストがあるようです。
#### Snapshot tests
生成されるCloudFormationのコードが変化ないかどうかを確認する。CDK自体のバージョンアップが早いため、その影響がないことを検証するのが目的。
#### Fine-grained assertions
作られるリソースが正しく設定されているかを確認する。Snapshot testsから変更が発生する場合に発動し、作られるリソースのパラメータと意図したものの比較が行われる。
#### Validation tests
作られるリソースが設定可能かどうかを確認する。例えば、SubnetMaskに33以上の値が入っていないかどうかをテストする。

Python版のCDKでは、対応してないようです。悲しい。
私としては他にテストの観点として、載せるアプリが要求通りにそのインフラ上で稼働するかをテストすることが必要だなと思っています。統合テストの自動化こそインフラCI/CDの妙味が仕込まれていると思います。

またブランチ戦略を適切に取り、circleCI上の環境変数を設定することで1つのコードでステージング用と本番用のインフラを構築できると思いました。ステージング環境と本番環境が同じソースでgit管理されてれば、ステージングと本番のバージョンが違うというあるあるな問題も解決しそうで興味深いです。


## まとめ
- 実際にCI/CDすることができた。インフラ部分のデプロイジョブの実行コードはこれで大きく変わらないと思う。
- CI/CDといってもテストをしていないので、CDだけである。Python版はテストがまだできない。
- ブランチ戦略を適切に取れば、ステージングの環境も同時に作ることができそう。デプロイ戦略になりうる気がする。

ありがとうございました！
## 参考
- https://aws.amazon.com/jp/blogs/developer/testing-infrastructure-with-the-aws-cloud-development-kit-cdk/
- https://qiita.com/it-akumi/items/df57cc5d28a3c7b50f3a
- https://www.skyarch.net/blog/?p=17254