Claude Code on Amazon Bedrockのお試し環境をCloudFormationテンプレートで構築した

こんにちは。SCSK渡辺(大)です。

Proプランをサブスクリプションする前にClaude Codeをお試しで触ってみたかったので、世の中的には何番煎じか分かりませんが、Claude CodeをAmazon Bedrock経由で利用するための環境を作りました。

環境構築に必要なリソース群はAWS CloudFormationテンプレート(YAML)1つにまとめたので、デプロイも後片付けもコマンド一発です。
Amazon Bedrock経由の場合は従量課金で青天井になるため、おまけ程度ですがトークン数の監視アラートも仕込んでいます。

リセラー経由のアカウントでは別途Anthropicモデルの承認等が必要な場合があります。

 

構成図

 

 

作るもの

YAMLのテンプレート1つで(AWS CloudFormationスタック1つで)以下を全部作ります。

  • VPC + パブリックサブネット(SSM用)
  • EC2(Amazon Linux 2023、Claude Code・uv・AWS MCP自動インストール)
  • Amazon Bedrock Application Inference Profile(Sonnet/Opus)※1
  • Claude Code設定(Amazon Bedrock接続、サブエージェントはSonnet、Haiku非推奨)
  • AWS MCPサーバー設定(.mcp.json)
  • ccstatusline設定(Model・Cost・Tokens表示)
  • トークン監視(CloudWatch Metric Filter + Alarm → SNSメール通知)※2
  • Amazon Bedrockログ用IAMロール※3

※1
Haikuは除外しています。
理由は、Haiku 4.5はClaude Codeの tool_reference blocks 機能に非対応で、ツール操作(ファイル読み書き、bash実行など)でAPIエラー(400)が出るためです。
参考:#14863 – Haiku agents fail with “tool_reference blocks not supported” error

※2
個人利用向けのおまけ程度の設計です。アカウント全体の合計トークン数で監視しています。複数人で「誰が何トークン使ったか」を把握したい場合は別の構成が必要です。

※3
Amazon Bedrockログが既存している場合には作成は任意。

 

前提条件

構築するためには様々な方法があります。
下記は参考として私が実施した環境を記載します。

  • ローカル
    • Windows 11
    • Kiro (ターミナルはpowershell)
    • AWS CLI (v2.32.3)
    • Session Manager Plugin (v1.2.804.0)
  • AWS
    • AWSアカウント
    • 管理者権限が付与されたIAMユーザー

 

事前準備

AWSアカウント、および管理者権限が付与されたIAMユーザーの準備については触れませんが、それ以外については出来る限り触れます。

Kiroをインストール

  1. 以下の公式サイトからダウンロードした後、インストールします。
    Downloads – Kiro
  2. KiroにGoogle、GitHub、またはAWS Builder IDでサインインします。
  3. 必要に応じてPro以上のプランをサブスクライブしてください。
    以降の手順の中でKiroとチャットはしないので、Freeプランでも今回の構築には影響はないかと思います。

AWS CLIをインストール

  1. 以下の公式ガイドからインストールします。
    AWS CLI の最新バージョンのインストールまたは更新 – AWS Command Line Interface

Session Manager Pluginをインストール

  1. 以下の公式ガイド通りにインストールしていきます。
    AWS CLI 用の Session Manager プラグインをインストールする – AWS Systems Manager

拡張機能「Open Remote – SSH」(jeanp413)をインストール

  1. Kiroで拡張機能「Open Remote – SSH」を検索すると出てくる「Open Remote – SSH」(jeanp413)をインストールします。

デプロイ準備

  1. Kiroのターミナルで以下のコマンドを入力してエンターを押します。
    値をクォーテーションで囲み、任意のスタック名を指定してください。
    この名前がAWS CloudFormationのスタック名になり、以降のコマンドでも使用します。
$STACK_NAME = "任意のスタック名"

     2. ワークスペースに claude-code-on-aws.yaml というファイルを作成します。

     3. claude-code-on-aws.yaml に以下を記載して保存します。長すぎるので畳んでいます。

 

