cloudflare-workflows

3 개의 포스트

cloudflare

수백만 개 리포지토리의 CI/CD를 실행하세요 — 여러분의 플랫폼에서, Cloudflare에서 (새 탭에서 열림)

Cloudflare는 코드 저장소인 Artifacts를 기반으로 빌드·테스트·배포까지 전 과정을 Cloudflare에서 실행하는 CI/CD 환경을 구축하고 있다. 새 CI SDK는 Cloudflare Workflows와 Sandbox SDK를 결합해 CI 파이프라인을 TypeScript로 정의하고, 코드가 Artifacts에 push될 때 자동으로 실행할 수 있게 한다. 각 단계의 재시도·타임아웃·캐싱·병렬 실행을 지원하며, 성공한 경우에만 자동 배포하거나 AI 에이전트를 통한 자동 수정도 가능하다. ## Cloudflare에서 완성되는 코드 개발 생태계 - Cloudflare는 다음 과정을 하나의 플랫폼으로 통합하려 한다. - 코드 저장: Artifacts - 빌드 및 테스트: CI SDK와 Workflows - 배포: `wrangler deploy` - Artifacts는 수백만 개의 저장소를 저장하고 버전을 관리할 수 있는 코드 저장소다. - `wrangler` 설정의 새로운 `events` 필드를 이용하면 Artifacts의 `push` 이벤트를 Workflow 실행으로 직접 연결할 수 있다. - 별도의 이벤트 구독, 큐, 큐 컨슈머를 구성하지 않아도 코드 push를 CI 작업의 시작점으로 사용할 수 있다. ## CI/CD 파이프라인은 하나의 Workflow - CI/CD는 정해진 순서로 여러 단계를 실행하고, 하나라도 실패하면 이후 단계를 중단하는 프로세스다. - Cloudflare는 이를 본질적으로 Workflow와 동일한 구조로 본다. - 기존 YAML 기반 도구 대신 TypeScript의 `step.do()`와 CI SDK를 사용해 파이프라인을 정의할 수 있다. - TypeScript를 사용하면 YAML보다 다음과 같은 장점이 있다. - 조건문과 동적 설정을 쉽게 적용 - 플랫폼별·저장소별 파이프라인 커스터마이징 - 일반 코드와 동일한 방식의 재사용 및 유지보수 ## 격리된 환경에서 실행되는 CI 단계 - CI SDK는 각 명령을 독립적인 Sandbox 환경에서 실행한다. - 대표적인 단계는 다음과 같다. - 의존성 설치 - 코드 빌드 - 린트 실행 - 타입 검사 - 단위 테스트 - 조건부 배포 - 각 Sandbox 명령은 Workflow의 단계로 실행되므로 Cloudflare Workflows가 제공하는 재시도와 타임아웃 기능을 활용할 수 있다. - 기존에는 Sandbox API를 직접 호출하고 단계 간 상태를 별도로 관리해야 했지만, CI SDK가 이 과정을 추상화한다. ## 의존성 캐싱과 병렬 실행 - `bun install --frozen-lockfile` 같은 설치 단계를 먼저 정의하고, `package.json`과 `bun.lock`을 캐시 입력으로 지정할 수 있다. - 의존성 캐시는 계정의 R2 버킷에 Sandbox 스냅샷 형태로 저장된다. - 이후 린트·테스트·타입 검사·빌드 단계는 의존성 설치를 반복하지 않는다. - 독립적인 단계는 `Promise.all()`로 병렬 실행할 수 있어 전체 CI 시간을 줄인다. - 배포 단계는 모든 검사가 성공한 뒤 실행되도록 마지막에 배치한다. ```ts const deps = await ci.runner({ name: "install", command: "bun install --frozen-lockfile", cache: { inputs: ["package.json", "bun.lock"] }, }); await Promise.all([ deps.runner({ name: "lint", command: "bun run lint" }), deps.runner({ name: "test", command: "bun run test" }), deps.runner({ name: "typecheck", command: "bun run typecheck" }), deps.runner({ name: "build", command: "bun run build" }), ]); await deps.runner({ name: "deploy", command: "bun wrangler deploy", cloudflareCredentials: { accountId: this.env.CLOUDFLARE_DEPLOY_ACCOUNT_ID, }, }); ``` ## 플랫폼 관리 CI와 사용자 정의 CI - 플랫폼 사업자는 고객 애플리케이션을 대신해 CI/CD 파이프라인을 관리할 수 있다. - 하나의 Workflow를 여러 고객 애플리케이션에 공유하면 고객마다 CI 환경을 직접 운영할 필요가 없다. - 반대로 특정 고객이 자체적인 빌드·테스트 규칙을 원한다면 Dynamic Workflows를 이용해 전용 CI를 정의할 수 있다. - 플랫폼이 관리하는 CI와 고객이 직접 작성한 CI는 동일한 namespace 안에서 동시에 실행할 수 있다. - 따라서 모든 고객에게 동일한 파이프라인을 강제하지 않고, 공통 규칙과 개별 요구사항을 함께 지원한다. ## AI 기반 셀프 힐링 CI - CI Workflow에 AI 리뷰 에이전트를 통합할 수 있다. - 빌드 단계가 실패하면 에이전트가 오류를 분석하고 수정 코드를 생성할 수 있다. - 수정 사항을 커밋으로 push해 사람이 검토하고 승인하는 흐름도 구성할 수 있다. - Cloudflare는 이러한 예제를 Project Think의 self-healing CI Workflow로 제공한다. ## 직접 CI Workflow 작성하기 - `@cloudflare/ci`의 `CIWorkflow`를 import해 자체 파이프라인을 작성한다. - 설치 단계에서 Vite, React, esbuild, ESLint, Vitest 등 필요한 패키지와 도구를 설치한다. - lockfile을 지정해 의존성 변경 여부를 검증한다. - 설치 결과를 캐시한 뒤 빌드와 각종 검사를 별도의 격리된 단계에서 실행한다. - 기본적으로 Workflow 단계는 독립적으로 실행되므로 병렬 처리가 가능하다. - 배포 전에 모든 검사가 끝나야 한다면 `Promise.all()`로 여러 검사를 묶어 완료를 기다린다. ## 실용적인 결론 Cloudflare 기반 플랫폼을 운영하거나 고객별 코드를 관리한다면, CI SDK와 Workflows를 이용해 공통 파이프라인을 먼저 만들고 저장소별 예외만 동적으로 추가하는 방식이 적합하다. 설치 단계의 캐싱과 독립 검사의 병렬 실행을 적용하면 CI 지연 시간을 줄일 수 있으며, 배포는 모든 검증 단계가 성공한 뒤에만 실행하도록 구성하는 것이 안전하다.

