Cloudflare 의 Hyperdrive 설정 삽질 과정

청록비
7 read수정하기

서론: 서버리스 데이터베이스 연결의 숨겨진 장벽

현대의 웹 생태계, 특히 Node.js와 JavaScript를 기반으로 하는 엣지 컴퓨팅(Edge Computing)과 서버리스(Serverless) 아키텍처에서 데이터베이스 연결 관리는

개발자들의 가장 큰 숙제 중 하나입니다.

기존의 방식대로 매 요청마다 데이터베이스 커넥션을 맺고 끊는 것은 심각한 병목 현상과 커넥션 풀(Connection Pool) 고갈을 초래하기 때문입니다.


이러한 한계를 극복하기 위해 등장한 혁신적인 솔루션이 바로 Cloudflare Hyperdrive입니다. 전역적인 커넥션 풀링을 통해 연결 지연 시간을 비약적으로 단축시켜 주는 이 기술은,

Cloudflare Workers를 사용하는 개발자들에게 필수 불가결한 요소로 자리 잡고 있습니다.

 

하지만, 클라우드 환경이 아닌 로컬 또는 온프레미스(On-premise) 인스턴스에 안전하게 격리된 내부 데이터베이스(MySQL, MariaDB 등)를 Cloudflare Tunnel을 통해

Hyperdrive와 연동하려고 할 때, 수많은 개발자들이 예기치 못한 거대한 장벽에 부딪히게 됩니다. 공식 문서의 안내를 충실히 따랐음에도 불구하고 마주하게 되는

[code: 2011] Network connection refused, 403 Forbidden, 그리고 끈질기게 괴롭히는 301 Moved Permanently 에러가 바로 그것입니다.

 

본 포스팅에서는 필자가 실제 운영 환경에서 겪었던 뼈아픈 실패 과정과 수많은 삽질의 기록을 투명하게 공유하고자 합니다.

겉보기에는 네트워크 보안 정책 문제 같았던 이 에러들의 진짜 원인을 분석하고, 결국 모든 복잡한 우회 설정을 원점으로 되돌린 후

단 2개의 설정 변경과 '새로운 서브도메인'을 통해 완벽한 연결을 이뤄낸 '유레카'의 순간을 서론, 본론, 결론의 구조로 상세히 풀어내겠습니다.

네트워크 인프라 구성 시 프로토콜의 특성을 이해하는 것이 얼마나 중요한지 깨닫는 계기가 되기를 바랍니다.

 

본론 1: 끝없는 삽질의 기록 (실패한 접근법들)

Hyperdrive를 생성하고 Wrangler CLI를 통해 내부 데이터베이스 연결을 시도했을 때, 가장 먼저 마주한 것은 403 Forbidden 에러였습니다.

(예: Connecting to database via Cloudflare Tunnel failed: 403 Forbidden)

 

첫 번째 오판: Zero Trust Access의 개입이라 맹신하다 Cloudflare의 에러 로그를 분석한 결과, 해당 도메인에 'App AUD' 로그가 찍히는 것을 확인했습니다.

이는 Cloudflare Zero Trust Access가 해당 접근을 차단하고 있다는 명백한 증거처럼 보였습니다.

데이터베이스 통신(TCP)은 브라우저를 통한 사용자 인증을 수행할 수 없으므로, 당연히 인증 벽에 막혔다고 판단했습니다.

이를 해결하기 위해 Access > Applications 메뉴에서 해당 서브도메인(예: db.example.com)에 대한 명시적인 Bypass(우회) 정책을 추가했습니다.

IP 대역 0.0.0.0/0에 대해 모든 인증을 건너뛰도록 설정한 것입니다. 하지만 결과는 처참했습니다. 403 에러는 사라지지 않았고, 오히려 에러의 양상만 복잡해졌습니다.

 

두 번째 오판: 전역 HTTPS 강제 설정을 해제하다 Bypass 정책 적용 후 터널을 재시작하자, 이번에는 301 Moved Permanently 에러가 등장했습니다.

301은 웹 브라우저에게 "이 페이지는 영구적으로 이동되었으니 새로운 주소로 가라"고 지시하는 HTTP 상태 코드입니다.

TCP 통신을 시도하는 Hyperdrive 입장에서는 이 웹 전용 응답을 이해할 수 없어 연결을 강제로 종료해 버린 것입니다.


이 문제를 해결하기 위해 Cloudflare 대시보드에서 도메인 전체에 적용된 'Always Use HTTPS (항상 HTTPS 사용)' 옵션을 완전히 비활성화(Off)해 보았습니다.

심지어 Page Rules를 통해 해당 서브도메인의 모든 보안 검사를 끄는 치트키까지 동원했습니다. 그러나 경악스럽게도, 터널 로그에는 여전히 301 리다이렉션 에러가 찍혀 있었습니다.

