문서 버킷 구성하기

CloudflareBeginner
지금 연습하기

소개

지원 팀에 소규모 문서 저장소가 필요합니다. **객체 (object)**는 파일의 바이트와 메타데이터로 구성되고, **버킷 (bucket)**은 객체를 그룹화하며, **키 (key)**는 객체의 전체 이름입니다. 키에 슬래시를 사용하면 유용한 접두사를 만들 수 있지만 일반적인 파일 시스템 디렉터리가 생성되는 것은 아닙니다. 이 실습에서는 비공개 버킷을 만들고, 테스트용 문서 두 개를 업로드하고, 메타데이터를 확인하고, 정확한 바이트를 다운로드한 다음, 선택한 문서만 삭제하고 정리합니다.

먼저 LabEx 를 Cloudflare 계정에 연결하기를 완료합니다. 이 과정에서는 LabEx 터미널, 디바이스 인증, 학습 계정 확인 및 계정 ID 구성을 설명합니다. 이 실습은 /home/labex/project/r2-lab에서 독립적으로 시작되며 Node.js 22.22.0, Wrangler 4.131.1 및 AWS SDK 3.888.0 이 준비되어 있습니다. 자신의 컴퓨터에서는 먼저 Node.js 를 설치한 다음 npm install로 Wrangler 와 AWS SDK 를 프로젝트 종속성으로 설치합니다.

시작하기 전에: 학습 계정에 활성 R2 구독이 있어야 합니다. Cloudflare 의 R2 설정에는 결제 절차가 포함되어 있으므로 R2 가 활성화되지 않았다면 직접 확인합니다. Free 계정에서는 R2 가 자동으로 활성화되지 않습니다. 스토리지 및 작업 요금은 가격 정책에서 확인합니다. 이 실습에서는 작은 테스트 파일만 사용하며 구매한 도메인은 필요하지 않습니다. 버킷 관리 권한과 새 버킷으로 범위를 제한한 사용자 R2 토큰을 만들 권한이 필요합니다. 공개 액세스는 비활성화된 상태로 유지합니다. 이 실습, 채팅 또는 스크린샷에 자격 증명을 절대 붙여 넣지 않습니다.

비공개 문서 버킷 만들기

이 단계에서는 이 VM 을 인증하고 일회용 버킷 하나를 만듭니다. 디바이스 인증은 학습 계정을 확인합니다. R2 버킷 관리는 해당 계정으로 제한된 별도의 API 토큰을 사용합니다.

아래에서 사용할 명령 구문을 위해 Bash 를 시작한 다음, 준비된 프로젝트로 이동하여 도구를 확인합니다. 리소스 이름 변수가 계속 유지되도록 같은 터미널을 열어 둡니다.

bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version

표시된 디바이스 코드를 자신의 브라우저에서 인증합니다. 동의하기 전에 학습 계정과 요청된 계정 및 사용자 읽기 범위를 확인합니다.

npx wrangler login --device --browser=false --scopes account:read user:read
npx wrangler whoami --json

loggedIn: true인지 확인합니다. 계정이 하나만 표시되더라도 계정 이름을 읽습니다. 아래의 YOUR_ACCOUNT_ID를 해당 계정의 실제 32 자 ID 로 바꿉니다. openssl rand -hex 6은 12 개의 임의 16 진수 문자를 생성하므로 이전 실행과 리소스 이름이 충돌하지 않습니다. 다음 here-document 는 표준 구성 파일을 작성하며, 셸이 변수 값을 파일에 삽입합니다.

ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r01-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON

버킷을 관리하려면 Cloudflare 프로필의 API Tokens 페이지를 열고 이 실습의 이름을 사용하여 사용자 지정 토큰을 만듭니다. Account → Workers R2 Storage → Edit 권한을 부여하고, Account Resources를 ID 를 저장한 학습 계정으로 제한합니다. 만료 기간은 짧게 설정합니다. 다른 계정이나 관련 없는 권한은 포함하지 않습니다. 이 계정 수준 권한으로 버킷을 생성하고 삭제할 수 있습니다. 다음 단계의 객체 전용 토큰으로는 이러한 작업을 수행할 수 없습니다.

