무색
기술블로그
에세이
연구
소개

무색

소프트웨어로 비즈니스의 가능성을 만듭니다. 웹·앱 개발, 음성 AI, 자동화 콘텐츠 제작까지 — 기술이 필요한 곳에 무색이 있습니다.

연락처

contact@museck.com

사업자 정보

상호: 무색

대표: 배성재

사업자등록번호: 577-58-00836

인천광역시 연수구 인천타워대로 323, 에이동 8층 801-802호 AB-132 (송도동, 송도 센트로드)

© 2026 무색. All rights reserved.
개인정보처리방침·이용약관·연락처
INCHEON, KR
와일드카드 서브도메인 멀티사이트 — generative 키 비주얼
홈랩 삽질기
2026. 2. 3.

와일드카드 서브도메인으로 멀티사이트 블로그 운영하기: Cloudflare Tunnel + Traefik v3

Cloudflare TunnelTraefikKubernetesKustomizeArgoCD홈랩멀티사이트

홈랩에서 블로그를 하나 더 띄워야 했다. 애드센스용 멀티사이트인데, 서브도메인마다 다른 콘텐츠를 서빙해야 한다. *.museck.com 와일드카드로 받아서 하나의 Next.js + PayloadCMS 인스턴스가 테넌트별 콘텐츠를 나눠주는 구조.

문제는 기존에 museck.com, translate.museck.com, asr.museck.com 같은 서비스들이 이미 돌아가고 있다는 거다. 와일드카드를 켜면서 기존 서비스를 안 건드리려면 라우팅 우선순위를 깔끔하게 설계해야 한다.

전체 아키텍처: 브라우저에서 Pod까지

트래픽 흐름은 이렇다:

  1. 브라우저가 xyz.museck.com에 접속
  2. Cloudflare Edge에서 DNS 해석 → Cloudflare Tunnel로 전달
  3. Tunnel의 라우트 규칙에 따라 구체적 호스트(museck.com, translate.museck.com)는 해당 서비스로, 나머지 *.museck.com은 Traefik으로 라우팅
  4. Traefik이 IngressRoute의 HostRegexp 매칭으로 adsense-blog Pod에 전달

핵심은 두 단계의 우선순위 분기다. Cloudflare Tunnel에서 한 번, Traefik IngressRoute에서 한 번. 이 두 레이어를 잘 맞춰야 기존 서비스와 새 멀티사이트가 공존한다.

Cloudflare Tunnel 라우트 우선순위

Cloudflare Tunnel은 라우트를 위에서부터 순서대로 매칭한다. 그래서 구체적인 호스트를 먼저, 와일드카드를 마지막에 배치하면 된다.

# Tunnel 라우트 (위에서부터 매칭)
museck.com             → museck-app Pod (기존 메인 사이트)
translate.museck.com   → Traefik → n8n (번역 서비스)
asr.museck.com         → Traefik → n8n (음성인식 서비스)
*.museck.com           → Traefik → adsense-blog (새 멀티사이트)

이 순서가 중요하다. museck.com이 와일드카드보다 먼저 나와야 메인 사이트가 멀티사이트 앱으로 빨려 들어가지 않는다. Cloudflare Tunnel 대시보드에서 드래그로 순서를 조정할 수 있지만, 실제로는 API로 config를 관리하는 게 안전하다.

Traefik v3 HostRegexp 삽질기

Cloudflare Tunnel을 넘어온 *.museck.com 트래픽을 Traefik이 받아서 adsense-blog Pod에 보내야 한다. IngressRoute에 HostRegexp 매칭을 설정하면 되는데, 여기서 삽질이 시작됐다.

1차 시도: Traefik v2 문법 그대로 복붙

match: HostRegexp(`{subdomain:.+}.museck.com`)

실패. Traefik v3에서 v2의 {name:regex} 문법은 더 이상 지원하지 않는다. v3는 순수 Go 정규식을 사용한다. 구글에 나오는 대부분의 예제가 v2 기준이라 이걸로 먼저 시도하게 되는데, 에러 메시지도 직관적이지 않아서 원인 파악에 시간이 걸렸다.