설정의 늪에 빠져버린 순간이었습니다.

 

본론 2: 문제의 본질 (숨겨진 3가지 근본 원인)

며칠간의 디버깅 끝에, 이 복잡한 에러 체인의 이면에 숨겨진 3가지 근본적인 원인을 마침내 규명할 수 있었습니다. 문제는 엉뚱한 곳에 있었습니다.

 

1. localhost127.0.0.1의 치명적인 차이 (IPv6 해석 문제) Cloudflare Tunnel 설정 시 Service URL을 tcp://localhost:3306으로 지정한 것이 첫 번째 화근이었습니다.

최신 리눅스(Ubuntu 등) OS 환경에서 시스템은 localhost를 IPv4인 127.0.0.1보다 IPv6 루프백 주소인 ::1로 우선 해석(Resolve)하는 경향이 있습니다.

만약 MariaDB가 0.0.0.0(IPv4) 대역에서만 수신 대기(Listen)하도록 설정되어 있다면, 터널은 존재하지 않는 IPv6 포트를 두드리다 결국 연결을 포기하게 됩니다.

(출처: Cloudflare Tunnel 공식 트러블슈팅 가이드 및 IETF RFC 3493)

 

2. 조용한 암살자, HSTS 하위 도메인 상속 (includeSubDomains) 'Always Use HTTPS'를 껐음에도 301 에러가 발생한 진짜 주범은 바로

HSTS (HTTP Strict Transport Security) 정책이었습니다.


SSL/TLS 설정 깊숙한 곳에 있는 includeSubDomains (하위 도메인에 HSTS 정책 적용) 옵션이 켜져 있으면, 최상위 도메인의 강력한 HTTPS 강제 정책이

DB 터널 용도로 파놓은 서브도메인까지 모조리 오염시킵니다. 웹 트래픽을 보호하기 위한 기능이, 순수한 TCP 트래픽을 강제로 암호화된 웹 포트(443)로 밀어 넣으려 하면서

시스템 간의 언어 불일치를 일으킨 것입니다.

 

3. 브라우저와 엣지 노드에 남은 '301 캐시 찌꺼기' 가장 개발자를 미치게 만드는 요소입니다.

301 리다이렉션은 '영구적인 이동'을 의미하기 때문에, 브라우저와 Cloudflare의 엣지 서버(Edge Server)는 이 응답을 매우 공격적으로 캐싱합니다.

즉, HSTS 설정을 끄고 올바르게 구성을 수정했음에도 불구하고, 중간 노드들이 과거의 301 에러를 기억하고 계속해서

못된 목적지로 트래픽을 던져버리는 '301 찌꺼기 현상'이 발생했던 것입니다.

 

본론 3: 모든 것을 비우고 찾아낸 '유레카'의 순간

원인을 완벽히 파악한 후, 지금까지 시도했던 모든 복잡한 설정들을 과감하게 폐기했습니다.

무의미했던 Access Bypass 정책을 완전히 삭제하고, 메인 도메인의 웹 보안을 위해 껐던 'Always Use HTTPS' 옵션도 다시 원래대로 활성화(On)하여 보안을 원상 복구했습니다.

그리고 딱 다음과 같은 핵심적인 조치만을 취했습니다.

 

  1. 명시적인 IPv4 주소 사용: 터널의 Service URL을 불확실한 localhost 대신 **tcp://127.0.0.1:3306**으로 명확하게 픽스했습니다.
  2. HSTS 상속 단절: SSL/TLS > Edge Certificates 메뉴에서 '하위 도메인에 HSTS 정책 적용' 옵션만을 정확하게 찾아 **[Off]**로 변경했습니다.
  3.  

하지만 여기서 끝이 아니었습니다. 앞서 언급한 악랄한 '301 리다이렉트 찌꺼기'를 피하기 위해 특단의 조치를 취했습니다.

기존에 사용하며 캐시가 묻어버린 서브도메인(예: db.example.com)을 과감히 버리고, 완전히 새로운 텍스트를 가진 새 서브도메인(예: db2.example.com)을 터널 호스트네임으로 추가한 것입니다.

새로운 도메인은 어떠한 캐시의 영향도 받지 않는 순백의 상태입니다.

 

터널 데몬(sudo systemctl restart cloudflared)을 재시작하고, 완전히 새로운 서브도메인을 향해 Hyperdrive 생성 명령어를 실행하는 순간!

화면에는 붉은색 에러 메시지 대신 영롱한 ID: xxxxxxxx... 성공 코드가 출력되었습니다. 모니터 앞에서 저도 모르게 "유레카!"를 외친 순간이었습니다.

 

결론: 네트워크 프로토콜에 대한 이해가 삽질을 줄인다

Cloudflare Hyperdrive와 Tunnel의 조합은 성공적으로 세팅만 끝난다면, 로컬 환경의 보안성을 완벽하게 유지한 채 전 세계 어디서든

