IaC × AIの新たな可能性：PulumiAI使ってみた。



# TLDR
- 一部界隈で話題になっているPulumiAIを使ってみた。
- 少し修正は必要だったが、Pulumi AIに入力を与えてから、実際に作りたいAWSの構成が動くまで30分程度だった。
- 手作業では構築にかなり時間のかかる、込み入ったAWSの構成が簡単に作れ、IaC × AIに未来を感じた。
- クラウド上でのプロトタイプ作りがIaC × AIで格段に早くなりそう。

ちょっと触ってみて、いいな、と思ったので簡単に紹介できればと思います。

# 前提知識
## Pulumiって？
IaCツールのひとつです。IaCツールの多くはCloudFormationのように、独自言語で書く必要があり、コーディングが難しいですが、PulumiはPythonやJavaScriptといった言語が使えるので、快適にIaCができます。以下のリンクからインストールできます。

https://www.pulumi.com/docs/get-started/install/

## IaCって？
インフラ・アズ・コード（Infrastructure as Code）の略です。いろいろ出来ますが、AWSのサービス（EC2やS3等）をコードから生成できる便利なツールです。

## Pulumi AIって？
実現したいクラウド構成の説明文を日常の言葉で与えると、それを実現するIaCコードをAIが生成してくれるサービスです。現在、英語のみ使用できます。以下のリンクから試せます。

https://www.pulumi.com/ai/

# 今回Pulumi AIで作ったもの

言葉で説明するより図を見てもらったほうが早いかもしれません。下図をご覧ください。
![CleanShot 2023-04-20 at 21.53.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/f8db6df2-af02-903e-8296-9ec72826b243.png)

Lambdaが10個あって、1個目のLambdaが2個目のLambdaにメッセージを渡し、2個目がそれを3個目に渡し…それが10個ある、という構成です。

## なぜこんな構成を作りたかったか
**Lambdaのオーバーヘッドの計測がしたかったからです。** 設計でLambdaを分けるかわけないか考えるとき、Lambdaをひとつ増やすとその分どれだけ時間のオーバーヘッドが発生するか計測したかった。ただ、手でこれを作っていくのはかなり面倒、ということで頭の中にずっとしまっていました。



# Pulumi AIの操作画面
入力を与えると、コードと具体的な指示が出力されます。言語はいろいろ選べますが、ここではPythonを選んでいます。

![CleanShot 2023-04-20 at 22.40.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/4feb9b34-87c0-e644-d5d5-25b5caf9e5ee.png)

# Pulumi AIに与えた入力
正確な入力は忘れてしまいましたが（すいません）、大体以下のような入力を試しました。

> 10 Lambda functions. Each Lambda function just pass its input to another Lambda function, and the last Lambda function return its value.

日本語にすると、大体以下の通りです。

「10個のLambda関数で、それぞれのLambda関数が、次のLambda関数に入力を渡し、最後のLambda関数がその値を返す」

# Pulumi AIが出力したコード
2つコードが生成されました。Lambdaを構築するためのいわゆるIaCコード(__main__.py)と、Lambdaの中身になるコード(index.py)です。「2つのコードを」

以下がAIが出力したコードですが、ミスがあるので直す必要があります。具体的には、各Lambdaが他のLambdaを起動する権限が付与されてない、ということです。

ですが、逆に言うとそれ以外は全部作れています。具体的には、

- 10個のLambda(それぞれ環境変数に次のLambdaのARNが入っている)
- それぞれのCloudWatch Logグループ
- Log格納に必要なロール及びポリシー

また、生成されたコードには、的確なコメントも付けられていました。

<details><summary>生成されたコード</summary>

```__main__.py
import pulumi
import pulumi_aws as aws
from pulumi_aws import lambda_
import json


# Helper function to create a chain of lambda functions
def create_lambda_function(idx, previous_lambda, name):
    role = aws.iam.Role(f"{name}-{idx}",
                         assume_role_policy=json.dumps({
                               "Version": "2012-10-17",
                               "Statement": [
                                   {
                                       "Action": "sts:AssumeRole",
                                       "Effect": "Allow",
                                       "Principal": {
                                           "Service": "lambda.amazonaws.com",
                                       },
                                   }
                               ],
                           })
    )

    policy_attachment = aws.iam.RolePolicyAttachment(f"{name}-{idx}",

policy_arn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
                                                      role=role.name
                                                      )

    if previous_lambda:
        env_vars = {'NEXT_FUNCTION_ARN': previous_lambda.arn}
    else:
        env_vars = {}

    func = lambda_.Function(f"{name}-{idx}",
                            runtime="python3.8",
                            role=role.arn,
                            handler="index.handler",
                            code=pulumi.AssetArchive({
                                'index.py': pulumi.FileAsset('index.py')
                            }),
                            environment=aws.lambda_.FunctionEnvironmentArgs(variables=env_vars)
                            )
    return func

# Create the 10 Lambda functions and chain them together
previous_lambda = None
for i in range(10):
    current_lambda = create_lambda_function(i, previous_lambda, "lambda-chain")
    previous_lambda = current_lambda

pulumi.export("first_lambda_arn", current_lambda.arn)
```

```index.py
import os
import json
import boto3

def handler(event, context):
    next_function_arn = os.environ.get("NEXT_FUNCTION_ARN", None)

    if next_function_arn:
        client = boto3.client("lambda")
        response = client.invoke(
            FunctionName=next_function_arn,
            InvocationType="RequestResponse",
            Payload=json.dumps(event)
        )
        return json.load(response['Payload'])
    else:
        return event
```
</details>


