APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

【AWS】AWSが開発したAI IDEのKiroを使ってCloudFormationファイルを生成してみる

こんにちは、クラウド事業部の山路です。

今回は2025年7月14日にプレビュー版がリリースされたKiroを使ってみました。KiroはAWSの開発したAI IDEなので、ここではAWSリソースの要件定義からCloudFormationファイルの生成までを試しました。

なお、私はこれまでCursorなどのAI IDEやClaude Codeなどは触ったことがなく、普段はGeminiやChatGPTといった対話型AIのチャット機能を使っています。今回は使ってみた使用感や感想をメインに紹介しますが、このあたりを前提に読んでいただければ幸いです。

kiro.dev

Kiroとは

Kiroはトップページで The AI IDE for prototype to production と掲げており、AIによる開発コードをプロトタイプで終わらせず本番運用にも耐えるものにすることを支援します。

Kiroは Spec-driven development (仕様駆動開発) を特徴としており、利用者は Spec という機能を利用することで最初に要件やタスクリストを生成し、それに沿ってコード生成などのタスクを実行できます。これにより利用者はアプリケーションの機能の検討などを通じて、開発物の仕様を十分に検討・精査することができます。またAIにタスクを依頼する際にも、求める仕様を詳細に記載したドキュメントを渡すことで、利用者の意図を伝えやすく、よりよい成果物が出来上がりやすくなることが期待できます。

またKiroには Hook という機能も備わっています。これはKiro上で特定のイベントが発生したときに、事前に定義したアクションを自動的に実行する機能です。例えば開発者がコードをコミットする前にテストコードやREADMEを更新したり、コミットするファイル上に秘匿情報が含まれないかをチェックする、といったアクションが可能となります。これにより定型的な作業をもれなく実行し、コードの安全性や一貫性を担保できます。

Kiroには大きく以下のような機能が備わっています。

  • VSCode-likeなIDE: 利用者も多いVSCodeライクな操作画面を提供します。VSCode拡張のインポートも可能です。
  • Spec: 利用者がChat欄から入力したプロンプトをもとに、要件の分解や設計ドキュメントの作成、タスクリストの生成などを行います。既存プロジェクトの要件をインポートしたい場合は手動でのインポートのほか、MCPとの統合も利用できます。
  • Vibe-coding: Kiroは Go from vibe coding to viable code を目指す製品ですが、従来のVibe-codingにも対応しています。
  • Hooks: Kiro上の特定のイベントに応答し、事前定義したアクションを実行する機能です。
  • Steering: プロジェクトの目的や技術的制約といった情報をMarkdown形式のファイルに記述し、Kiroに前提となる知識を与えます。

Kiroを使ってCloudFormationファイルを用意する

ここから実際にKiroを使う様子を紹介します。今回はKiroの目玉機能である Spec を利用し、要件定義からAWSリソースの設計、CloudFormationファイルの生成までを実施しました。作成後は手動でデプロイし動作確認を行います。

Kiroのインストール

※2025/7/18追記: 現時点ではWaitlistになっており、すぐにインストールはできなくなっております。

Kiroのインストール方法は公式ドキュメントに従って行います。なお今回はWindows端末を使いましたが、MacOS / Linuxもサポートしています。

手順は簡単ですので該当箇所を折りたたんでおきます。興味のある方は以下をクリックして展開してください。

Kiroインストール方法


Kiroのトップページからインストーラーをダウンロードします。インストーラーをダブルクリックするとセットアップ画面が表示され、最終的に以下のような画面が表示されれば完了です。

Kiroを開くと、最初に任意のプロバイダーでログインすることを求められます。今回はGitHubを選択しました。

GitHubの場合は以下のような画面が表示されるので、 Authorize kirodotdev を選択しログインを完了します。

VSCode拡張機能をインポートするか質問されます。今回はスキップしました。

テーマの選択やShellのセットアップを行って完了です。


インストールから初期セットアップを完了すると、以下のような状態になります。

ここでは Open a project を選択して任意のフォルダ (kiro_test_project) を用意します。

Specによる要件書の作成

ここからKiro上での実際の操作を行います。

まずは上記画面から Spec モードを選択し、Kiroへの命令文を入力します。なお今回使用するモデルは Claude Sonnet 4.0 、Autopilotはオフにしています。

