Use Cases (업데이트: 2026. 6. 3.)

Claude Code × Amazon Bedrock 실전 가이드: AWS에서 Claude 안전하게 운영하기

Claude Code와 Amazon Bedrock으로 운영 가능한 Claude 기능을 만든다. IAM, Converse API, 로그, Guardrails, 비용 제어 포함.

Claude Code × Amazon Bedrock 실전 가이드: AWS에서 Claude 안전하게 운영하기

Claude를 빠르게 실험하려면 Anthropic API를 직접 호출하는 방식이 가장 쉽습니다. 하지만 AWS에서 프로덕션으로 운영하려면 API 키 관리, IAM 경계, 감사 로그, 비용 배분, 데이터 보관, 재시도 정책까지 설명해야 합니다. 이때 현실적인 선택지가 Amazon Bedrock을 통해 Claude를 호출하는 구조입니다.

Amazon Bedrock은 AWS의 관리형 foundation model 서비스입니다. 쉽게 말하면, Claude를 별도 외부 API처럼 다루지 않고 AWS 인증, 권한, 로그, 과금, 운영 체계 안에서 호출하게 해주는 실행 기반입니다. Claude Code는 이 구조를 빠르게 구현하는 데 유용하지만, “동작하는 데모”가 아니라 검토 가능한 IAM, 모델 호출, Guardrails, 로그, 재시도, 비용 제한까지 만들도록 지시해야 합니다.

Masa가 실제 프로젝트에서 배운 점은 분명했습니다. Bedrock 데모는 금방 만들 수 있지만, 보안 리뷰에서는 “어떤 role이 어떤 모델을 호출하는가?”, “throttling이 나면 어떻게 처리하는가?”, “prompt가 로그에 남는가?”, “팀별 비용은 어떻게 추적하는가?”를 묻습니다. 이 글은 그 질문을 코드와 Claude Code 프롬프트로 바꿉니다.

이 글은 2026년 6월 3일 기준 AWS 공식 문서를 바탕으로 작성했습니다. Bedrock 모델 ID, Region, quota, 가격은 바뀔 수 있으므로 배포 전 반드시 공식 문서를 확인하세요.

프로덕션 구조

Claude Code에 바로 “Bedrock 채팅 API를 추가해줘”라고 말하지 마세요. 먼저 호출자, 런타임, 모델, Guardrails, 로그, 비용 귀속을 고정합니다.

flowchart LR
  U["User / Admin UI"] --> A["API Gateway or ALB"]
  A --> R["Lambda or ECS task"]
  R --> G["Input validation and budget guard"]
  G --> B["Amazon Bedrock Runtime<br/>Converse / ConverseStream"]
  B --> C["Claude model"]
  R --> L["App logs<br/>CloudWatch Logs"]
  B --> M["Model invocation logs<br/>CloudWatch Logs or S3"]
  R --> K["Knowledge Bases<br/>optional RAG"]
  R --> Q["Cost Explorer / CUR<br/>IAM principal attribution"]

Converse API는 Bedrock의 통합 대화형 API입니다. Guardrails는 사용자 입력과 모델 응답을 설정한 안전 정책으로 평가합니다. model invocation logging은 모델 호출 데이터를 CloudWatch Logs나 S3로 보낼 수 있습니다. IAM principal attribution은 Bedrock 추론 비용을 IAM 사용자나 role 단위로 추적하는 기능입니다.

적합한 사용 사례

첫 번째는 내부 문서 Q&A입니다. runbook, 제품 사양, 지원 절차를 S3와 Knowledge Bases에 두고 관련 조각을 검색한 뒤 Claude가 인용과 함께 답변하게 합니다. 이것이 RAG, 즉 검색 증강 생성입니다.

두 번째는 고객지원 답변 초안입니다. 고객 질문, 플랜 정보, 기존 템플릿을 바탕으로 Claude가 초안을 만들고 상담원이 승인합니다. 완전 자동화 전에는 사람의 검토, Guardrails, 개인정보 처리, 감사 로그가 필요합니다.

세 번째는 개발팀 운영 도우미입니다. CloudWatch 로그 요약, 배포 체크리스트, 장애 기록, runbook 기반 작업 목록 작성에 쓸 수 있습니다. Claude Code는 API, Lambda, IAM, 테스트, 문서를 한 번에 수정할 수 있어 팀 표준화에 유리합니다.