保存時の文字コードはBOMなしUTF-8にしてください。
BOM付きUTF-8やShift_JIS(cp932)で保存するとデプロイ時にエラーになります。
Kiroの場合、下部のステータスバーに「UTF-8」と表示されてればOKです。
「UTF-8 with BOM」など他のものになっている場合には、そこをクリックして「UTF-8」に変更してから保存してください。
▶ claude-code-on-aws.yaml(クリックで展開)

AWSTemplateFormatVersion: "2010-09-09"
Description: >
  Claude Code on AWS - Linux EC2 + Bedrock + Token Monitoring.
  Single stack. SSM tunnel SSH for VSCode Remote SSH. Cost-aware.

# ============================================================
# Parameter Groups (for AWS Console display)
# ============================================================
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Project Settings
        Parameters:
          - ProjectName
          - CostTag
      - Label:
          default: EC2 Configuration
        Parameters:
          - InstanceType
          - VolumeSize
      - Label:
          default: Network Configuration
        Parameters:
          - VpcCidr
          - SubnetCidr
      - Label:
          default: Bedrock Model Configuration
        Parameters:
          - SonnetInferenceProfileId
          - OpusInferenceProfileId
      - Label:
          default: Token Monitoring
        Parameters:
          - AlertEmail
          - TokenThreshold

# ============================================================
# Parameters
# ============================================================
Parameters:
  ProjectName:
    Type: String
    Default: claude-code
    Description: "Prefix for all resource names (e.g. claude-code -> claude-code-vpc)"
    AllowedPattern: "^[a-zA-Z][a-zA-Z0-9-]*$"
    ConstraintDescription: "Must start with a letter, then alphanumeric and hyphens only"

  CostTag:
    Type: String
    Default: claude-code
    Description: "Cost allocation tag applied to all resources"

  InstanceType:
    Type: String
    Default: t3.medium
    Description: "EC2 instance type (e.g. t3.small, t3.medium, t3.large)"

  VolumeSize:
    Type: Number
    Default: 20
    MinValue: 8
    MaxValue: 100
    Description: "EBS disk size in GiB. Amazon Linux uses ~2GB. 20GB is sufficient"

  VpcCidr:
    Type: String
    Default: 10.0.0.0/16
    Description: "VPC CIDR block"
    AllowedPattern: "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/\\d{1,2}$"

  SubnetCidr:
    Type: String
    Default: 10.0.1.0/24
    Description: "Public subnet CIDR block"
    AllowedPattern: "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/\\d{1,2}$"

  SonnetInferenceProfileId:
    Type: String
    Default: jp.anthropic.claude-sonnet-4-6
    Description: "Bedrock Sonnet inference profile ID for ap-northeast-1"

  OpusInferenceProfileId:
    Type: String
    Default: global.anthropic.claude-opus-4-6-v1
    Description: "Bedrock Opus inference profile ID (global prefix, cross-region)"

  AlertEmail:
    Type: String
    Description: "Email address for token usage alerts"
    AllowedPattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
    ConstraintDescription: "Must be a valid email address"

  TokenThreshold:
    Type: Number
    Default: 150000
    Description: "Token alert threshold (input+output+cache total per hour). Alert if exceeded"

