소개
내보내기 서비스는 보존해야 하는 문서를 삭제하지 않고 임시 다운로드 파일을 제거해야 합니다. 새 비공개 버킷에 접두사 범위 만료, 스토리지 클래스 전환 및 완료되지 않은 업로드 정리 규칙을 적용한 다음 실제 정책과 객체 메타데이터를 확인합니다.
먼저 객체 관리와 멀티파트 정리를 완료합니다. 이 새 VM 에는 Node.js 22.22.0, Wrangler 4.131.1 및 AWS SDK 3.888.0 이 설치되어 있습니다. R2 가 활성화되어 있어야 하며 새 버킷을 구성할 권한이 필요합니다. 수명 주기 동작과 가격 정책을 확인합니다. 여기에는 Infrequent Access 의 최소 보관 기간 및 검색 요금도 포함됩니다. 이 실습에서 생성하는 파일은 Standard 스토리지에 유지되며, 이번 세션에서 명시적으로 삭제합니다. 평가에서는 며칠 후 실제로 삭제되었는지 또는 전환되었는지가 아니라 적용된 규칙과 현재 메타데이터를 확인합니다. 도메인은 필요하지 않습니다.
비공개 문서 버킷 만들기
이 단계에서는 이 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
표시된 디바이스 코드를 본인의 브라우저에서 인증합니다. 동의하기 전에 학습 계정과 요청된 account 및 user 읽기 범위를 확인합니다.
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은 무작위 16 진수 문자 12 개를 생성하므로 이전 실행과 리소스 이름이 충돌하지 않습니다. 이 here-document 는 표준 구성 파일을 작성하며, 셸이 변수 값을 파일에 삽입합니다.
ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r07-$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를 저장한 학습 계정으로 제한합니다. 만료 기간은 짧게 설정합니다. 다른 계정이나 관련 없는 권한은 포함하지 않습니다. 이 관리 토큰은 생성과 삭제를 포함한 버킷 관리에 사용합니다. 이 단계의 뒷부분에서는 S3 SDK 의 객체 작업을 위해 이 버킷으로 권한이 제한된 별도의 객체 토큰을 생성합니다.
토큰을 한 번만 이 숨겨진 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 에 표시된 버킷 이름으로 대상을 확인할 수 있으며, 나중에 다운로드 확인으로 저장된 바이트를 검증합니다.
S3 호환 API 를 사용하면 표준 스토리지 SDK 로 R2 에 접근할 수 있습니다. 이 API 는 Wrangler 의 디바이스 토큰이 아니라 별도의 액세스 키 쌍을 사용합니다. R2 Overview 에서 Account Details → API Tokens → Manage를 선택한 다음, 이 실습에서 생성한 리소스 이름을 포함한 User API token을 만듭니다. Object Read & Write를 선택하고 이 새 버킷 하나로만 범위를 제한합니다. 양식에 만료 기간을 설정하는 항목이 있으면 짧은 기간을 선택합니다. 모든 버킷 또는 Admin 권한은 선택하지 않습니다. 일회성 비밀 값을 저장할 때까지 이 토큰 페이지를 열어 둡니다.
VM 에서 다음 Bash 프롬프트를 사용합니다. read -s는 입력 내용을 숨기고, umask 077은 자격 증명 파일을 본인 사용자만 읽을 수 있게 합니다. 다음 이름은 AWS SDK 의 표준 환경 변수입니다. Access Key ID 와 Secret Access Key 를 각각 해당 프롬프트에 붙여 넣고 Enter 키를 누릅니다. 일반 API 토큰 값은 붙여 넣지 않습니다.
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
접두사별 수명 주기 규칙 적용하기
이 단계에서는 객체의 사용 기간에 따라 R2 가 적용하는 스토리지 작업 모음인 lifecycle policy를 구성합니다. 임시 내보내기 파일은 만료되어야 하지만, 보존할 핸드북은 이 규칙의 대상이 아니어야 합니다. 스토리지 클래스 전환은 요금 및 접근 클래스만 변경하며 객체를 삭제하지는 않습니다.
이 새 임시 버킷에는 두 가지 규칙을 사용합니다. temporary/ 접두사의 객체는 2 일 후 만료되고, 해당 접두사 아래의 완료되지 않은 업로드는 1 일 후 중단됩니다. archive/ 접두사의 객체는 30 일 후 Infrequent Access 로 전환됩니다. retained/를 대상으로 하는 규칙은 없습니다.
API 는 기간을 초 단위로 표현합니다. 하루는 86,400 초입니다. JSON 이 그대로 유지되도록 따옴표로 묶은 here-document 를 사용하여 정책을 작성합니다.
cat > lifecycle.json <<'JSON'
{
"rules": [
{
"id": "temporary-exports",
"enabled": true,
"conditions": {
"prefix": "temporary/"
},
"deleteObjectsTransition": {
"condition": {
"type": "Age",
"maxAge": 172800
}
},
"abortMultipartUploadsTransition": {
"condition": {
"type": "Age",
"maxAge": 86400
}
}
},
{
"id": "archive-transition",
"enabled": true,
"conditions": {
"prefix": "archive/"
},
"storageClassTransitions": [
{
"condition": {
"type": "Age",
"maxAge": 2592000
},
"storageClass": "InfrequentAccess"
}
]
}
]
}
JSON
npx wrangler r2 bucket lifecycle set "$BUCKET" --file lifecycle.json --env-file=.env.management
npx wrangler r2 bucket lifecycle list "$BUCKET" --env-file=.env.management
set 명령은 정책 전체를 교체하므로, 이 새 실습용 버킷에만 적용했는지 확인합니다. 정확히 두 개의 접두사, 활성화 상태 및 기간이 표시되는지 확인합니다. 기존 애플리케이션 버킷에는 이 교체 작업을 적용하지 않습니다. Dashboard 에서 같은 버킷의 Settings → Object Lifecycle Rules를 열고 작업 내용을 확인하되 변경하지 않습니다.
비용 관련 주의: Infrequent Access 에는 검색 요금과 최소 스토리지 기간이 적용됩니다. 이 실습에서는 향후 전환을 구성하고 정리 단계에서 새 Standard 객체를 삭제합니다. 30 일을 기다리거나 전환을 강제로 실행하지 않으며, 실제로 전환되었다고 말하지 않습니다.
실제 Dashboard 화면에는 앞으로 실행될 작업 설정이 표시됩니다. temporary/ 객체는 2 일 후 삭제하고 해당 접두사의 미완료 업로드는 1 일 후 중단하며, archive/ 객체는 30 일 후 Infrequent Access 로 전환합니다. 이 화면은 대기 기간이 지났거나 작업이 실행되었음을 의미하지 않습니다. 다음 단계에서 현재 객체 메타데이터를 확인합니다. retained/는 두 접두사의 대상이 아닙니다.

