저희는 서버를 한 VPS 제공업체에서 다른 VPS 제공업체로 이전했고, launcher rebuild을 사용하여 인스턴스를 3.5.0.beta3에서 3.5.0.beta4로 최신 버전으로 업그레이드했습니다.
인스턴스는 항상 Cloudflare 뒤에서 정상적으로 작동했지만, 이제 접근을 시도하면 끝없는 5개 점 로딩 애니메이션이 표시됩니다.
ISP(Deutsche Telekom AG)의 피어링 정책이 좋지 않아 Cloudflare를 통한 접근 속도가 때때로 매우 낮기 때문에, Cloudflare를 우회하기 위해 로컬 시스템에 hosts 파일 항목을 설정해 두었습니다. 그래서 처음에는 문제를 인지하지 못했는데, Cloudflare를 우회하면 접근이 정상적으로 이루어졌기 때문입니다. 인스턴스를 업그레이드한 후, 이제 VPS 변경인지 Discourse 업그레이드 중 어떤 것이 관련 있는 변경 사항인지 확신이 서지 않습니다. VPN과 모바일 네트워크를 통해 확인한 결과, 이 문제는 이제 Cloudflare 자체에서 비롯된 것이며, ISP의 피어링 문제나 다른 사용자들도 같은 문제를 겪고 있음이 분명합니다. 구 VPS와 신 VPS 모두 IPv6을 지원하며, 전체 시스템은 동일하게 유지되어 원본 이미지 파일로 이전되었습니다.
브라우저(콘솔), 호스트 시스템의 프록시, 컨테이너 내 Nginx, Rails 또는 다른 어디에서도 에러 메시지가 전혀 없습니다. HTML 문서와 여러 스크립트는 정상적으로 로드되며, Cloudflare를 우회했을 때 제공되는 것과 비교했을 때 모든 것(확인해 본 부분)이 동일합니다. 응답 헤더도 Cloudflare 특유의 몇 가지 항목을 제외하면 대부분 동일해 보입니다. 마지막으로 로드되는 것으로 보이는 것은 미니 프로파일러입니다:
물론 브라우저 캐시 삭제, 시크릿 모드 사용 등 모든 시도는 아무런 변화를 가져오지 못했습니다. 또한 Cloudflare 캐시를 비활성화/삭제해도 도움이 되지 않으므로 캐시가 문제가 아닙니다. 저는 임시로 전체 포럼에 대해 CF 캐시를 완전히 비활성화했습니다.
포럼은 호스트의 Apache 프록시 뒤의 서브패스에서 실행되며, 다음 지침을 따르고 있습니다: Serve Discourse from a subfolder (path prefix) instead of a subdomain
이전에는 uploads/backups 심볼릭 링크 대신 ln -s . forum 심볼릭 링크만 생성하고 지침의 rewrite를 두 번 적용했는데, 이는 수년간 잘 작동했습니다(현재 Cloudflare 없이도 작동 중). 하지만 디버깅 노력의 일환으로 내부 프록시가 의도대로 모든 규칙을 적용하는지 확인하기 위해 해당 지침으로 전환했습니다. 신뢰 헤더는 CF-Connecting-IP이지만, 약간 중복되더라도 cloudflare.template.yml도 활성화했습니다. 또한 프록시 IP 헤더가 차이를 만드는지 확인하기 위해 이러한 템플릿과 위 지침의 다양한 부분을 오가며 변경해 보기도 했습니다. Cloudflare를 우회할 때 CF-Connecting-IP가 누락되는 것이 하나의 차이점이므로 이를 확인하기 위함이었죠.
이 시점에서 저는 완전히 아이디어가 고갈되었고, 문제가 어디서 오는지 단서조차 하나 없습니다. 관련 로그/출력도 어디에도 없습니다. Cloudflare를 통해 접속하면 Discourse가 추가적인 흔적 없이 로딩 애니메이션에서 멈춰 있습니다.
누군가 이 문제를 디버깅하는 방법에 대한 아이디어가 있거나, 3.5.0.beta3과 3.5.0.beta4 사이에서 관련이 있을 수 있는 변경 사항이 있었는지 알려주시면 좋겠습니다. 다운그레이드는 문제가 될까요?
수정: 아, 잠깐, 잘못 확인했네요. 다른 페이지들은 다른 지점에서 멈춥니다. 즉, 이 특정 HTML 요소/속성 때문은 아니네요.
네, 각 페이지/HTML 문서는 브라우저로 다시 로드할 때마다, 시크릿 모드 등으로 열 때마다 정확히 같은 문자에서 반복적으로 멈춥니다. 하지만 다른 페이지는 다른 지점에서 멈춥니다. 그리고 curl로 로드할 때도 항상 정확히 같은 지점에서 멈추지만, 다른 페이지는 또 다른 지점에서 멈춥니다. wget도 항상 같은 지점에서 멈추지만, 약간 다른 지점입니다. 정말 이상하네요.
참고로, content-length 응답 헤더가 없습니다. 이것이 문제를 일으킬 수 있을까요? Cloudflare를 우회할 때도 존재하지 않지만, 아마도 Cloudflare 쪽에 어떤 문제가 있는 것일까요? 수정: 동적 페이지의 경우 정상적인 것 같습니다. 문제 없는 우리 Wordpress와 Matomo 페이지도 마찬가지입니다.
그리고 curl을 가지고 놀면서 또 다른 발견을 했습니다. STDOUT으로 출력하면 전체 HTML 문서가 표시되지만, 여전히 멈추고 마지막에 다음과 같은 상태가 됩니다:
<p class='powered-by-link'>Powered by <a href="https://www.discourse.org">Discourse</a>, best viewed with JavaScript enabled</p>
</footer>
</body>
</html>
그러나 -o를 통해 저장하거나 단순히 리다이렉트하거나, 심지어 grep으로 파이프할 때만 다른 곳에서 멈춥니다:
그리고 https://www.dietpi.com/forum/에 curl로 액세스할 때 콘솔에 즉시 출력하지 않고 73728바이트라는 동일한 크기를 100% 재현할 수 있습니다. 정말 이상하네요 .
즉:
모든 클라이언트는 우리 Discourse 인스턴스에서의 모든 HTML 문서 로딩 시 멈춥니다.
각 클라이언트는 동일한 페이지를 로딩할 때 동일한 바이트 위치에서 멈춥니다.
서로 다른 클라이언트는 다른 지점에서 멈추지만, 동일한 클라이언트로 반복할 경우 동일한 바이트 위치에서 멈춥니다.
각 페이지는 문서의 다른 지점과 다른 다운로드 크기에서 멈춥니다.
동일한 도구인 curl은 STDOUT으로 출력하는 경우와 파이프하거나 문서를 어딘가에 저장하는 경우 다른 지점에서 멈춥니다.
wget은 전체 문서(최소 https://www.dietpi.com/forum/)를 파일로 다운로드할 수 있지만, 여전히 끝에서 멈춥니다. curl https://www.dietpi.com/forum/이 콘솔에 전체 문서를 출력하는 경우에도 마찬가지이며, 끝에서 멈춥니다.
네, wget은 항상 전체 문서를 다운로드하고, curl은 콘솔에 직접 출력할 때 전체를 다운로드하지만 연결은 닫히지 않습니다. 제가 2개의 게시물만 있는 14KB 크기의 주제 문서를 테스트해 보았듯이, 훨씬 더 작은 문서에서도 동일한 현상이 발생합니다. 그러나 더 작은 문서의 경우에도 curl을 파이프하거나 파일로 저장할 때, 또는 브라우저에서 사용할 때 완전히 다운로드되지 않는 경우가 대부분입니다.
두 도구 모두 항상 HTTP/2를 표시하며, Cloudflare에서는 HTTP/2 오리지널 요청이 활성화되어 있습니다. 하지만 명시적으로 다른 HTTP 버전을 사용하여 테스트해 볼 가치가 있습니다. 어제 위 스크린샷에서 확인된 Cloudflare의 모든 프로토콜 설정을 비활성화해 보았지만 도움이 되지 않았습니다. 하지만 다시 시도해 보겠습니다. 또한 서버에서 액세스 로그를 활성화하여 Cloudflare에서 실제로 들어오는 요청을 확인할 수도 있습니다.
지원되는 HTTP(1.1-3) 및 TLS(1.2-1.3) 버전의 모든 조합을 시도해 보았지만, 결과는 차이가 없었습니다. 또한 HTTP3 지원, HTTP2 오리진 요청, 0-RTT 연결 재개도 비활성화해 보았지만, 역시 차이가 없었습니다. curl은 여전히 https://www.dietpi.com/forum/에서 정확히 73,728바이트 지점에서 멈춰 있습니다.
문서 크기가 너무 크다는 이론에 관해, https://www.dietpi.com/dietpi-software.html은 199,475바이트이지만 완벽하게 로딩됩니다. 참고로 서버(동일한 웹서버)에는 정적 웹사이트, MkDocs 인스턴스, WordPress, Matomo가 호스팅되어 있으며, 모두 완벽하게 작동합니다. 또한 프론트 웹서버가 UNIX 소켓을 통해 프록시로 작용하는 Grafana 인스턴스도 있습니다.
그러나 버퍼나 청크 크기 등과 관련이 있다는 데에는 동의합니다. 프로토콜 버전을 변경해도 다운로드 후 멈추는 크기가 항상 정확히 동일하게 유지되는 반면, 클라이언트와 페이지마다 멈추기 전까지의 다운로드 크기가 크게 달라지는 것이 이상합니다. 또한 문서가 완전히 다운로드되었음에도 연결이 닫히지 않습니다. 마치 종료 신호가 없는 것 같은데, 이 시점에서 저는 HTTP에 대한 통찰력이 부족합니다. 그래서 content-length 헤더를 생각해 보았지만, 이 헤더는 당연히 필수 사항이 아닙니다.
웹서버는 UNIX 소켓을 통해 Discourse 컨테이너의 프록시 역할도 합니다. 컨테이너 내부의 Nginx를 그대로 두면서, TCP 리스너를 활성화하여 Discourse 인스턴스를 프록시 없이도 추가로 사용할 수 있도록 할 수 있습니다.
추가로, HTTP/1.0 클라이언트와의 Keep-Alive 연결은 콘텐츠 길이가 사전에 알려진 경우에만 사용할 수 있습니다.
따라서 content-length가 누락되어 있기 때문에, 이 요청에 대해 Keep-Alive가 사용되지 않는 것이 합리적일 수 있습니다.
재빌드가 필요하기 때문에, 우리가 함께 사용하는 웹사이트 활동이 최소로 줄어드는 시간에 조금 늦게 진행하겠습니다. 음, HTTPS에 대해 생각해보니… 호스트의 TLS 인증서를 사용하여 HTTPS를 위한 추가 포트에서 경청하되 HTTPS 리디렉션/강제는 하지 않으면서, UNIX 소켓과 일반 HTTP 연결이 모두 작동하도록 내부 Nginx 구성에 대한 사용자 지정 조정이 필요한 것 같습니다. … 그리고 HSTS를 무시할 수 있는 클라이언트를 위해 추가적인 일반 HTTP TCP 포트도 흥미로울 것입니다.
혹시 CloudFlare에서 RocketLoader를 사용하고 계신가요? 다른 스크립트와 함께 사용할 때 문제가 발생하는 경우가 있습니다.
또한 CF 캐시를 정리해 보셨나요?
구 VPS IP 주소에 연결되어 있었고 새 IP 주소로 업데이트되지 않은 CF의 인바운드 규칙을 사용하고 계신 건 아닌가요?
VPS 전환 시 무엇이 변경되었는지, 그리고/또는 Discourse 3.5.0.beta3에서 3.5.0.beta4로 업그레이드할 때 무엇이 변경되었는지, 혹은 우연히 같은 시점에 Discourse에서 다른 문제가 발생했는지는 정확히 모르겠습니다. 하지만 Discourse의 HTML, CSS 또는 JavaScript 문서 중 무언가가 Cloudflare의 내장 URL HTTPS 재작성 기능을 막히는 것처럼 보입니다. 부분적이고 중단된 curl 요청이 실제로는 관련이 없었던 것 같기도 하고, 아니면 관련이 있을 수도 있습니다. 브라우저의 네트워크 탭에서 HTML 문서의 일부 내용을 볼 수 있다는 점이 이상합니다. 이는 HTTPS 재작성 기능이 문서를 스트리밍하는 동안 처리하고 있기 때문인 것처럼 보입니다.
혹시 다른 분도 인스턴스와 Cloudflare 계정을 가지고 계신다면, 이것이 일반적인 문제인지 아니면 우리 인스턴스/설정과 관련된 것인지 테스트해 주실 수 있을까요?
참고로, 프록시 우회 및 HTTP를 테스트하면서 프록시를 통한 연결은 활성화된 상태로 유지하기 위해, 컨테이너 내부의 Nginx 구성을 다음과 같이 수동으로 조정하면 완벽하게 잘 동작합니다:
또 다른 발견: 우리는 text/html 응답의 </head> 종료 태그 바로 앞에 Matomo 트래커 코드를 추가하기 위해 mod_sed를 사용하고 있습니다. Cloudflare의 자동 HTTPS 리라이트가 활성화되어 있음에도 불구하고, Discourse에 대해 이 기능을 비활성화하거나(또는 Apache2 프록시를 우회하면) 문제가 해결됩니다. 둘 중 하나를 비활성화하는 것만으로도 문제가 해결됩니다. 다른 모든 페이지에서는 이 조합이 잘 작동하며, 실패하는 포럼 페이지보다 훨씬 큰 매우 큰 페이지에서도 마찬가지입니다. 따라서 프록시에서 먼저 적용되는 mod_set과 Cloudflare에 의해 임베드되는 URL 리라이트라는 두 가지 필터가 문서 크기나 청크 크기 등과 관련하여 무언가를 깨뜨리는 것일 수 있습니다.
현재 우리는 Discourse 테마 편집을 통해 트래커를 임베드하고 있으며, 추가로 Cloudflare의 자동 HTTPS 리라이트도 비활성화했습니다. 웹사이트 전체에 혼합 콘텐츠(mixed content)가 없습니다. 만약 존재한다면, Cloudflare가 이를 영원히 가려 두는 것보다 확인하고 수정하는 것이 좋습니다.
이름만 보고 "Cloudflare Automatic HTTPS Rewrites"가 무엇을 하는지 오해할 수 있다고 생각합니다. Cloudflare에는 두 가지 기능이 있습니다:
"Always Use HTTPS"는 모든 일반 HTTP 요청을 HTTPS로 리디렉션하며, 이는 Discourse의 force_https와 동일한 동작을 합니다. 이전에는 이 두 기능이 모두 활성화되어 있었으며, HTTPS가 문제와 무관한지, 또는 Discourse 페이지의 끝없는 로딩과 멈추는 curl 요청과 관련이 있는지 테스트하기 위해 이 두 기능을 모두 비활성화했습니다. 이 조치로 문제가 완벽하게 해결되었지만, 이는 HTTPS 요청에 대해서도 마찬가지였으며, 단순히 같은 시점에 "Cloudflare Automatic HTTPS Rewrites"를 비활성화했기 때문이었습니다.
"Cloudflare Automatic HTTPS Rewrites"는 HTML, CSS, JavaScript 문서 내의 모든 내장된 일반 HTTP URL을 HTTPS 버전으로 교체합니다. 이는 Cloudflare가 해당 호스트가 HTTPS를 통해 접근 가능하다고 판단하는 경우(HSTS 사전 로드 목록 등 기준)에만 적용됩니다. 이 기능은 혼합 콘텐츠(mixed content) 경고 발생을 방지하기 위한 것입니다.
Cloudflare, 호스트 프록시 또는 Discourse에서 HTTPS를 강제하거나 강제하지 않는 것은 중요하지 않습니다. 실제 문제를 일으킨 것은 호스트 프록시의 mod_sed 필터와 Cloudflare의 내장된 일반 HTTP 수정이 결합된 것이었습니다. 즉, 문서 내용이 두 단계의 필터를 통과한 것입니다. 문제는 실제 콘텐츠가 변경된 것이 아니라(우리 사이트에는 혼합 콘텐츠가 없으므로 "Cloudflare Automatic HTTPS Rewrites"는 문서 본문을 실제로 변경하지 않습니다) 아마도 청크(chunk), 버퍼 또는 타이밍과 관련이 있을 것입니다.