Resources:
  # ============================================================
  # VPC / Network
  # ============================================================
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-vpc"
        - Key: Cost
          Value: !Ref CostTag

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-igw"
        - Key: Cost
          Value: !Ref CostTag

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref SubnetCidr
      AvailabilityZone: !Select [0, !GetAZs ""]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-public-subnet"
        - Key: Cost
          Value: !Ref CostTag

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-public-rt"
        - Key: Cost
          Value: !Ref CostTag

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  SubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

  # ============================================================
  # Security Group - HTTPS outbound only, no inbound (SSM access)
  # ============================================================
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub "${ProjectName} - HTTPS outbound only, SSM access"
      VpcId: !Ref VPC
      SecurityGroupIngress: []
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-sg"
        - Key: Cost
          Value: !Ref CostTag

  # ============================================================
  # EC2 Key Pair (for VSCode Remote SSH via SSM tunnel)
  # Private key is stored in Systems Manager Parameter Store
  # ============================================================
  EC2KeyPair:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: !Sub "${ProjectName}-keypair"
      KeyType: rsa
      KeyFormat: pem

  # ============================================================
  # Bedrock Application Inference Profiles
  # Enables per-model cost tracking via Cost Explorer tags
  # ============================================================
  SonnetProfile:
    Type: AWS::Bedrock::ApplicationInferenceProfile
    Properties:
      InferenceProfileName: !Sub "${ProjectName}-sonnet"
      Description: !Sub "Sonnet profile: ${SonnetInferenceProfileId}"
      ModelSource:
        CopyFrom: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/${SonnetInferenceProfileId}"
      Tags:
        - Key: Application
          Value: !Ref ProjectName
        - Key: Cost
          Value: !Ref CostTag

  OpusProfile:
    Type: AWS::Bedrock::ApplicationInferenceProfile
    Properties:
      InferenceProfileName: !Sub "${ProjectName}-opus"
      Description: !Sub "Opus profile: ${OpusInferenceProfileId}"
      ModelSource:
        CopyFrom: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/${OpusInferenceProfileId}"
      Tags:
        - Key: Application
          Value: !Ref ProjectName
        - Key: Cost
          Value: !Ref CostTag

  # ============================================================
  # IAM Role - SSM + Bedrock + AWS MCP (least privilege)
  # ============================================================
  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-ec2-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Policies:
        - PolicyName: BedrockAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: InvokeModels
                Effect: Allow
                Action:
                  - bedrock:InvokeModel
                  - bedrock:InvokeModelWithResponseStream
                Resource:
                  - !GetAtt SonnetProfile.InferenceProfileArn
                  - !GetAtt OpusProfile.InferenceProfileArn
                  - "arn:aws:bedrock:*:*:foundation-model/*"
                  - "arn:aws:bedrock:*:*:inference-profile/*"
              - Sid: ListModels
                Effect: Allow
                Action:
                  - bedrock:ListFoundationModels
                  - bedrock:ListInferenceProfiles
                  - bedrock:GetFoundationModel
                Resource: "*"
        - PolicyName: AWSMCPAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: AllowAWSMCP
                Effect: Allow
                Action:
                  - aws-mcp:InvokeMcp
                  - aws-mcp:CallReadOnlyTool
                  - aws-mcp:CallReadWriteTool
                Resource: "*"
        - PolicyName: MarketplaceAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: AllowMarketplace
                Effect: Allow
                Action:
                  - aws-marketplace:ViewSubscriptions
                  - aws-marketplace:Subscribe
                Resource: "*"
                Condition:
                  StringEquals:
                    aws:CalledViaLast: bedrock.amazonaws.com
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-ec2-role"
        - Key: Cost
          Value: !Ref CostTag

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub "${ProjectName}-instance-profile"
      Roles:
        - !Ref EC2Role

  # ============================================================
  # EC2 Instance - Amazon Linux 2023
  # Auto-installs: Git, Node.js, Python, Claude Code, uv, AWS MCP
  # ============================================================
  EC2Instance:
    Type: AWS::EC2::Instance
    DependsOn:
      - SonnetProfile
      - OpusProfile
    Properties:
      ImageId: "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}"
      InstanceType: !Ref InstanceType
      KeyName: !Ref EC2KeyPair
      IamInstanceProfile: !Ref InstanceProfile
      SubnetId: !Ref PublicSubnet
      SecurityGroupIds:
        - !Ref SecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref VolumeSize
            VolumeType: gp3
            Encrypted: true
      PropagateTagsToVolumeOnCreation: true
      UserData:
        Fn::Base64: !Sub
          - |
            #!/bin/bash
            set -e
            exec > >(tee /var/log/user-data.log) 2>&1
            echo "=== Setup started: $(date) ==="

            # System update
            dnf update -y

            # Install Node.js 22, Git, Python3
            curl -fsSL https://rpm.nodesource.com/setup_22.x | bash -
            dnf install -y nodejs git python3 python3-pip

            # Setup as ec2-user
            sudo -u ec2-user bash << 'USEREOF'
            set -e
            export HOME=/home/ec2-user
            cd $HOME

            # Install Claude Code (native installer)
            curl -fsSL https://claude.ai/install.sh | bash -s stable
            export PATH="$HOME/.local/bin:$PATH"

            # Install uv (required for uvx / AWS MCP proxy)
            curl -LsSf https://astral.sh/uv/install.sh | sh

            # Bedrock environment variables
            cat << EOF >> ~/.bashrc
            # Claude Code on Bedrock
            export CLAUDE_CODE_USE_BEDROCK=1
            export AWS_REGION=${AWS::Region}
            export AWS_DEFAULT_REGION=${AWS::Region}
            export ANTHROPIC_DEFAULT_SONNET_MODEL="${SonnetArn}"
            export ANTHROPIC_DEFAULT_OPUS_MODEL="${OpusArn}"
            export CLAUDE_CODE_SUBAGENT_MODEL="${SonnetArn}"
            export CLAUDE_CODE_MAX_OUTPUT_TOKENS=16384
            export MAX_THINKING_TOKENS=10000
            export PATH="\$HOME/.local/bin:\$HOME/.cargo/bin:\$PATH"
            EOF

            # Claude Code user settings
            mkdir -p ~/.claude
            cat << 'SETTINGS' > ~/.claude/settings.json
            {
              "provider": "bedrock",
              "autoUpdates": true,
              "autoUpdatesChannel": "stable",
              "language": "japanese",
              "statusLine": {
                "type": "command",
                "command": "npx -y ccstatusline@latest",
                "padding": 0
              },
              "permissions": {
                "deny": [
                  "Bash(rm -rf:*)",
                  "Bash(sudo:*)",
                  "Read(.env)"
                ]
              }
            }
            SETTINGS

            # AWS MCP Server (managed, via mcp-proxy-for-aws)
            cat << 'MCP' > ~/.mcp.json
            {
              "mcpServers": {
                "aws-mcp": {
                  "command": "uvx",
                  "timeout": 100000,
                  "transport": "stdio",
                  "args": [
                    "mcp-proxy-for-aws@latest",
                    "https://aws-mcp.us-east-1.api.aws/mcp",
                    "--metadata",
                    "AWS_REGION=us-west-2"
                  ]
                }
              }
            }
            MCP

            # ccstatusline config (Model | Session Cost | Tokens Total)
            mkdir -p ~/.config/ccstatusline
            cat << 'CCSTATUS' > ~/.config/ccstatusline/config.json
            {
              "lines": [
                [
                  {"category": "model", "widget": "model"},
                  {"category": "separator", "widget": "separator"},
                  {"category": "cost", "widget": "session_cost"},
                  {"category": "separator", "widget": "separator"},
                  {"category": "tokens", "widget": "tokens_total"}
                ]
              ]
            }
            CCSTATUS

            # Verify installations
            source ~/.bashrc
            claude --version && echo "Claude Code OK" || echo "Claude Code FAILED"
            USEREOF

            echo "=== Setup completed: $(date) ==="
          - SonnetArn: !GetAtt SonnetProfile.InferenceProfileArn
            OpusArn: !GetAtt OpusProfile.InferenceProfileArn
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-ec2"
        - Key: Cost
          Value: !Ref CostTag

  # ============================================================
  # Bedrock Model Invocation Logging
  # Required for token monitoring via Metric Filters.
  # After deploy, enable logging in Bedrock console and
  # point to this log group and select the logging role.
  # ============================================================
  BedrockLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/bedrock/${ProjectName}"
      RetentionInDays: 30
      Tags:
        - Key: Cost
          Value: !Ref CostTag

  BedrockLoggingRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-bedrock-logging-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:*"
      Policies:
        - PolicyName: BedrockLoggingCloudWatch
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/bedrock/${ProjectName}:*"
      Tags:
        - Key: Cost
          Value: !Ref CostTag

  # ============================================================
  # Token Monitoring - SNS Topic (KMS encrypted)
  # Alert emails may contain IAM ARN info, so encryption applied.
  # After deploy, confirm the subscription email!
  # ============================================================
  AlertTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub "${ProjectName}-token-alert"
      Tags:
        - Key: Cost
          Value: !Ref CostTag

  AlertSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      TopicArn: !Ref AlertTopic
      Protocol: email
      Endpoint: !Ref AlertEmail

  # ============================================================
  # Token Monitoring - CloudWatch Metric Filters
  # Extracts token counts from Bedrock invocation logs and
  # publishes as custom CloudWatch metrics. No Lambda needed.
  # Includes cache tokens (cacheRead/cacheWrite) for accurate totals.
  # ============================================================
  InputTokenMetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName: !Ref BedrockLogGroup
      FilterPattern: '{ $.input.inputTokenCount = "*" }'
      MetricTransformations:
        - MetricNamespace: !Sub "${ProjectName}/BedrockTokens"
          MetricName: InputTokenCount
          MetricValue: "$.input.inputTokenCount"
          DefaultValue: 0

  CacheReadTokenMetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName: !Ref BedrockLogGroup
      FilterPattern: '{ $.input.cacheReadInputTokenCount = "*" }'
      MetricTransformations:
        - MetricNamespace: !Sub "${ProjectName}/BedrockTokens"
          MetricName: CacheReadInputTokenCount
          MetricValue: "$.input.cacheReadInputTokenCount"
          DefaultValue: 0

  CacheWriteTokenMetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName: !Ref BedrockLogGroup
      FilterPattern: '{ $.input.cacheWriteInputTokenCount = "*" }'
      MetricTransformations:
        - MetricNamespace: !Sub "${ProjectName}/BedrockTokens"
          MetricName: CacheWriteInputTokenCount
          MetricValue: "$.input.cacheWriteInputTokenCount"
          DefaultValue: 0

  OutputTokenMetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName: !Ref BedrockLogGroup
      FilterPattern: '{ $.output.outputTokenCount = "*" }'
      MetricTransformations:
        - MetricNamespace: !Sub "${ProjectName}/BedrockTokens"
          MetricName: OutputTokenCount
          MetricValue: "$.output.outputTokenCount"
          DefaultValue: 0

  # ============================================================
  # Token Monitoring - CloudWatch Alarms
  # Fires when total tokens (input + output) exceed threshold
  # within the monitoring window. Uses Metric Math to sum both.
  # ============================================================
  TokenUsageAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub "${ProjectName}-token-threshold-alarm"
      AlarmDescription: !Sub "Bedrock token usage exceeded ${TokenThreshold} in monitoring window"
      ComparisonOperator: GreaterThanThreshold
      EvaluationPeriods: 1
      Threshold: !Ref TokenThreshold
      TreatMissingData: notBreaching
      AlarmActions:
        - !Ref AlertTopic
      Metrics:
        - Id: input_tokens
          ReturnData: false
          MetricStat:
            Metric:
              Namespace: !Sub "${ProjectName}/BedrockTokens"
              MetricName: InputTokenCount
            Period: 3600
            Stat: Sum
        - Id: cache_read_tokens
          ReturnData: false
          MetricStat:
            Metric:
              Namespace: !Sub "${ProjectName}/BedrockTokens"
              MetricName: CacheReadInputTokenCount
            Period: 3600
            Stat: Sum
        - Id: cache_write_tokens
          ReturnData: false
          MetricStat:
            Metric:
              Namespace: !Sub "${ProjectName}/BedrockTokens"
              MetricName: CacheWriteInputTokenCount
            Period: 3600
            Stat: Sum
        - Id: output_tokens
          ReturnData: false
          MetricStat:
            Metric:
              Namespace: !Sub "${ProjectName}/BedrockTokens"
              MetricName: OutputTokenCount
            Period: 3600
            Stat: Sum
        - Id: total_tokens
          Expression: "input_tokens + cache_read_tokens + cache_write_tokens + output_tokens"
          Label: "Total Tokens (1 hour)"
          ReturnData: true