修正した結果が以下です。といっても足りなかった権限を追加しただけです。ちなみにLambdaの中身であるindex.pyは修正していません。

<details><summary>修正後のコード</summary>

```__main__.py
"""An AWS Python Pulumi program"""

import pulumi
import pulumi_aws as aws
from pulumi_aws import s3
import json


# below is pasted from the code generated by Pulumi AI

import pulumi
from pulumi_aws import lambda_

# Helper function to create a chain of lambda functions


def create_lambda_function(idx, previous_lambda, name):
    role = aws.iam.Role(f"{name}-{idx}",
                        assume_role_policy=json.dumps({
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                       "Action": "sts:AssumeRole",
                                       "Effect": "Allow",
                                       "Principal": {
                                           "Service": "lambda.amazonaws.com",
                                       },
                                }
                            ],
                        })
                        )

    aws.iam.RolePolicyAttachment(f"{name}-{idx}",
                                 policy_arn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
                                 role=role.name
                                 )

    aws.iam.RolePolicyAttachment(f"{name}-{idx}-2",
                                 policy_arn="arn:aws:iam::aws:policy/service-role/AWSLambdaRole",
                                 role=role.name
                                 )

    if previous_lambda:
        env_vars = {'NEXT_FUNCTION_ARN': previous_lambda.arn}
    else:
        env_vars = {}

    func = lambda_.Function(f"{name}-{idx}",
                            runtime="python3.8",
                            role=role.arn,
                            handler="index.handler",
                            code=pulumi.AssetArchive({
                                'index.py': pulumi.FileAsset('index.py')
                            }),
                            environment=aws.lambda_.FunctionEnvironmentArgs(
                                variables=env_vars)
                            )
    return func


# Create the 10 Lambda functions and chain them together
previous_lambda = None
for i in range(10):
    current_lambda = create_lambda_function(i, previous_lambda, "lambda-chain")
    previous_lambda = current_lambda

pulumi.export("first_lambda_arn", current_lambda.arn)
```

</details>


# 生成されたコードからLambdaの構成を作ってみる
ターミナルから``pulumi up``コマンドで作成します。作成中は以下のように、細かい進捗状況が確認できます。

![CleanShot 2023-04-21 at 00.05.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/5a0fc99b-cc36-6e63-0596-7090c4475ddf.png)


AWSコンソールへ。

ちゃんと10個できています。

![CleanShot 2023-04-19 at 21.48.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/98cd44fe-8d76-2743-71d3-03893387722c.png)

環境変数にそれぞれ次のLambdaのARNが入っています。

![CleanShot 2023-04-21 at 00.12.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/8224656a-3784-d3ad-a448-92018e98300b.png)



# ちゃんと動くのか検証した

中身を見る限り、Lambdaの9番から始まって最後に0番にいくようなので、9番のLambdaに適当な入力を渡してみます。すると…

↓1個目のLambdaのLog
![CleanShot 2023-04-19 at 22.11.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/ccbf4dbe-6788-caf3-f803-06ad03d591be.png)

↓10個目のLambdaのLog
![CleanShot 2023-04-19 at 22.10.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/549bd919-2b53-898d-e6cf-9b82350fcb9e.png)

しっかり動いてますね。**10個数珠つなぎのLambdaで合計3秒ほどかかっているのがわかります。** 

ちなみに、上記はコンテナが上がっているときの挙動で、一定時間経ってコンテナがkillされた後は、

↓1個目のLambdaのLog
![CleanShot 2023-04-19 at 22.24.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/53fab448-5f26-21cd-78bf-d328856ec113.png)

↓10個目のLambdaのLog
![CleanShot 2023-04-19 at 22.25.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/c498a5b9-9148-e028-fe75-51393a3a8f9d.png)

14秒くらいかかっています。コンテナが上がるまでのオーバーヘッドもこれでなんとなくわかりますね。**ここまで30分くらいでした。早い。** 

# 何がすごいの？

## IaCにも進出してきたAI
頭の中で思いついたAWSの構成が、説明文をAIに与えるだけで、それを実際に構築するためのコードが生成され、ちょっとの修正を加えただけで実際に構成が構築されました。ChatGPTやGitHubのCopilotなど、AIがコーディングの強力なお供になり、著者の周りでも、むしろ手練れのプログラマーの方がAIを積極的に活用しているという現状があります。IaCでも同じような現象が起こりそうです。

## クラウド上での実験や検証が超速で回せる未来
今回はPulumi AIを触ってから実際に計測が完了するまで、30分ほどでした。また、今回は10個のLambdaでしたが、やろうと思えば理論的には100個でも1000個でもいけます。複雑な構成にどれくらいAIで対応できるかは今後もっと使っていかないとわかりませんが、今回いくつか試してみた限りでは、大体ちょこちょこ修正する箇所はありますが(Pulumi上の文法ミスや引数がスネークケースではなくキャメルケースになっている等)、**「実戦でかなり使える」という印象を受けました。** 特に日々、AWSをあーでもないこーでもないと調べたり検証したりしている自分のようなエンジニアには、**検証のPDCAサイクルを早め、定時退社を可能にする救世主** になるかも知れません。興味を持った方はぜひ、試してみてください。(ただし、構築されたAWSサービスの料金や損害等は自己責任でお願いします)

# ちなみに
裏側ではchatGPTでお馴染みのGPTが動いてます(下記画像)。chatGPTでは課金しないと使えないGPTのv4もPulumiAIでは無料で使えます。

![CleanShot 2023-05-08 at 18.25.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001699/b53442fe-92f3-8bee-41d7-36e76d556c08.png)


https://www.pulumi.com/docs/get-started/install/

https://www.pulumi.com/ai/