Skip to main content

Command Palette

Search for a command to run...

[Cloudflared] Cloud Tunnel 도입기

Cloudflare Tunnel 502 에러 해결기

Published
3 min read

1. 시작은 단순한 요구였다

외부에서 접근만 되면 되잖아?

프로젝트를 진행하면서 외부에서 접근 가능한 API 엔드포인트가 필요했다. 하지만 서버 환경은 꽤 제한적이었다.

  • 내부 네트워크 또는 로컬 환경

  • 인바운드 포트 개방 불가

  • 공인 IP 사용 지양

  • VPN은 과한 해결책

이 조건들을 모두 만족시키면서 서버를 외부에 노출할 방법이 필요했고, 이때 선택한 것이 Cloudflare Tunnel이었다.

포트 포워딩 없이도 로컬 서버를 외부로 노출할 수 있다는 점이 좋아 보였다.
하지만 이 편리함이 첫 번째 오해였다..


2. 처음엔 Cloud Tunnel을 너무 쉽게 봤다

Cloud Tunnel을 처음 접했을 때의 인식은 이랬다.

로컬 서버를 외부에 잠깐 열어주는 도구겠지

그래서 구조에 대한 깊은 이해 없이 바로 설정부터 시작했다. 처음 Cloud Tunnel을 도입했을 때의 전제는 단순했다.

  • 하나의 인스턴스에 올라와 있는 dev, prod 서버

  • 둘 다 외부에서 접근 가능해야 함

  • Cloud Tunnel은 어차피 연결만 해주는 역할

그래서 자연스럽게 “인스턴스는 하나니깐 Tunnel도 하나 만들어서 dev 도메인, prod 도메인 둘 다 연결하면 되는 거 아닌가?”

이 판단은 틀리지는 않았지만, 불완전했다… 🥹


3. 최초 구조: 하나의 Tunnel + 두 개의 Hostman

초기 구성은 대략 이런 형태였다.

tunnel: finders-tunnel
ingress:
    - hostname: dev-api.finders.it.kr
      service: http://localhost:8081
    - hostname: api.finders.it.kr
      service: http://localhost:8080

DNS 역시 다음처럼 설정했다.

  • dev-api.finders.it.kr → Cloud Tunnel

  • api.finders.it.kr → Cloud Tunnel

이론적으로는 완벽해 보였다.

  • hostname으로 환경 구분

  • 포트로 서비스 구분

  • Tunnel은 공통

hostname으로 환경을 구분하고 포트로 서비스를 나누니, 이론적으로는 완벽해 보였다.
실제로 dev 환경 배포 직후에는 모든 것이 잘 동작했다!! 새로운 기술을 적용했기에 기뻐했는데,,

진짜 문제는 prod 서버를 올리면서 시작되었다..


4. 첫 번째 이상 징후: 200 → 502 → CORS 에러?

main 브랜치를 배포하고 나서 갑자기 API 응답이 요동치기 시작했다. 어떨 때는 200 OK가 뜨다가, 갑자기 502 Bad Gateway와 함께 CORS 에러가 터져 나왔다!!!!!

CORS 설정은 완벽했기에 대체 왜 이런 일이 발생했는지 의문이었다.
대체 뭐가 문제인가 싶어서 확인했더니 Cloud Tunnel 문제였다.👿

내가 했던 착각

hostname이 다르면 Cloudflare가 알아서 완벽하게 분리해 주겠지?

하지만 정말 착각이었따..

터널의 기준은 hostname이 아니라 Tunnel Demon(Cloudflared) 그 자체였다. 하나의 터널 프로세스가 여러 호스트네임의 트래픽을 동시에 처리하다 보니, Connection Pool의 혼선이나 라우팅 우선순위 문제가 발생했던 것이다.
Cloudflare Edge 서버 입장에서는 같은 터널로 들어오는 요청이 꼬이면서 응답을 제대로 받지 못해 502를 던졌고, 이 과정에서 필수 헤더가 누락되어 브라우저가 이를 CORS 에러로 오인한 것이었다.


5. 해결책 1: 터널 분리

hostname 기준으로 다 나뉠 줄 알았다.. (그렇게 해도 된다며..!!!)

결국 터널을 물리적으로 두 개로 분리하기로 했다. prod용 터널과 dev용 터널을 각각 생성하여 각자의 설정 파일과 프로세스를 가지게 했다.

이렇게 하면 장애 전파 범위를 완벽히 격리할 수 있었다. dev 터널에 과부하가 걸리거나 설정 오류가 나더라도 prod 터널은 물리적으로 분리된 통로를 사용하므로 영향을 받지 않게 되는 것이다!

→ 이것이 바로 격리(Isolation)이었다.


6. 두 번째 문제: 사라지지 않는 유령(Zombie) 설정

dev를 고쳐놨더니 prod가 말썽이네?

dev가 너무 정상적으로 실행되었고, 에러가 하나도 나지 않았기 때문에 prod도 문제 없을 거라 생각했다.

하지만 prod 환경이 여전히 불안정했다. 설정은 완벽한데 왜일까?

Tunnel을 분리함으로써 각각의 역할을 명시해주었고, 그에 따라 동일한 설정을 가지고 있음에도 dev는 멀쩡했는데 prod만 문제가 발생하니 어떻게 해야할지가 막막했다..