네 번째는 ClaudeCodeLab 같은 콘텐츠 운영입니다. 기사 QA, description 길이, 내부 링크 후보, 코드 블록 검사를 AWS IAM과 과금 체계 안에서 돌릴 수 있습니다. 콘텐츠 품질이 수익과 연결된다면 Claude Code API 비용 절감 가이드검증 영수증 워크플로도 함께 보세요.

설정

Node.js 20 이상, AWS CLI 인증, Bedrock을 사용할 AWS 계정, 대상 Region에서 사용 가능한 Anthropic 모델이 필요합니다. 계정에 따라 Anthropic 모델 최초 사용 정보, AWS Marketplace 권한, 결제 수단이 필요할 수 있습니다.

블로그에서 본 모델 ID를 그대로 고정하지 마세요. 현재 계정에서 사용 가능한 모델을 확인하고 환경 변수로 넣습니다.

export AWS_REGION=us-east-1

aws bedrock list-foundation-models \
  --region "$AWS_REGION" \
  --query "modelSummaries[?providerName=='Anthropic'].[modelId,modelName]" \
  --output table

export BEDROCK_MODEL_ID="anthropic.claude-sonnet-4-20250514-v1:0"
aws bedrock get-foundation-model \
  --region "$AWS_REGION" \
  --model-identifier "$BEDROCK_MODEL_ID" \
  --query "modelDetails.{input:inputModalities,output:outputModalities,streaming:responseStreamingSupported}"

작은 TypeScript 프로젝트를 만듭니다.

mkdir bedrock-claude-lab
cd bedrock-claude-lab
npm init -y
npm install @aws-sdk/client-bedrock @aws-sdk/client-bedrock-runtime @aws-sdk/client-bedrock-agent-runtime
npm install --save-dev typescript tsx @types/node
npx tsc --init --module NodeNext --moduleResolution NodeNext --target ES2022
mkdir -p src/lambda

package.json에 스크립트를 추가합니다.

{
  "type": "module",
  "scripts": {
    "chat": "tsx src/chat.ts",
    "stream": "tsx src/stream.ts",
    "typecheck": "tsc --noEmit"
  }
}

IAM 기준선

Converse API를 쓰더라도 IAM에서는 모델 호출 권한이 필요합니다. 비스트리밍은bedrock:InvokeModel, 스트리밍은bedrock:InvokeModelWithResponseStream을 허용합니다. 애플리케이션 로그 권한과 Bedrock 모델 호출 로그 설정은 별도로 봅니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBedrockModelsForStartupCheck",
      "Effect": "Allow",
      "Action": ["bedrock:ListFoundationModels", "bedrock:GetFoundationModel"],
      "Resource": "*"
    },
    {
      "Sid": "InvokeOnlyApprovedClaudeModels",
      "Effect": "Allow",
      "Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
      "Resource": [
        "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-*",
        "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-*",
        "arn:aws:bedrock:us-east-1:123456789012:inference-profile/*"
      ]
    },
    {
      "Sid": "ApplyApprovedGuardrail",
      "Effect": "Allow",
      "Action": ["bedrock:ApplyGuardrail"],
      "Resource": "arn:aws:bedrock:us-east-1:123456789012:guardrail/your-guardrail-id"
    }
  ]
}

계정 ID, Region, Guardrail ID, 모델 범위를 자신의 환경에 맞게 바꿉니다. Cross-Region inference를 쓰면 inference profile과 대상 Region 모델 ARN도 포함해야 합니다. 최소 권한 검토는 Claude Code AWS IAM 가이드와 연결됩니다.

Claude 호출

운영 코드에서 중요한 부분은requestMetadata입니다. AWS 문서에서는 모델 호출 로그 필터링에 쓸 수 있다고 설명하므로 request ID, 기능명, 호출자 종류를 넣습니다.

// src/bedrock-client.ts
import { randomUUID } from "node:crypto";
import {
  BedrockRuntimeClient,
  ConverseCommand,
  ConverseStreamCommand,
  type ConverseCommandInput,
} from "@aws-sdk/client-bedrock-runtime";