토큰을 한 번만 복사하여 이 숨겨진 VM 프롬프트에 입력합니다. umask 077은 파일 액세스를 사용자로 제한하고, read -s는 입력을 화면에 표시하지 않습니다. 이 파일은 Wrangler 의 표준 토큰 변수를 사용하며 Git 에서 제외됩니다.

umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN

R2 관리 명령에만 --env-file=.env.management를 사용합니다. 일반 whoami 명령은 계속 VM 의 디바이스 인증을 확인합니다.

--env-file을 각 Wrangler 명령의 끝에 배치하여 파일 인수 목록에 명령 이름까지 포함되지 않게 합니다. 각 버킷을 만든 후 Wrangler 가 구성에 바인딩을 추가할지 물으면 n을 입력하고 Enter 를 누릅니다. 필요한 바인딩은 이미 구성에 포함되어 있습니다.

npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management

버킷을 나열하고 정확히 생성된 이름을 찾습니다. 다른 버킷은 다른 작업에 속하므로 그대로 둡니다.

npx wrangler r2 bucket list --env-file=.env.management

Dashboard 에서 Storage & databases → R2 → Overview를 열고, 방금 만든 정확한 버킷을 선택한 다음 비어 있는 객체 목록을 확인합니다. 버킷 설정에서는 공개 개발 URL 과 사용자 지정 도메인을 비활성화된 상태로 둡니다. Dashboard 에 표시되는 버킷 이름으로 버킷의 대상을 확인할 수 있으며, 이후 다운로드 확인에서 저장된 바이트를 검증합니다.

객체가 없는 비공개 Standard 버킷

이 예시는 Standard 스토리지와 Public Access Disabled 상태를 보여 줍니다. 생성된 버킷 이름은 다릅니다.

메타데이터와 함께 문서 업로드하기

이 단계에서는 SDK 가 이 버킷 하나에만 액세스하도록 설정하고 문서 두 개를 저장합니다. Content type은 클라이언트가 바이트를 해석하는 방법을 알려 주며, custom metadata는 객체와 함께 자체적인 작은 레이블을 저장합니다. 둘 다 액세스 제어 규칙은 아닙니다.

S3 호환 API 를 사용하면 표준 스토리지 SDK 로 R2 에 액세스할 수 있습니다. 이 API 는 Wrangler 의 디바이스 토큰이 아니라 별도의 액세스 키 쌍을 사용합니다. R2 Overview 에서 Account Details → Manage API Tokens를 열고, 이 실습에서 생성한 리소스 이름을 사용하여 User API token을 만듭니다. Object Read & Write를 선택하고 정확히 새 버킷 하나로 범위를 제한합니다. 양식에 옵션이 있으면 짧은 만료 기간을 선택합니다. 모든 버킷 또는 Admin 액세스를 선택하지 않습니다. 일회성 시크릿을 저장할 때까지 이 토큰 페이지를 열어 둡니다.

VM 에서 다음 Bash 프롬프트를 사용합니다. read -s는 입력을 숨기고, umask 077은 자격 증명 파일을 사용자만 읽을 수 있도록 만듭니다. 다음 이름은 AWS SDK 의 표준 환경 변수입니다. Access Key ID 와 Secret Access Key 를 각각 해당 프롬프트에 붙여 넣은 다음 Enter 를 누릅니다. 일반 API 토큰 값은 붙여 넣지 않습니다.

토큰 양식의 TTL에서 24 hours를 선택하고, 생성 전에 정확한 버킷과 Object Read & Write 권한을 확인합니다. 실습이 끝나면 토큰을 폐기합니다. 자동 만료는 보조 수단일 뿐입니다.

