見出し画像

MyGPTsでAWS操作用のGPTを作成してみた

こんにちは。しろまです。
ChatGPTには、自分専用にGPTをカスタマイズできるMyGPTsという機能があります。
今回は、そのMyGPTsを使って、AWSを操作するGPTを作成したので、備忘録的な感じで記事にしてみました。

MyGPTsとAWSの連携について

AWSの操作の多くは、AWSマネジメントコンソールへログインし、GUIで操作しますが、AWS SDK for Python (Boto3)ライブラリを使用すれば、pythonのプログラムで、ほとんどの操作が可能になります。

今回は、このBoto3をサーバーレス環境である AWS Lambda 上で動作させ、MyGPTsとAPIで連携させることでMyGPTsのプロンプトからの指示でAWSを操作できるようにしてみました。

MyGPTs側で利用する機能
・コードインタープリタ
 プロンプトの入力内容を解析し、適切なアクション(APIリクエスト)を選択して実行
・MyGPTsのアクション スキーマ
 APIのエンドポイントを定義し、API Gatewayへリクエストを送信

AWS側で使用するサービス
・API Gateway
 MyGPTsからのリクエストを受け取り、AWS Lambdaへ実行指示
・AWS Lambda 
 Boto3を実行させ、AWSの操作を行う

また、ChatGPTのコードインタープリタ機能は、そもそもpythonコードを実行できるので、「AWS LambdaやAPI Gateway は要らないのでは?」
と思う方もいると思うので、補足ですが、コードインタープリタ機能には制限があり、現在はChatGPT内での実行にのみ対応しており、直接の外部アプリケーションやサービスとの接続は不可で、ライブラリを追加インストールすることもできないため、Boto3 を使用することができません。
そのため、Boto3 を外部で実行させAPI連携させる必要があるため、前述のやり方になりました。

ではさっそく、作成したMyGPTsを動かしてみます。

作成したMyGPTsの動作

まずは、「Linuxサーバを1台作成して」と指示をしてみます。

※MyGPTsからの返答内に、APIエンドポイントURLが表示されるので、そこだけ黒塗りにしてます。

Linuxサーバの作成を指示しているMyGPTsのプロンプト画像

指示を出して数秒で、1台作成できました。
パブリックIPアドレスについては、割り当てまでに多少時間がかかるのでPending になっています。

パブリックIPアドレスが割り当てられたら表示して」と指示を出すと、インスタンス情報が表示されました。

AWSインスタンス情報を表示しているMyGPTsのプロンプト画像

AWSマネジメントコンソール上でも確認。
該当のインスタンスがちゃんと作成できています。

AWSマネジメントコンソールでのインスタンス情報を表示している画像

削除します。

AWSインスタンスの削除指示をしているMyGPTsのプロンプト画像

今度は、Linuxサーバを複数台作成してみます。
Linuxサーバを10台作成して」と指示を出します。

Linuxサーバ10台の作成を指示しているMyGPTsのプロンプト画像

作成できました。こちらも数秒で完了します。

AWSマネジメントコンソールでも確認。
ちゃんと作成されてます。

AWSマネジメントコンソールでの作成されたインスタンス情報を表示している画像

今度は、先月の請求金額を聞いてみます。

AWSの請求金額の表示を指示しているMyGPTsのプロンプト画像

金額が細かくでちゃいました。
AWSマネジメントコンソールでは、丸められて0.5 USD になってました。

AWSマネジメントコンソールの請求金額を表示している画像

指示を出せば、数字を丸めて表示することもできます。

MyGPTsのプロンプトで請求金額を丸めて表示するように指示している画像


では、設定のほうを記載していきます。

AWS側の設定

(大まかな流れです)

① IAMユーザの作成とポリシーの適用
今回は、EC2インスタンスの作成や削除などの操作と、請求金額を読み取れるようにするため、最低でも下記のポリシーを適用させる
・AmazonEC2FullAccess
・AWSBillingReadOnlyAccess
※必要に応じて AdministratorAccess(すべての操作を行う場合)

② キーペアの作成
SSH接続や、RDP接続の際に使用する

③ セキュリティグループの作成
SSH(22)や、RDP(3389)を許可する

