AgentSkillsCN

kb-amplify-cdk

Amplify Gen2 + CDK 的知识。包括沙盒管理、生产环境部署、Hotswap 等功能。

SKILL.md
--- frontmatter
name: kb-amplify-cdk
description: Amplify Gen2 + CDK のナレッジ。sandbox管理、本番デプロイ、Hotswap等
user-invocable: true

Amplify Gen2 + CDK ナレッジ

Amplify Gen2とCDKの統合に関する学びを記録する。

Amplify Gen2 基本構造

code
amplify/
├── auth/
│   └── resource.ts    # Cognito認証設定
├── agent/             # カスタムリソース(例:AgentCore)
│   └── resource.ts
└── backend.ts         # バックエンド統合

カスタムCDKスタックの追加

typescript
// amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { createMyCustomResource } from './custom/resource';

const backend = defineBackend({ auth });

// カスタムスタックを作成
const customStack = backend.createStack('CustomStack');

// Amplifyの認証リソースを参照
const userPool = backend.auth.resources.userPool;
const userPoolClient = backend.auth.resources.userPoolClient;

// カスタムリソースを作成
const { endpoint } = createMyCustomResource({
  stack: customStack,
  userPool,
  userPoolClient,
});

カスタム出力の追加

フロントエンドからカスタムリソースの情報にアクセスする方法:

typescript
// amplify/backend.ts
backend.addOutput({
  custom: {
    myEndpointArn: endpoint.arn,
    environment: 'sandbox',
  },
});
typescript
// フロントエンドでアクセス
import outputs from '../amplify_outputs.json';
const endpointArn = outputs.custom?.myEndpointArn;

環境分岐(sandbox vs 本番)

typescript
// amplify/backend.ts
const branch = process.env.AWS_BRANCH;  // Amplify Consoleが設定
const isSandbox = !branch || branch === 'sandbox';
const nameSuffix = isSandbox ? 'dev' : branch;

// リソース名に環境サフィックスを付与
const runtimeName = `my_agent_${nameSuffix}`;  // my_agent_dev, my_agent_main

sandbox環境

起動

bash
npx ampx sandbox

特徴

  • ファイル変更を検知して自動デプロイ(ホットリロード)
  • amplify_outputs.json が自動生成される
  • CloudFormationスタック名: amplify-{appName}-{identifier}-sandbox-{hash}

Dockerビルド(AgentCore等)

  • sandbox環境では fromAsset() でローカルビルド可能
  • Mac ARM64でビルドできるなら deploy-time-build は不要

本番環境(Amplify Console)

Dockerビルド対応

デフォルトビルドイメージにはDockerが含まれていないが、カスタムビルドイメージを設定することでDocker buildが可能。

code
public.ecr.aws/codebuild/amazonlinux-x86_64-standard:5.0

設定手順

  1. Amplify Console → 対象アプリ
  2. HostingBuild settingsBuild image settingsEdit
  3. Build imageCustom Build Image を選択
  4. イメージ名を入力: public.ecr.aws/codebuild/amazonlinux-x86_64-standard:5.0
  5. Save

カスタムビルドイメージの要件

  • Linux(x86-64、glibc対応)
  • cURL、Git、OpenSSH、Bash
  • Node.js + NPM(推奨)

環境変数の設定

Amplify Console → Environment variables で設定:

  • APIキー等の機密情報はここで設定
  • CDKのビルド時に参照可能

CDK Hotswap

  • CDK v1.14.0〜 で Bedrock AgentCore Runtime に対応
  • Amplify toolkit-lib の対応バージョンへの更新を待つ必要あり

Amplify で AgentCore Hotswap を先行利用する方法(Workaround)

Amplify の公式アップデートを待たずに Hotswap を試す場合、package.jsonoverrides を使用:

json
{
  "overrides": {
    "@aws-cdk/toolkit-lib": "1.14.0",
    "@smithy/core": "^3.21.0"
  }
}
パッケージバージョン理由
@aws-cdk/toolkit-lib1.14.0AgentCore Hotswap 対応版
@smithy/core^3.21.0AWS SDK のリグレッションバグ対応

注意事項:

  • 正攻法ではないので、お試し用途で使用
  • Amplify の公式アップデートが来たら overrides を削除する
  • 参考: go-to-k/amplify-agentcore-cdk

