AWS CDKとは？インフラ構築をコード化する新しいアプローチ

# はじめに
アドベントカレンダーの季節なので、 AWS CDK について学び直してみようと思い、ブログにまとめます。
全17回くらいの1回目です。

## インフラ構築をコードで管理する「Infrastructure as Code (IaC)」とは？

AWS CDK を学び直す前に、そもそも IaC ってなにか？というところから振り返ってみます。

IaC、つまり、 Infrastructure as Code とは、名前のとおりコードでインフラストラクチャーを定義し、仮想マシンなどのインフラリソースを自動構築する仕組みです。
IaC を使うことで、心のこもった生温かい手作業で仮想マシンなどの起動をしなくて済むようになります。

AWS では AWS CloudFormation で yaml や json でテンプレートファイルを記載することで IaC を利用することができます。

## AWS CDK（Cloud Development Kit）の登場背景

そんな便利な IaC ですが、AWS CloudFromation では前述の通り、 yaml や json で定義していくわけですが、以下のような課題があります。

- 学習コスト: YAMLやJSONベースの記述が初学者には直感的でない
- 柔軟性の限界: プログラム的な処理（ループや条件分岐）が難しく、構成の複雑化に対応しきれない

これら課題を打破するために、 もっとプログラマブルな IaC のサービスとして AWS CDK が登場しました。

AWS CDK は次のようなプログラミング言語でインフラストラクチャーを定義することが可能です。

* TypeScript
* JavaScript
* Python
* Java
* C#
* Go

そのため、これらのプログラミング言語で開発をしたことがある技術者であれば、対応しやすいと言えます。
また、プログラムとしてインフラストラクチャーを定義できるということは、プログラム的な処理を導入して、判定やループといった処理を用いて複雑な構成も定義しやすくなります。

では、次に AWS CDK の特徴についてみていきましょう。

# AWS CDKの特徴

## プログラミング言語を用いてリソースを定義
これまでお伝えした通り、従来のAWS CloudFormation では、YAMLやJSONのような宣言的なテンプレート形式でリソースを定義していました。しかし、これらはプログラム的な操作（ループ、条件分岐、再利用可能な関数の作成など）が難しいという課題がありました。

AWS CDKでは、開発者が慣れ親しんだプログラミング言語を用いてリソースを定義できます。これにより、次の利点が得られます。

* 柔軟性: 条件分岐やループを利用した動的な構成が可能
* 統合: アプリケーションコードとインフラコードを同じ言語で記述できる
* 再利用: 関数やクラスを作成して、再利用可能なインフラ構成を定義できる

以下は、AWS CDKでS3バケットを作成するPythonコードの例です。
単にひとつのバケットを作るだけなので重厚長大な感じがしますが、class 化するなどプログラミング言語で最適化することにより、例えば末尾が000〜100までのバケットを一気に作るといったことも可能です。

```python
from aws_cdk import core
from aws_cdk import aws_s3 as s3

class S3BucketStack(core.Stack):

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

        # S3 バケットを作成
        bucket = s3.Bucket(
            self, 
            "MyBucket",
            versioned=True,  # バージョニングを有効化
            removal_policy=core.RemovalPolicy.DESTROY,  # スタック削除時にバケットも削除
            auto_delete_objects=True  # オブジェクトも自動削除
        )

        core.CfnOutput(
            self, 
            "BucketName",
            value=bucket.bucket_name,
            description="The name of the S3 bucket"
        )

# アプリケーションのエントリーポイント
app = core.App()
S3BucketStack(app, "S3BucketStack")
app.synth()
```

## 高レベルな抽象化（L2/L3 Constructs）による効率化

AWS CDKは、Constructと呼ばれる抽象化レイヤーを提供します。これにより、AWSリソースをプログラム的に管理しやすくしています。特に、L2（Level 2）およびL3（Level 3）のConstructは、CloudFormationのような詳細なリソース設定（L1）をより簡潔かつ効率的に記述できるようにします。

Constructのレイヤー
* L1 Constructs（低レベル抽象化）
  * CloudFormationリソースをそのまま利用する形式
   例: CfnBucket
   設定項目が多く、初心者には難しい場合がある。<br><br>

 * L2 Constructs（高レベル抽象化）
   * AWSリソースに適したベストプラクティスを組み込んだ抽象化
   例: Bucket
   設定が簡単で、一般的なユースケースに対応。<br><br>

 * L3 Constructs（ユースケース特化）
   * 特定のユースケース向けに設計された複合リソース
   例: VPCとセキュリティグループ、サブネットなどを組み合わせたネットワーク環境を作成。


以下は、L2 Constructs を使用してVPCとEC2インスタンスを作成する例です。

