boto3로 R2 사용하기
Python boto3로 R2를 사용하는 예시예요. S3 클라이언트를 구성하고 객체를 다루며, 멀티파트 업로드 성능 최적화 방법과 사전 서명 URL 생성까지 다뤄요.
출처: 문서
본문
boto3 ↗︎를 R2 엔드포인트를 사용하도록 구성해요:
import boto3
s3 = boto3.client(
service_name="s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<ACCESS_KEY_ID>",
aws_secret_access_key="<SECRET_ACCESS_KEY>",
region_name="auto",
)
AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY 환경 변수 ↗︎를 설정했다면 aws_access_key_id와 aws_secret_access_key는 생략할 수 있어요.
이 클라이언트를 사용한 일반적인 작업이에요:
# Get object metadata
s3.head_object(Bucket="my-bucket", Key="dog.png")
# Get object
response = s3.get_object(Bucket="my-bucket", Key="dog.png")
# Upload single file
with open("./dog.png", "rb") as f:
s3.upload_fileobj(f, "my-bucket", "dog.png")
# Delete object
s3.delete_object(Bucket="my-bucket", Key="dog.png")
업로드 성능 최적화 (Optimizing upload performance)
여러 GB 크기의 파일(학습 데이터나 영상 등) 같은 큰 객체에서는 upload_fileobj가 처리량 병목이 될 수 있어요. 내부 스레드 풀은 Python의 GIL ↗︎에 제약을 받고, TransferConfig의 max_concurrency를 올려도 약 10개 스레드 이상에서는 효과가 줄어들어요.
대신 ThreadPoolExecutor와 함께 저수준 멀티파트 API를 사용해요:
import boto3
import math
import os
from concurrent.futures import ThreadPoolExecutor
s3 = boto3.client(
service_name="s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<ACCESS_KEY_ID>",
aws_secret_access_key="<SECRET_ACCESS_KEY>",
region_name="auto",
)
bucket = "my-bucket"
key = "large-file.bin"
file_path = "./large-file.bin"
part_size = 16 * 1024 * 1024 # 16 MiB per part
max_workers = 10
# Step 1: Create the multipart upload
upload_id = None
mpu = s3.create_multipart_upload(Bucket=bucket, Key=key)
upload_id = mpu["UploadId"]
def upload_part(part_number, data):
response = s3.upload_part(
Bucket=bucket,
Key=key,
UploadId=upload_id,
PartNumber=part_number,
Body=data,
)
return {"PartNumber": part_number, "ETag": response["ETag"]}
try:
file_size = os.path.getsize(file_path)
part_count = math.ceil(file_size / part_size)
# Step 2: Upload parts in parallel
with ThreadPoolExecutor(max_workers=max_workers) as pool:
futures = []
with open(file_path, "rb") as f:
for i in range(part_count):
data = f.read(part_size)
futures.append(pool.submit(upload_part, i + 1, data))
parts = [future.result() for future in futures]
# Step 3: Complete the upload
s3.complete_multipart_upload(
Bucket=bucket,
Key=key,
UploadId=upload_id,
MultipartUpload={"Parts": parts},
)
print("Multipart upload complete.")
except Exception:
if upload_id:
try:
s3.abort_multipart_upload(Bucket=bucket, Key=key, UploadId=upload_id)
except Exception:
pass
raise
멀티파트 업로드의 파트 크기 제한과 수명 주기 관리에 대해 더 자세히 알아보려면 객체 업로드를 참고하세요.
사전 서명 URL 생성 (Generate presigned URLs)
버킷에 대한 임시 공개 읽기 또는 쓰기 접근 권한을 공유하는 사전 서명 링크를 생성해요.
# Generate presigned URL for reading (GET)
get_url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "my-bucket", "Key": "dog.png"},
ExpiresIn=3600, # Valid for 1 hour
)
# Generate presigned URL for writing (PUT)
put_url = s3.generate_presigned_url(
"put_object",
Params={
"Bucket": "my-bucket",
"Key": "dog.png",
"ContentType": "image/png",
},
ExpiresIn=3600,
)
https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=<timestamp>&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature>
https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=<timestamp>&X-Amz-Expires=3600&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=<signature>
사전 서명 PUT URL로 업로드해요. ContentType이 포함된 사전 서명 URL을 사용할 때는 클라이언트가 일치하는 Content-Type 헤더를 꼭 포함해야 해요:
curl -X PUT "https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=..." \
-H "Content-Type: image/png" \
--data-binary @dog.png
CORS와 Content-Type으로 업로드 제한하기 (Restrict uploads with CORS and Content-Type)
업로드용 사전 서명 URL을 생성할 때 다음 방법으로 남용과 오용을 제한할 수 있어요:
-
Content-Type 제한: 사전 서명 URL 매개변수에 허용된 content type을 지정해요. 클라이언트가 다른
Content-Type헤더를 보내면 업로드는 실패해요. -
CORS 구성: 버킷에 CORS 규칙을 설정해 어떤 오리진이 파일을 업로드할 수 있는지 제어해요. Cloudflare 대시보드 ↗︎에서 버킷 설정에 JSON 정책을 추가해 CORS를 구성할 수 있어요:
[
{
"AllowedOrigins": ["https://example.com"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
그다음 Content-Type 제한이 적용된 사전 서명 URL을 생성해요:
put_url = s3.generate_presigned_url(
"put_object",
Params={
"Bucket": "my-bucket",
"Key": "dog.png",
"ContentType": "image/png",
},
ExpiresIn=3600,
)
클라이언트가 이 사전 서명 URL을 사용할 때는 반드시 다음을 지켜야 해요:
-
허용된 오리진에서 요청할 것 (CORS로 강제됨)
-
Content-Type: image/png헤더를 포함할 것 (서명으로 강제됨)