umask 077
read -r -s -p 'Access Key ID: ' AWS_ACCESS_KEY_ID; printf '\n'
read -r -s -p 'Secret Access Key: ' AWS_SECRET_ACCESS_KEY; printf '\n'
printf 'AWS_ACCESS_KEY_ID=%s\nAWS_SECRET_ACCESS_KEY=%s\n' "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" > .env.s3
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

재사용할 수 있는 표준 SDK 클라이언트를 작성합니다. SDK 에는 리전 문자열이 필요하며 R2 에서는 auto를 사용합니다. 기존 구성을 읽으면 CLI 와 SDK 작업이 같은 계정과 버킷을 대상으로 합니다.

cat > storage.mjs <<'JS'
import { S3Client } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
export const Bucket = config.r2_buckets[0].bucket_name;
export const s3 = new S3Client({
  region: "auto",
  endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  }
});
JS

이제 업로드 프로그램을 만듭니다. PutObjectCommand는 지정한 키에 바이트를 저장합니다. 두 문서는 모두 테스트용이며, 나중에 선택한 삭제를 수행한 뒤에도 보존된 핸드북으로 관련 없는 키가 삭제되지 않았음을 확인합니다.

cat > upload.mjs <<'JS'
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
await s3.send(new PutObjectCommand({
  Bucket, Key: "documents/report.txt", Body: readFileSync("document.txt"),
  ContentType: "text/plain", Metadata: { team: "blue", revision: "1" }
}));
await s3.send(new PutObjectCommand({
  Bucket, Key: "retained/handbook.txt", Body: readFileSync("retained.txt"),
  ContentType: "text/plain"
}));
console.log("Uploaded two synthetic documents");
JS

--env-file은 자격 증명 값을 출력하지 않고 불러옵니다.

node --env-file=.env.s3 upload.mjs

두 API 호출이 모두 완료된 후에만 성공 메시지가 출력됩니다. 플랫폼 확인에서는 실제 객체와 메타데이터를 별도로 읽습니다.

메타데이터를 나열하고 다운로드한 바이트 비교하기

이 단계에서는 모든 객체를 다운로드하지 않고 키를 확인한 다음 report 를 가져옵니다. ListObjectsV2는 키를 나열하고, HeadObject는 메타데이터만 가져옵니다. 이 작은 버킷은 목록 한 페이지에 들어가지만, 실제 환경에서는 IsTruncatedtrue일 때 continuation token 을 사용하여 목록을 계속 가져와야 합니다.

