【Pulumi】Pulumi AIが生成したコードでEC2インスタンスを構築したい～第1章：YAML編～

## はじめに

最近、Terraformを触る機会があったのですが、独自言語(HCL)を覚えるのに時間が
かかりそうで手を付けられずにいました。  
そのような時にPulumiは、Pulumi AIでコードを自動生成できてかつ、
YAMLで書けるということを知りました。学習コストが低そうだったので実際に使ってみることにしました。

## TL;DR

- Pulumiはプログラミング言語でインフラを構築可能なプロビジョニングツールであり、  
慣れ親しんだ開発言語(Python/TypeScript/Goなど)やマークアップ言語(YAMLなど)で記述可能
- さらにAWS、Azure、GCP、Kubernetes など、さまざまなクラウドプラットフォームをサポートしている
- Pulumi AIは、コードを自動生成してくれる。ただし、多少の修正は必要
- YAMLで書けてAIも使えるので、個人的には学習コストを低く抑えられたと感じる

## 前提条件

PulumiでAWSを操作するには以下が必要です

- Pulumiのアカウントを所持していること
  - https://www.pulumi.com/
- AWSにてIAMユーザーのアクセスキーが作成してあること

## 実行環境

- Amazon マシンイメージ（AMI）
  - Amazon Linux 2023 AMI
- Pulumi
  - v3.112.0

## 詳細

### 1. アクセストークンの作成

1. 以下にアクセスする
    - https://app.pulumi.com/account/tokens
2. [Create token]を選択する
3. `Description`にトークン名を入力し、[Create token]を選択する
4. トークンの内容をコピーしておく

### 2. EC2インスタンスにPulumiをインストール

1. TeraTermなどでEC2インスタンスにログインする
2. Pulumiをインストールする

    ```console
    $ wget https://get.pulumi.com/releases/sdk/pulumi-v3.112.0-linux-x64.tar.gz
    $ mkdir -p ~/.pulumi/bin
    $ tar -xzvf pulumi-v3.112.0-linux-x64.tar.gz -C ~/.pulumi/bin --strip-components=1
    ```

3. PATHを通す

    ```console
    $ echo 'export PATH="$HOME/.pulumi/bin:$PATH"' >> ~/.bashrc
    ```

4. 変更したPATHを有効化する

    ```console
    $ source ~/.bashrc
    ```

### 3. Pulumi Projectの作成

1. Pulumiにログインする。ここで、作成したトークンを入力する

    ```console
    $ pulumi login
    ```

2. 作業用ディレクトリを作成する

    ```console
    $ mkdir quickstart && cd quickstart
    ```

3. 作業用Projectを作成する
    - 今回はYAMLでAWS用の定義ファイルを書くので`aws-yaml`としています

    ```console
    $ pulumi new aws-yaml
    ```

    - 色々と聞かれますが、特に何も入力せずに`Enter`でも問題ありません(ここではRegionを`us-west-2`に変更しました)

    ```console
    This command will walk you through creating a new Pulumi project.

    Enter a value or leave blank to accept the (default), and press <ENTER>.
    Press ^C at any time to quit.

    project name (quickstart):  
    project description (A minimal AWS Pulumi YAML program):  
    Created project 'quickstart'

    Please enter your desired stack name.
    To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
    stack name (dev):  
    Created stack 'dev'

    aws:region: The AWS region to deploy into (us-east-1): us-west-2 
    Saved config

    Your new project is ready to go! 

    To perform an initial deployment, run `pulumi up`
    ```

4. `Pulumi.dev.yaml`と`Pulumi.yaml`が作成されたことを確認する
    - Pulumi.yaml: AWSリソースの定義ファイル
    - Pulumi.dev.yaml: 環境変数などを記述するファイル

    ```console
    $ ls
    Pulumi.dev.yaml  Pulumi.yaml
    ```

    なお、 `Pulumi.dev.yaml`は以下のようになっているので、そのまま使います。

    ```yaml:Pulumi.dev.yaml
    config:
      aws:region: us-west-2
    ```

