서론
새벽 3시, PagerDuty 알림이 울린다. 프로덕션 환경의 Cloudflare 설정이 누락되어 장애가 발생했다. 원인을 추적해보니, 누군가 대시보드에서 수동으로 WAF 규칙을 수정하면서 기존 설정을 덮어쓴 것이다. 이런 경험, 한 번쯤 있지 않나요?
Cloudflare는 단순한 CDN을 넘어 DDoS 방어, WAF, DNS, Workers, Pages, R2, D1, KV 등 100개가 넘는 제품을 제공하는 거대한 에코시스템이 되었습니다. 문제는 이 많은 서비스를 각각의 대시보드와 API로 관리해야 한다는 것입니다. 팀이 성장할수록 설정의 불일치, 드리프트(Drift), 재현 불가능한 인프라 상태가 골칫거리가 됩니다.
Cloudflare가 발표한 통합 CLI 프로젝트는 바로 이 문제를 해결합니다. 기존 Workers 배포 도구인 Wrangler를 확장하여, 모든 Cloudflare 제품과 약 3,000개의 API 작업을 단일 CLI와 코드 기반(IaC)으로 관리할 수 있게 됩니다. 이 글에서는 이것이 왜 게임체인저인지, 그리고 실무에 어떻게 적용할 수 있는지 살펴보겠습니다.
Cloudflare IaC 관리의 현재와 한계
기존 도구들의 파편화 문제
현재 Cloudflare 리소스를 코드로 관리하려면 여러 도구를 조합해야 합니다.
| 관리 도구 | 관리 대상 | IaC 지원 | 한계점 | | :— | :— | :— | :— | | Wrangler | Workers, Pages, R2, KV | 부분적 | 제한된 리소스만 관리 | | Terraform Provider | DNS, WAF, LB 등 전반 | 전체 | HCL 학습 필요, 느린 실행 | | API 직접 호출 | 모든 리소스 | 커스텀 | 스크립트 유지보수 부담 | | Dashboard | 모든 리소스 | 없음 | 수동 조작, 감사 추적 어려움 |
각 도구는 자신만의 설정 파일 포맷, 인증 방식, 워크플로우를 요구합니다. DNS는 Terraform으로, Workers는 Wrangler로, 보안 정책은 API 스크립트로 관리하는 식이면, 인프라 전체 상태를 파악하기 어렵습니다.
Wrangler의 진화: Workers CLI에서 통합 IaC 도구로
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| graph LR
A[Wrangler v1] --> B[Workers 전용 배포]
C[Wrangler v2] --> D[Workers + Pages + KV]
E[Wrangler v3] --> F[R2, D1, Queues 추가]
G[Unified CLI] --> H[100개 제품 통합 관리]
B --> D
D --> F
F --> H
H --> I[DNS]
H --> J[WAF]
H --> K[Load Balancer]
H --> L[Access]
H --> M[모든 Cloudflare 서비스]
|
통합 CLI의 아키텍처와 작동 원리
API-first 설계 철학
Cloudflare의 모든 제품은 이미 REST API로 노출되어 있습니다. 통합 CLI는 이 기존 API 생태계를 활용하며, 각 제품의 OpenAPI 스펙을 자동으로 참조합니다.
1
2
3
| API 엔드포인트 구조:
https://api.cloudflare.com/client/v4/zones/{zone_id}/{product}/{resource}
https://api.cloudflare.com/client/v4/accounts/{account_id}/{product}/{resource}
|
약 3,000개의 API 작업이 존재한다는 것은, CLI가 이 모든 엔드포인트에 대한 CRUD(Create, Read, Update, Delete) 작업을 지원한다는 의미입니다. 이를 Declarative(선언적) 방식으로 관리할 수 있게 됩니다.
선언적 설정 파일 구조
Wrangler가 확장되면, 단일 설정 파일로 여러 Cloudflare 서비스를 정의할 수 있습니다. 예상되는 설정 구조를 살펴보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| # wrangler.toml - 통합 설정 파일 (예상 구조)
name = "my-project"
main = "src/worker.js"
compatibility_date = "2024-01-01"
# Workers 설정
[workers]
route = "api.example.com/*"
env.production = { name = "my-project-prod" }
# DNS 레코드 관리
[[dns.records]]
type = "A"
name = "api.example.com"
content = "203.0.113.1"
ttl = 3600
proxied = true
[[dns.records]]
type = "CNAME"
name = "www.example.com"
content = "example.com"
proxied = true
# WAF 규칙
[[waf.rules]]
action = "block"
expression = "(ip.geoip.country eq "XX" and http.request.uri.path contains "/admin")"
description = "Block admin access from specific countries"
[[waf.rules]]
action = "challenge"
expression = "(http.request.method eq "POST" and cf.bot_management.score < 30)"
description = "Challenge suspected bot POST requests"
# R2 스토리지
[[r2.buckets]]
name = "my-assets"
location_hint = "APAC"
# KV 네임스페이스
[[kv.namespaces]]
binding = "CACHE"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# D1 데이터베이스
[[d1.databases]]
binding = "DB"
database_name = "production-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
Imperative vs Declarative 워크플로우
통합 CLI는 두 가지 모드를 모두 지원할 것으로 예상됩니다.
1
2
3
4
5
6
7
8
9
| # Imperative (명령형) - 즉시 실행
wrangler dns create --zone example.com --type A --name api --content 203.0.113.1
wrangler waf rule create --action block --expression "(ip.geoip.country eq "XX")"
wrangler r2 bucket create my-assets
# Declarative (선언형) - 상태 동기화
wrangler apply # wrangler.toml 기반으로 모든 리소스 동기화
wrangler plan # 변경 사항 미리 보기 (Terraform plan과 유사)
wrangler diff # 현재 상태와 원하는 상태 비교
|
실무 적용: Step-by-step 가이드
Step 1: Wrangler 최신 버전 설치 및 인증
1
2
3
4
5
6
7
8
9
10
11
| # Wrangler 최신 버전 설치
npm install -g wrangler@latest
# 버전 확인 (v3 이상 필요)
wrangler --version
# Cloudflare 계정 인증
wrangler login
# OAuth 브라우저 인증 후 계정 정보 확인
wrangler whoami
|
인증이 완료되면 ~/.wrangler/config/default.toml에 OAuth 토큰이 저장됩니다. CI/CD 환경에서는 API 토큰을 사용합니다.
Step 2: API 토큰 기반 CI/CD 인증 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| # GitHub Actions 워크플로우
name: Deploy Cloudflare Infrastructure
on:
push:
branches: [main]
paths:
- 'cloudflare/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Wrangler
run: npm install -g wrangler@latest
- name: Validate Configuration
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
wrangler deploy --dry-run
- name: Deploy Infrastructure
if: github.ref == 'refs/heads/main'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
wrangler deploy
|
Step 3: 환경별 설정 분리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| # wrangler.toml - 환경별 구성
name = "my-project"
main = "src/worker.js"
# 공통 DNS 설정
[[dns.records]]
type = "A"
name = "example.com"
content = "203.0.113.1"
proxied = true
# 개발 환경
[env.dev]
name = "my-project-dev"
[env.dev.d1_databases]
binding = "DB"
database_name = "dev-db"
preview_database_id = "dev-preview-db-id"
[[env.dev.dns.records]]
type = "CNAME"
name = "dev.example.com"
content = "dev-worker.example.workers.dev"
proxied = false
# 스테이징 환경
[env.staging]
name = "my-project-staging"
[env.staging.d1_databases]
binding = "DB"
database_name = "staging-db"
[[env.staging.waf.rules]]
action = "log"
expression = "(http.request.uri.path contains "/api/")"
description = "Log all API requests in staging"
# 프로덕션 환경
[env.production]
name = "my-project-production"
routes = ["api.example.com/*"]
[env.production.d1_databases]
binding = "DB"
database_name = "production-db"
[[env.production.waf.rules]]
action = "block"
expression = "(cf.threat_score > 10)"
description = "Block high threat score requests"
[[env.production.waf.rules]]
action = "managed_challenge"
expression = "(http.request.method eq "POST" and not cf.bot_management.verified_bot)"
description = "Challenge non-verified bot POST requests"
|
1
2
3
4
5
6
7
8
9
| # 환경별 배포
wrangler deploy --env dev
wrangler deploy --env staging
wrangler deploy --env production
# 특정 리소스 상태 확인
wrangler d1 list --env production
wrangler r2 bucket list
wrangler kv namespace list
|
Step 4: 상태 관리 및 Drift 감지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 현재 인프라 상태를 코드와 비교
wrangler diff --env production
# 출력 예시:
# ~ dns.record "api.example.com"
# content: "203.0.113.1" => "203.0.113.2" (changed externally)
#
# ~ waf.rule "block-suspicious-bots"
# action: "block" => "challenge" (manual dashboard change detected)
# 상태를 코드에 맞게 강제 동기화
wrangler apply --env production --auto-approve
# 현재 상태를 코드로 내보내기 (기존 인프라 마이그레이션)
wrangler import --env production --output ./imported-config.toml
|
Step 5: GitOps 워크플로우 구성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # .github/workflows/cloudflare-gitops.yml
name: Cloudflare GitOps Sync
on:
schedule:
- cron: '0 */6 * * *' # 6시간마다 드리프트 체크
workflow_dispatch:
jobs:
drift-detection:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for Configuration Drift
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
wrangler diff --env production > drift-report.txt 2>&1
if grep -q "changed externally\|manual dashboard change" drift-report.txt; then
echo "::warning::Configuration drift detected!"
cat drift-report.txt
# Slack 알림
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-H 'Content-type: application/json' \
-d "{"text":"⚠️ Cloudflare config drift detected in production. Check drift-report.txt"}"
exit 1
fi
echo "No drift detected. Infrastructure is in sync."
|
통합 Wrangler CLI가 등장하면서 Terraform Cloudflare Provider와의 선택이 고민될 수 있습니다. 두 도구의 포지션을 비교해봅시다.
| 비교 항목 | Terraform + Cloudflare Provider | Wrangler 통합 CLI | | :— | :— | :— | | 학습 곡선 | 중간 (HCL 문법) | 낮음 (JavaScript/TypeScript 친화적) | | 실행 속도 | 느림 (Provider 다운로드, 상태 잠금) | 빠름 (경량 CLI) | | 상태 관리 | 외부 State 파일 (S3, Terraform Cloud) | 로컬 또는 Cloudflare 관리 | | 에코시스템 연동 | AWS, GCP 등 멀티클라우드 | Cloudflare 전용 | | CI/CD 통합 | GitHub Actions, GitLab CI 지원 | 네이티브 Wrangler Actions | | 적합 시나리오 | 멀티클라우드, 기존 Terraform 코드베이스 | Cloudflare 중심, Edge-first 아키텍처 |
추천 전략: Cloudflare를 주 인프라로 사용하는 스타트업이나 Edge-first 프로젝트는 Wrangler를, 멀티클라우드 환경에서 Cloudflare를 일부 사용하는 기업은 Terraform을 유지하는 것이 합리적입니다. 두 도구는 경쟁보다 보완 관계입니다.
모니터링 및 관측성
Wrangler로 관리되는 인프라의 상태를 Prometheus와 Grafana로 모니터링할 수 있습니다.
1
2
3
4
5
6
7
8
| # prometheus.yml - Cloudflare API 메트릭 수집
scrape_configs:
- job_name: 'cloudflare_exporter'
static_configs:
- targets: ['cloudflare-exporter:9199']
metrics_path: '/metrics'
params:
zones: ['your-zone-id']
|
1
| # Wrangler 상태 점검 스크립트 (cron
|
출처: https://news.hada.io/topic?id=28522