2차 시도: Go 정규식으로 바꿨지만 앵커 누락

match: HostRegexp(`.+\.museck\.com`)

부분 매칭 이슈가 발생했다. ^/$ 앵커가 없으면 something.museck.com.evil.com 같은 호스트도 매칭될 수 있다. 실제 환경에서는 Cloudflare가 앞단에서 걸러주니까 보안 이슈까지는 아니지만, 의도하지 않은 매칭은 디버깅을 어렵게 만든다.

3차 시도: 앵커는 넣었는데 더블 이스케이프

match: HostRegexp(`^.+\\.museck\\.com$`)

이것도 실패. YAML 파서와 정규식 이스케이프의 이중 처리 문제다. 백틱(`)으로 감싸면 YAML 파서가 이스케이프를 처리하지 않는다. 그래서 \\.이 정규식 엔진에 \\.으로 그대로 전달돼서 매칭이 안 됐다.

최종: 싱글 이스케이프 + 앵커

match: HostRegexp(`^.+\.museck\.com$`)

이게 정답이다. 정리하면:

  • Traefik v3는 순수 Go 정규식 ({name:regex} 문법 폐기)
  • ^/$ 앵커 필수 — 부분 매칭 방지
  • 백틱 안에서는 YAML 이스케이프가 적용되지 않으므로 싱글 \.만 쓸 것
  • 더블 이스케이프(\\.)는 백틱 없이 따옴표로 감쌀 때만 필요

IngressRoute Priority 설계

Traefik IngressRoute의 기본 priority는 match rule 문자열 길이에 비례한다. 긴 규칙일수록 높은 우선순위. 와일드카드 규칙은 짧으니까 자연스럽게 낮은 우선순위를 갖는다.

그래도 명시적으로 priority: 1을 설정해서 fallback으로 동작하게 만들었다. 나중에 다른 서브도메인 서비스가 추가돼도 충돌할 일이 없다.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: adsense-blog
  namespace: adsense-blog
spec:
  entryPoints:
    - web
  routes:
    - match: HostRegexp(`^.+\.museck\.com$`)
      kind: Rule
      priority: 1
      services:
        - name: adsense-blog-app
          port: 3000

기존 translate.museck.com이나 asr.museck.com은 Cloudflare Tunnel 레벨에서 이미 분기되니까 Traefik까지 올 일이 없다. 하지만 K8s 내부에서 다른 IngressRoute가 추가될 수도 있으니 방어적으로 priority를 최저값으로 설정해두는 게 좋다.

Kustomize base/overlay 구조

K8s 매니페스트는 Kustomize base/overlay 패턴으로 구성했다. base에 공통 리소스를 두고, overlay에서 환경별 차이만 덮어쓴다.

k8s-company-public/adsense-blog/
├── base/
│   ├── kustomization.yaml
│   ├── namespace.yaml
│   ├── mongodb/
│   │   ├── deployment.yaml   # mongo:7, nodeSelector: infra
│   │   ├── pvc.yaml          # 10Gi local-path
│   │   └── service.yaml
│   └── app/
│       ├── deployment.yaml   # Next.js + PayloadCMS
│       ├── pvc.yaml          # 20Gi media 스토리지
│       ├── service.yaml
│       └── ingressroute.yaml # HostRegexp 와일드카드
└── overlays/production/
    ├── kustomization.yaml    # 이미지 태그 오버라이드
    └── configmap.yaml        # ADSENSE_CLIENT_ID 등

MongoDB 7과 Next.js 앱을 같은 네임스페이스에 배포한다. MongoDB는 nodeSelector: infra로 인프라 노드에 고정하고, PVC는 local-path 스토리지 클래스로 할당했다. 앱 쪽에는 startupProbe를 걸어서 Next.js 빌드 완료 후에만 트래픽을 받도록 했다.

ArgoCD로 GitOps 배포

ArgoCD Application을 등록해서 Git push만 하면 자동 배포되게 만들었다. overlay/production의 이미지 태그를 바꾸고 push하면 ArgoCD가 감지해서 rolling update를 수행한다.

기존 museck.com 배포 파이프라인과 동일한 패턴이라 특별히 새로운 건 없다. CI에서 Docker 이미지를 빌드하고 레지스트리에 push, overlay의 태그를 업데이트, ArgoCD가 sync하는 흐름.

삽질에서 배운 것들

이번 작업에서 가장 시간을 잡아먹은 건 Traefik v3의 HostRegexp 문법이었다. 3번의 시도 끝에 성공했는데, 돌이켜보면 공식 문서를 먼저 읽었으면 1번에 끝났을 일이다. 검색 결과의 v2 예제에 속은 거다.

그 외에 기억해둘 것들:

  • Cloudflare Tunnel 라우트는 순서가 곧 우선순위다. 구체적 호스트를 위에, 와일드카드를 아래에.
  • YAML 이스케이프와 정규식 이스케이프를 혼동하지 말 것. 백틱 안에서는 YAML 이스케이프가 적용 안 된다.
  • IngressRoute priority는 명시적으로 설정하는 게 안전하다. 기본값에 의존하면 나중에 규칙이 늘어날 때 충돌한다.
  • 홈랩에서 공인 IP 없이도 Cloudflare Tunnel로 와일드카드 서브도메인 운영이 가능하다. 비용도 무료 플랜 범위 내.

마무리

한 대의 홈랩 서버에서 와일드카드 서브도메인으로 멀티사이트를 운영하는 구조를 완성했다. Cloudflare Tunnel의 라우트 우선순위, Traefik v3의 HostRegexp, Kustomize overlay 패턴, ArgoCD GitOps까지. 각각은 단순한데 조합하면 고려할 게 많아진다.

특히 기존 서비스와 공존시키는 부분이 핵심이었다. 새 서비스를 추가할 때 기존 것을 안 건드리는 게 인프라 설계의 기본인데, 와일드카드가 끼면 우선순위 설계를 확실히 해둬야 한다. 이 글이 비슷한 구성을 고민하는 사람에게 도움이 되면 좋겠다.

자주 묻는 질문

Cloudflare Tunnel에서 와일드카드 서브도메인과 기존 서비스를 어떻게 공존시키나?
Tunnel 라우트를 위에서부터 순서대로 매칭하므로, 구체적인 호스트(museck.com, translate.museck.com)를 먼저 배치하고 와일드카드(*.museck.com)를 마지막에 배치한다. 구체적 호스트가 먼저 매칭되어 기존 서비스로 라우팅되고, 나머지만 와일드카드로 넘어간다.
Traefik v3에서 HostRegexp 와일드카드 매칭 시 주의할 점은?
v2의 {name:regex} 문법은 v3에서 폐기됐다. 순수 Go 정규식을 사용해야 하며, ^와 $ 앵커를 반드시 포함해야 부분 매칭을 방지할 수 있다. 백틱으로 감싸면 YAML 이스케이프가 적용되지 않으므로 싱글 이스케이프(\.)만 사용한다.
홈랩에서 공인 IP 없이 와일드카드 서브도메인 운영이 가능한가?
가능하다. Cloudflare Tunnel을 사용하면 공인 IP 없이도 Cloudflare Edge를 통해 트래픽을 받을 수 있다. DNS에서 *.museck.com을 Cloudflare에 위임하고, Tunnel에서 와일드카드 라우트를 설정하면 된다. 무료 플랜으로도 충분하다.
홈랩 삽질기(16/19)
Prev

GPU 하나를 여러 서비스가 나눠 쓸 때, ComfyUI 온디맨드 관리

Next

ArgoCD 배포 알림 고도화: oncePer 중복 방지부터 resync 튜닝까지