export const AWS_REGION = process.env.AWS_REGION ?? "us-east-1";
export const BEDROCK_MODEL_ID =
  process.env.BEDROCK_MODEL_ID ?? "anthropic.claude-sonnet-4-20250514-v1:0";

export const bedrock = new BedrockRuntimeClient({
  region: AWS_REGION,
  maxAttempts: Number(process.env.BEDROCK_MAX_ATTEMPTS ?? "3"),
});

type AskClaudeInput = {
  prompt: string;
  system?: string;
  maxTokens?: number;
  temperature?: number;
  userId?: string;
  feature?: string;
};

function optionalGuardrail(): ConverseCommandInput["guardrailConfig"] | undefined {
  const guardrailIdentifier = process.env.BEDROCK_GUARDRAIL_ID;
  if (!guardrailIdentifier) return undefined;

  return {
    guardrailIdentifier,
    guardrailVersion: process.env.BEDROCK_GUARDRAIL_VERSION ?? "DRAFT",
    trace: "enabled",
  };
}

export async function askClaude(input: AskClaudeInput) {
  const requestId = randomUUID();
  const startedAt = Date.now();

  const response = await bedrock.send(
    new ConverseCommand({
      modelId: BEDROCK_MODEL_ID,
      system: input.system ? [{ text: input.system }] : undefined,
      messages: [{ role: "user", content: [{ text: input.prompt }] }],
      inferenceConfig: {
        maxTokens: input.maxTokens ?? 800,
        temperature: input.temperature ?? 0.2,
      },
      guardrailConfig: optionalGuardrail(),
      requestMetadata: {
        requestId,
        feature: input.feature ?? "local-cli",
        userId: input.userId ?? "anonymous",
      },
    })
  );

  const text =
    response.output?.message?.content
      ?.map((block: { text?: string }) => block.text ?? "")
      .join("") ?? "";

  console.log(
    JSON.stringify({
      level: "info",
      event: "bedrock_converse",
      requestId,
      modelId: BEDROCK_MODEL_ID,
      latencyMs: Date.now() - startedAt,
      stopReason: response.stopReason,
      usage: response.usage,
      metrics: response.metrics,
    })
  );

  return { text, usage: response.usage, stopReason: response.stopReason, requestId };
}

export async function streamClaude(prompt: string) {
  const response = await bedrock.send(
    new ConverseStreamCommand({
      modelId: BEDROCK_MODEL_ID,
      messages: [{ role: "user", content: [{ text: prompt }] }],
      inferenceConfig: { maxTokens: 1200, temperature: 0.2 },
      guardrailConfig: optionalGuardrail(),
      requestMetadata: { feature: "stream-cli", requestId: randomUUID() },
    })
  );

  if (!response.stream) throw new Error("Bedrock did not return a stream.");

  for await (const event of response.stream) {
    const text = event.contentBlockDelta?.delta?.text;
    if (text) process.stdout.write(text);

    if (event.metadata?.usage) {
      process.stderr.write(`\nusage=${JSON.stringify(event.metadata.usage)}\n`);
    }
  }
}
// src/chat.ts
import { askClaude } from "./bedrock-client.js";

const prompt = process.argv.slice(2).join(" ").trim();
if (!prompt) {
  console.error('Usage: npm run chat -- "Summarize Amazon Bedrock in three bullets"');
  process.exit(1);
}

const result = await askClaude({
  prompt,
  system: "You are a concise AWS assistant. If you are unsure, say what to verify.",
  maxTokens: 600,
  feature: "developer-chat",
});

console.log(result.text);
// src/stream.ts
import { streamClaude } from "./bedrock-client.js";

const prompt = process.argv.slice(2).join(" ").trim();
if (!prompt) {
  console.error('Usage: npm run stream -- "Write a deployment checklist"');
  process.exit(1);
}

await streamClaude(prompt);

실행합니다.

export AWS_REGION=us-east-1
export BEDROCK_MODEL_ID="anthropic.claude-sonnet-4-20250514-v1:0"

npm run chat -- "Amazon Bedrock을 세 줄로 설명해줘"
npm run stream -- "Bedrock 프로덕션 체크리스트를 만들어줘"
npm run typecheck

Lambda 패턴

Lambda에서는 클라이언트를 handler 밖에서 초기화하고, 서버 측에서 입력과maxTokens를 제한합니다. 프론트엔드 제한만 믿으면 안 됩니다.