답은 라우팅 전파(Propagation)의 지연에 있었다..

찾아보니 기존 터널 정보를 지우고 새 터널을 연결해도, 퍼져 있는 Cloudflare의 Edge 노드들에는 이전의 라우팅 캐시나 ‘좀비 커넥션’이 남아있을 수도 있다고 했다.

그래서 바로 완전히 새로 터널을 생성하여서 ID 자체를 갱신하였다.

두근.. 두근..!!!

결론적으로는 그 이후로는 아주 안정화되었다!!! 아마도 이전 설정 기록들이 어딘가에 남아있었고, 그게 계속 문제를 일으키고 있었던 것 같다.


7. 마치며

이제는 왜 두 개의 Tunnel을 쓰는지 설명할 수 있다

이번 경험을 통해 단순한 네트워크 도구라도 운영 환경에서는 책임과 경계를 명확히 해야 함을 배웠다.

  1. 터널은 단순한 통로가 아니다: 터널은 하나의 리소스 단위이며 장애 전파의 단위였다.

  2. 논리적 분리보다 물리적 분리: hostname(논리적) 구분보다 Tunnel Process(물리적) 분리가 훨씬 안전하다.

  3. 인프라 설정은 때로 재생성이 답이다: 때로는 수정보다 ‘재생성’이 더 빠르고 확실한 해결책이 될 수 있다.

처음엔 Tunnel 하나로 dev / prod를 동시에 운영하려 했다. 그리고 실제로 한동안은 잘 되는 것처럼 보였다. 하지만 dev와 prod의 분리는 필요하였고, 이는 편의가 아니라 안정성과 책임의 문제였다.

바이요

More from this blog

[Elasticsearch] Elasticsearch 기본용어와 CRUD 명령어

-elastic ▶ 들어가며 이번 글에서는 Elasticsearch를 공부하면서 가장 먼저 익혀야 하는 기본 용어를 정리하고,직접 코드를 쳐가며 CRUD(Create / Read / Update / Delete) 명령어에 익숙해지는 시간을 가져보려고 한다. Elasticsearch는 처음 보면 생소한 용어가 많아서 막막할 수 있는데,사실 구조적으로는 우리가 익숙한 MySQL과 닮은 부분이 굉장히 많다. 둘 다 데이터베이스라는 큰 틀 안에서 데...

Feb 17, 20264 min read

Jpa N+1 문제, 우리는 이렇게 잡았다 — 1:1 문의 Api 실전 최적화기

코드 리뷰 한 줄에서 시작된 쿼리 최적화 여정 1. 시작 — "일단 돌아가게 만들자" Finders 프로젝트에서 1:1 문의(Inquiry) API를 맡았다. 현상소에 문의를 남기고, 답변을 받고, 목록을 조회하는 — 평범한 CRUD다. "JPA 쓰면 쿼리 안 짜도 되는 거 아니야?" 솔직히 처음엔 그렇게 생각했다. JpaRepository에 findAll, findById 쓰면 끝이니까. // 첫 번째 버전의 목록 조회 (QueryDS...

Feb 11, 20268 min read

[모니터링] Sentry 도입부터 Discord 에러 알림까지 — 서버 감시 시스템 구축기

"서버 죽었는데 아무도 몰랐다"에서 "에러나면 1분 안에 안다"까지 1. 모니터링을 시작한 계기 프론트엔드: "API 안 되는데요?"백엔드: "엥? 언제부터요?"프론트엔드: "...2시간 전부터요?"백엔드: 😱 어느 날 서버가 죽어있었는데 아무도 몰랐다. 그날 이후, 모니터링 시스템 구축을 결심했다. (Issue #102) 2. 모니터링 도구 비교: 뭘 쓸까? 처음에는 여러 도구를 비교했다. 도구무료 티어장점단점 Sent...

Feb 11, 20268 min read

[CI/CD] 수동 태그에서 자동 릴리즈까지 — Git Flow와 Auto Release

🚀 우리 auto-release.yml 바로 보러가기 → 이 글에서 설명하는 워크플로우의 전체 코드를 바로 확인할 수 있다! main에 머지만 하면 버전 태그부터 릴리즈 노트까지 알아서 생긴다 1. 우리의 Git 전략: Git Flow (경량 버전) Finders 프로젝트는 Git Flow 전략을 사용하고 있다. 다만 hotfix나 release 브랜치 없이, 조금 가볍게 운영한다. main ← 운영 서버 (prod) 배포 브...

Feb 11, 20265 min read

[ci/cd] 대학생 팀의 배포 파이프라인 진화기

PR 하나면 끝나는 무중단 배포까지, 삽질의 기록 1. 시작은 단순했다 "서버에 올려야 하는데... 어떻게 하지?" Finders 프로젝트를 시작했을 때, 배포라는 걸 해본 적이 없었다. 그래서 처음에는 이렇게 했다. 1. SSH로 서버 접속 2. git pull 3. ./gradlew build 4. java -jar app.jar 당연히 문제가 생겼다. 빌드하는 동안 서버가 꺼져있음 (프론트: "API 왜 안 돼요?" 🔥) 빌...

Feb 11, 20267 min read
F

Finders Tech Blog

16 posts