cat > inspect.mjs <<'JS'
import { ListObjectsV2Command, HeadObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const page = await s3.send(new ListObjectsV2Command({ Bucket }));
console.log(page.Contents.map(object => object.Key));
const metadata = await s3.send(new HeadObjectCommand({ Bucket, Key: "documents/report.txt" }));
console.log({ contentType: metadata.ContentType, metadata: metadata.Metadata });
const object = await s3.send(new GetObjectCommand({ Bucket, Key: "documents/report.txt" }));
writeFileSync("download.txt", await object.Body.transformToByteArray());
JS
node --env-file=.env.s3 inspect.mjs

목록에는 documents/report.txtretained/handbook.txt가 포함됩니다. report 의 값은 text/plain, team: blue, revision: 1이어야 합니다. 메타데이터 출력 순서는 달라질 수 있습니다.

cmp는 바이트를 비교하며 두 파일이 일치하면 아무것도 출력하지 않습니다. 다음 메시지는 비교에 성공한 경우에만 표시됩니다.

cmp document.txt download.txt && printf "Downloaded bytes match\n"

같은 버킷의 Dashboard 객체 목록을 새로 고친 다음 report 의 세부 정보를 엽니다. 키와 content type 을 SDK 출력과 비교합니다. 현재 Dashboard 에서 사용자 지정 메타데이터 필드를 표시하지 않으면 CLI 출력을 메타데이터의 근거로 사용합니다.

보고서 객체 유형, 사용자 지정 메타데이터 및 미리 보기

이 예시는 text/plain, revision 1, team blue 및 합성 보고서 미리 보기를 보여 줍니다. 버킷 이름과 생성 날짜는 다릅니다.

선택한 report 만 삭제하기

이 단계에서는 전체 객체 키 하나를 삭제하면서 핸드북은 유지합니다. 접두사는 삭제할 디렉터리가 아니므로, API 에 report 의 정확한 키를 전달합니다.

cat > remove-report.mjs <<'JS'
import { DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
import { s3, Bucket } from "./storage.mjs";
await s3.send(new DeleteObjectCommand({ Bucket, Key: "documents/report.txt" }));
const page = await s3.send(new ListObjectsV2Command({ Bucket }));
console.log(page.Contents.map(object => object.Key));
JS
node --env-file=.env.s3 remove-report.mjs

retained/handbook.txt만 남아 있어야 합니다. 플랫폼 확인에서는 핸드북을 다운로드하여 내용이 변경되지 않았는지도 확인합니다. 전체 정리를 진행하기 전에 이 확인을 실행합니다.

소유한 버킷 정리하기

이 단계에서는 남은 객체를 삭제한 다음 비어 있는 버킷을 삭제합니다. 원격 삭제가 확인될 때까지 자격 증명을 활성 상태로 유지합니다.

cat > cleanup.mjs <<'JS'
import { DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
import { s3, Bucket } from "./storage.mjs";
await s3.send(new DeleteObjectCommand({ Bucket, Key: "retained/handbook.txt" }));
const page = await s3.send(new ListObjectsV2Command({ Bucket }));
console.log("Remaining objects:", page.KeyCount);
JS
node --env-file=.env.s3 cleanup.mjs

Remaining objects: 0인지 확인합니다. 새 터미널을 열었다면 구성 파일에서 생성된 버킷 이름을 읽습니다. node -p는 해당 필드 하나만 출력합니다.

BUCKET=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).r2_buckets[0].bucket_name")
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management

프롬프트가 표시되면 정확히 이 실습의 버킷만 확인합니다. 버킷을 다시 나열하여 성공한 목록에 해당 이름이 없는지 확인합니다. 인증 또는 네트워크 오류가 발생했다고 해서 삭제가 완료된 것은 아닙니다.

npx wrangler r2 bucket list --env-file=.env.management

같은 Dashboard 목록을 새로 고친 다음, 아직 로그인한 상태에서 이 단계의 플랫폼 확인을 실행합니다.

실습 자격 증명 폐기 및 로그아웃하기

이 단계에서는 이 실습으로 남은 액세스를 종료합니다. R2 API Tokens 페이지에서 이 실습의 이름으로 만든 객체 토큰만 폐기합니다. 프로필의 API Tokens 페이지에서는 이 실습을 위해 만든 별도의 R2 관리 토큰을 폐기합니다. 버킷을 삭제해도 토큰이 폐기되는 것은 아니며, Wrangler 에서 로그아웃해도 S3 자격 증명은 폐기되지 않습니다.

폐기한 후 로컬 자격 증명 파일을 삭제하고 이 VM 에서 로그아웃합니다.

rm .env.s3 .env.management
npx wrangler logout

구조화된 ID 정보를 확인합니다. 로그아웃한 상태에서는 종료 상태가 0 이 아닌 것이 정상입니다.

npx wrangler whoami --json || true

loggedIn: false인지 확인하고 일반적인 Dashboard 로그인은 유지합니다. 플랫폼 확인에서는 로컬 자격 증명 파일 삭제와 Wrangler 로그아웃을 확인합니다. 두 토큰의 폐기는 파일 삭제만으로 추론하지 않으며, 이 실습에서는 Dashboard 에서 직접 확인해야 합니다.

요약

비공개 R2 버킷을 만들고 객체의 바이트와 메타데이터를 저장했으며, 문서를 나열하고 다운로드하고, 선택한 객체만 삭제되는지 확인한 후 버킷 액세스를 정리했습니다.