// src/lambda/assistant-handler.ts
import { askClaude } from "../bedrock-client.js";

type ApiEvent = {
  body?: string | null;
  requestContext?: { requestId?: string };
};

const headers = { "content-type": "application/json; charset=utf-8" };

export const handler = async (event: ApiEvent) => {
  try {
    const body = JSON.parse(event.body ?? "{}") as {
      prompt?: string;
      maxTokens?: number;
      userId?: string;
    };

    if (!body.prompt || body.prompt.length > 8000) {
      return {
        statusCode: 400,
        headers,
        body: JSON.stringify({ error: "prompt is required and must be <= 8000 chars" }),
      };
    }

    const result = await askClaude({
      prompt: body.prompt,
      maxTokens: Math.min(body.maxTokens ?? 800, 1200),
      userId: body.userId ?? "anonymous",
      feature: "support-assistant",
    });

    return {
      statusCode: 200,
      headers,
      body: JSON.stringify({
        text: result.text,
        usage: result.usage,
        stopReason: result.stopReason,
        requestId: result.requestId,
      }),
    };
  } catch (error) {
    const name =
      typeof error === "object" && error && "name" in error ? String(error.name) : "UnknownError";
    const retryable = ["ThrottlingException", "ServiceUnavailableException", "InternalServerException"].includes(name);

    console.error(JSON.stringify({ level: "error", event: "assistant_failed", name, retryable }));

    return {
      statusCode: retryable ? 503 : 500,
      headers,
      body: JSON.stringify({ error: retryable ? "Please retry later" : "Generation failed" }),
    };
  }
};

ValidationException은 대부분 입력이나 파라미터 문제라 그대로 재시도해도 해결되지 않습니다. ThrottlingExceptionServiceUnavailableException은 지수 백오프와 지터로 재시도할 수 있습니다. 서버리스 전체 흐름은 Claude Code AWS Lambda 가이드와 함께 보세요.

Knowledge Bases로 RAG 시작하기

내부 문서 챗봇은 직접 vector DB를 만들기보다 Bedrock Knowledge Bases부터 검증하는 편이 빠릅니다. 답변에 출처를 포함할 수 있어 운영자가 근거를 확인하기 쉽습니다. 다만 Guardrails는 입력과 생성 응답에 적용되고, 검색된 참조 자체를 정화하지는 않습니다. 민감 문서는 S3, KMS, IAM, 데이터 분류로 먼저 제한해야 합니다.

// src/rag.ts
import {
  BedrockAgentRuntimeClient,
  RetrieveAndGenerateCommand,
} from "@aws-sdk/client-bedrock-agent-runtime";

const agentRuntime = new BedrockAgentRuntimeClient({
  region: process.env.AWS_REGION ?? "us-east-1",
});

export async function askKnowledgeBase(question: string) {
  const knowledgeBaseId = process.env.BEDROCK_KNOWLEDGE_BASE_ID;
  const modelArn = process.env.BEDROCK_GENERATION_MODEL_ARN;

  if (!knowledgeBaseId || !modelArn) {
    throw new Error("Set BEDROCK_KNOWLEDGE_BASE_ID and BEDROCK_GENERATION_MODEL_ARN");
  }

  const response = await agentRuntime.send(
    new RetrieveAndGenerateCommand({
      input: { text: question },
      retrieveAndGenerateConfiguration: {
        type: "KNOWLEDGE_BASE",
        knowledgeBaseConfiguration: {
          knowledgeBaseId,
          modelArn,
          retrievalConfiguration: {
            vectorSearchConfiguration: { numberOfResults: 5 },
          },
        },
      },
    })
  );

  const sources =
    response.citations
      ?.flatMap((citation) => citation.retrievedReferences ?? [])
      .map((reference) => reference.location?.s3Location?.uri)
      .filter(Boolean) ?? [];

  return { answer: response.output?.text ?? "", sources };
}

로그와 비용

로그는 두 층으로 나눕니다. 애플리케이션 로그에는requestId, 기능명, 호출자 종류, 모델 ID,usage, 지연시간, stop reason을 넣습니다. 개인정보와 내부 정보가 포함될 수 있다면 원문 prompt 저장은 신중해야 합니다.