지연 없는 데이터 접근을 가능하게 하는 마법 같은 인프라입니다.


그러나 이 과정에서 겪은 수많은 실패는, 겉으로 보이는 에러 코드 이면에 숨겨진 네트워크 계층(OSI 7 Layer)의 본질을 이해해야 함을 강력하게 시사합니다.

우리가 겪었던 403과 301 에러의 본질은 결국 **"Cloudflare의 강력한 웹(HTTP/HTTPS) 중심 보안 철학이,

시스템 간의 순수한 TCP 통신에 잘못 개입하면서 발생한 오해"**였습니다.

 

문제가 발생했을 때 무작정 우회(Bypass) 정책을 남발하거나 전역 보안 설정을 해제하는 것은 정답이 아닙니다.

127.0.0.1이라는 명시적인 주소 사용의 중요성, HSTS 정책의 상속 범위 제어, 그리고 강력한 캐시 메커니즘을 우회하기 위한

'새로운 네임스페이스(서브도메인) 사용'이라는 발상의 전환이야말로 이 문제를 해결하는 가장 우아하고 정확한 방법이었습니다.

 

[핵심 요약] 바쁜 개발자를 위한 Hyperdrive + Tunnel 설정 5단계 액션 플랜

앞서 설명한 복잡한 네트워크 이론과 수많은 시행착오를 단 5개의 핵심 단계로 압축했습니다. Zero Trust Access의 Bypass 정책 추가나 전역 HTTPS 설정 해제 없이,

오직 본질적인 라우팅과 캐시 문제만을 해결하여 완벽한 연결을 구성하는 방법입니다.

Step 1. 명시적인 IPv4 루프백 주소 할당

  • 경로: Cloudflare 대시보드 > Networks > Tunnels > [터널 선택] > Public Hostname
  • 작업: Service Type을 TCP로 설정하고, URL을 localhost:3306 대신 **127.0.0.1:3306**으로 명확하게 지정합니다.
  • 목적: OS의 네트워크 스택이 localhost를 IPv6(::1)로 우선 해석하여 발생하는 Connection Refused 에러를 원천 차단합니다.

Step 2. HSTS 하위 도메인 상속 차단 (가장 중요)

  • 경로: Cloudflare 대시보드 > SSL/TLS > Edge Certificates > HSTS 설정
  • 작업: '하위 도메인에 HSTS 정책 적용(includeSubDomains)' 옵션을 찾아 반드시 [Off(끔)] 상태로 변경합니다. (메인 도메인의 Always Use HTTPS는 켜두어도 무방합니다.)
  • 목적: 순수 TCP 통신을 시도하는 터널 서브도메인에 강제 HTTPS 웹 리다이렉션 정책이 상속되어 발생하는 301 Moved Permanently 에러를 방지합니다.

Step 3. 엣지 캐시 회피를 위한 '새로운 서브도메인' 발급

  • 작업: 기존에 403이나 301 에러를 겪었던 서브도메인(예: db.example.com)은 브라우저 및 엣지 노드에 악성 리다이렉트 캐시가 남아있을 확률이 매우 높습니다.
  • 과감히 폐기하고 완전히 새로운 서브도메인(예: db2.example.com)을 Public Hostname에 추가하십시오.
  • 목적: 캐시 찌꺼기로 인한 유령 에러를 회피하고, 순백의 상태에서 새로운 라우팅 규칙을 적용받기 위함입니다.

Step 4. Cloudflare 데몬(cloudflared) 재시작

  • 작업: 데이터베이스가 위치한 호스트 서버의 터미널에 접속하여 아래 명령어를 실행합니다.
  • Bash
   sudo systemctl restart cloudflared
  • 목적: 대시보드에서 변경한 터널 라우팅 정보와 새로운 호스트네임 정책을 서버 데몬이 즉각적으로 동기화하도록 강제합니다.

Step 5. Hyperdrive 최종 생성 및 연결 확인

  • 작업: Wrangler CLI 또는 Cloudflare 대시보드를 통해 새로 만든 서브도메인으로 연결을 시도합니다.
  • Bash
   npx wrangler hyperdrive create <이름> --connection-string="mysql://<유저>:<비밀번호>@<새로운서브도메인>:3306/<DB명>"
  • 결과: 지긋지긋한 에러 메시지 대신 성공적으로 생성된 ID 값을 획득할 수 있습니다. 축하합니다. 데이터베이스 고속도로가 개통되었습니다!


 

이 치열했던 트러블슈팅 기록이, 지금 이 순간에도 Connection Refused와 싸우며 밤을 지새우고 있을 수많은 개발자들에게 명확한 해답과 안도감을 주기를 바랍니다. 기본으로 돌아가면 답이 보입니다.

#Cloud#Infrastructure#Serverless#Tech2024