cloudflare

대규모 도그푸딩: cdnjs를 Cloudflare의 개발자 플랫폼으로 마이그레이션하기 (새 탭에서 열림)

cdnjs는 2026년 6월 23일부터 Cloudflare Developer Platform만으로 운영되며, 이를 통해 하루 90억 건의 요청을 처리하는 대규모 오픈소스 CDN의 배포 파이프라인까지 단일 플랫폼으로 통합했다. 기존 시스템은 안정적이고 높은 캐시 적중률을 유지했지만, GCP·VM·GitHub·Cloudflare로 분산된 구조 때문에 관측성과 유지보수가 어려웠다. 이번 마이그레이션은 성능 개선보다 배포·처리 과정의 단순화, 추적 가능성, 보안성 향상에 초점을 둔 것이다. ### cdnjs가 여전히 대규모 트래픽을 처리하는 이유 - cdnjs는 JavaScript와 CSS 오픈소스 라이브러리를 무료로 제공하는 커뮤니티 기반 CDN이다. - 사용자는 API 키나 회원가입 없이 `<script>` 태그만으로 jQuery, Bootstrap, Lodash 등을 불러올 수 있다. - 전체 웹사이트의 약 12%, JavaScript CDN 시장의 48.3%가 cdnjs를 사용한다. - 하루 약 90억 건, 초당 평균 10만 8천 건의 요청을 처리하며 330개 이상의 Cloudflare 데이터센터에서 서비스한다. - 캐시 적중률은 98.6%에 달한다. - URL 규칙이 일관되고 버전이 불변이어서 ChatGPT, Claude, Cursor 같은 LLM이 예제 코드에 안정적으로 활용하기 쉽다. - 각 파일에 SRI 해시가 제공되고 미러가 감사 가능해 공급망 공격 대응에도 유리하다. - 무료·무제한 서비스라는 점 역시 cdnjs가 계속 사용되는 중요한 이유다. ### 기존 아키텍처의 배경 - 2020년 파일 제공 영역은 Cloudflare Workers와 KV로 이전됐다. - KV 장애 시에는 베어메탈 오리진을 사용하는 구조로 복원력을 높였다. - Brotli와 gzip으로 모든 자산을 사전 압축해 응답 크기도 줄였다. - 그러나 새 버전 감지, 패키지 다운로드, 압축, 처리 결과 저장을 담당하는 퍼블리싱 파이프라인은 GCP에 남아 있었다. - 당시 Workers에는 장시간 실행 작업, 대용량 아카이브 처리, 다단계 오케스트레이션을 지원할 기능이 충분하지 않았다. - 이에 따라 GCP Functions, VM 기반 `git-sync`, GCS, Pub/Sub, GitHub 저장소가 서로 연결된 형태로 운영됐다. ### 관측성과 데이터 일관성의 문제 - 하나의 패키지 업데이트가 Cloud Functions, GCS 이벤트, Pub/Sub, `git-sync` VM, Workers KV를 거쳤지만 공통 correlation ID가 없었다. - GCP Logging과 Cloudflare Logpush의 로그를 연결할 기준이 없어 장애 원인을 수작업으로 추적해야 했다. - 처리 결과가 KV에는 기록됐지만 GitHub 저장소 반영에 실패하는 부분 성공(partial success)이 발생할 수 있었다. - 두 저장소가 불일치해도 전체 파이프라인 상태를 아는 시스템이나 자동 알림이 없었다. - 파일은 엣지의 KV와 GitHub 저장소에 동시에 존재했지만 어느 쪽도 명확한 단일 원본이 아니었다. ### 이벤트 기반 파이프라인의 한계 - 여러 Cloud Functions가 공유 스토리지에 파일을 기록하고, 스토리지의 새 파일 이벤트가 다음 함수를 호출하는 방식이었다. - 스토리지가 메시지 큐 역할까지 맡으면서 구조가 복잡해졌다. - 명시적인 dead-letter queue가 없어 실패한 작업을 확인하기 어려웠다. - 대기 중인 작업의 backlog를 파악하기 어렵고, 특정 단계부터 깔끔하게 재실행하기도 힘들었다. - npm 업데이트 확인만 해도 알파벳별로 나눈 26개의 Cloud Functions가 필요했다. - 각 함수마다 별도의 배포와 로그가 있어 전체 시스템의 정상 여부를 확인하려면 26개를 모두 점검해야 했다. ### GitHub 저장소의 규모 문제 - `git-sync` VM은 처리된 모든 파일을 GitHub 저장소에 미러링했다. - 저장소의 packed storage가 1.1TB를 넘으면서 GitHub의 tarball·zip 아카이브 생성이 불가능해졌다. - 클론 속도가 느려지고 포크도 사실상 어려워졌다. - 비정상적이거나 처리하기 곤란한 릴리스를 차단하기 위한 `.gitignore` 항목이 274개까지 늘어났다. - GitHub 저장소는 원본 저장소이면서 동시에 배포 미러 역할까지 맡아 장기적인 확장에 부적합해졌다. ### 보안과 운영 부담 - GCP Functions, `git-sync` VM, 컨테이너 이미지, GCS 버킷, 서비스 계정 키 등 관리해야 할 보안 대상이 많았다. - 각 구성 요소마다 패치, 접근 권한 관리, 감사가 필요했다. - 기존 파이프라인을 폐기하면서 최근 발견된 cdnjs 관련 취약점도 함께 줄일 수 있었다. - 시스템 구성 요소가 줄어들어 장애 대응과 보안 관리가 단순해졌다. ### Cloudflare Developer Platform으로의 재구축 - 새 아키텍처는 Workers, Workflows, D1, Queues, Workers Cache, R2, KV, Containers를 사용해 Cloudflare 안에서 전체 파이프라인을 운영한다. - R2가 파일 콘텐츠의 단일 원본(single source of truth)이 됐다. - 실질적인 크기 제한이 없어 기존 KV에 저장하기 어려웠던 소스 맵, 대형 번들, 폰트 패키지도 함께 저장할 수 있다. - R2의 S3 호환 API를 통해 cdnjs 전체 카탈로그를 일반적인 S3 클라이언트로 접근하고 미러링할 수 있다. - 저장소와 처리 시스템을 하나의 플랫폼으로 통합함으로써 배포 상태 추적, 재처리, 장애 분석을 개선할 기반을 마련했다. ### 실용적인 시사점 대규모 서비스에서는 높은 성능이나 캐시 적중률만으로 충분하지 않다. 여러 클라우드와 저장소에 걸친 파이프라인은 부분 성공, 데이터 불일치, 로그 단절 문제를 만들 수 있으므로, 명확한 단일 원본과 공통 추적 ID, 재처리 가능한 작업 큐를 설계하는 것이 중요하다. 또한 플랫폼이 장시간 작업과 대용량 데이터를 지원할 만큼 성숙해졌다면, 분산된 운영 스택을 통합하는 것이 기능 추가와 보안 관리에 큰 이점이 된다.