Bedrock 모델 호출 로그는 CloudWatch Logs나 S3로 보낼 수 있습니다. 감사와 디버깅에는 좋지만 민감한 입력과 출력도 저장될 수 있으므로 보존 기간, 암호화, S3 lifecycle, 접근 권한을 먼저 정하세요.

비용 제어는 API 계층에서maxTokens를 제한하는 것부터 시작합니다. 작업 난이도별로 모델을 나누고,usage를 로그로 남기며, IAM principal attribution으로 role별 비용을 추적합니다. 긴 고정 system prompt를 반복한다면 Prompt caching도 검토할 수 있지만, 짧거나 매번 바뀌는 prompt에는 효과가 제한적입니다.

Claude Code 프롬프트

claude -p "
이 repository에 Amazon Bedrock을 통한 Claude 호출을 추가하세요.

조건:
- AWS SDK v3 Converse API 사용
- 모델 ID는 BEDROCK_MODEL_ID에서 읽기
- AWS_REGION이 없으면 us-east-1 기본값
- 서버에서 maxTokens를 1200 이하로 제한
- requestMetadata에 requestId, feature, userId 포함
- BEDROCK_GUARDRAIL_ID가 있을 때만 guardrailConfig 추가
- usage, latencyMs, stopReason을 JSON 로그로 출력
- ValidationException은 재시도하지 않기
- ThrottlingException과 ServiceUnavailableException은 재시도 가능하게 분류
- README에 최소 권한 IAM 정책 예시 작성
- 테스트에서는 Bedrock client를 mock하고 실제 API 호출 금지

수정 전 계획을 보여주고, 완료 후 typecheck와 테스트 결과를 보고하세요.
"

흔한 함정

첫째, 모델 접근 확인 없이 코드를 쓰는 것입니다. list-foundation-models와 작은 Converse 호출로 계정, Region, 권한을 먼저 확인합니다.

둘째, 글에서 본 모델 ID를 코드에 고정하는 것입니다. Bedrock 모델 ID와 Region 지원은 변합니다. 환경 변수와 시작 시 검증을 사용하세요.

셋째, Guardrails를 정확성 보장으로 착각하는 것입니다. 안전 정책에는 유용하지만 도메인 검증, 권한 확인, 사람의 리뷰를 대신하지 않습니다.

넷째, 로그를 과하게 남기는 것입니다. 원문 prompt는 디버깅에는 좋지만 개인정보와 내부 정보를 장기 로그에 남길 수 있습니다.

다섯째, 잘못된 에러를 재시도하는 것입니다. Validation 오류는 입력이나 코드 수정이 필요하고, throttling이나 임시 서비스 오류는 재시도가 적합합니다.

여섯째, 비용 제한을 프론트엔드에만 두는 것입니다. token 제한, 기능별 quota, 사용자별 제한은 API에서 강제해야 합니다.

수익화와 연결하기

Bedrock 콘텐츠의 가치는 SDK 조각이 아니라 데모를 프로덕션으로 옮기는 데 있습니다. IAM, 로그, 비용, Guardrails, 리뷰, 팀 도입이 핵심입니다. 개인은 ClaudeCodeLab 제품에서 템플릿을 시작할 수 있고, 팀은 실제 repository에 맞춘CLAUDE.md, IAM 리뷰, 검증 영수증, CI gate를 Claude Code 교육 및 상담으로 설계할 수 있습니다.

관련 글: Claude Code AWS Lambda, Claude Code AWS IAM, Claude Code API 비용 절감, 검증 영수증 워크플로.

실제로 해본 뒤의 결론

직접 시험해보니 가장 큰 개선은 코드가 아니라 Claude Code에 주는 요구사항이었습니다. “모델 ID는 환경 변수”, “usage 로그 필수”, “ValidationException 재시도 금지”, “Guardrails는 환경 변수로 선택”, “IAM 정책은 README에 남기기”를 명시하자 diff가 작아지고 리뷰 포인트가 분명해졌습니다. Bedrock 도입의 성공은 SDK 호출을 외우는 것보다, 코딩 전 운영 제약을 명확히 말할 수 있는지에 달려 있습니다.

#claude-code #aws #bedrock #anthropic #typescript #generative-ai
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.