④ AWS Lambdaの設定
下記をLambda関数へ記載しデプロイ
・キーペア名
・AMI ID
・セキュリティグループID

Lambda関数

import json
import boto3
import datetime
import os
from typing import Any, Dict

# 環境変数から設定を取得
REGION_EC2 = os.getenv('REGION_EC2', 'ap-northeast-1')
REGION_CE = os.getenv('REGION_CE', 'us-east-1')
KEY_NAME = os.getenv('KEY_NAME', 'your-key-pair')  # 実際のキーペア名を記載
AMI_ID = os.getenv('AMI_ID', 'ami-xxxxxxxxxxxx')  # 該当のAMIIDを記載
SECURITY_GROUP_ID = os.getenv('SECURITY_GROUP_ID', 'sg-xxxxxxxxxxxx')  # セキュリティグループIDを記載

# クライアントの初期化
ec2 = boto3.client('ec2', region_name=REGION_EC2)
ce = boto3.client('ce', region_name=REGION_CE)

# 定数定義
ERROR_MISSING_COUNT = 'Count parameter is missing'
ERROR_INVALID_COUNT = 'Invalid count parameter. Must be an integer'
ERROR_MISSING_INSTANCE_ID = 'Instance ID is missing'
ERROR_INVALID_OPERATION = 'Invalid operation'

def create_instance(event: Dict[str, Any]) -> Dict[str, Any]:
    """指定された数のEC2インスタンスを作成します。"""
    count = event.get('count')
    if count is None:
        return error_response(400, ERROR_MISSING_COUNT)

    try:
        instance_count = int(count)
    except ValueError:
        return error_response(400, ERROR_INVALID_COUNT)

    try:
        response = ec2.run_instances(
            ImageId=AMI_ID,
            InstanceType='t2.micro',
            KeyName=KEY_NAME,
            MinCount=instance_count,
            MaxCount=instance_count,
            SecurityGroupIds=[SECURITY_GROUP_ID],
        )
        instances = [{'InstanceId': instance['InstanceId'], 'PublicIpAddress': instance.get('PublicIpAddress', 'Pending')} for instance in response['Instances']]
        return success_response({'message': 'EC2 instances created successfully', 'instances': instances, 'KeyName': KEY_NAME})
    except boto3.exceptions.Boto3Error as e:
        return error_response(500, f'Boto3 error: {str(e)}')
    except Exception as e:
        return error_response(500, str(e))

def get_instance_info(event: Dict[str, Any]) -> Dict[str, Any]:
    """指定されたインスタンスの情報を取得します。"""
    instance_id = event.get('instance_id')
    if instance_id is None:
        return error_response(400, ERROR_MISSING_INSTANCE_ID)

    try:
        response = ec2.describe_instances(InstanceIds=[instance_id])
        instance = response['Reservations'][0]['Instances'][0]
        return success_response({'InstanceId': instance_id, 'PublicIpAddress': instance.get('PublicIpAddress'), 'KeyName': instance.get('KeyName')})
    except boto3.exceptions.Boto3Error as e:
        return error_response(500, f'Boto3 error: {str(e)}')
    except Exception as e:
        return error_response(500, str(e))

def get_last_month_billing(event: Dict[str, Any]) -> Dict[str, Any]:
    """先月の請求金額を取得します。"""
    try:
        end = datetime.datetime.now()
        start = end - datetime.timedelta(days=30)
        response = ce.get_cost_and_usage(
            TimePeriod={'Start': start.strftime('%Y-%m-%d'), 'End': end.strftime('%Y-%m-%d')},
            Granularity='MONTHLY',
            Metrics=['UnblendedCost']
        )
        amount = response['ResultsByTime'][0]['Total']['UnblendedCost']['Amount']
        return success_response({'last_month_cost': str(amount), 'currency': 'USD'})
    except boto3.exceptions.Boto3Error as e:
        return error_response(500, f'Boto3 error: {str(e)}')
    except Exception as e:
        return error_response(500, str(e))

def stop_instance(event: Dict[str, Any]) -> Dict[str, Any]:
    """指定されたインスタンスを停止します。"""
    return modify_instance(event, ec2.stop_instances, 'stopped')

def start_instance(event: Dict[str, Any]) -> Dict[str, Any]:
    """指定されたインスタンスを起動します。"""
    return modify_instance(event, ec2.start_instances, 'started')