cloudflare

Cloudflare Workflows를 위한 사가 롤백 구축 방법 (새 탭에서 열림)

Cloudflare Workflows에 Saga 패턴 기반의 롤백 기능이 추가되어, 각 `step.do()`에 보상 작업을 함께 선언할 수 있게 되었습니다. 여러 외부 시스템을 거치는 워크플로에서 중간 단계가 실패해도 이전 작업을 역순으로 되돌릴 수 있으며, 롤백 자체도 내구성 있는 단계로 실행됩니다. 이를 통해 개발자가 별도의 `try-catch`, 실행 이력 추적, 수동 롤백 순서 관리를 구현할 필요가 줄어듭니다. ## 분산 작업에서 롤백이 필요한 이유 - Workflow는 여러 단계에 걸쳐 외부 시스템을 호출하고, 각 단계의 상태를 저장하며 실패 시 재시도합니다. - 그러나 이미 완료된 외부 작업은 단순히 “취소”할 수 없습니다. - 예: Bank A에서 출금이 성공한 뒤 Bank B 입금이 실패하면, Bank A의 출금을 삭제하는 대신 다시 입금해야 합니다. - 원래 작업과 이를 의미적으로 되돌리는 보상 작업의 조합을 Saga 패턴이라고 합니다. - 기존에는 개발자가 성공한 단계를 추적하고, 실패 시 어떤 작업을 어떤 순서로 취소할지 직접 관리해야 했습니다. ## `step.do()`에 보상 로직 선언 - 이제 `step.do()`의 마지막 인자로 `rollback` 함수를 전달할 수 있습니다. ```ts await step.do( "debit-bank-a", () => bankA.debit(from, amount), { rollback: async ({ output }) => bankA.credit(from, amount, output.id), } ); ``` - 각 정방향 작업과 롤백 작업이 같은 위치에 정의됩니다. - 새로운 단계를 추가할 때 해당 단계의 보상 로직도 함께 추가할 수 있습니다. - 별도의 대형 `catch` 블록이나 성공 단계 추적 변수, 수동 실행 순서 관리가 필요하지 않습니다. - 롤백 함수는 정방향 작업의 결과인 `output`을 받아 보상 작업에 활용할 수 있습니다. ## 롤백 실행 순서와 실패한 단계 처리 - 어떤 단계에서 오류가 발생하면, 롤백 핸들러는 단계가 시작된 순서의 역순으로 실행됩니다. - 출금 → 입금 → 알림 순서라면, 롤백은 입금 취소 → 출금 환불 순서입니다. - 오류가 발생한 단계 자체도 롤백 대상이 될 수 있습니다. - 외부 시스템에는 작업이 반영됐지만, 결과를 Workflow에 반환하기 전에 단계가 실패할 수 있기 때문입니다. - 예를 들어 결제 제공자가 금액을 승인한 뒤 `chargeId`를 반환하기 전에 오류가 발생할 수 있습니다. - 따라서 롤백 함수는 `output === undefined`인 경우도 안전하게 처리해야 합니다. - 사용자가 오류를 잡고 Workflow를 정상적으로 계속 진행하면 롤백은 시작되지 않습니다. - 다만 오류를 잡은 뒤 Workflow가 나중에 다른 이유로 실패하면, 그때까지 등록된 롤백 핸들러가 역순으로 실행될 수 있습니다. ## 롤백도 내구성 있는 작업으로 실행 - 롤백은 단순한 메모리상의 정리 코드가 아니라 Workflow의 내구성 모델에 따라 실행됩니다. - 재시작이나 일시적인 장애가 발생해도 롤백 작업을 추적하고 재시도할 수 있습니다. - 롤백 과정에서 하나의 보상 작업이 실패하더라도 이후 롤백을 계속 진행할 수 있도록 설계해야 합니다. - 롤백 실패는 운영자가 대응할 수 있도록 알림이나 별도 모니터링을 연결하는 것이 필요합니다. ## 멱등성 보장의 중요성 - 일반 Workflow 단계와 마찬가지로 롤백 함수도 멱등적이어야 합니다. - 같은 롤백이 여러 번 실행되어도 결과가 중복 적용되면 안 됩니다. - 권장 방식: - 결제 환불에는 결제 제공자의 멱등성 키 사용 - 재고 해제는 여러 번 호출해도 한 번만 해제되도록 구현 - 출금·입금과 롤백 각각에 고유한 멱등성 키 부여 - 예시에서는 다음과 같이 작업별 키를 사용합니다. ```ts `${transferId}:debit-account-a` `${transferId}:rollback-debit-account-a` ``` - 이를 통해 Workflow 재시도나 롤백 재실행으로 인해 동일한 이체가 중복 처리되는 것을 방지합니다. ## 실용적인 적용 권장사항 - 외부 시스템을 변경하는 모든 단계에 가능한 한 명시적인 롤백 함수를 함께 정의하세요. - 롤백 함수는 `output`이 없거나 일부 작업만 반영된 상황도 처리해야 합니다. - 정방향 작업과 롤백 모두에 안정적인 멱등성 키를 사용하세요. - 롤백 실패는 조용히 무시하지 말고 알림, 재처리 큐, 운영 대시보드 등으로 추적하세요. - Saga 롤백은 트랜잭션을 원자적으로 만드는 기능이 아니라, 실패 후 상태를 보정하는 보상 처리机制이므로 외부 API의 보상 연산을 신중히 설계해야 합니다.