# ============================================================
# Outputs
# ============================================================
Outputs:
  InstanceId:
    Description: "EC2 Instance ID"
    Value: !Ref EC2Instance

  SSMConnectCommand:
    Description: "Connect via SSM Session Manager (terminal only)"
    Value: !Sub "aws ssm start-session --target ${EC2Instance}"

  SSMPortForwardCommand:
    Description: "SSM tunnel for VSCode Remote SSH (port 22 -> localhost:10022)"
    Value: !Sub "aws ssm start-session --target ${EC2Instance} --document-name AWS-StartPortForwardingSession --parameters portNumber=22,localPortNumber=10022"

  KeyPairName:
    Description: "EC2 Key Pair name (retrieve private key from Systems Manager Parameter Store)"
    Value: !Ref EC2KeyPair

  ConsoleURL:
    Description: "EC2 Console direct link"
    Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/ec2/home?region=${AWS::Region}#InstanceDetails:instanceId=${EC2Instance}"

  SonnetProfileArn:
    Description: "Bedrock Sonnet Application Inference Profile ARN"
    Value: !GetAtt SonnetProfile.InferenceProfileArn

  OpusProfileArn:
    Description: "Bedrock Opus Application Inference Profile ARN"
    Value: !GetAtt OpusProfile.InferenceProfileArn

  BedrockLogGroupName:
    Description: "Enable Bedrock Model Invocation Logging to this log group"
    Value: !Ref BedrockLogGroup

  BedrockLoggingRoleName:
    Description: "Select this role in Bedrock console when enabling invocation logging"
    Value: !Ref BedrockLoggingRole

  AlertTopicArn:
    Description: "SNS Topic for alerts (confirm email subscription after deploy!)"
    Value: !Ref AlertTopic

  StopCommand:
    Description: "Stop instance (saves cost)"
    Value: !Sub "aws ec2 stop-instances --instance-ids ${EC2Instance}"

  StartCommand:
    Description: "Start instance"
    Value: !Sub "aws ec2 start-instances --instance-ids ${EC2Instance}"

  CleanupCommand:
    Description: "Delete all resources"
    Value: !Sub "aws cloudformation delete-stack --stack-name ${AWS::StackName}"

 