새로 적용된 만료 메타데이터 확인하기
이 단계에서는 정책을 적용한 후에 새 객체를 업로드합니다. R2 문서에 따르면 새 객체에는 적용 가능한 만료 정보가 x-amz-expiration에 표시됩니다. 기존 객체는 변경된 규칙이 반영되기까지 더 오래 걸릴 수 있습니다. SDK 에서는 이 헤더를 Expiration으로 제공합니다.
cat > seed.mjs <<'JS'
import { PutObjectCommand, HeadObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
for (const Key of ["temporary/export.txt", "archive/export.txt"]) {
await s3.send(new PutObjectCommand({ Bucket, Key, Body: readFileSync("document.txt"), ContentType: "text/plain" }));
}
await s3.send(new PutObjectCommand({ Bucket, Key: "retained/handbook.txt", Body: readFileSync("retained.txt"), ContentType: "text/plain" }));
for (const Key of ["temporary/export.txt", "archive/export.txt", "retained/handbook.txt"]) {
const head = await s3.send(new HeadObjectCommand({ Bucket, Key }));
console.log({ key: Key, expiration: head.Expiration || "none", storageClass: head.StorageClass || "STANDARD" });
}
JS
node --env-file=.env.s3 seed.mjs
temporary/export.txt에는 만료 날짜가 표시되고, 보존할 핸드북에는 삭제 만료 정보가 표시되지 않으며, 새 아카이브 객체의 스토리지는 Standard 인지 확인합니다. 향후 전환 여부는 현재 IA 스토리지 클래스가 아니라 원격 규칙으로 확인합니다. 새 객체에 예상한 만료 메타데이터가 없으면 적용된 접두사와 정책을 점검합니다. 이를 성공적인 만료 확인으로 간주하지 않습니다.
보존할 객체를 다운로드하여 원본 바이트와 비교합니다.
npx wrangler r2 object get "$BUCKET/retained/handbook.txt" --remote --file retained-download.txt --env-file=.env.management
cmp retained.txt retained-download.txt
정책을 성공적으로 읽고 보존할 객체를 읽을 수 있으면 이 실습에서 요구하는 범위의 결과를 확인한 것입니다. 실제 수명 주기 삭제는 비동기적으로 실행되며 예정된 만료 시점 이후에 발생할 수 있습니다. 이 실습에서는 몇 시간 후 발생하는 이벤트를 평가하지 않습니다.
실습용 스토리지를 명시적으로 비우기
이 단계에서는 향후 수명 주기 작업에 의존하지 않고 지금 세 개의 테스트 객체를 삭제합니다. 이 실습에서는 완료되지 않은 업로드를 만들지 않았지만, 버킷에 완료되지 않은 파트가 없는지 확인하려면 해당 목록도 조회해야 합니다. 객체 목록만으로는 완료되지 않은 파트가 없다는 것을 증명할 수 없습니다.
cat > empty.mjs <<'JS'
import { DeleteObjectCommand, ListObjectsV2Command, ListMultipartUploadsCommand } from "@aws-sdk/client-s3";
import { s3, Bucket } from "./storage.mjs";
for (const Key of ["temporary/export.txt", "archive/export.txt", "retained/handbook.txt"]) await s3.send(new DeleteObjectCommand({ Bucket, Key }));
const objects = await s3.send(new ListObjectsV2Command({ Bucket }));
const uploads = await s3.send(new ListMultipartUploadsCommand({ Bucket }));
console.log("Objects:", objects.Contents || []);
console.log("Incomplete uploads:", uploads.Uploads || []);
JS
node --env-file=.env.s3 empty.mjs
객체 배열과 완료되지 않은 업로드 배열이 모두 비어 있는지 확인합니다. 실험 중 멀티파트 세션을 만들었다면, 이전 실습에서 사용한 중단 작업을 해당 키와 업로드 ID 에 정확히 적용한 후 이 읽기 전용 목록 조회를 다시 실행합니다. 목록 조회 실패를 절대 조용히 무시하지 않습니다.
빈 버킷과 정책 삭제하기
이 단계에서는 객체와 업로드 확인이 통과한 후 소유한 버킷을 삭제합니다. 정책은 버킷 구성의 일부이므로 버킷과 함께 사라집니다.
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
npx wrangler r2 bucket list --env-file=.env.management
정확히 생성된 이름인지 확인합니다. 인증된 목록 조회가 성공했을 때 해당 이름이 목록에 없는지 확인합니다. 관리 자격 증명을 폐기하기 전에 플랫폼 삭제 확인을 실행합니다.
실습 자격 증명 폐기 및 로그아웃
이 단계에서는 이 실습이 남긴 접근 권한을 제거합니다. 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 를 통해 수동으로 확인해야 하며, 파일 삭제만으로 폐기되었다고 판단하지 않습니다.
요약
범위가 지정된 만료, 향후 스토리지 전환 및 멀티파트 정리 규칙을 적용하고, 현재 메타데이터를 확인하며, 보존할 데이터를 유지한 후 명시적으로 정리합니다.