```python
from aws_cdk import core
from aws_cdk import aws_ec2 as ec2

class VpcEc2Stack(core.Stack):

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

        # VPC を作成
        vpc = ec2.Vpc(
            self, 
            "MyVpc",
            max_azs=2,  # 使用するアベイラビリティゾーンの数
            nat_gateways=1  # NAT ゲートウェイの数
        )

        # セキュリティグループを作成
        security_group = ec2.SecurityGroup(
            self,
            "MySecurityGroup",
            vpc=vpc,
            description="Allow SSH access",
            allow_all_outbound=True
        )

        # SSH アクセスを許可
        security_group.add_ingress_rule(
            peer=ec2.Peer.any_ipv4(),
            connection=ec2.Port.tcp(22),
            description="Allow SSH access from anywhere"
        )

        # EC2 インスタンスを作成
        instance = ec2.Instance(
            self,
            "MyInstance",
            instance_type=ec2.InstanceType("t3.micro"),  # インスタンスタイプ
            machine_image=ec2.MachineImage.latest_amazon_linux(),  # AMI
            vpc=vpc,
            security_group=security_group,
            key_name="my-key-pair"  # EC2 に接続するためのキーペア名を指定
        )

        # VPC と EC2 の情報を出力
        core.CfnOutput(
            self,
            "VpcId",
            value=vpc.vpc_id,
            description="The ID of the VPC"
        )
        core.CfnOutput(
            self,
            "InstanceId",
            value=instance.instance_id,
            description="The ID of the EC2 Instance"
        )

# アプリケーションのエントリーポイント
app = core.App()
VpcEc2Stack(app, "VpcEc2Stack")
app.synth()

```

## 高レベル抽象化がもたらす効率化のメリット

抽象化することで、以下のような効率化のメリットが享受できます。

### コードの簡潔化
L2やL3 Constructsを利用することで、短いコードで複雑なリソース構成を作成可能。

### ベストプラクティスの適用
安全性や効率性を考慮したAWSの推奨するベストプラクティスが組み込まれており、デフォルトで適用される。

### ユースケースに応じた柔軟性
L3 Constructは複数のリソースを組み合わせたパッケージとして利用できるため、特定のユースケース（例: Webアプリケーションやデータベースバックエンド）に最適化されている。

# CDKを選ぶ理由

さまざまなメリットがある AWS CDK ですが、 IaC のサービスであるAWS CloudFormation が既にある状況で新たに選ぶのはなぜなのでしょうか？

## CloudFormationとの違い

AWS CDK と AWS CloudFromation を比較した際、既に記載もしていますが、以下のような違いがあります。

### 宣言型 vs 命令型
 * AWS CloudFormation: YAMLやJSONを使用した宣言型アプローチ。リソースを「何を作るか」を記述するが、「どう作るか」の柔軟性は限られている
 * AWS CDK: 命令型アプローチで、プログラミング言語を用いてリソースを「どのように作るか」まで制御できる

### プログラム的な操作
CloudFormationでは、テンプレートの中で条件分岐やループのような処理を記述するのが難しいですが、CDKではプログラミング言語を活用することで、これらが簡単に実現可能です。

例: 環境ごとの構成の切り替え
CDKでは以下のように環境ごとに異なる構成を動的に生成できます。


```python
import os
from aws_cdk import core
from aws_cdk import aws_s3 as s3

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

        # 環境変数に基づいてバケット名を切り替える
        env = os.getenv("DEPLOY_ENV", "dev")
        bucket_name = f"my-app-{env}-bucket"

        s3.Bucket(self, "MyBucket", bucket_name=bucket_name)
```

これくらいであれば、 AWS CloudFormation でも、「あるアカウントIDだったらこの名称」のようなことはできますが、限定的です。

### 再利用性
 * AWS CloudFormation: テンプレートを再利用するには、外部からパラメータを渡す必要があり、複雑化する場合がある
   * 別のテンプレートで出力した Output を参照するなど...  
 * AWS CDK: モジュール化が容易で、Constructを使うことでリソース定義をカプセル化し、複数プロジェクトで簡単に再利用可能

## シンプルな管理と高い柔軟性

AWS CDKは、シンプルさと柔軟性を兼ね備えています。

### 記述が簡潔
CloudFormationで同じ構成を記述する場合、数十行を要するテンプレートも、CDKでは数行で表現可能です。

例: S3バケットの作成
CloudFormationテンプレート（YAML）の場合:

```yaml
Resources:
  MyBucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: "my-sample-bucket"
      VersioningConfiguration:
        Status: "Enabled"
```

CDK（Python）の場合:
```python
s3.Bucket(self, "MyBucket", bucket_name="my-sample-bucket", versioned=True)
```

### ベストプラクティスの自動化
CDKでは、AWSのベストプラクティスがデフォルトで組み込まれています。

例: S3バケットの暗号化やログ設定などが簡単に適用可能。


# デモ
## CDKで簡単なS3バケットを作成するコード例を提示

ここでは、AWS CDKを使った簡単なS3バケット作成のデモを紹介します。以下のコードは、バケットを作成し、その名前を出力するスタックを定義します。

```python
from aws_cdk import core
from aws_cdk import aws_s3 as s3

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

        # S3バケットの作成
        bucket = s3.Bucket(
            self,
            "MySimpleBucket",
            versioned=True,  # バージョニングを有効化
            encryption=s3.BucketEncryption.S3_MANAGED,  # S3による暗号化を適用
            public_read_access=False,  # パブリックアクセスを無効化
        )

        # バケット名を出力
        core.CfnOutput(
            self, 
            "BucketNameOutput", 
            value=bucket.bucket_name, 
            description="The name of the created S3 bucket"
        )
```

### このコードのポイント
 * 短いコードで複雑な構成を表現:
   CDKはバケットの設定を簡潔に記述可能

 * AWSのベストプラクティスに準拠:
   暗号化をデフォルトで利用
   パブリックアクセスをデフォルトで無効化

# まとめ
AWS CDKは、CloudFormationの強力な機能を活かしつつ、プログラミング言語の柔軟性を提供することで、開発者の生産性を大幅に向上させます。そのシンプルさと高い抽象化レベルにより、初心者からエキスパートまで幅広い層にとって魅力的なツールとなっています。