English {#english}
Step-by-step check of token, account, Zone, R2, and DNS before using Pulumi for R2/DNS. Prerequisites: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID in .env.local. Covers R2 bucket names, CORS (prego-static-assets), and DNS. Full details: see Korean section below.
한국어 {#korean}
Runbook: Cloudflare 접속·권한 단계별 점검
목적: Pulumi로 R2·DNS를 쓰기 전에, 토큰·계정·Zone·R2·DNS를 순서대로 확인하는 방법.
사전: .env.local에 CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID(R2/계정 확인용) 설정.
0. R2 버킷·CORS 정리 (참고)
버킷 이름 (코드와 동일, prego- 통일)*
| Cloudflare 버킷 이름 | Pulumi 리소스 이름 | 용도 |
|---|---|---|
| prego-static-assets | prego-static-assets | 정적 에셋(폰트 등). CORS 적용 대상. |
| prego-usage-raw | prego-usage-raw | Usage 원시 데이터. CORS 없음. |
| prego-logs | prego-logs | Logpush·관측성 로그. Ref: logpush-setup.md. |
- 409 방지: 코드에서
get_r2_bucket로 Cloudflare 존재 여부를 먼저 확인한다. 이미 있으면 버킷 생성은 하지 않고 CORS만 적용한다. state에 없어도 409 없이pulumi up한 번에 진행된다. (수동 import 없이 동작.)
CORS (prego-static-assets 전용) · 리소스: prego-static-assets-cors · Origins: https://app.pregoi.com, https://pregoi.com, https://x.pregoi.com, https://prego-web-anm.pages.dev, http://localhost:3000, http://127.0.0.1:3000 · Methods: GET, HEAD · Headers: * · Max age: 3600초
수동 import가 필요한 경우: state와 Cloudflare가 어긋나면 ./run-up.sh import 'cloudflare:index/r2Bucket:R2Bucket' prego-static-assets <ACCOUNT_ID>/prego-static-assets/default (및 prego-usage-raw) 후 ./run-up.sh up. Cloudflare R2 → prego-static-assets → Settings → CORS에서 값 확인.
0-2. Control Plane용 D1·KV (Pulumi 생성)
기획서(api-control-plane, tenant-onboarding-demo-www-plan)에 따라 prego-pulumi가 다음 리소스를 생성한다.
| 리소스 | Pulumi 이름 | 용도 |
|---|---|---|
| D1 | prego-d1 | Control Plane Worker 상태 DB (tenants_master, provision_jobs 등). |
| KV | prego-tenant-origins (title) | TENANT_ORIGINS 바인딩 — hostname → origin URL 라우팅. |
| R2 | §0 참고 | prego-static-assets, prego-usage-raw, prego-db-backups, prego-logs. |
생성 절차
pulumi config set cloudflare:accountId <ACCOUNT_ID>또는CLOUDFLARE_ACCOUNT_ID환경 변수 설정.- prego-pulumi에서
pulumi up실행 → D1·KV·R2 생성(이미 있으면 스킵 또는 import). - 스택 출력에서 d1_database_id, kv_namespace_id 확인:
Terminal window pulumi stack output d1_database_idpulumi stack output kv_namespace_id - prego-control-plane
wrangler.toml에 반영:[[d1_databases]]→database_id = "<d1_database_id>",database_name = "prego-d1".[[kv_namespaces]]→binding = "TENANT_ORIGINS",id = "<kv_namespace_id>".
- Control Plane 마이그레이션 적용:
Terminal window cd prego-control-planewrangler d1 migrations apply prego-d1 --remote - Worker 배포:
wrangler deploy.
신규 환경: D1·KV를 Pulumi로 먼저 만든 뒤 위 4~6으로 바인딩·마이그레이션·배포하면 된다. Worker 코드는 prego-control-plane 레포에서 wrangler로 배포한다.
1단계 — 토큰 유효성
source .env.localcurl -s "https://api.cloudflare.com/client/v4/user/tokens/verify" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"- 기대:
"success": true,"status": "active" - 실패: 401/403 → 토큰 재발급 또는 권한 확인
2단계 — Account ID 확인
- Account ID는 32자리 16진수 (예:
3a555a2adb6585031cb8541ef5e6d2a0). Zone ID(영문+숫자 혼합)와 다름. - 목록 조회 (토큰에 계정 읽기 권한 필요):
curl -s "https://api.cloudflare.com/client/v4/accounts?per_page=1" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"result[0].id가 Account ID..env.local의CLOUDFLARE_ACCOUNT_ID와 일치하는지 확인.
3단계 — Zone(도메인) 접근
pregoi.com Zone ID 확인:
curl -s "https://api.cloudflare.com/client/v4/zones?name=pregoi.com" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"- 기대:
result[0].id에 Zone ID (예:07904653049eb89fb9c4accb41009411) - 실패:
result빈 배열 → 해당 도메인 Zone이 없거나, 토큰에 Zone 읽기 권한 없음
4단계 — R2 버킷 목록
curl -s "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/r2/buckets" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"- 기대:
"success": true,result안에 버킷 목록 (또는 빈 배열) - 실패: 7003 “object identifier is invalid” → Account ID가 32자리 hex가 아님. 403 → R2 권한 없음.
4-2. R2 쓰기 권한 확인
목록(4단계)만으로는 읽기만 확인된 상태. 쓰기는 버킷 생성(POST)으로 확인.
1) 테스트 버킷 생성 (쓰기 권한)
source .env.local# 이름은 고유하게 (이미 있으면 409)TEST_BUCKET="prego-write-test-$(date +%s)"curl -s -w "\nHTTP_CODE:%{http_code}" -X POST \ "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/r2/buckets" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"name\":\"${TEST_BUCKET}\"}"- 성공: 응답에
"success": true, HTTP 200. → 버킷 생성(쓰기) 권한 있음. - 실패: 403 → 토큰에 R2 쓰기 권한 없음. 대시보드에서 API 토큰에 R2 Object Read & Write 또는 R2 Edit 추가.
2) 테스트 버킷 삭제 (정리)
쓰기 확인 후 같은 토큰으로 삭제해 두면 정리됨.
curl -s -w "\nHTTP_CODE:%{http_code}" -X DELETE \ "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/r2/buckets/${TEST_BUCKET}" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"- 참고: 버킷 안의 객체(파일) 업로드(PUT) 는 R2 S3 호환 API + R2 전용 API 토큰/키를 쓰며, 위 Cloudflare API 토큰과는 별도. “버킷을 만들 수 있는가”만 확인하려면 1)으로 충분.
5단계 — DNS 레코드 목록 (node-01 존재 여부)
Zone ID를 위 3단계에서 확인한 값으로 넣음:
ZONE_ID="07904653049eb89fb9c4accb41009411" # 3단계 result[0].idcurl -s "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=node-01.pregoi.com" \ -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"- 기대:
result에node-01A 레코드가 있으면, 이미 존재 → Pulumi는 create 대신 import 해야 함. result[0].id가 해당 레코드의 Record ID (import 시 사용).
6단계 — “An identical record already exists” (81058) 해결
Cloudflare에 이미 node-01 A 레코드가 있어서 Pulumi가 create 시 81058을 내는 경우.
1) Record ID 확인 (5단계 curl 응답의 result[0].id)
2) Pulumi로 해당 레코드 import
cd /Users/marco/prego-pulumisource .env.localpulumi import 'cloudflare:index/dnsRecord:DnsRecord' prego-node-01-dns <ZONE_ID>/<RECORD_ID>예 (Zone ID·Record ID는 실제 값으로 교체):
pulumi import 'cloudflare:index/dnsRecord:DnsRecord' prego-node-01-dns 07904653049eb89fb9c4accb41009411/필요한레코드ID3) 다시 up
./run-up.sh up- CORS 등 아직 안 만들어진 리소스만 생성되고, DNS는 state에 있으므로 변경 없음.
요약 체크리스트
| 순서 | 확인 항목 | 성공 시 |
|---|---|---|
| 0-2 | Control Plane D1/KV | Pulumi up → d1_database_id, kv_namespace_id 출력 → wrangler.toml 반영 → migrations apply → deploy |
| 1 | 토큰 verify | success true |
| 2 | Account ID | 32자리 hex, .env와 일치 |
| 3 | Zone pregoi.com | result[0].id 존재 |
| 4 | R2 buckets | success true (목록 또는 빈 배열) |
| 4-2 | R2 쓰기 | POST로 테스트 버킷 생성 → success true면 쓰기 권한 있음 |
| 5 | DNS node-01 | 이미 있으면 record id 확보 → import |
| 6 | 81058 시 | import 후 pulumi up |
문서 위치: docs/runbook/cloudflare-step-by-step-check.md