sandbox管理

正しい停止方法

sandboxを停止する際は npx ampx sandbox delete を使用する。

bash
# 正しい方法
npx ampx sandbox delete --yes

# NG: pkillやkillでプロセスを強制終了すると状態が不整合になる

複数インスタンスの競合

症状:

code
[ERROR] [MultipleSandboxInstancesError] Multiple sandbox instances detected.

原因: 複数のsandboxプロセスが同時に動作している

解決策:

  1. すべてのampxプロセスを確認
    bash
    ps aux | grep "ampx" | grep -v grep
    
  2. .amplify/artifacts/ をクリア
    bash
    rm -rf .amplify/artifacts/
    
  3. npx ampx sandbox delete --yes で完全削除
  4. 新しくsandboxを1つだけ起動

ファイル変更が検知されない

症状: agent.pyなどを変更してもデプロイがトリガーされない

原因: sandboxが古い状態で動作している、または複数インスタンス競合

解決策:

  1. sandbox deleteで完全削除
  2. 新しくsandbox起動
  3. ファイルをtouchしてトリガー
    bash
    touch amplify/agent/runtime/agent.py
    

Docker未起動エラー

症状:

code
ERROR: Cannot connect to the Docker daemon at unix:///...
[ERROR] [UnknownFault] ToolkitError: Failed to build asset

原因: Docker Desktopが起動していない

解決策:

  1. Docker Desktopを起動
  2. ファイルをtouchしてデプロイ再トリガー

deploy-time-build(本番環境ビルド)

概要

sandbox環境ではローカルでDockerビルドできるが、本番環境(Amplify Console)ではCodeBuildでビルドする必要がある。deploy-time-build パッケージを使用してビルドをCDK deploy時に実行する。

環境分岐の実装

typescript
// amplify/agent/resource.ts
import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets';

const isSandbox = !branch || branch === 'sandbox';

const artifact = isSandbox
  ? agentcore.AgentRuntimeArtifact.fromAsset(runtimePath)  // ローカルビルド
  : agentcore.AgentRuntimeArtifact.fromAsset(runtimePath, {
      platform: ecr_assets.Platform.LINUX_ARM64,
      bundling: {
        // deploy-time-build でCodeBuildビルド
      },
    });

⚠️ コンテナイメージのタグ指定に関する重要な注意

tag: 'latest' を指定すると、コード変更時にAgentCoreランタイムが更新されない問題が発生する。