def terminate_instance(event: Dict[str, Any]) -> Dict[str, Any]:
    """指定されたインスタンスを終了します。"""
    return modify_instance(event, ec2.terminate_instances, 'terminated')

def modify_instance(event: Dict[str, Any], action: callable, action_name: str) -> Dict[str, Any]:
    """指定されたアクションをインスタンスに適用します。"""
    instance_id = event.get('instance_id')
    if instance_id is None:
        return error_response(400, ERROR_MISSING_INSTANCE_ID)

    try:
        action(InstanceIds=[instance_id])
        return success_response({'message': f'EC2 instance {instance_id} {action_name} successfully'})
    except boto3.exceptions.Boto3Error as e:
        return error_response(500, f'Boto3 error: {str(e)}')
    except Exception as e:
        return error_response(500, str(e))

def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
    """受け取ったイベントに基づいて適切な操作を実行します。"""
    operation = event.get('operation')
    operations = {
        'create': create_instance,
        'stop': stop_instance,
        'start': start_instance,
        'terminate': terminate_instance,
        'info': get_instance_info,
        'billing': get_last_month_billing,
    }

    if operation in operations:
        return operations[operation](event)
    else:
        return error_response(400, ERROR_INVALID_OPERATION)

def success_response(body: Dict[str, Any]) -> Dict[str, Any]:
    """成功レスポンスを生成します。"""
    return {
        'statusCode': 200,
        'body': json.dumps(body)
    }

def error_response(status_code: int, message: str) -> Dict[str, Any]:
    """エラーレスポンスを生成します。"""
    return {
        'statusCode': status_code,
        'body': json.dumps({'error': message})
    }

⑤ API Gatewayの作成
作成後、生成されたAPIエンドポイントをのちほどMyGPTsの設定で使用する


MyGPTs側の設定

① 右上のプロフィールアイコンをクリック
② 「MyGPTs」をクリック

MyGPTsの画面に切り替え

③ 「Configure」をクリック
④ MyGPTsの名前を入力
⑤ 「Instructions」 に下記を入力

"manage_ec2.py" ファイルについて

このPythonスクリプトは、EC2インスタンスを管理するためのものです。具体的な操作としては、以下です。

1. EC2インスタンスの作成
2. EC2インスタンスの停止
3. EC2インスタンスの起動
4. EC2インスタンスの削除
5. EC2インスタンスの情報取得
6. 先月の請求金額の取得

⑥「Upload files」をクリックし、下記内容のpythonファイルをアップロード

import requests
import re

API_URL_TEMPLATE = "https://xxxxxxxxxxxxxxxxxxxxxxxx"
HEADERS = {'Content-Type': 'application/json'}

def manage_ec2_instance(operation, payload=None):
    url = API_URL_TEMPLATE.format(operation=operation)
    try:
        response = requests.post(url, json=payload, headers=HEADERS)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        return {"error": str(e)}

def extract_value(prompt, pattern):
    match = re.search(pattern, prompt)
    return match.group(1) if match else None

def handle_prompt(prompt):
    if "Linuxサーバを" in prompt and "台作成して" in prompt:
        count = extract_value(prompt, r"Linuxサーバを(\d+)台作成して")
        if count:
            return manage_ec2_instance('create', {"count": int(count)}).get("message", "EC2 instance creation failed")
    instance_id = extract_value(prompt, r"ID: (\w+)")
    if instance_id:
        if "インスタンスを停止して" in prompt:
            return manage_ec2_instance('stop', {"instance_id": instance_id}).get("message", "EC2 instance stop failed")
        elif "インスタンスを起動して" in prompt:
            return manage_ec2_instance('start', {"instance_id": instance_id}).get("message", "EC2 instance start failed")
        elif "インスタンスを削除して" in prompt:
            return manage_ec2_instance('terminate', {"instance_id": instance_id}).get("message", "EC2 instance termination failed")
        elif "のログイン情報を教えて" in prompt:
            response = manage_ec2_instance('info', {"instance_id": instance_id})
            if 'error' in response:
                return response['error']
            return (f"インスタンス ID: {response['InstanceId']} のログイン情報:\n"
                    f"パブリックIPアドレス: {response['PublicIpAddress']}\n"
                    f"キーペア名: {response['KeyName']}")
    if "請求金額を教えて" in prompt:
        response = manage_ec2_instance('billing')
        if 'error' in response:
            return response['error']
        return f"先月の請求金額: {response['last_month_cost']} {response['currency']}"
    return "不明な指示です"