AWS CLIの認証設定

credentials ファイル で認証設定済みの場合はスキップしてください。
SSO利用の場合は aws sso login を使ってください。
  1. ブラウザを開き、管理者権限が付与されたIAMユーザーでAWSマネジメントコンソールにログインします。
  2. Kiroでターミナルを開き、aws login と入力してエンターを押します。
  3. ブラウザの別タブで Continue with an active session の画面が出るので、AWSマネジメントコンソールにログインしているIAMユーザーをクリックします。
  4. 「Your credentials have been shared successfully and can be used until your session expires.」が表示されたらタブを閉じます。
  5. Kiroのターミナルで、aws sts get-caller-identity と入力してエンターを押します。
  6. 出力結果を確認し、AWSIDとIAMユーザー名が想定通りであることを確認する。

 

推論プロファイルIDを確認

  1. 現在のIDを確認します。
    Kiroのターミナルで以下のコマンドを入力してエンターを押します。
aws bedrock list-inference-profiles `
--region ap-northeast-1 `
--query "inferenceProfileSummaries[?contains(inferenceProfileName, 'Claude')].{Name:inferenceProfileName, Id:inferenceProfileId}" `
--output table

 

環境構築

デプロイ

  1. AWS CloudFormationスタックをデプロイします。
    Kiroのターミナルで以下のコマンドを入力してエンターを押します。
    デプロイは約5〜10分で完了します。
AlertEmail だけデフォルト値がないので必ず書き換えが必要です。
SonnetInferenceProfileId と OpusInferenceProfileId は事前準備で確認したIDと異なる場合に書き換えてください。
ProjectName もスタック名と揃えておくとリソース名が統一されて管理しやすいですが、お好みで書き換えてください。
aws cloudformation deploy `
  --template-file claude-code-on-aws.yaml `
  --stack-name $STACK_NAME `
  --parameter-overrides `
  ProjectName=$STACK_NAME `
  CostTag=claude-code `
  InstanceType=t3.medium `
  VolumeSize=20 `
  VpcCidr=10.0.0.0/16 `
  SubnetCidr=10.0.1.0/24 `
  SonnetInferenceProfileId=jp.anthropic.claude-sonnet-4-6 `
  OpusInferenceProfileId=global.anthropic.claude-opus-4-6-v1 `
  AlertEmail=your-email@example.com `
  TokenThreshold=150000 `
  --capabilities CAPABILITY_NAMED_IAM `
  --region ap-northeast-1
 
