From 3dfceb81b7d780f1bcf04948e74b9f3ce4498498 Mon Sep 17 00:00:00 2001 From: kaffa Date: Tue, 21 Apr 2026 17:13:33 +0900 Subject: [PATCH] =?UTF-8?q?ironclad=20production=20cutover=20(2026-04-21):?= =?UTF-8?q?=20apex=20=E2=86=92=20Worker=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit products/ironclad-website.md: 배포 표에 라우팅 방식 컬럼 추가. production만 zone route 방식(custom_domain 대신 zone_name + /*)을 써서 기존 APISIX A record를 건드리지 않고 Cloudflare 엣지에서 Worker가 매칭 요청을 가로채는 구조. 전제 조건(crowdsec bouncer wildcard가 apex를 가로채지 않도록 *.ironclad.it.com/* 수정) 명시. history/2026-04-21: 3차에 걸친 cutover 경로(DNS 충돌 → route 전환 → crowdsec wildcard dot 누락 수정) + 현재 prod 상태 + 후속 정리 항목. --- .../2026-04-21-ironclad-production-cutover.md | 76 +++++++++++++++++++ products/ironclad-website.md | 14 ++-- 2 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 history/2026-04-21-ironclad-production-cutover.md diff --git a/history/2026-04-21-ironclad-production-cutover.md b/history/2026-04-21-ironclad-production-cutover.md new file mode 100644 index 0000000..a807d91 --- /dev/null +++ b/history/2026-04-21-ironclad-production-cutover.md @@ -0,0 +1,76 @@ +--- +title: 2026-04-21 Ironclad production cutover — apex 도메인 Worker 전환 +updated: 2026-04-21 +tags: [history, ironclad, website, cloudflare, production, crowdsec] +--- + +## 요약 + +`ironclad.it.com` (apex) production이 이전 경로(Cloudflare → APISIX → Caddy 컨테이너, Stamp/Flux volume_shared) 에서 **Cloudflare Worker `ironclad-site` (Next.js 16 + opennextjs-cloudflare)** 로 전환됨. 고객이 보는 화면이 6주 묵은 영문 "Ironclad Corp — Managed Security Hosting" 에서 최신 한국어 "Ironclad — 올어라운드 호스팅 & 보안서비스" 로 즉시 교체. + +## 진행 경로 + +### 1차 시도 — `v2026.04.21` 태그 push (실패) +- `deploy-production.yml` 트리거, wrangler 빌드·업로드 성공 +- DNS 등록 단계에서 Cloudflare API 실패: + ``` + code: 100117 + Hostname 'ironclad.it.com' already has externally managed DNS records (A, CNAME). + Either delete them, try a different hostname, or use the option + 'override_existing_dns_record' to override. + ``` +- 원인: 기존 `ironclad.it.com A 172.233.93.180 (Proxied)` 레코드가 먼저 있어 `custom_domain: true` 등록 거부 + +### 2차 시도 — custom_domain 제거하고 zone route로 전환 (성공) +- `wrangler.jsonc` production env routes 변경: + - Before: `{ pattern: "ironclad.it.com", custom_domain: true }` + - After: `{ pattern: "ironclad.it.com/*", zone_name: "ironclad.it.com" }` +- 로컬에서 `CLOUDFLARE_API_TOKEN=... pnpm deploy:production` 실행 → Worker 업로드 + route 등록 성공 +- 결과: Cloudflare에 `ironclad.it.com/* → ironclad-site` route binding 생성됨 + +### 3차 보정 — crowdsec bouncer wildcard route 버그 수정 (결정타) +- 배포 후에도 prod 응답이 여전히 구 HTML. 원인 조사 결과 Cloudflare Workers Routes 상태: + ``` + www.ironclad.it.com/* -> ironclad-site + ironclad.it.com/* -> ironclad-site + *ironclad.it.com/* -> crowdsec-cloudflare-worker-bouncer ← 문제 + ``` +- `*ironclad.it.com/*` 패턴이 의도는 "subdomain wildcard" 였으나 **dot 누락**으로 apex까지 포괄(prefix wildcard로 해석되어 `ironclad.it.com/*` 도 매칭). crowdsec bouncer Worker가 먼저 잡아서 origin(APISIX → Caddy)로 pass-through. +- Cloudflare API로 zone route `c148ae77ebb74f20b61ecd71d0e86b77`를 `*.ironclad.it.com/*`로 변경: + ```bash + curl -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE/workers/routes/$ROUTE_ID" \ + -d '{"pattern":"*.ironclad.it.com/*","script":"crowdsec-cloudflare-worker-bouncer"}' + ``` +- 수정 직후 `ironclad.it.com` 응답에 `x-opennext: 1` + `x-powered-by: Next.js` 확인 → Worker가 정상 서빙 + +## 현재 prod 상태 (2026-04-21 post-cutover) + +- `https://ironclad.it.com/` → Cloudflare → `ironclad-site` Worker (Next.js 16) + - title: `Ironclad — 올어라운드 호스팅 & 보안서비스 | Ironclad` + - `x-opennext: 1`, `x-powered-by: Next.js` +- DNS A record `172.233.93.180`은 건드리지 않음 (롤백 준비) +- apex 도메인의 bot 차단은 Next.js Worker 내부의 CrowdSec middleware (KV binding `CROWDSECCFBOUNCERNS = 9af0d1c1c14a4bc1a3835c2a5b22fd7a`)가 담당. Edge Worker bouncer는 서브도메인(`*.ironclad.it.com`)만 커버. + +## 후속 정리 필요 + +| 항목 | 담당 | 우선순위 | +|------|------|----------| +| APISIX 라우트 `ironclad.it.com` 제거 (volume_shared 모드) | 씬 | 낮음 (트래픽 안 감, 남아있어도 무해) | +| Stamp-Flux 파이프라인에서 ironclad.it.com 배포 경로 deprecate | 헤임달 | 중 (혼선 방지) | +| `openclaw/openclaw-agents.md` volume_shared 모드 ironclad 관련 기록 정리 | 헤임달 | 중 | +| Cloudflare DNS A record 변경 (172.233.93.180 → Cloudflare 기본) | kappa | 낮음 (zone route 방식이라 origin 무관) | +| `v2026.04.21` 태그는 `deploy-production.yml`에서 실패로 남음 → 재태깅 불필요 (로컬에서 직접 배포 완료) | — | — | + +## 커밋 + +- `hosting/ironclad`: + - `v2026.04.21` (태그, f736487) — Header dark nav 승격 시점 + - `6f2e2cc` fix(deploy): custom_domain → zone route로 전환 (DNS 충돌 회피) +- Cloudflare API 변경: zone route `c148ae77ebb74f20b61ecd71d0e86b77` pattern 업데이트 (wrangler 소스 외) + +## 학습 포인트 + +- **apex 도메인에 `custom_domain: true`를 쓰려면 기존 DNS 레코드가 없어야 한다**. Stamp/Flux 같이 기존 정적 배포 경로가 있는 도메인은 `zone_name` 기반 route 방식이 자연스럽다. +- **Cloudflare Worker Routes wildcard의 dot 누락은 apex까지 포괄**. `*example.com/*` vs `*.example.com/*`는 전혀 다른 패턴. `*example.com/*`은 prefix wildcard로 apex 포함. +- 기존 wildcard route가 apex로 해석되면 "내가 새로 만든 route가 우선" 될 거라 기대하기 어렵다. Cloudflare는 specificity tie-breaker가 있지만 명확히 의도한 대로 동작하지 않음. 가장 안전한 건 **wildcard pattern이 apex를 포함하지 않게 수정**. +- `x-opennext: 1` 헤더는 OpenNext가 서빙하고 있음을 확인하는 가장 빠른 시그널. diff --git a/products/ironclad-website.md b/products/ironclad-website.md index e97910b..57dba32 100644 --- a/products/ironclad-website.md +++ b/products/ironclad-website.md @@ -31,11 +31,15 @@ ironclad.it.com 공식 홈페이지. 호스팅 상품 소개, 가격표, 회사 → Worker ironclad-site-{staging|preview|} + R2 ironclad-cache-{staging|preview|prod} ``` -| 환경 | Worker | R2 버킷 | 도메인 | 배포 트리거 | -|------|--------|---------|--------|------------| -| production | `ironclad-site` | `ironclad-cache-prod` | `ironclad.it.com` · `www.ironclad.it.com` | manual workflow_dispatch (`deploy-production.yml`) | -| staging | `ironclad-site-staging` | `ironclad-cache-staging` | `staging.ironclad.it.com` | main push 자동 (`deploy-staging.yml`) | -| preview | `ironclad-site-preview` | `ironclad-cache-preview` | `preview.ironclad.it.com` | manual workflow_dispatch (`deploy-preview.yml`) | +| 환경 | Worker | R2 버킷 | 도메인 | 배포 트리거 | 라우팅 방식 | +|------|--------|---------|--------|------------|------------| +| production | `ironclad-site` | `ironclad-cache-prod` | `ironclad.it.com` · `www.ironclad.it.com` | `v*` 태그 push (`deploy-production.yml`) | **zone route** (`pattern: ironclad.it.com/*, zone_name: ironclad.it.com`) — 기존 A record(APISIX 172.233.93.180) 유지, Worker가 엣지에서 가로챔 | +| staging | `ironclad-site-staging` | `ironclad-cache-staging` | `staging.ironclad.it.com` | main push 자동 (`deploy-staging.yml`) | `custom_domain: true` | +| preview | `ironclad-site-preview` | `ironclad-cache-preview` | `preview.ironclad.it.com` | manual workflow_dispatch (`deploy-preview.yml`) | `custom_domain: true` | + +**왜 production만 zone route?** apex 도메인(`ironclad.it.com`)은 이미 APISIX origin을 가리키는 A record가 등록되어 있어 wrangler의 `custom_domain` 등록이 실패(`code: 100117`). `custom_domain` 대신 zone route를 쓰면 기존 DNS 레코드를 건드리지 않고 Cloudflare 엣지에서 Worker가 매칭된 요청만 처리. 롤백 시 DNS 수동 복구 불필요. + +**전제 조건**: Cloudflare zone에 등록된 Worker route 중 apex를 가로채는 wildcard가 없어야 한다. [[crowdsec-safeline|crowdsec-cloudflare-worker-bouncer]]의 zone route가 `*ironclad.it.com/*`로 오설정되어 있었고 이걸 `*.ironclad.it.com/*`로 수정해야 apex가 `ironclad-site` Worker로 직행 (2026-04-21 조치). - 공통 KV: `CROWDSECCFBOUNCERNS` (id `9af0d1c1c14a4bc1a3835c2a5b22fd7a`) — CrowdSec bouncer 미들웨어 상태 공유 - 배포 후 smoke test: `curl -skL -w '%{http_code}' https:///` 5xx면 실패