Add an offline page to display when Discourse is rebuilding or starting up

이 가이드를 따랐는데도 포럼에 500kb가 넘는 .png 파일을 업로드할 수 없는 분명한 이유가 있을까요?

client_max_body_size 0; 이 줄에 대한 이야기가 몇 가지 보이는데, 그게 문제가 아닐 것 같은데 맞나요?

수정: 여기에 글을 올린 직후 바로 원인을 찾았습니다. 설정에서 :white_check_mark: force https를 활성화해야 했습니다. 향후 비슷한 문제를 겪는 사람들을 위해 이 글을 남겨두겠습니다.

3개의 좋아요

여기에서 확인했습니다:

nginx에서는 Connection: close 헤더를 비활성화하고 proxy_http_version 1.1을 설정하는 것을 권장합니다. 즉, 다음과 같은 설정이 될 수 있습니다:

    proxy_http_version 1.1;
    # 기본 "Connection: close" 비활성화
    proxy_set_header "Connection" "";

Connection: close가 유닉스 도메인 소켓에 어떤 영향을 미치는지에 대한 문서를 찾을 수 없지만, 이 문서는 별도 시스템에서 외부 프록시를 실행하는 데에도 유용하므로 헤더를 제거하는 것이 해가 되지 않을 것으로 예상됩니다. 따라서 여기서도 이를 제거하는 것을 권장하는 것이 합리적일까요?

1개의 좋아요

SELinux(enforcing)가 활성화된 시스템에 배포한 경우, 컨테이너와 호스트 간 통신을 위한 유닉스 도메인 소켓을 사용할 수 없습니다. 유닉스 도메인 소켓에 레이블을 다시 지정하더라도 컨테이너를 재시작할 때마다 레이블 없이 다시 생성되기 때문입니다. 대신 두 가지 변경 사항이 필요합니다.

nginx가 에러 페이지에 접근할 수 있도록 허용하고, 유닉스 도메인 소켓을 통한 프록싱 대신 포트 사용으로 전환해야 합니다. SELinux를 보안 계층 중 하나로 nginx를 실행하는 대가로 요청당 몇 마이크로세컨드(µs)의 지연 시간이 발생하지만, 사용자는 이를 인지하지 못할 것입니다.

먼저, nginx가 네트워크 연결을 만들고 에러 페이지에 접근할 수 있도록 허용하기 위해 다음 명령을 실행하세요:

setsebool -P httpd_can_network_connect 1
semanage fcontext -a -t httpd_sys_content_t /var/www
restorecon -R -v /var/www

그 다음 app.yaml 파일에서 - "templates/web.socketed.template.yml" 주석을 처리하거나 삭제하고, 포트 80을 로컬 머신의 다른 포트에 노출한 후 컨테이너를 다시 빌드하세요.

expose:
  - "8008:80"   # http

여기서 https를 사용하지 마세요. 외부 nginx에서 SSL을 종료(terminate)했기 때문에, X-Forwarded-Proto 헤더가 Discourse에 요청이 https를 통해 들어왔음을 알려줍니다. 방화벽 설정에서 포트 8008(또는 선택한 다른 포트)가 공개적으로 노출되지 않도록 확인하세요.

그 후 외부 nginx 설정에서 nginx.http.sock를 통한 프록싱을 http://127.0.0.1:8008(또는 선택한 포트)으로 변경하고, 기본 Connection: close 헤더를 제거하여 외부 nginx가 각 요청마다 새로운 IP 연결을 설정하지 않도록 하세요.