各パラメーターの説明は以下の通りです。
パラメータ デフォルト値 説明
ProjectName claude-code リソース名のプレフィックス
CostTag claude-code コスト配分タグ
InstanceType t3.medium EC2インスタンスタイプ
VolumeSize 20 EBSディスクサイズ(GiB)
VpcCidr 10.0.0.0/16 VPCのCIDRブロック
SubnetCidr 10.0.1.0/24 パブリックサブネットのCIDRブロック
SonnetInferenceProfileId jp.anthropic.claude-sonnet-4-6 Sonnetの推論プロファイルID
OpusInferenceProfileId global.anthropic.claude-opus-4-6-v1 Opusの推論プロファイルID
AlertEmail なし(必ず指定) トークンアラート通知先メールアドレス
TokenThreshold 150000 トークンアラート閾値(1時間あたり)

 

トークン数監視アラート関連の追加設定

  1. Kiroのターミナルで以下のコマンドを入力してエンターを押します。
    これでBedrockモデル呼び出しログを有効化します。
$ACCOUNT_ID = aws sts get-caller-identity --query "Account" --output text

$json = @"
{
  "cloudWatchConfig": {
    "logGroupName": "/aws/bedrock/$STACK_NAME",
    "roleArn": "arn:aws:iam::${ACCOUNT_ID}:role/${STACK_NAME}-bedrock-logging-role"
  },
  "textDataDeliveryEnabled": true,
  "imageDataDeliveryEnabled": false,
  "embeddingDataDeliveryEnabled": false,
  "videoDataDeliveryEnabled": false
}
"@

