CDK - Infrastructureの新たなBoilerplateの時代

実はCDKにを始めてまだ２ヶ月程度なのですが、CDKの世界感がとても気持ちがよく、のめり込んでしまいました。
インフラ界隈では以下のような変遷がありますが、

- __Script__によるインフラ構築
- __IaC__ (Infrastructure as Code)
- __CaD__ (Configuration as Data)

私はTemplateによるインフラ構築が最終形だとばかり思ってました。
しかし、CloudFormationやk8s manifestなどTemplateの管理やメンテにも以下のような課題があり、限界を感じてきています。

- システムが肥大化するにつれTemplate管理が難しくなってきた
- Templateの引き継ぎで設計意図が伝わらない

## CDKとは (個人見解)

[[AWS Black Belt Online Seminer]AWS Cloud Development Kit (CDK)](https://d1.awsstatic.com/webinars/jp/pdf/services/20200303_BlackBelt_CDK.pdf)によると__「あるべき状態の定義がコードで可能+抽象化」__とあります。

今までCloudFormationやk8sのManifestなどの宣言的Templateにより__Reconciliation loop__を実現してきましたが、Codeにより__Reconciliation loop__を実現するというのがCDKのコンセプトだという認識です。
CDKはIaCやCaDとは一線を画すもので、IaCやCaDの上位概念になるものかと理解してます。

ということは、k8s manifestをCaDで書くにはどうしたらいいのかという疑問が湧いてきましたが、ちゃんとありましたね。CDK for Kubernetes (CDK8s)が。このAdvent Calenderでどなたか記事を書いてくれるのを期待してます。


### Infrastructure as Boilerplate
__「あるべき状態の定義がコードで可能」__
CDKがIaCやCaDの上位概念だとすると、TemplateとCodeの違いはなにかが気になります。

[Boilerplate と Template は何が違うのでしょうか？](https://qiita.com/e99h2121/items/995671a83be1fa7b74bf)

ここを参考に考えると、CDK-constructはCodeというよりBoilplateなのではないかと。
また、CDKではCustom Construct LibraryをnpmやPyPIに公開し共有可能ということからもBoilerplateのほうがしっくり来ます。

つらつらと書いてしまいましたが私の考えをまとめると・・・
CDKは以下のようになります。

- __IaC、CaDの上位概念である。__
- __Reconciliation loopを実現するBoilPlateである。__

こうなるとIaCやCaDのような新しい用語が生まれてほしいなあと。

- ~~Infrastructure as Boilerplate~~
- ~~Configuration as Boilerplate~~

いやあ、私の乏しい発想ではいいものが考えられません。
CDKを作った方にこれを表現する用語を作ってもらいたいものですね。
そうすれば、CDKの世界も一気に広がるのではないでしょうか。

## CDK construct library - Python
CDK constructはBoilerplateであると言いましたが、私も流れにのってconstructを公開していきたいと思ってます。(いいものができたらPyPIに公開していきたいと思います。）

また、私はpythonianなのでCDKをPythonで書きたいのすが、巷にあるCDKのサンプルはTypeScriptが主流でPythonは少なく寂しい限りです。なのでいくつかPythonのサンプルをGitHubにUpしてます。

- [Github: cdk-ssm-parameter-store](https://github.com/rafty/cdk-ssm-parameter-store)
- [Github: cdk_lambda_layers](https://github.com/rafty/cdk_lambda_layers)
- [Github: cdk-eks-vpc](https://github.com/rafty/cdk-eks-vpc)

苦情や質問、Issueなどありましたら是非お願いいたします。

## 1.  SSM Parameter Store

CDKでSSM Parameter StoreのParameterのサンプルを見つけられなかったのですが、Documentを調べるとちゃんとありました。
[aws_cdk.aws_ssm.StringParameter](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ssm/StringParameter.html)
[aws_cdk.aws_ssm.StringListParameter](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ssm/StringListParameter.html)
こちらを参考に作ったものが[GitHub: cdk-ssm-parameter-store](https://github.com/rafty/cdk-ssm-parameter-store)です。
SsmStringParameterクラスは単純なものなのですがconstructのレイヤーで作成しました。
この程度のものでしたらあえてconstructを作る必要はないかもしれませんが、将来のためにもStackにベダ書きしたくないのでconstructのレイヤーを作成しています。

### Parameterの作成

#### String parameter
```Python:ssm_parameter_construct.py

# Stringパラメータの作成
class SsmStringParameter(Construct):
    def __init__(self,
                 scope: Construct,
                 id: str,
                 parameter_name: str,
                 string_value: str,
                 ) -> None:
        super().__init__(scope, id)

        aws_ssm.StringParameter(
            scope=self,
            id=f'{id}-construct',
            parameter_name=parameter_name,
            string_value=string_value,
            type=aws_ssm.ParameterType.STRING,
            tier=aws_ssm.ParameterTier.STANDARD,
        )
```

#### Secure String parameter 
aws_ssmには安全のためにSecureStringParameterのconstructがありません(パスワードをCodeに書かないよう配慮されています)。そのため、SecureStringParameterは、CLIなどを使って作成する必要があります。

```shell
$ aws ssm put-parameter \
  --name "/my-site/token" \
  --value "xxxxxxxxx" \
  --type "SecureString"
```

#### String List parameter
```python:ssm_parameter_construct.py
# StringListパラメータの作成
class StringListParameter(Construct):
    def __init__(self,
                 scope: Construct,
                 id: str,
                 parameter_name: str,
                 string_list_value: list,
                 ) -> None:
        super().__init__(scope, id)

        aws_ssm.StringListParameter(
            scope=self,
            id=f'{id}-construct',
            parameter_name=parameter_name,
            string_list_value=string_list_value,
            tier=aws_ssm.ParameterTier.ADVANCED
        )
```
### Parameter取得

#### String parameter取得
```Python:ssm_parameter_read_stack.py

email = aws_ssm.StringParameter.value_for_string_parameter(
    scope=self,
    parameter_name='/my-site/alerts-email-dev'
)

```
#### Secure String parameter取得
```Python:ssm_parameter_read_stack.py
token = aws_ssm.StringParameter.value_for_secure_string_parameter(
    scope=self,
    parameter_name='/my-site/token',
    version=1
)
```

#### String List parameter取得
String List parameterを取得して、Outputに文字列出力を試みたのですが、単純には行きませんでした。

```Python:ssm_parameter_read_stack.py
environments = aws_ssm.StringListParameter.from_string_list_parameter_name(
    scope=self,
    id='GetEnvironmentFromSsmParameter',
    string_list_parameter_name='/my-site/environments',
)

cdk.CfnOutput(
    scope=self,
    id='parameter_environments',
    value=json.dumps(self.resolve(environments.string_list_value))
)
```
こちらの[GitHub issues](https://github.com/aws/aws-cdk/issues/15222)にあるようにaws_cdk . resolve()を使い対応しましたが、以下のように表示するのが限界のようです。

```
{"Fn::Split": [",", "dev,stg,prod"]}
```

## 2. Lambda Layer construct
[GitHub: cdk_lambda_layers](https://github.com/rafty/cdk_lambda_layers)
__docker-lambda__をつかってLambda Layerを作成しました。
以前はローカルでDockerコンテナをマウントして作成していましたがaws_core.BundlingOptionsを使えば簡単につくれますね。

```Python:lambda_layer_construct.py
from aws_cdk import BundlingOptions
from constructs import Construct
from aws_cdk import aws_lambda


class LambdaLayerConstruct(Construct):
    def __init__(self,
                 scope: Construct,
                 id: str,
                 layer_version_name: str,
                 ) -> None:
        super().__init__(scope, id)

        self._layer_version_name = layer_version_name

    def lambda_layer(
            self,
            language: str,
            layer_code_path: str) -> aws_lambda.LayerVersion:

        lang_path = language
        commands = [
            '/bin/bash',
            '-c',
            f"""
            pip install -r requirements.txt -t /asset-output/{lang_path} &&
            cp -au . /asset-output/{lang_path}
            """
        ]

        bundling = BundlingOptions(
            image=aws_lambda.Runtime.PYTHON_3_8.bundling_image,
            command=commands
        )

        code = aws_lambda.Code.from_asset(
            path=layer_code_path,
            bundling=bundling
        )

        layer = aws_lambda.LayerVersion(
            scope=self,
            id='LambdaConstructLayerVersion',
            layer_version_name=self._layer_version_name,
            code=code
        )
        return layer

```

### 3. EKS Container Stack
[Github: cdk-eks-vpc](https://github.com/rafty/cdk-eks-vpc)
このCDK appはVPC、EKS Cluster、Containerの３つのStackを持ちます。
Container生成する Stackを分離させてるところがポイントかと思います。

以下はコンテナを生成するStackで、ArgoCD、nginxのコンテナ生成constructがあります。
Container Stackを分離することで、非常に見通しの良いものになっているのではないでしょうか。

```python:container_stack.py
class ContainerStack(Stack):

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

        # ArgoCD
        self._container_tool_construct = ContainerToolConstruct(
            self, 'ContainerToolConstruct', cluster)
        # eginx
        self._container_construct = ContainerConstruct(
            self, 'ContainerConstruct', cluster)
``` 



###　おわりに
CDK constructはBoilerplateであることを意識すれば、CDKのコードの見通しが非常に高まると思いました。また、3. EKS Clusterに関しては、現在のコードをベースにPipeline化を進めていきたく、最終的にはGitOpsにしていきたいと考えてます。CDKであればTemplateに比べアジャイルチックに進めていくのもとてもやりやすいので、CDKの「コードで可能」というメリットを教授できます。CDKによりインフラのアジャイル開発が活発化すればいいなと思いました。