※Kiroは現状2つのモデル (Claude Sonnet 4.0 Claude Sonnet 3.7) を選択できます。またAutopilotをオフにすると、Kiroが変更を加えるたびに利用者の承認を要求されます。

今回は以下のような命令文を入力し、API Gateway + Lambdaで文字列を返すだけの簡単なものを要求しました。具体的な挙動やリソース名は記載していますが、実際に構築するとなるといくつか足りないリソースもある、くらいの内容にしています。

あるURLにアクセスすると “Hello Kiro” という文字列を返すだけの機能が欲しいです。デプロイはAWS CloudFormation経由で実施するので、Amazon API GatewayとAWS Lambdaを定義するCloudFormationファイルを納品物としたいです。

上記命令文を実行するとIDEの右側にChat欄が登場し、Kiroの処理内容や進行状況、利用者への操作ボタンなどが表示されます。なお処理中や待機中はChat欄のKiroのアイコンが目線を動かしたり瞬きしてます。

Kiroは最初に requirements.md というファイル上に要件を出力します。利用者はファイルの内容を確認し、問題なければ Move to design phase というボタンを選択して次の工程に進みます。

修正したい場合はファイルを直接修正するかChat欄から命令します。ここではすべて日本語で書くように命令しています。

この時にできた requirements.md はこちらです。命令文に含まれる要件を分解して受け入れ基準を設定するほか、不足する要件も追加しています。今回作りたかったものとしてはオーバースペックな気もしますが、いったんKiroに従ってみます。

requirements.md

# 要件書


## 概要


この機能は、特定のURLにアクセスすると"Hello Kiro"という文字列を返すシンプルなAPIを提供します。インフラストラクチャはAWS CloudFormationを使用してAmazon API GatewayとAWS Lambdaで構築され、完全にサーバーレスアーキテクチャで実装されます。


## 要件


### 要件1


**ユーザーストーリー:** 開発者として、特定のURLにアクセスして"Hello Kiro"のレスポンスを受け取りたい。これによりAPIが正常に動作していることを確認できる。


#### 受け入れ基準


1. ユーザーがAPIエンドポイントにGETリクエストを送信した時、システムは"Hello Kiro"をプレーンテキストとして返す
2. APIが呼び出された時、システムはHTTPステータスコード200で応答する
3. APIがGET以外のHTTPメソッドを受信した時、システムは適切なエラーレスポンスを返す


### 要件2


**ユーザーストーリー:** DevOpsエンジニアとして、CloudFormationを使用してインフラストラクチャをデプロイしたい。これにより一貫性があり再現可能な方法でリソースを管理できる。


#### 受け入れ基準


1. CloudFormationテンプレートをデプロイした時、システムはAPI Gateway REST APIを作成する
2. CloudFormationテンプレートをデプロイした時、システムはLambda関数を作成する
3. CloudFormationテンプレートをデプロイした時、システムはAPI GatewayとLambda間の統合を適切に設定する
4. CloudFormationスタックがデプロイされた時、システムはAPIエンドポイントURLを出力する


### 要件3


**ユーザーストーリー:** システム管理者として、Lambda関数に適切なIAM権限を設定したい。これによりセキュリティ問題なく適切に実行できる。


#### 受け入れ基準


1. Lambda関数が作成された時、システムは適切な実行ロールを割り当てる
2. Lambda関数が実行される時、最小限必要な権限のみを持つ
3. API GatewayがLambda関数を呼び出す時、システムは適切なリソースベースポリシーを通じて呼び出しを許可する


### 要件4


**ユーザーストーリー:** 開発者として、APIをインターネットからアクセス可能にしたい。これによりどの場所からでもテストできる。


#### 受け入れ基準


1. API Gatewayがデプロイされた時、パブリックにアクセス可能である
2. APIエンドポイントにアクセスする時、認証を必要としない
3. インターネット上のどの場所からAPIが呼び出された時でも、正常に応答する

次に Move to design phase を選択し、 design.md に設計書を作成します。

design.md の内容は以下の通りとなり、簡単なアーキテクチャ図やリソース、エラーハンドリングにテスト戦略、CloudFormationのテンプレート構造まで記載されています。

design.md