[System.IO.File]::WriteAllText("$PWD\logging-config.json", $json)

aws bedrock put-model-invocation-logging-configuration `
  --region ap-northeast-1 `
  --logging-config file://logging-config.json

     

     2. AlertEmailに指定したメールアドレスに以下件名のメールが届きます。

           件名:AWS Notification – Subscription Confirmation

        そのメールを開き、「Confirm subscription」のリンクをクリックします。
        これでトークン数が設定した閾値を超過した場合指定したメールアドレスに通知が飛ぶようになります。

 

接続確認

EC2インスタンスへの接続確認

  1. EC2インスタンスのIDを取得します。
    Kiroのターミナルで以下のコマンドを入力してエンターを押します。
$INSTANCE_ID = aws cloudformation describe-stacks `
  --stack-name $STACK_NAME `
  --region ap-northeast-1 `
  --query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" `
  --output text

Write-Host "Instance ID: $INSTANCE_ID"

     2. EC2インスタンスに接続します。
         Kiroのターミナルで以下のコマンドを入力してエンターを押します。
         「TargetNotConnected」となった場合には10分後くらいに再度試してみてください。

aws ssm start-session --target $INSTANCE_ID --region ap-northeast-1

     3. EC2インスタンスへ接続でき、ターミナルにて表示が「sh-5.2$」のように変わったことを確認します

 

KiroからEC2へのリモート接続設定

  1. SSMで接続すると ssm-user というユーザーでログインします。
    Claude Codeは ec2-user にインストールされてるので、ユーザーを切り替える必要があります。
    Kiroのターミナルで以下のコマンドを入力してエンターを押します。
sudo su - ec2-user

     2. Claude Codeが導入されていることを確認します。
         Kiroのターミナルで以下のコマンドを入力してエンターを押します。
         バージョンが出ればOKです。

claude --version

      3. Kiroのターミナルで「exit」を入力してエンターを押し、ec2-userを抜けます。
          もう一度、「exit」を入力してエンターを押し、SSMセッションを抜けます。   

      4.  EC2キーペアIDを取得します。
           Kiroのターミナルで以下のコマンドを入力してエンターを押します。

$keyId = aws ec2 describe-key-pairs `
  --key-names "$STACK_NAME-keypair" `
  --query "KeyPairs[0].KeyPairId" `
  --output text `
  --region ap-northeast-1

       5. 秘密鍵をダウンロードします。
           Kiroのターミナルで以下のコマンドを入力してエンターを押します。

New-Item -ItemType Directory -Path ~\.ssh -Force