if __name__ == "__main__":
    prompt = input("プロンプトを入力してください: ")
    result =  handle_prompt(prompt)
    print(result)

⑦ 画像生成はしないため、「DALL・E Image Generation」のチェックを外す
⑧ 「Create new action」をクリック

⑨ 下記OpenAPI仕様書に、APIエンドポイントを記載し、「Schema」に入力

openapi: 3.0.0
info:
  title: MyGPT API
  description: API for managing EC2 instances using Lambda and API Gateway.
  version: 1.0.0
servers:
  - url: https://xxxxxxxxxxxxxxxxxxxxxxxx  # APIエンドポイントを記載する
paths:
  /create-instance:
    post:
      summary: Create EC2 instance
      operationId: createEc2Instance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                count:
                  type: integer
                  example: 1
      responses:
        '200':
          description: EC2 instance created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: EC2 instance created successfully
                  instances:
                    type: array
                    items:
                      type: object
                      properties:
                        InstanceId:
                          type: string
                          example: i-0123456789abcdef0
                        PublicIpAddress:
                          type: string
                          example: 203.0.113.0
                  KeyName:
                    type: string
                    example: your-key-pair-name
        '500':
          description: Error creating EC2 instance
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Error creating EC2 instance
  /stop-instance:
    post:
      summary: Stop EC2 instance
      operationId: stopEc2Instance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                instance_id:
                  type: string
                  example: i-0123456789abcdef0
      responses:
        '200':
          description: EC2 instance stopped successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: EC2 instance stopped successfully
        '500':
          description: Error stopping EC2 instance
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Error stopping EC2 instance
  /start-instance:
    post:
      summary: Start EC2 instance
      operationId: startEc2Instance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                instance_id:
                  type: string
                  example: i-0123456789abcdef0
      responses:
        '200':
          description: EC2 instance started successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: EC2 instance started successfully
        '500':
          description: Error starting EC2 instance
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Error starting EC2 instance
  /terminate-instance:
    post:
      summary: Terminate EC2 instance
      operationId: terminateEc2Instance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                instance_id:
                  type: string
                  example: i-0123456789abcdef0
      responses:
        '200':
          description: EC2 instance terminated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: EC2 instance terminated successfully
        '500':
          description: Error terminating EC2 instance
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Error terminating EC2 instance
  /get-instance-info:
    post:
      summary: Get EC2 instance information
      operationId: getEc2InstanceInfo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                instance_id:
                  type: string
                  example: i-0123456789abcdef0
      responses:
        '200':
          description: EC2 instance information retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  InstanceId:
                    type: string
                    example: i-0123456789abcdef0
                  PublicIpAddress:
                    type: string
                    example: 203.0.113.0
                  KeyName:
                    type: string
                    example: your-key-pair-name
        '500':
          description: Error retrieving EC2 instance information
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Error retrieving EC2 instance information
  /get-last-month-billing:
    get:
      summary: Get last month's billing information
      operationId: getLastMonthBilling
      responses:
        '200':
          description: Last month's billing information retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  last_month_cost:
                    type: string
                    example: "123.45"
                  currency:
                    type: string
                    example: "USD"
        '500':
          description: Error retrieving billing information
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Error retrieving billing information

設定は以上になります。

まとめ

今回作成したMyGPTsは、手始めとして、EC2インスタンスの基本的な操作と請求金額の確認のみですが、設定次第ではもっと色々なことができます。
個人的には、できることをもっと追加していこうと思っています。

AWSには200種類以上のサービスがあり、それぞれが独自のサービス名であったり、設定や操作方法も独特なところがあり、なかなか習得できないかたもいると思います。

今回のようなMyGPTsを作成して連携してしまえば、チャット形式で操作ができるので、AWSの知識がない方でも、容易に操作ができるようになります。
また、AWSの学習にも活用できますので、用途は様々広がると思いますので、ぜひ機会があれば試してみてくださいね!


著者:


この記事が参加している募集