### 4. Pulumi AIでAWSリソースの定義ファイル作成

1. Pulumiのホーム画面右上の[Pulumi AI]を選択する
    - https://app.pulumi.com/
2. [YAML]を選択し、処理内容を記述して[Update program]を選択する。処理内容は可能な限り具体的に書くとよい
    - 記述した内容
      - AWSでインターネット経由で接続可能なEC2インスタインス、VPC、サブネット、セキュリティグループを作成する

    ![pulumi_ai.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554812/20f84125-b58e-be43-6fe0-cef840f5f2c4.png)

    - 出力結果

      ```yaml
      name: aws-ec2-instance-with-internet-access
      runtime: yaml
      description: A program to create an AWS EC2 instance within a VPC with a subnet and security group, accessible over the internet.

      resources:
        # Create a new VPC
        myVpc:
          type: aws:ec2:Vpc
          properties:
            cidrBlock: "10.0.0.0/16"
            tags:
              Name: "my-vpc"
          options:
            provider: aws  # Explicitly choose the aws provider

        # Create an Internet Gateway
        myInternetGateway:
          type: aws:ec2:InternetGateway
          properties:
            vpcId: ${myVpc.id}
            tags:
              Name: "my-internet-gateway"

        # Create a new Subnet within the VPC
        mySubnet:
          type: aws:ec2:Subnet
          properties:
            vpcId: ${myVpc.id}
            cidrBlock: "10.0.1.0/24"
            mapPublicIpOnLaunch: true
            tags:
              Name: "my-subnet"

        # Create a new Route Table
        myRouteTable:
          type: aws:ec2:RouteTable
          properties:
            vpcId: ${myVpc.id}
            routes:
              - destinationCidrBlock: "0.0.0.0/0"
                gatewayId: ${myInternetGateway.id}
            tags:
              Name: "my-route-table"

        # Associate the Route Table with the Subnet
        myRouteTableAssociation:
          type: aws:ec2:RouteTableAssociation
          properties:
            subnetId: ${mySubnet.id}
            routeTableId: ${myRouteTable.id}

        # Create a Security Group within the VPC
        mySecurityGroup:
          type: aws:ec2:SecurityGroup
          properties:
            vpcId: ${myVpc.id}
            description: "Allow SSH inbound traffic"
            tags:
              Name: "my-security-group"
            ingress:
              - protocol: "tcp"
                fromPort: 22
                toPort: 22
                cidrBlocks: ["0.0.0.0/0"]
            egress:
              - protocol: "-1" # Allow all outbound traffic
                fromPort: 0
                toPort: 0
                cidrBlocks: ["0.0.0.0/0"]

        # Launch an EC2 instance within the subnet
        myInstance:
          type: aws:ec2:Instance
          properties:
            ami: ami-0c55b159cbfafe1f0 # Replace with the correct AMI for your region
            instanceType: "t2.micro"
            subnetId: ${mySubnet.id}
            securityGroupIds: [${mySecurityGroup.id}]
            tags:
              Name: "my-instance"

      outputs:
        vpcId: ${myVpc.id}
        subnetId: ${mySubnet.id}
        internetGatewayId: ${myInternetGateway.id}
        routeTableId: ${myRouteTable.id}
        routeTableAssociationId: ${myRouteTableAssociation.id}
        securityGroupId: ${mySecurityGroup.id}
        instanceId: ${myInstance.id}
        instancePublicIp: ${myInstance.publicIp}
      ```