※記事中の表示の都合上、実際のファイル中のグレイヴ・アクセント (`) はシングルクォーテーション (') に置き換えています。

# 設計書


## 概要


この機能は、AWS CloudFormationを使用してサーバーレスアーキテクチャでシンプルなAPIを構築します。API GatewayがHTTPリクエストを受け取り、Lambda関数を呼び出して"Hello Kiro"という文字列を返します。


## アーキテクチャ


'''mermaid
graph TD
    A[インターネット] --> B[API Gateway]
    B --> C[Lambda関数]
    C --> D["Hello Kiro"レスポンス]
    D --> B
    B --> A
   
    E[CloudFormation] --> F[IAM実行ロール]
    E --> B
    E --> C
    F --> C
'''


### アーキテクチャの特徴


- **サーバーレス**: サーバー管理不要、使用量に応じた課金
- **スケーラブル**: 自動スケーリング対応
- **高可用性**: AWSマネージドサービスによる冗長化
- **セキュア**: IAMによる最小権限の原則


## コンポーネントと インターフェース


### 1. Lambda関数
- **名前**: HelloKiroFunction
- **ランタイム**: Python 3.11
- **ハンドラー**: index.lambda_handler
- **メモリ**: 128MB(最小構成)
- **タイムアウト**: 30秒


**インターフェース**:
'''python
def lambda_handler(event, context):
    # API Gatewayからのイベントを処理
    # "Hello Kiro"を返す
'''


### 2. API Gateway
- **タイプ**: REST API
- **リソース**: /hello
- **メソッド**: GET
- **統合タイプ**: Lambda Proxy Integration
- **認証**: なし(パブリックアクセス)


**レスポンス形式**:
'''json
{
    "statusCode": 200,
    "headers": {
        "Content-Type": "text/plain"
    },
    "body": "Hello Kiro"
}
'''


### 3. IAM実行ロール
- **名前**: HelloKiroLambdaExecutionRole
- **管理ポリシー**: AWSLambdaBasicExecutionRole
- **権限**: CloudWatch Logsへの書き込みのみ


## データモデル


### Lambda関数のイベント構造
'''json
{
    "httpMethod": "GET",
    "path": "/hello",
    "headers": {},
    "queryStringParameters": null,
    "body": null
}
'''


### レスポンス構造
'''json
{
    "statusCode": 200,
    "headers": {
        "Content-Type": "text/plain"
    },
    "body": "Hello Kiro"
}
'''


## エラーハンドリング


### Lambda関数内のエラー処理
1. **予期しないエラー**: 500 Internal Server Errorを返す
2. **不正なHTTPメソッド**: 405 Method Not Allowedを返す
3. **ログ出力**: CloudWatch Logsにエラー詳細を記録


### API Gatewayレベルのエラー処理
1. **Lambda関数エラー**: 502 Bad Gatewayを返す
2. **タイムアウト**: 504 Gateway Timeoutを返す
3. **権限エラー**: 403 Forbiddenを返す


## テスト戦略


### 1. 単体テスト
- Lambda関数の動作確認
- 正常系: "Hello Kiro"が返されることを確認
- 異常系: エラーハンドリングの確認


### 2. 統合テスト
- API Gateway + Lambda統合の確認
- HTTPリクエスト/レスポンスの確認
- エラーレスポンスの確認


### 3. CloudFormationテンプレートテスト
- テンプレートの構文チェック
- デプロイ/削除の確認
- 出力値の確認


### 4. エンドツーエンドテスト
- 実際のHTTPリクエストでの動作確認
- 異なるHTTPメソッドでのテスト
- レスポンス時間の確認


## CloudFormationテンプレート構造


### リソース構成
1. **AWS::IAM::Role** - Lambda実行ロール
2. **AWS::Lambda::Function** - Hello Kiro Lambda関数
3. **AWS::ApiGateway::RestApi** - REST API
4. **AWS::ApiGateway::Resource** - /helloリソース
5. **AWS::ApiGateway::Method** - GETメソッド
6. **AWS::ApiGateway::Deployment** - APIデプロイメント
7. **AWS::ApiGateway::Stage** - prodステージ
8. **AWS::Lambda::Permission** - API Gatewayからの呼び出し許可


### 出力
- **ApiEndpoint**: デプロイされたAPIのエンドポイントURL


## セキュリティ考慮事項


### 1. IAM権限
- Lambda実行ロールは最小権限(CloudWatch Logsのみ)
- API GatewayからLambdaへの呼び出し許可のみ設定


### 2. ネットワークセキュリティ
- パブリックアクセス(認証なし)
- HTTPS通信(API Gatewayデフォルト)


### 3. ログとモニタリング
- CloudWatch Logsでの実行ログ記録
- API Gatewayアクセスログ(オプション)

※生成されたアーキテクチャ図が問題なく表示できることも確認しました。

最後に tasks.md に実装計画を記述します。

出来上がった tasks.md はこちらです。

tasks.md

# 実装計画


- [ ] 1. プロジェクト構造とLambda関数の実装
  - プロジェクトディレクトリ構造を作成
  - "Hello Kiro"を返すPython Lambda関数を実装
  - Lambda関数の単体テストを作成
  - _要件: 1.1, 1.2_


- [ ] 2. CloudFormationテンプレートの基本構造作成
  - CloudFormationテンプレートファイルを作成
  - テンプレートのメタデータとパラメータセクションを定義
  - 出力セクションでAPIエンドポイントURLを定義
  - _要件: 2.1, 2.4_


- [ ] 3. IAM実行ロールの定義
  - Lambda関数用のIAM実行ロールをCloudFormationで定義
  - AWSLambdaBasicExecutionRole管理ポリシーをアタッチ
  - 最小権限の原則に従った権限設定を実装
  - _要件: 3.1, 3.2_


- [ ] 4. Lambda関数リソースの定義
  - CloudFormationでLambda関数リソースを定義
  - Python 3.11ランタイム、適切なメモリとタイムアウト設定
  - IAM実行ロールとの関連付けを設定
  - インラインコードまたはZipファイル参照を実装
  - _要件: 2.2, 3.1_


- [ ] 5. API Gateway REST APIの定義
  - CloudFormationでAPI Gateway REST APIリソースを定義
  - API名と説明を設定
  - エンドポイント設定(REGIONAL)を実装
  - _要件: 2.1, 4.1_


- [ ] 6. API Gatewayリソースとメソッドの定義
  - /helloリソースをCloudFormationで定義
  - GETメソッドを定義し、Lambda統合を設定
  - Lambda Proxy Integrationを有効化
  - メソッドレスポンスの設定を実装
  - _要件: 1.1, 1.3, 4.2_


- [ ] 7. API Gatewayデプロイメントとステージの設定
  - CloudFormationでAPI Gatewayデプロイメントリソースを定義
  - prodステージを作成し、適切な設定を実装
  - ステージ変数やログ設定を構成
  - _要件: 2.1, 4.1_


- [ ] 8. Lambda呼び出し許可の設定
  - API GatewayからLambda関数への呼び出し許可を定義
  - AWS::Lambda::Permissionリソースを実装
  - 適切なソースARNとプリンシパルを設定
  - _要件: 3.3_


- [ ] 9. CloudFormationテンプレートの統合テスト
  - テンプレートの構文検証を実行
  - 依存関係とリソース参照の確認
  - 出力値の正確性を検証
  - _要件: 2.1, 2.2, 2.3, 2.4_


- [ ] 10. エラーハンドリングの実装
  - Lambda関数に適切なエラーハンドリングを追加
  - 不正なHTTPメソッドに対する405レスポンスを実装
  - 予期しないエラーに対する500レスポンスを実装
  - CloudWatch Logsへのエラーログ出力を追加
  - _要件: 1.3_


- [ ] 11. デプロイメントドキュメントの作成
  - CloudFormationスタックのデプロイ手順を文書化
  - 必要なAWS権限とプリリクイジットを記載
  - テスト方法とトラブルシューティングガイドを作成
  - _要件: 2.1, 2.4, 4.3_

ここまでの内容に問題がなければ finalize task list Accept all を選択します。

タスクリストに従ってコードを生成する。

ここからは生成したドキュメントに従ってコードを生成します。

コードを生成するには tasks.md にある Start task をクリックします。クリックすると各タスクが開始され、Chat欄で進行状況などが表示されます。

タスクが完了すると Task completed と表示されるので、次のタスクを順に実行します。

タスクによってはテストコマンドが表示され、Kiro上からコマンドの実行を指示できます。

他のタスクが完了していない状態で別のタスクを開始しようとすると、以下のようにタスクの実行は保留され、別タスクの完了とともに実行されました。

Kiroの処理内容を少し見ると、 tasks.md や作成中のファイルを読み取り、必要ならファイルの作成や修正を行います。次にタスクリストの内容を網羅したか確認し、完了したかをチェックします。

一連のタスクを完了し、CloudFormationファイルが生成されたので手動でデプロイしました。タスク完了直後のファイルにはいくつか問題がありデプロイに失敗したので、その都度Kiroにエラー文を渡すことでテンプレートを修正します。

最終的に出来上がったのは以下のようなテンプレートです。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Hello Kiro API - Simple serverless API that returns "Hello Kiro" message'

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "API Configuration"
        Parameters:
          - StageName
          - ApiName
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaTimeout
          - LambdaMemorySize
    ParameterLabels:
      StageName:
        default: "API Gateway Stage Name"
      ApiName:
        default: "API Name"
      LambdaTimeout:
        default: "Lambda Function Timeout"
      LambdaMemorySize:
        default: "Lambda Function Memory Size"

Parameters:
  StageName:
    Type: String
    Default: prod
    Description: The name of the API Gateway stage
    AllowedValues:
      - dev
      - test
      - prod
  
  ApiName:
    Type: String
    Default: hello-kiro-api
    Description: The name of the API Gateway REST API
    MinLength: 1
    MaxLength: 128
  
  LambdaTimeout:
    Type: Number
    Default: 30
    Description: Lambda function timeout in seconds
    MinValue: 1
    MaxValue: 900
  
  LambdaMemorySize:
    Type: Number
    Default: 128
    Description: Lambda function memory size in MB
    AllowedValues:
      - 128
      - 256
      - 512
      - 1024

Resources:
  # API Gateway REST API
  RestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Ref ApiName
      Description: 'Hello Kiro API - Simple serverless API that returns "Hello Kiro" message'
      EndpointConfiguration:
        Types:
          - REGIONAL
      Policy:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: '*'
            Action: 'execute-api:Invoke'
            Resource: '*'
      Tags:
        - Key: Project
          Value: HelloKiroAPI
        - Key: Environment
          Value: !Ref StageName

  # IAM Role for API Gateway CloudWatch Logs
  ApiGatewayCloudWatchRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${AWS::StackName}-apigateway-cloudwatch-role'
      Description: 'IAM role for API Gateway to write to CloudWatch logs'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: apigateway.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
      Tags:
        - Key: Project
          Value: HelloKiroAPI
        - Key: Environment
          Value: !Ref StageName

  # API Gateway Account Configuration
  ApiGatewayAccount:
    Type: AWS::ApiGateway::Account
    Properties:
      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchRole.Arn

  # IAM Role for Lambda Function
  HelloKiroLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${AWS::StackName}-lambda-execution-role'
      Description: 'IAM role for Hello Kiro Lambda function execution'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: CloudWatchLogsPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: 
                  - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-hello-kiro-function'
                  - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-hello-kiro-function:*'
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: 
                  - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/apigateway/${AWS::StackName}-${ApiName}'
                  - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/apigateway/${AWS::StackName}-${ApiName}:*'
      Tags:
        - Key: Project
          Value: HelloKiroAPI
        - Key: Environment
          Value: !Ref StageName

  # API Gateway Resource for /hello path
  HelloResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApi
      ParentId: !GetAtt RestApi.RootResourceId
      PathPart: hello

  # API Gateway Method for GET /hello
  HelloMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !Ref HelloResource
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloKiroFunction.Arn}/invocations'
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"
              method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true
          ResponseModels:
            application/json: Empty
        - StatusCode: 400
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
          ResponseModels:
            application/json: Error
        - StatusCode: 500
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
          ResponseModels:
            application/json: Error

  # OPTIONS method for CORS preflight requests
  HelloOptionsMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !Ref HelloResource
      HttpMethod: OPTIONS
      AuthorizationType: NONE
      Integration:
        Type: MOCK
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"
              method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
            ResponseTemplates:
              application/json: ''
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true
          ResponseModels:
            application/json: Empty

  # API Gateway Deployment
  ApiDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - HelloMethod
      - HelloOptionsMethod
    Properties:
      RestApiId: !Ref RestApi
      Description: !Sub 'Deployment for ${ApiName} API'

  # API Gateway Stage
  ApiStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref RestApi
      DeploymentId: !Ref ApiDeployment
      StageName: !Ref StageName
      Description: !Sub '${StageName} stage for ${ApiName} API'
      Variables:
        Environment: !Ref StageName
        Version: '1.0'
      MethodSettings:
        - ResourcePath: '/*'
          HttpMethod: '*'
          LoggingLevel: INFO
          DataTraceEnabled: true
          MetricsEnabled: true
          ThrottlingBurstLimit: 100
          ThrottlingRateLimit: 50
      AccessLogSetting:
        DestinationArn: !GetAtt ApiGatewayLogGroup.Arn
        Format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","caller":"$context.identity.caller","user":"$context.identity.user","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength","responseTime":"$context.responseTime","error":"$context.error.message","integrationError":"$context.integration.error"}'
      Tags:
        - Key: Project
          Value: HelloKiroAPI
        - Key: Environment
          Value: !Ref StageName

  # CloudWatch Log Group for API Gateway Access Logs
  ApiGatewayLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/apigateway/${AWS::StackName}-${ApiName}'
      RetentionInDays: 14
      Tags:
        - Key: Project
          Value: HelloKiroAPI
        - Key: Environment
          Value: !Ref StageName

  # Lambda Function
  HelloKiroFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub '${AWS::StackName}-hello-kiro-function'
      Runtime: python3.11
      Handler: index.lambda_handler
      Role: !GetAtt HelloKiroLambdaExecutionRole.Arn
      Timeout: !Ref LambdaTimeout
      MemorySize: !Ref LambdaMemorySize
      Description: 'Lambda function that returns Hello Kiro message'
      Code:
        ZipFile: |
          import json
          import logging

          # Configure logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
              """
              Lambda function handler for Hello Kiro API
              
              Args:
                  event: API Gateway event object
                  context: Lambda context object
                  
              Returns:
                  dict: HTTP response with status code, headers, and body
              """
              
              try:
                  logger.info(f"Received event: {json.dumps(event)}")
                  
                  # Create response with Hello Kiro message
                  response_body = {
                      "message": "Hello Kiro"
                  }
                  
                  # Create HTTP response
                  response = {
                      "statusCode": 200,
                      "headers": {
                          "Content-Type": "application/json",
                          "Access-Control-Allow-Origin": "*",
                          "Access-Control-Allow-Methods": "GET",
                          "Access-Control-Allow-Headers": "Content-Type"
                      },
                      "body": json.dumps(response_body)
                  }
                  
                  logger.info(f"Returning response: {json.dumps(response)}")
                  
                  return response
                  
              except json.JSONDecodeError as e:
                  # Handle JSON serialization errors
                  error_message = f"JSON serialization error: {str(e)}"
                  logger.error(error_message)
                  
                  return create_error_response(400, "Bad Request", error_message)
                  
              except KeyError as e:
                  # Handle missing required fields in event
                  error_message = f"Missing required field in event: {str(e)}"
                  logger.error(error_message)
                  
                  return create_error_response(400, "Bad Request", error_message)
                  
              except Exception as e:
                  # Handle all other unexpected errors
                  error_message = f"Internal server error: {str(e)}"
                  logger.error(error_message, exc_info=True)
                  
                  return create_error_response(500, "Internal Server Error", "An unexpected error occurred")


          def create_error_response(status_code, error_type, error_message):
              """
              Create standardized error response
              
              Args:
                  status_code (int): HTTP status code
                  error_type (str): Type of error
                  error_message (str): Error message for logging
                  
              Returns:
                  dict: HTTP error response
              """
              try:
                  error_body = {
                      "error": error_type,
                      "statusCode": status_code
                  }
                  
                  response = {
                      "statusCode": status_code,
                      "headers": {
                          "Content-Type": "application/json",
                          "Access-Control-Allow-Origin": "*",
                          "Access-Control-Allow-Methods": "GET",
                          "Access-Control-Allow-Headers": "Content-Type"
                      },
                      "body": json.dumps(error_body)
                  }
                  
                  logger.error(f"Error response created: {json.dumps(response)}")
                  
                  return response
                  
              except Exception as e:
                  # Fallback error response if even error handling fails
                  logger.critical(f"Failed to create error response: {str(e)}")
                  
                  return {
                      "statusCode": 500,
                      "headers": {
                          "Content-Type": "application/json"
                      },
                      "body": json.dumps({
                          "error": "Critical Error",
                          "statusCode": 500
                      })
                  }

  # Lambda Permission for API Gateway to invoke the function
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref HelloKiroFunction
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*'

Outputs:
  ApiEndpoint:
    Description: URL of the deployed API Gateway endpoint
    Value: !Sub 'https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/hello'
    Export:
      Name: !Sub '${AWS::StackName}-ApiEndpoint'
  
  LambdaFunctionArn:
    Description: ARN of the Lambda function
    Value: !GetAtt HelloKiroFunction.Arn
    Export:
      Name: !Sub '${AWS::StackName}-LambdaFunctionArn'
  
  RestApiId:
    Description: ID of the REST API
    Value: !Ref RestApi
    Export:
      Name: !Sub '${AWS::StackName}-RestApiId'
  
  StackName:
    Description: Name of the CloudFormation stack
    Value: !Ref 'AWS::StackName'
  
  LambdaExecutionRoleArn:
    Description: ARN of the Lambda execution IAM role
    Value: !GetAtt HelloKiroLambdaExecutionRole.Arn
    Export:
      Name: !Sub '${AWS::StackName}-LambdaExecutionRoleArn'

上記テンプレートをデプロイしたところ、問題なくデプロイが完了し、出力された ApiEndpoint にアクセスすると Hello Kiro の文字列が表示されるのを確認できました。

使ってみて気になった点

KiroのSpecを使った操作の大まかな流れは上記の通りですが、使ってみて気になった点をいくつか紹介します。ただし現状はプレビュー版なので、今後改善される可能性は十分あります。

Retryがたびたび発生する

Kiroがタスクを処理している最中、処理が途中で中断する事態に何度か遭遇しました。Kiroからはモデルの変更と再実行を要求されますが、モデルを変更せず何度かRetryして対応しました。これは現状でKiroが提供するモデルの利用形態によるものかと思うので、正式リリース後は有料版などを利用すれば解消されるんじゃないかと思います (有料プランの予定はこちら) 。

Spec機能で仕様を定義するプロセス中にRetryを実行すると、 requirements.md の生成から再実行されました。

タスクリスト作成以降の処理では (途中までタスクが進捗していれば) タスクの中断された箇所から再開しました。これは各タスクのプロセスの最初にファイルを読み取る工程が含まれており、その内容に沿ってタスクを開始しているからかと思われます。

テストフェーズを省略することがある

Kiroの処理中にテストコードの生成と実行を行うことが何度かありましたが、テストの再実行を繰り返したりRetryを行うと、そのステップを省略することがありました。

例えばCloudFormationファイルを修正するタスク中に aws cloudformation validate-template コマンドで静的チェックを行おうとすることがありました。今回は私が設定するデフォルトのシェル環境にAWS CLIがインストールされていないので失敗しました。

テストが失敗すると、Kiroは様々な代案を提案します。例えばPythonのテストコードが実行できなかったときはシェル環境にあるPythonのバージョンを確認しようとしたり、あるいは別のツールを使ったテストコマンドを提案します。

ただしこういったやり取りを何度か繰り返すと、最終的にKiroがテストの処理自体をあきらめたのか (?) タスクリストのチェックを行い、タスクが完了したとみなすケースを何度か見ました。

なぜこのような挙動をとるのかはよくわかりませんが、利用するモデルへの度重なるリクエストを避けるため、だったりするのでしょうか。

さいごに

今回はAWSの提供するAI IDEのKiroを触ってみました。普段インフラをメインで扱うエンジニアの目線から見ると、Specはかなり魅力的に感じています。インフラの要件定義や設計を行うと、特に非機能要件を考えたり悩まされる場面が多くなりますが、Kiroはその辺もある程度カバーしつつコードにも反映できそうな点が良いと感じました。あとアイコンがかわいい。

まだ使い始めたばかりなので詳しい挙動などは見えていませんが、しばらく使って見ようと思います。

最後に、APCはAWS Advanced Tier Services(アドバンストティアサービスパートナー)認定を受けております。


その中で私達クラウド事業部はAWSなどのクラウド技術を活用したSI/SESのご支援をしております。

www.ap-com.co.jp

また、一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。

www.ap-com.co.jp