aws ssm get-parameter `
  --name "/ec2/keypair/$keyId" `
  --with-decryption `
  --query "Parameter.Value" `
  --output text `
  --region ap-northeast-1 | Out-File -Encoding ascii ~\.ssh\claude-code-key.pem

        6. SSH Configを作成します。
            <ユーザー名> は自分のWindowsユーザー名に置き換えてください。
            Kiroのターミナルで以下のコマンドを入力してエンターを押します。

Set-Content -Path ~\.ssh\config -Value "Host claude-code`n HostName localhost`n Port 10022`n User ec2-user`n IdentityFile C:\Users\<ユーザー名>\.ssh\claude-code-key.pem"

         7. SSMトンネルを開始します。
            Kiroのターミナルで以下のコマンドを入力してエンターを押します。
            「Waiting for connections…」と表示されたら準備完了です。

aws ssm start-session `
  --target $INSTANCE_ID `
  --document-name AWS-StartPortForwardingSession `
  --parameters "portNumber=22,localPortNumber=10022" `
  --region ap-northeast-1

 

KiroからRemote SSH接続

  1. Ctrl+Shift+P → 「Remote-SSH: Connect to Host…」→ claude-code と入力してエンターを押します。
  2. 自動でKiroがもうひとつ立ち上がります。
  3. Kiroにサインインします。
  4. 下図の赤矢印の先のように、新たに開かれたKiroでは左下にと表示されていることを確認します。

 

Claude Codeを起動

Remote SSHで接続しているほうのKiroで実施します。
  1. 念のためにルートディレクトリから移動した後、Claude Codeを起動します。
mkdir ~/projects && cd ~/projects
claude

     2. テーマを選択してエンターを押します。

       

     3. セキュリティに関する注意事項が出るので確認したらエンターを押します。

     

     4. ワークスペースの確認で問題なければエンターを押します。

     

     5. AWSのMCPサーバーをテンプレートで入れているので出てくる選択肢です。
         お試ししてみたいということであれば2で良いかと思います。不要な場合には3を選択してください。

     

     6. チャット画面が開きます。

     

 

テンプレートでccstatuslineも入れています。
下図のようにコストとトークン数などを表示することが出来ます。    

同様の情報は /cost でも見れますが、表示させたいという場合には下記を実施してください。

Claudeとの会話ではなくターミナル上で「npx ccstatusline@latest」を入力してエンターを押します。
Main Menu から Edit Lines を選択してエンターを押します。
Line 1 を選択してエンターを押します。
Model以外を選択した状態でdを押して削除します。(Modelのみ残す)
aを押した後、All を選択してエンターを押します。
追加したいものを追加します。
Escを数回押して Main Menu まで戻ります。
Save & Exit を選択してエンターを押します。
Claudeを起動して下部に追加されていることを確認します。

 

お試し後のリソース削除

リソース削除はスタック消すだけです。

aws cloudformation delete-stack --stack-name $STACK_NAME --region ap-northeast-1

Amazon Bedrockコンソールのモデル呼び出しログ設定だけ手動で無効化してください。

aws bedrock delete-model-invocation-logging-configuration --region ap-northeast-1

 

おまけ程度のトークン数の監視アラートについて

テンプレートにはおまけ程度ですが、トークン数の監視アラートを仕込んでいます。

  • 仕組み
    Amazon Bedrock のモデル呼び出しログを CloudWatch Logs に出力し、Metric Filter でトークン数(Input・Output・Cache Read・Cache Write)を抽出し、CloudWatchメトリクスに変換しています。
    1時間あたりの合計トークン数が閾値を超えると CloudWatch Alarm がSNS経由でメール通知します。
    Lambda不要で、全てマネージドサービスの組み合わせです。
    閾値を超えると件名:ALARM: “claude-code-daisuke-token-threshold-alarm” in Asia Pacific (Tokyo) のようなメールが届きます。
  • 注意点
    個人利用向けの設計のため、AWSアカウント全体の合計トークン数で監視しています。
    複数人で「誰が何トークン使ったか」を把握したい場合は別の構成が必要です。
    トークン数での監視であり、料金そのものの監視ではありませんので、各モデルの料金差異は考慮されていません。
    閾値はあくまで目安として捉えてください。
    ALARMからOKに戻るまで、CloudWatchの評価範囲の仕様により数十分かかることがあります。
    Amazon Bedrockのモデル呼び出しログの有効化はテンプレートとは別にコマンドまたはAWSマネジメントコンソールにて手動で行う必要があります。

 

まとめ

Claude Code on Amazon Bedrockはレートリミットなしで使えるのが魅力ですが、従量課金なのでコスト管理が大事です。
ご利用になる際はお気を付けください。

当記事において不備がございましたらご連絡いただけますと幸いです。

最後に、ここまで協力してくれたKiroから「いいね」を貰うことができて嬉しかったです。

タイトルとURLをコピーしました