3. Pulumi AIの結果をPulumiのドキュメント([Pulumi > AWS > API Docs][7])などをもとに修正し、`Pulumi.yaml`に記入する
    - 細かい修正をしたい場合は、別途Pulumi AIで作成した定義ファイルを参考にしてもよい

      ```yaml
      ---
      name: quickstart # 修正
      runtime: yaml
      description: A program to create an AWS EC2 instance within a VPC with a subnet and security group, accessible over the internet.

      resources:
        # Create a new VPC
        myVpc:
          type: aws:ec2:Vpc
          properties:
            cidrBlock: "10.0.0.0/16"
            tags:
              Name: "my-vpc"
          # options:  # 削除
          #   provider: aws

        # Create an Internet Gateway
        myInternetGateway:
          type: aws:ec2:InternetGateway
          properties:
            vpcId: ${myVpc.id}
            tags:
              Name: "my-internet-gateway"

        # Create a new Subnet within the VPC
        mySubnet:
          type: aws:ec2:Subnet
          properties:
            vpcId: ${myVpc.id}
            cidrBlock: "10.0.1.0/24"
            mapPublicIpOnLaunch: true
            availabilityZone: ${aws:region}a # 追加
            tags:
              Name: "my-subnet"

        # Create a new Route Table
        myRouteTable:
          type: aws:ec2:RouteTable
          properties:
            vpcId: ${myVpc.id}
            # routes:  # 削除
            #   - destinationCidrBlock: "0.0.0.0/0"
            #     gatewayId: ${myInternetGateway.id}
            tags:
              Name: "my-route-table"

        # Create a route that directs 0.0.0.0/0 to the Internet Gateway(追加)
        myRoute:
          type: aws:ec2:Route
          properties:
            destinationCidrBlock: "0.0.0.0/0"
            gatewayId: ${myInternetGateway.id}
            routeTableId: ${myRouteTable.id}

        # Associate the Route Table with the Subnet
        myRouteTableAssociation:
          type: aws:ec2:RouteTableAssociation
          properties:
            subnetId: ${mySubnet.id}
            routeTableId: ${myRouteTable.id}

        # Create a Security Group within the VPC
        mySecurityGroup:
          type: aws:ec2:SecurityGroup
          properties:
            vpcId: ${myVpc.id}
            description: "Allow SSH inbound traffic"
            tags:
              Name: "my-security-group"
            ingress:
              - protocol: "tcp"
                fromPort: 22
                toPort: 22
                cidrBlocks: ["0.0.0.0/0"]
            egress:
              - protocol: "-1" # Allow all outbound traffic
                fromPort: 0
                toPort: 0
                cidrBlocks: ["0.0.0.0/0"]

        # Launch an EC2 instance within the subnet
        myInstance:
          type: aws:ec2:Instance
          properties:
            ami: ami-0395649fbe870727e # 修正
            instanceType: "t2.micro"
            subnetId: ${mySubnet.id}
            # securityGroupIds: [${mySecurityGroup.id}] # 削除
            securityGroups: ["${mySecurityGroup.id}"] # 追加
            availabilityZone: ${aws:region}a # 追加
            keyName: my-key # 追加
            tags:
              Name: "my-instance"

      outputs:
        vpcId: ${myVpc.id}
        subnetId: ${mySubnet.id}
        internetGatewayId: ${myInternetGateway.id}
        routeTableId: ${myRouteTable.id}
        routeTableAssociationId: ${myRouteTableAssociation.id}
        routeID: ${myRoute.id} # 追加
        securityGroupId: ${mySecurityGroup.id}
        instanceId: ${myInstance.id}
        instancePublicIp: ${myInstance.publicIp}
      ```

補足として、`Pulumi.dev.yaml`にある`aws:region`の値は、
`${aws:region}`と記載します（詳細は、[Pulumi > Configuration][8]を参照）

### 5. Pulumiによるリソースの作成

1. IAMユーザーのアクセスキーを登録する

    ```console
    $ export AWS_ACCESS_KEY_ID="<YOUR_ACCESS_KEY_ID>"
    $ export AWS_SECRET_ACCESS_KEY="<YOUR_SECRET_ACCESS_KEY>"
    ```

2. リソースを作成する

    ```console
    $ pulumi up
    ```