...
  location / {
    proxy_pass http://127.0.0.1:8008;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    # 기본 "Connection: close" 비활성화
    proxy_set_header "Connection" "";
...
1개의 좋아요

@sam (그리고 @falco에게도). 저는 이 #documentation:sysadmin 문서 중 일부를 정리하는 일을 맡게 되었습니다. 이 문서는 조회수가 매우 높지만, 가장 도움이 되지 않는 문서 중 하나라고 생각합니다.

https://hub.docker.com/_/haproxy 와 nginx - Official Image | Docker Hub 를, 아마도 docker compose를 사용하여, nginx 컨테이너가 discourse 컨테이너의 인증서를 마운트하고 haproxy가 tcp 모드로 동작하도록 설정하는 대체 문서를 작성하는 것이 합리적이라고 생각하시나요? 다음과 같은 설정을 하려는 것입니다(이것이 작동하지 않을 것이라 확신하지만, 작동하는 방법을 알아낼 수 있을 것이라 생각합니다):

backend my_app_be
	balance roundrobin
	option httpchk HEAD /srv/status
        server discourse app:443 check
	server fallback nginx:80 check backup

이것은 이 토픽보다 따라 하기 쉬운 실행 가능한 솔루션이 될 수 있다고 생각합니다. 그러면 이 토픽은 역사적 기록을 위해 남겨두되(아마도 닫을 수도 있겠네요) 위에서 설명한 문서로 링크를 걸겠습니다.

3개의 좋아요

이 주제는 다음 주제와 상당 부분 겹친다는 점을 참고하세요:

해당 주제는 최근 상당한 업데이트가 이루어졌으며(이로 인해 겹치는 부분이 더 늘었습니다). 이미 별도의 Nginx 인스턴스를 실행 중이라면 추가하기 쉽기 때문에, 여기의 오프라인 페이지 부분을 노트로써 해당 주제로 병합하는 것이 적절할 수 있습니다. 그런 다음 이 주제는 비추천(deprecated)으로 표시하고(대안으로 링크) 처리하면 어떨까요?

또한, 다른 이유로 프론트 Nginx를 설치하지 않으려는 사용자들을 위한 기본 방식으로서 제안하신 HAProxy 주제도 여전히 추가적으로 의미가 있을 것입니다.

2개의 좋아요

제가 어떻게 알았겠습니까?

아.

하지만 진지하게 말씀드리자면, 제 해결책보다 당신의 해결책이 더 좋습니다!

그리고 그 토픽에는 큰 글씨로 고급 토픽이라고 되어 있잖아요.

하지만 아마 그것도 필요 없을지도 모르겠네요. 대부분의 사람들이 nginx를 더 잘 이해하니까요. 하지만 이제 제가 그걸 생각하기 시작했으니, 이제부터는 저를 멈추게 하는 것이 더 어려운 부분이 될 것 같습니다. :slight_smile:

3개의 좋아요

대체 방안이 다양하게 존재하는 것은 항상 좋은 일입니다. 하지만 이 옵션은 가장 쉬운 방법 중 하나(사실 꽤 많은 옵션이 있지만…)이며, 많은 사용자에게 익숙한 선택지이기도 합니다.

그러니 이 옵션은 건드리지 말아 주세요.

이 옵션을 그대로 유지하는 것에는 또 다른 장점이 있습니다. 바로 검색 결과입니다. 높은 트래픽(그리고 태그 사용이 매우 제한적인…) 때문에 요즘은 여기서 특정 내용을 찾는 것이 꽤 어렵습니다. 하지만 이 옵션은 찾기 쉽고 목적성이 매우 명확합니다. 이 주제가 다른 곳으로 이동하게 되면 찾기가 더 어려워질 것입니다.

이 옵션이 이렇게 인기 있는 데에는 이유가 있습니다… 도커(Docker)나 하프록시(Haproxy)를 사용하고자 하는 영감을 받는 사람이 그렇게 많지는 않으니까요.

2개의 좋아요

하아. 글쎄요, 그 말도 맞을 수 있지만, 적어도 4년은 지난 정보입니다. 요즘에는 직접 파일을 수정할 필요가 없어요. acme(혹은 비슷한 도구?)가 대신 처리해 주니까요.

솔직히 제 생각에는 재빌드하는 동안 페이지를 유지하기 위해 이런 우회 방법을 거치는 것보다, 다운타임이 거의 없는 두 컨테이너 설치를 사용하는 것이 훨씬 합리적이라고 생각합니다. 하지만 이 점에 대해서도 사람들을 설득하는 데는 실패하네요.

그래서 아마도 이 글을 현재 상황에 맞게 다시 작성하는 것이 좋겠습니다.

1개의 좋아요

그리고 훨씬 더 어렵습니다. certbot을 설치하고 사용하는 방법을 가르쳐 주는 것보다, 언제 어디서 SQL 측 등을 업데이트해야 하는지를 가르쳐 주는 설치 지침을 수정하는 것이 더 쉬울 것 같습니다.

게다가 Docker에 반대하는 또 다른 점이 있습니다(심지어 Discourse도 그렇게 작동하긴 하지만…): Docker를 처음부터 어떻게 사용하는지, 또는 yml 파일에서 오타를 어떻게 피하는지 같은 아주 기초적인 질문들을 많이 볼 수 있습니다. :slight_smile:

그럼에도 불구하고 작동합니다(SSL 섹션이 약간 혼란스럽긴 하지만, 4년 전에도 이미 그랬거든요 :wink: )

아니요. 저는 다른 솔루션에 반대하는 것이 아닙니다. 아주 확실한 이유 없이 오래된 링크와 텍스트를 새로운 위치로 옮기려는 시도에 대해서는 매우 반대합니다.

2개의 좋아요

그 부분은 의견이 다를 수밖에 없겠네요. 하지만 당신과 같은 의견을 가진 사람들이 꽤 많을 것이라 생각합니다. (아마 이 솔루션은 “설정하고 잊어버리면 되는” 방식이고, 두 컨테이너 솔루션은 Postgres 업그레이드가 있을 때 주의를 기울여야 하는데, 이는 약 2년마다 발생하는 일이니까요.)

네. 그 부분은 동의합니다! 그래서 앞으로의 방향은 그 부분을 어떻게 정리할 수 있는지 살펴보고, haproxy 솔루션은 보류하는 것이라고 생각합니다.

1개의 좋아요

Discourse에서 이 기능이 구현되었으면 좋겠습니다. 하지만 @fefrei 님, 정말 감사합니다! 놀라운 작업입니다! 저는 Apache를 사용해서 구현할 예정이지만, 기본적인 단계는 동일할 것입니다.

1개의 좋아요

OK, 결국 2시간을 씨름한 끝에 원하는 대로 만들었습니다!

Apache2를 사용한 Discourse 유지보수 페이지

root 권한으로 실행

cd /var/discourse
nano containers/app.yml

다음 줄들을 주석 처리합니다:

  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

expose:
  #- "80:80"   # http
  #- "443:443" # https

templates 섹션의 마지막에 아래 내용을 추가합니다(반드시 마지막이어야 합니다):

  - "templates/web.socketed.template.yml"

참고: 이렇게 하면 Discourse는 내부 IP에서만 리스닝하게 되고, apache2가 80/443 포트와 SSL 종단(termination)을 담당하게 됩니다.

참고: 이 설정이 적용되려면 Discourse를 다시 빌드해야 합니다:

cd /var/discourse
./launcher rebuild app

apache2와 certbot 설치

apt install -y apache2 certbot python3-certbot-apache

html 페이지를 위한 디렉터리 생성:

mkdir /var/www/discourse_maintenance

HTML 페이지:
/var/www/discourse_maintenance/discourse_maintenance.html

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="refresh" content="5">
        <title>Discourse Maintenance</title>
        <style>
            .center {
                display: flex;
                justify-content: center;
            }
            .container {
                max-width: 500px;
                padding: 50px 50px 30px 50px;
            }
            .title {
                padding-top: 20px;
            }
            h1, p {
                font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
            }
        </style>
    </head>
    <body>
        <div class="center">
            <div class="container">
                <h1 class="title">Discourse Maintenance&hellip;</h1>
                <p>We are currently upgrading the site, or performing scheduled maintenance.</p>
                <p>You'll automatically be redirected to the site once it's available.</p>
            </div>
        </div>
    </body>
</html>

Proxy 모듈 활성화:

a2enmod proxy
a2enmod proxy_http
a2enmod headers

Apache vhost 파일:

<IfModule mod_ssl.c>
<VirtualHost *:443>
  ServerName your.discourse.domain
  ServerAdmin your@email.com
  DocumentRoot /var/www/discourse_maintenance

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined

  # Maintenance Mode
  RewriteEngine On
  RewriteCond /var/www/under_maintenance -f
  # safety check to prevent redirect loops 
  RewriteCond %{REQUEST_URI} !/discourse_maintenance.html$
  # redirect internally all requests to maintenance.html 
  RewriteRule ^.*$ /var/www/discourse_maintenance/discourse_maintenance.html

  ProxyPass / unix:///var/discourse/shared/standalone/nginx.http.sock|http://127.0.0.1/
  ProxyPassReverse / unix:///var/discourse/shared/standalone/nginx.http.sock|http://127.0.0.1/

  SSLCertificateFile /etc/letsencrypt/live/your.discourse.domain/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/your.discourse.domain/privkey.pem
  Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

유지보수 모드를 활성화하려면 touch /var/www/under_maintenance 명령을 실행하세요.

유지보수 모드를 비활성화하려면 touch /var/www/under_maintenance 명령을 실행하세요.

크레딧: 초기 아이디어, HTML 페이지(내 취향에 맞게 다듬고 편집함) 및 Apache 설정의 기반이 된 nginx 설정은 Add an offline page to display when Discourse is rebuilding or starting up 에서 가져왔습니다.

수정: 502/503 응답일 때 자동으로 작동하도록 하는 제안 환영합니다. 시도해 보았지만 원하는 대로 작동하지 않아, 백엔드 애플리케이션이 유지보수로 다운되어 있을 때 다른 웹서버에서 사용하는 잘 알려진 방법으로 대체했습니다.

2개의 좋아요

시스템 재부팅 시, 이는 시스템 부팅보다 훨씬 더 오래 걸리는 docker가 시작될 때까지 오류/유지보수 페이지 표시를 지연시킵니다. 또한 시스템 nginx에 대한 시스템 제공 SELinux 보호 기능을 사용할 수 있는 옵션도 제공하지 않습니다. systemd로 관리되는 시스템에서 빠른 부팅이 가능하다면, 시스템 nginx를 사용하면 부팅 후 몇 초 안에 오류 페이지를 표시할 수 있습니다. 저의 경우, 이는 재부팅이 필요한 시스템 업데이트 중에 시스템이 매우 빠르게 유지보수 페이지로 응답하도록 해줍니다. (호스트에는 AlmaLinux 9를 실행 중이며, nginx로 매우 빠르게 부팅됩니다.)

haproxy 대안을 문서화하고 경험을 비교하는 것은 합리적일 수 있지만, docker 내 haproxy는 외부 nginx의 1:1 대체재가 아니므로 이 주제를 닫는 것은 실수가 될 것입니다.

단순히 가용성 문제만이 아닙니다.

IPv4를 통해 docker를 사용하여 외부 트래픽을 처리하면, 내부 nginx와 Discourse에서 외부 IPv6 주소를 숨기게 됩니다. haproxy에서도 동일한 문제가 발생합니다. 127.0.0.1 또는 172.* RFC1918 로컬 전용 IP 주소 공간에 대한 로그를 확인해 보세요. 외부 프록시를 사용하지 않으면 모든 IPv6 트래픽이 동일한 IP로 표시되어, 모든 IPv6 트래픽을 단일 존으로 취급하는 내부 nginx의 존별 속도 제한이 깨지게 됩니다.

IPv6은 점점 더 중요해지고 있습니다.

2개의 좋아요

오늘 아침에 우연히 이 단계가 유닉스 소켓 적용을 피할 뿐만 아니라 실제로 real_ip 모듈 사용도 제거한다는 것을 발견했습니다. 이로 인해 IP별 모든 연결이 아니라 모든 연결을 합산하여 레이트 리미팅이 적용됩니다. 변수가 포함된 새 템플릿을 기여하는 것이 좋을 것 같지만, 현재는 앱 컨테이너 YAML 파일에 다음 내용을 추가했습니다:

run:
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from 172.0.0.0/24;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from 172.0.0.0/24;

유닉스 도메인 소켓을 사용하지 않고 주소를 변수로 가지는 templates/web.httpratelimit.yml 같은 파일에 이와 유사한 내용을 포함하는 것이 의미 있는지 모르겠습니다. 이에 대한 의견이 있으신가요?

2개의 좋아요
server {
  listen 80; listen [::]:80; listen 443 ssl http2; listen [::]:443 ssl http2;
  server_name DOMAIN;
  ssl_certificate      /etc/letsencrypt/live/DOMAIN/fullchain.pem;
  ssl_certificate_key  /etc/letsencrypt/live/DOMAIN/privkey.pem;

ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
  ssl_protocols TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  add_header Strict-Transport-Security "max-age=63072000;";
  ssl_stapling on;
  ssl_stapling_verify on;

  client_max_body_size 0;

  location / {
    error_page 502 =502 /errorpages/offline.html;
    proxy_intercept_errors on;

    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
  }

  location /errorpages/ {
    alias /var/www/errorpages/;
  }
}

! # 도메인과 에러 파일 경로를 변경하세요

SSL이 포함된 이 스크립트를 사용했고 정상적으로 작동했습니다.

2개의 좋아요

방금 이 가이드를 사용해 보았지만 작동하지 않았습니다.

Nginx 프록시를 사용하여 단일 Discourse 컨테이너에서 두 개의 사이트를 실행하는 것을 시작했습니다. 에러 페이지 설정 부분만 추가하고 싶었기 때문에 Run other websites on the same machine as Discourse 와 겹치는 부분은 건너뛰었습니다. 하지만 중요한 단계를 놓친 것 같습니다. 결국 필요한 내용을 이 DigitalOcean 튜토리얼 에서 찾았습니다. 수동으로 설정하는 것이 어려운 것은 아니지만, 더 나은 방법이 있을 것 같습니다.

Docker가 Discourse를 실행하는 표준 방식인 점을 고려하면, 이 제안이 더 좋아 보입니다. 설정해 두고는 잊어버릴 수 있는 종류의 작업이 될 것이라 생각합니다.

3개의 좋아요

이 스레드에서 제시된 아이디어는 caddy를 리버스 프록시로 사용하는 우리들에게도 훌륭한 방법입니다. 단독 애플리케이션으로 사용하거나 Cloudflare Tunnels를 이용하는 경우 모두에 해당합니다.

discourse.example.org {
        reverse_proxy <host | ip>:port

        handle_errors 5xx {
                root * /path/to/error-pages
                rewrite * /error.html
                file_server {
                        status 404
                }
        }
}

status 404 섹션은 Cloudflare Tunnels를 사용할 때만 중요합니다. caddy가 Cloudflare에 5xx를 반환하면, Cloudflare Tunnel은 자체 연결 끊김 오류를 표시합니다. 상태 코드를 변경하면 Cloudflare에 유효한 활성 연결이 존재하며 에러 페이지를 서빙할 것임을 알릴 수 있습니다.

2개의 좋아요

아마도 이 작동 방식을 오해하고 있는 것 같은데, 새로고침은 이미 있는 페이지를 다시 불러오는 것 아닌가요? 그렇다면 어떻게 다른 URL로 돌아갈 수 있는 건가요?

다른 URL로 갈 필요는 없습니다. 요령은 사용자가 접근하려고 했던 URL(예: https://meta.discourse.org/t/add-an-offline-page-to-display-when-discourse-is-rebuilding-or-starting-up/45238/158)에서 에러 페이지가 직접 서빙된다는 것이며, 바로 그 URL이 새로고침되어 다시 동일한 에러가 표시되거나 사용자가 원했던 페이지가 로드됩니다. :slight_smile:

아, 그렇군요. 제 경우 오프라인 페이지를 위해 다른 서버를 사용하고 있어서, 설치를 처음부터 끝까지 꼼꼼히 읽지는 않았습니다. 만약 내 머신이 어떤 이유로 완전히 다운되면 대비하기 위해서였죠.
그래도 말이 됩니다. 이제 원래 URL로 리다이렉트되도록 설정해둔 제 JS가 정상적으로 작동하도록 하는 것만 남았네요…