問題の仕組み

  1. コードをプッシュ → ECRに新イメージがプッシュ(タグ: latest
  2. CDKがCloudFormationテンプレートを生成
  3. CloudFormation: 「タグは同じ latest だから変更なし」と判断
  4. ターゲットリソース(AgentCore Runtime等)が更新されない

NG: 固定タグを使用

typescript
containerImageBuild = new ContainerImageBuild(stack, 'ImageBuild', {
  directory: path.join(__dirname, 'runtime'),
  platform: Platform.LINUX_ARM64,
  tag: 'latest',  // ❌ CloudFormationが変更を検知できない
});

OK: タグを省略してassetHashを使用

typescript
containerImageBuild = new ContainerImageBuild(stack, 'ImageBuild', {
  directory: path.join(__dirname, 'runtime'),
  platform: Platform.LINUX_ARM64,
  // tag を省略 → assetHashベースのタグが自動生成される
});

// 古いイメージを自動削除(直近N件を保持)
// ⚠️ repository は IRepository 型のため、型アサーションが必要
import * as ecr from 'aws-cdk-lib/aws-ecr';

(containerImageBuild.repository as ecr.Repository).addLifecycleRule({
  description: 'Keep last 5 images',
  maxImageCount: 5,
  rulePriority: 1,
});

⚠️ addLifecycleRule の型エラーについて

containerImageBuild.repositoryIRepository インターフェース型で返される。addLifecycleRule() メソッドは Repository クラス固有のため、直接呼び出すとTypeScriptエラーになる。

typescript
// ❌ TypeScriptエラー: Property 'addLifecycleRule' does not exist on type 'IRepository'
containerImageBuild.repository.addLifecycleRule({...});

// ✅ 型アサーションで解決
(containerImageBuild.repository as ecr.Repository).addLifecycleRule({...});

なぜこうなるか: deploy-time-buildは外部から既存リポジトリを渡せるよう IRepository 型で公開している。実際には内部で new Repository() を生成しているため、型アサーションで動作する。

注意: 型アサーションは型安全性を失う。将来ライブラリが変更されると壊れる可能性あり。

OSS改善提案: Issue #76lifecycleRules オプション追加を提案済み。

比較表

項目tag: 'latest'タグ省略(推奨)
デプロイ時の更新❌ 反映されないことがある✅ 常に反映される
ECRイメージ数1つのみ蓄積(要Lifecycle Policy)
ロールバック❌ 不可✅ 可能

参考


開発時のトラブルシューティング

Tailwind: レスポンシブクラス変更がPC表示に反映されない

症状: text-[8px] に変更しても、PC画面で文字サイズが変わらない

原因: md:text-xs などのレスポンシブクラスがPC表示で優先されるため、ベースクラスの変更だけでは反映されない

解決策: ベースクラスとレスポンシブクラスの両方を変更する

tsx
// NG: ベースのみ変更 → PCではmd:text-xsが適用される
className="text-[8px] md:text-xs"

// OK: 両方変更
className="text-[8px] md:text-[10px]"

dotenv: .env.local が読み込まれない

症状: .env.localに環境変数を設定したが、Node.js(Amplify CDK等)で読み込まれない

原因: dotenvパッケージはデフォルトで.envのみ読む。.env.localはVite/Next.jsの独自サポート

解決策: .env.local.env にリネーム(Viteは.envも読むため互換性あり)


CloudFront + S3 OAC(匿名公開コンテンツ配信)

概要

S3バケットを直接公開せず、CloudFront経由でのみアクセスを許可する構成。 defineStorageはCognito認証ユーザー向けなので、匿名公開にはカスタムCDKが必要。

実装例

typescript
// amplify/storage/resource.ts
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import { Construct } from 'constructs';

export class SharedContentConstruct extends Construct {
  public readonly bucket: s3.Bucket;
  public readonly distribution: cloudfront.Distribution;

  constructor(scope: Construct, id: string, nameSuffix: string) {
    super(scope, id);

    // S3バケット(パブリックアクセスブロック有効)
    this.bucket = new s3.Bucket(this, 'Bucket', {
      bucketName: `my-shared-content-${nameSuffix}`,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      lifecycleRules: [{
        id: 'DeleteAfter7Days',
        expiration: cdk.Duration.days(7),  // 自動削除
      }],
    });

    // CloudFront(OAC経由でS3アクセス)
    this.distribution = new cloudfront.Distribution(this, 'Distribution', {
      defaultBehavior: {
        origin: origins.S3BucketOrigin.withOriginAccessControl(this.bucket),
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
      },
    });
  }
}

backend.tsでの統合

typescript
// amplify/backend.ts
import { SharedContentConstruct } from './storage/resource';

const customStack = backend.createStack('SharedContentStack');
const sharedContent = new SharedContentConstruct(customStack, 'SharedContent', nameSuffix);

// フロントエンドに出力
backend.addOutput({
  custom: {
    distributionDomain: sharedContent.distribution.distributionDomainName,
  },
});

AgentCore/Lambdaへの権限付与

typescript
runtime.addToRolePolicy(new iam.PolicyStatement({
  actions: ['s3:PutObject'],
  resources: [`${sharedContent.bucket.bucketArn}/*`],
}));

// 環境変数で渡す
environmentVariables: {
  SHARED_BUCKET: sharedContent.bucket.bucketName,
  CLOUDFRONT_DOMAIN: sharedContent.distribution.distributionDomainName,
}

defineStorage vs カスタムCDK

観点defineStorageカスタムCDK
認証ユーザー向け✅ 最適可能
匿名公開❌ 不向き✅ 最適
CloudFront連携❌ 非対応✅ 柔軟
Lifecycle Rule制限あり✅ 自由

よくあるエラー

amplify_outputs.json が見つからない

  • sandbox が起動していない
  • npx ampx sandbox を実行する

カスタム出力が反映されない

  • backend.addOutput() を追加後、sandbox再起動が必要