3. リソースが作成されていることを確認する(以下はログの一部)

    ```console
        Type                              Name                     Status            Info
    +   pulumi:pulumi:Stack               quickstart-dev           created (52s)     
    +   ├─ aws:ec2:Vpc                    myVpc                    created (2s)      
    +   ├─ aws:ec2:RouteTable             myRouteTable             created (1s)      
    +   ├─ aws:ec2:SecurityGroup          mySecurityGroup          created (2s)      1 warning
    +   ├─ aws:ec2:InternetGateway        myInternetGateway        created (1s)      
    +   ├─ aws:ec2:Subnet                 mySubnet                 created (12s)     
    +   ├─ aws:ec2:Route                  myRoute                  created (1s)      
    +   ├─ aws:ec2:Instance               myInstance               created (33s)     
    +   └─ aws:ec2:RouteTableAssociation  myRouteTableAssociation  created (1s)    
    ```

### 6. Pulumiマイページからの結果確認

1. Pulumiのマイページにアクセス
2. 画面左の[Stacks]を選択する
3. 作成したStackを選択する
4. `Output`に作成したリソース情報が表示されていることを確認する

    ![output.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554812/0e2efe9c-6146-a327-82a9-73e3ae8bcd69.png)

### 7. Pulumiによるリソースの削除

1. リソースを削除する

    ```console
    $ pulumi down
    ```

2. リソースが削除されていることを確認する(以下はログの一部)

    ```console
         Type                              Name                     Status              
     -   pulumi:pulumi:Stack               quickstart-dev           deleted (0.19s)     
     -   ├─ aws:ec2:Instance               myInstance               deleted (41s)       
     -   ├─ aws:ec2:RouteTableAssociation  myRouteTableAssociation  deleted (1s)        
     -   ├─ aws:ec2:Route                  myRoute                  deleted (1s)        
     -   ├─ aws:ec2:Subnet                 mySubnet                 deleted (0.71s)     
     -   ├─ aws:ec2:RouteTable             myRouteTable             deleted (1s)        
     -   ├─ aws:ec2:InternetGateway        myInternetGateway        deleted (1s)        
     -   ├─ aws:ec2:SecurityGroup          mySecurityGroup          deleted (1s)        
     -   └─ aws:ec2:Vpc                    myVpc                    deleted (1s) 
    ```

## さいごに

- Pulumiの定義ファイルの書き方がよくわからなくても、Pulumi AIの結果を少し修正するだけでAWSに対するリソースの作成ができました
- AIを活用することで、学習コストや作業時間の削減が期待できそうです

## 参考URL

- [Think IT > Infrastructure-as-Codeアプローチと「Pulumi」の概要][1]
  - Pulumiの構成・料金体系
- [PulumiはIaCの革命児になれるか][2]
  - TerraformやAWS CDKとの比較
  - Pulumi AIの説明
- [Pulumi > Download & install Pulumi][3]
  - Pulumiのインストール方法(1)
- [GitHub > pulumi > pulumi install for linux does not work][4]
  - Pulumiのインストール方法(2)
    - 今回はこちらを採用
- [テラフォーム vs プルミ][5]
  - TerraformとPulumiの比較
- [Pulumi > Pulumi & AWS: Create new project][6]
  - AWSにおけるPulumiプログラムの作成
- [Pulumi > AWS > API Docs][7]
  - PulumiでAWSを操作するための記述方法
- [Pulumi > Configuration][8]
  - Pulumiで環境ごとに異なる変数を使用する方法

## 関連記事

- [【Pulumi】Pulumi AIが生成したコードでEC2インスタンスを構築したい～第2章：Python編～][9]

---

[1]: https://thinkit.co.jp/article/21057?page=0%2C1
[2]: https://zenn.dev/yuta28/articles/pulumi-ai-revolutionary
[3]: https://www.pulumi.com/docs/install/
[4]: https://github.com/pulumi/pulumi/issues/9660
[5]: https://logicmonitor.jp/blog/terraform-vs-pulumi
[6]: https://www.pulumi.com/docs/clouds/aws/get-started/create-project/
[7]: https://www.pulumi.com/registry/packages/aws/api-docs/
[8]: https://www.pulumi.com/docs/concepts/config/#code
[9]: https://qiita.com/masa2223/items/327795933573dbeff6a4