MKJ의 의견 있는 Discourse 배포 구성

지난 몇 년간 상당한 양의 콘텐츠와 많은 이미지를 갖춘 Discourse 포럼을 운영해 왔습니다. Maker Forums에는 100GB가 넘는 이미지와 40만 개 이상의 게시물이 있으며, 이 중 상당량은 주로 Google+에서 가져온 것이고 나머지는 사이트 내에서 생성된 것입니다. 이 게시글은 제가 최종적으로 Maker Forums 및 이후 몇몇 다른 Discourse 인스턴스를 구성한 방식의 요소를 설명합니다. 이는 제가 시작할 때 알았으면 좋았을 내용이며, 다른 사람들이 자신의 Discourse 인스턴스에서 동일한 함정을 피하는 데 도움을 주기 위해 사용해 온 것입니다.

더 넓은 독자를 위해 공개합니다.

:warning: 경고: 리눅스 시스템 관리자로서 작업하는 데 익숙하지 않다면, 이 가이드는 당신을 위한 것이 아닐 가능성이 높습니다. 리눅스에 대한 지식을 전제로 하는 모든 방식을 제가 인지하고 있을 수도 없습니다. 이 글이 깨달음을 주는 것처럼 읽힌다면, 당신은 대상 독자일 수 있습니다. 혼란스럽게 읽힌다면, 당신은 아마 대상 독자가 아닙니다. 이 글이 일처럼 느껴진다면, CDCK 또는 @pfaffman에게 비용을 지불하고 Discourse를 운영하도록 고려해 보십시오. 그들은 자신이 하는 일을 잘 알고 있습니다. 아니면 무료 discourse.group 사이트로 시작하고, 성장한 만큼 비용을 지불하는 것도 좋습니다. :warning:

:warning: 그것만으로도 부족하다면: 저는 Discourse 전문성보다 리눅스에 대한 전문성이 더 높습니다. 제 의견에는 보증이 없습니다. 제 조언을 따르다 보니 여러분의什么东西(디스코어 포럼, 호스트 시스템, 또는 마음)이 깨진다면, 날카로운 모서리까지 모두 두 조각을 유지하게 됩니다. 이 게시글의 내용에 대한任何形式的 지원도 제공할 계획이 없습니다. :warning:

제가 유지보수에 참여하는 Discourse 인스턴스를 다루는 제 관행에 따라 이 문서를 최신 상태로 유지할 계획(하지만 약속은 아님)입니다. 이는 조언의 형태로 작성되었지만, 주로 저 자신과 제가 책임져 온 Discourse 배포를 물려받을 관리자들을 위한 조언으로 의도하고 있습니다. 그렇지 않다면, 이것이 나만의 연구를 시작하는 하나의 출발점이라고 생각하여 Discourse를 배포하는 방식을 결정하는 데 사용하십시오.

시스템 설정

CentOS 파생 또는 Ubuntu LTS 운영 체제를 사용하십시오. Docker를 지원하는 것은 아마도 작동할 수 있지만, 저는 그 두 가지를 사용했습니다.

Docker

저는 Fedora 사용자입니다. 저는 Red Hat의 첫 번째 Fedora 프로젝트 리드였으며, Docker보다 보안 모델이 더 좋다고 주장하기 때문에 Discourse를 Podman 위에 실행하는 것을 훨씬 선호합니다. 그러나 Discourse 배포는 Docker에서만 지원되며, 다른 것으로 실행을 시도한다면 꽤 선구자가 될 것입니다. (docker-compose가 지원되는 날이 오면, podman-compose를 사용하여 Podman과 함께 작동할 수도 있습니다.)

이제 Docker가 cgroups v2를 지원하므로, CentOS 파생 시스템에 공식 Docker 빌드를 설치할 수 있습니다:

dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install --allowerasing docker-ce docker-ce-cli

(podman, runc, buildah와의 충돌로 인해 이미 설치되어 있을 수 있으므로 --allowerasing을 포함하십시오; docker를 설치하려면 이들을 삭제해야 합니다.)

systemctl enable --now docker

이것은 AlmaLinux 9에서 테스트했습니다.

보안

이 섹션은 실제로 Discourse와는 직접적인 관련이 없지만, 제 일상적인 보안 관행의 일부입니다. 네트워크 내의 모든 시스템, Discourse가 실행되는 VM을 포함하여, 비밀번호만 있는 셸 접근을 허용하지 마십시오. 패스프레이즈로 암호화된 SSH 키를 사용하여 SSH 기반 접근을 설정하고, VM의 ssh 서버가 비밀번호 접근을 허용하지 않도록 구성하십시오.

laptop$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/.../.ssh/id_rsa):
Enter passphrase (empty for no passphrase): SOME LONG PHRASE
Enter same passphrase again: SOME LONG PHRASE
Your identification has been saved in .ssh/id_rsa
Your public key has been saved in .ssh/id_rsa.pub

리눅스 배포판은 일반적으로 메모리에 패스프레이즈를 기억하도록 설정되어 있으므로, 부팅당 한 번만 입력하면 됩니다. Windows는 그렇게 편리하지 않습니다; Pageant를 PuTTY와 함께 사용하여 같은 일을 하는 것을 고려해 볼 수 있습니다.

먼저, 비밀번호 없이 들어오는 SSH가 작동하는지 확인하십시오. 그렇게 한 후, 서버에서 /etc/ssh/sshd_config 파일을 수정하고 PasswordAuthentication 줄을 찾으십시오. 들어오는 비밀번호 접근을 비활성화하려면 no로 설정하십시오.

PasswordAuthentication no

방화벽

letsencrypt가 SSL 인증서를 생성하고 갱신할 수 있도록, Discourse가 대중에게 공개되기 전이라도 포트 80과 443을 일반적으로 열려 있어야 합니다.

firewalld를 사용 중이라면, 이 명령들이 이것을 수행할 것입니다:

firewall-cmd --add-service http --add-service https --zone public
firewall-cmd --runtime-to-permanent

별도의 장치와 파일 시스템

/var/discourse/shared를 20GB의 공간과 이미지에 필요한 것보다 최소 2배 많은 공간을 가진 별도의 장치로, 자체 파일 시스템과 함께 만드십시오; prometheus를 사용할 경우 더 많은 공간을 추가하십시오. 장치가 나중에 쉽게 확장될 수 있다면 (LVM 또는 AWS elastic block storage와 같은 클라우드 블록 저장소 등), 필요에 따라 모니터링하고 확장할 수 있습니다; 그렇지 않으면 처음부터 넉넉하게 하십시오. 네트워크 저장소 블록 장치를 사용 중이라면, 파티션 테이블을 넣지 마십시오. 파티션 테이블 없이 사용하면 확장이 더 쉬워집니다; 파티션 테이블을 수정할 필요가 없습니다. 많은 경우 시스템 다운타임 없이 확장할 수 있습니다.

Maker Forums에서는, Maker Forums가 실행되는 VM에 연결된 네트워크 저장소 블록 장치입니다. 다른 Discourse 포럼에서는, Digital Ocean Block Storage Volume입니다. Amazon에서는, AWS Elastic Block Storage가 될 것입니다. Fedora에서 libvirt 하의 KVM VM을 실행하는 제 테스트 시스템에서는, AlmaLinux VM에 가상 디스크로 내보낸 Fedora 호스트의 LVM 볼륨입니다. 각 경우에서, 저는 새로운 VM을 만들고, 주요 파일을 그곳으로 복사하고, 오래된 VM을 중지하고, /var/discourse/shared 볼륨을 새로운 VM에 연결하고, 몇 분 안에 다시 실행할 수 있었습니다. 이는 VM에서 운영 체제 업그레이드를 상대적으로 낮은 위험으로 만듭니다.

VM의 루트 파일 시스템에 /var/discourse/shared 공간을 제외하고 최소 25GB로 시작하는지 확인하십시오. 이것은 모든 docker 컨테이너에 사용되며, discourse launcher는 언제든지 5GB 미만이 비어 있으면 실패합니다. 시스템 업데이트를 위한 충분한 공간도 필요로 합니다. 디스크 공간이 충분하지 않으면, 이는 복구하기 어렵습니다.

사이트 구성에서 force_https를 설정하되, 경고에 유의하십시오. Discourse 사이트를 공개하기 전에 테스트에서 설정하십시오. force_https가 있어도 포트 80이 열려 있어야 한다는 점에 유의하십시오. 포트 443으로 SSL로 리디렉션하고 letsencrypt SSL 인증서를 갱신하기 위해서입니다. (그러나 cloudflare를 사용 중이라면, 그 기능을 사용하십시오; Discourse의 force_https와 호환되지 않는 것으로 보고되었습니다.)

커널 구성

Redis (Discourse가 구축된 주요 구성 요소 중 하나)는 디스크 기반 영속성을 사용할 때 투명 거대 페이지를 비활성화하는 것을 강력히 권장합니다 (Discourse가 수행하는 것), 그리고 저도 메모리 오버커밋을 허용합니다.

echo 'w /sys/kernel/mm/transparent_hugepage/enabled - - - - never
w /sys/kernel/mm/transparent_hugepage/defrag  - - - - never' > /etc/tmpfiles.d/thp.conf
systemd-tmpfiles --create
echo 'vm.overcommit_memory=1' > /etc/sysctl.d/90-vm_overcommit_memory.conf
sysctl --system

Discourse 설치

기본 설치가 단일 컨테이너이지만, 이는 매월 권장되는 모든 업그레이드를, 커맨드 라인에서 수행해야 하는 경우(보안 또는 새로운 기능, 또는 UI에서 라이브 업데이트가 어떤 이유로든 실패할 때 Discourse가 구축된 도구를 업데이트하는 업데이트를 포함하여) 일반적으로 10-15분의 다운타임을 유발합니다. 두 컨테이너 설계를 통해 실제 다운타임을 줄일 수 있습니다.

두 컨테이너 설치

두 컨테이너로 구성을 시작하십시오.

./discourse-setup --two-container --skip-rebuild
${EDITOR:-nano} containers/data.yml
./launcher rebuild data
# app.yml을 사용하는 것이 제 선호이지만 web_only.yml을 유지할 수도 있습니다, 텍스트를 읽으십시오
mv containers/web_only.yml containers/app.yml
${EDITOR:-nano} containers/app.yml
./launcher rebuild app

이는 몇 개월마다 필요한 시스템 다운타임을 매우 짧게, 거의 눈에 띄지 않게 만듭니다; 많은 사용자는 장애 동안 콘텐츠에 클릭하거나 스크롤하지 않으면 전혀 알아채지 못할 것입니다. 이는 대부분의 보안 업데이트를 적용하기 쉽게 합니다; 모든 것을 다시 구축하는 약 15분이 아니라 일시적인 현상일 뿐입니다. 다음 프로세스는 대부분의 업데이트에 작동하며, 주로 호스트 시스템의 성능과 설치된 플러그인 세트에 따라 약 30-90초의 다운타임을 제공합니다.

cd /var/discourse
git pull
./launcher bootstrap app
./launcher destroy app && ./launcher start app
./launcher cleanup

bootstrap과 destroy/start 호출 사이에 지연을 두지 마십시오. 드물게 (실제로는 아마도 연 1-2회), bootstrap 단계 말기에 수행되는 데이터베이스 마이그레이션으로 인해, 업데이트된 데이터베이스에 액세스하는 오래된 코드로 인해 앱 사용자에게 더 적거나 더 심각한 오류가 발생할 수 있습니다.

이것은 실제로 Discourse를 업데이트할 때 data 컨테이너를 업데이트해야 하는지 확인해야 함을 의미하지만, 이는 드물게 필요합니다 (일반적으로 연 1-2회 예상). data와 app 컨테이너의 내용과 시스템의 속도에 따라, 이는 일반적으로 5분에서 20분 사이의 다운타임을 초래합니다.

cd /var/discourse
git pull
./launcher stop app
./launcher rebuild data
./launcher rebuild app

data 컨테이너를 업데이트해야 하는 시기를 아는 것에 대해 더 알아보기:

(제 자신의 배포에서, 저는 web_only 컨테이너를 app이라고 부르는 것을 개인적으로 선택했는데, 이는 입력하기가 더 쉽고 대부분의 지시를 따르기 쉽게 만들기 때문입니다. 이는 비표준이지만 사용의 용이성을 계속 높이 평가하고 있습니다. 그러나, 이는 추가적인 작업이었고, 제가 무슨 일이 일어나고 있는지 알고 있기 때문에 저에게는 잘 작동합니다. 그것이 나쁘게 들린다면, 멀티 컨테이너 배포를 위해 기본 web_only을 유지하십시오.)

미래의 어느 시점에, Docker가 컨테이너 연결을 위한 새로운 구성으로의 마이그레이션을 강제할 수 있다는 점에 유의하십시오:

4GB 이상의 메모리 또는 여러 CPU가 있다면, 다음 주소에서 조언을 읽으십시오:

업데이트 일정

release-notes 태그를 지켜보십시오 (#release-notes를 클릭하고 오른쪽 상단의 종을 클릭하십시오; 저는 "첫 게시물 보기"를 사용함) 및/또는 릴리스가 있을 때 알기 위해 https://meta.discourse.org/tag/release-notes.rss를 RSS 피드에 추가하십시오. 업데이트하기 전에 릴리스 노트를 읽으십시오. 데이터베이스 변경이 있으면, 릴리스 노트에서 언급됩니다. 보안 업데이트가 포함된 릴리스도 강조 표시합니다. 일부 버전을 실제로 업데이트하지 않고 건너뛰더라도 모든 릴리스 노트를 읽으십시오; 데이터베이스를 업데이트하는 릴리스의 릴리스 노트를 읽지 않으면, 읽지 않은 릴리스 노트의 데이터베이스 업데이트 지시를 놓칠 수 있습니다.

메일

메일은 여전히 사람들을 연결하는 주요 방식 중 하나입니다. 메일이 작동하도록 하는 것을 위해 발신 및 수신 메일을 설정하십시오. 문제가 있으면 다음을 확인하십시오:

계속 접촉하기

Maker Forums에서는 오랜 기간 동안 사라졌다가 돌아오는 간헐적인 방문자들을 목격해 왔습니다. 기본적으로, Discourse는 1년 후 다이제스트 이메일 전송을 중단합니다. 간헐적인 방문자들이 새롭고 흥미로운 것을 볼 때 돌아오도록 장려하려면, suppress_digest_email_after_days를 기본 365일보다 더 긴 값으로 설정하는 것을 고려하십시오. 저는 간헐적인 방문자들이 최신 정보를 유지하도록 지원하기 위해 Maker Forums에서 훨씬 더 길게 설정했습니다. 다이제스트 이메일을 읽는 것은 포럼에서 "잠복"하는 유효한 방식이며, 누군가의 참여에 대한 관심이 촉발될 시기를 알 수 없습니다.

마찬가지로, 기본적으로, 상호작용이 적은(게시물이 없는 신뢰 수준 0) 비특권 사용자는 로그인하지 않은 730일 후에 최종적으로 삭제됩니다. 사용자가 다이제스트 이메일을 읽으며 무기한 잠복할 수 있도록 하려면, "비활성 사용자 정리 후 일수"를 0으로 설정하여 사용자 삭제를 비활성화하십시오.

연례 리뷰 플러그인을 추가하는 것을 고려하십시오. 이는 연 1회, 2020: The Year in Review 같은 게시물을 생성하고 결국 비활성 사용자에게 이메일로 보내어 참여를 갱신하도록 장려할 수 있습니다.

메일 수신 컨테이너

세 번째 컨테이너를 메일 수신기로 설정하십시오. 이는 바운스 처리를 보장하고, 발신 메일 제공자와 독립적인 바운스 처리를 제공하며, 이메일로 답장하는 옵션을 제공합니다.

이메일 발신자를 신뢰하기 위해 SPF가 설정되어 있는지 확인하십시오; 최소한, 같은 MX로 보내고 받는 경우 v=spf1 +mx -all과 같은 정책이지만, 더 구체적인 것이 스팸 방지로서 더 잘 신뢰될 수 있습니다. DKIM도 고려하십시오.

이메일을 수신하기 위해 동일한 호스트 이름을 사용한다면, “오프라인 페이지” (아래 참조)와 같이 컨테이너 외부에서 SSL을 종료하는 것이 실제로 필요하며, certbot 인증서를 컨테이너로 매핑하고 certbot 실행 후 컨테이너를 재시작해야 합니다.

컨테이너 외부에서 사용자 SSL 연결 종료

컨테이너 외부에서 SSL을 종료하는 두 가지 선택지가 있으며, 둘 다 컨테이너 내부에서 종료하는 것보다 상당한 이점을 가져옵니다. discourse-setup을 성공적으로 완료하고 포럼을 부트스트랩한 후, 그중 하나를 설정하십시오.

외부 nginx

호스트 시스템에서 실행되는 nginx를 컨테이너에서만이 아니라, 유지 관리 페이지를 호스팅하고 호스트에 IPv6 지원이 있는 경우 IPv6 주소 로깅을 지원하기 위해 사용하십시오. (그렇지 않으면, 모든 IPv6 연결이 로컬 docker 가상 네트워크 인터페이스와 관련된 내부 RFC1918 주소에서 온 것으로 기록됩니다.) 이 구성은 대부분의 유지 관리 작업 동안 임시 유지 관리 페이지를 표시하며, 최종적으로 사용자가 보고 있던 페이지로 다시 리디렉션됩니다.

그 페이지의 지시사항(현재)은 letsencrypt라는 패키지를 설치하는 것을 제안하지만, 이제 일반적으로 certbot이라고 불립니다. 그 페이지의 지시사항을 따서 --certonly를 사용하면, certbot의 nginx 플러그인이 필요하지 않지만, nginx 플러그인을 설치하는 것은 또 다른 메커니즘입니다. CentOS 파생 버전에서 그것은:

dnf config-manager --set-enabled crb
dnf install epel-release
dnf install certbot python3-certbot-nginx
systemctl enable --now certbot-renew.timer

certbot이 nginx와 메일 수신 컨테이너를 재시작하여, 사이트가 오래된 만료된 인증서를 계속 사용하기 때문에 브라우저나 이메일이 트래픽을 차단하지 않도록 확인하십시오.

# systemctl edit certbot-renew

메일 수신기가 없는 시스템에서, 저는 두 줄을 추가했습니다:

[Service]
ExecStartPost=/bin/systemctl reload nginx

시스템에서 시스템과 인증서를 공유하는 별도의 메일 수신기 컨테이너를 사용하는 경우:

[Service]
ExecStartPost=/bin/systemctl reload nginx
ExecStartPost=/bin/sh -c 'cd /var/discourse && ./launcher restart mail-receiver'

SELinux를 사용 중이라면, Ubuntu 컨테이너는 외부 nginx가 액세스할 수 있도록 nginx.http.sock 파일을 httpd_sys_content_t로 라벨링하도록 설정되어 있지 않습니다. 두 가지 선택지가 있습니다.

첫째는 nginx를 permissive 모드로 실행하여 SELinux 보호를 제거하는 것입니다: semanage permissive -a httpd_t

그러나, 이는 아마도 가장 관련이 있는 서비스에서 SELinux 보호를 제거합니다! SELinux를 활성화된 상태로 유지하려면, nginx가 오류 페이지에 액세스할 수 있도록 허용하고 unix 도메인 소켓을 통한 프록시에서 포트(몇 µs 더 느리지만, 사용자에게는 눈에 띄지 않을 것입니다)로 전환해야 합니다.

먼저, nginx가 오류 페이지에 액세스할 수 있도록 허용하기 위해 이 명령들을 실행하십시오:

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를 사용하지 마십시오 — SSL은 외부 nginx에서 종료되었으며, X-Forwarded-Proto 헤더는 요청이 https를 통해 들어왔음을 Discourse에 알립니다. 포트 8008 (또는 선택한 다른 포트)가 방화벽 설정에 의해 공개적으로 노출되지 않도록 확인하십시오.

그런 다음, nginx가 네트워크를 통해 컨테이너에 연결할 수 있도록 허용하기 위해 이 명령을 실행하십시오:

setsebool -P httpd_can_network_connect 1

그런 다음, 외부 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;
    # Disable default "Connection: close"
    proxy_set_header "Connection" "";
...

web.socketed.template.yml을 제거하는 것은 real_ip 호출도 제거했으므로, 그것을 다시 추가하십시오. 사용하는 IP 주소 범위가 합리적인지 확인하십시오; Docker의 기본은 정책상 공개 인터넷에서 라우팅되지 않는 172.16* RFC1918 주소 공간을 사용하는 것입니다. app.yml 파일의 run 섹션에, 배포에 적합한 RFC1918 주소 공간 중 하나 이상 또는 다른 것을 선택하여 다음과 같은 것을 추가하십시오:

run:
  - file:
     path: /etc/nginx/conf.d/outlets/server/real-ip-recursive.conf
     chmod: 644
     contents: |
       real_ip_recursive on;
  - file:
     path: /etc/nginx/conf.d/outlets/server/real-ip-header.conf
     chmod: 644
     contents: |
       real_ip_header X-Forwarded-For;
  - file:
     path: /etc/nginx/conf.d/outlets/server/set-real-ip-from.conf
     chmod: 644
     contents: |
       set_real_ip_from 192.168.0.0/16;
       set_real_ip_from 172.16.0.0/12;
       set_real_ip_from 10.0.0.0/8;

이는 속도 제한이 올바르게 작동하는 데 필요하며, 사용자의 등록 및 마지막 사용 IP 주소를 귀속하는 데도 필요합니다.

더 많은 정보:

외부 서비스

저는 Discourse 앞에 Fastly 또는 Cloudflare를 구성하지 않았지만, 다른 사람들은 구성했으며, 호스트에서 실행되는 외부 nginx와 달리, 호스트 시스템이 완전히 다운된 동안에도 유지 관리 페이지를 제공할 수 있습니다. 예를 들어, 호스트에서 시스템 업데이트 중에 재부팅할 때. 이것이 당신에게 가치 있는 것이라면, 이렇게 하십시오:

S3 업로드에 서두르지 마십시오

설정 중에 enable_s3_uploads를 활성화하거나 나중에 마이그레이션하기 전에, 업로드된 이미지에 S3 (또는 동등한 것)를 항상 사용하려는 것이 확실한지 매우 확인하십시오. 이미지에 S3 (s3_endpoint)와 관련된 CDN (s3_cdn_url)을 사용하면, 해당 CDN을 통해 javascript를 서빙하는 결과도 초래한다는 점을 인지하십시오. S3에서 로컬 저장소로의 마이그레이션은 지원되지 않으며, 현재 이를 구현할 구체적인 계획이 없습니다. 이는 전체 백업 및 복원으로도 되돌릴 수 없는 "단방향 문"입니다. S3 또는 유사한 것을 사용한다면, S3 대신 Digital Ocean Spaces를 사용하지 마십시오. 여기 meta에서 그것이 신뢰할 수 없다는 것에 대한 언급이 있습니다.

저는 초기에 제 사이트를 Digital Ocean Spaces와 그 관련 CDN을 통해 이미지를 서빙하도록 옮겼고, "단방향 문"이 잘 이해되지 않았기 때문에 로컬 저장소로 다시 마이그레이션하기 위해 수백 줄의 사용자 정의 코드를 작성해야 했으며, 그 과정에서 제 Discourse 인스턴스에 약간의 손상을 입혔습니다.

더 많은 정보:

Discourse에 CDN을 사용하려면 S3 업로드를 활성화할 필요가 없습니다. Discourse가 자체 이미지를 관리하는 것 앞에 독립적인 CDN (예: Cloudflare, CloudFront, Fastly, GCS CDN)을 사용하는 것을 고려하십시오. Cloudflare가 권장되지 않는다는 경고가 "Rocket Loader"가 JavaScript를 수정하기 때문이라는 것이 제 2차 이해이며, 현재 "Rocket Loader"를 사용하지 않는 한 올바르게 작동한다는 것입니다.

중재를 위한 Discourse 설정

중재가 활성화된 모든 사이트에서, 중재자와 관리자가 주제에 대해 인라인으로 대화할 수 있도록 하는 enable_whispers 구성을 강력히 고려하십시오. 또한, 카테고리 중재자는 최근 Discourse 버전에서 더 많은 기능을 부여받았습니다. 자신의 카테고리를 가진 다양한 주제에 대한 전문가가 있거나, 지원과 같이 기능적으로 분리된 카테고리가 있다면 enable_category_group_moderation을 아는 것이 가치가 있습니다.

계정이 legitimate한지 이해하려고 할 때 지리 위치는 도움이 될 수 있습니다.

Discourse Templates 기능은 중재자에게 정말 도움이 됩니다. 그것은 일반적인 응답에 대해 협력할 수 있게 해줍니다. 우리는 Maker Forums에서 몇 dozen 있습니다. 그것은 그것이 대체하는 이전 “Canned Responses” 플러그인보다 더 많은 기능을 가지고 있습니다.

User Notes 기능은 중재자가 사용자에 대한 노트를 공유하는 데 도움이 됩니다. 이것들을 다음과 같은 용도로 사용할 수 있습니다:

  • “이 사용자를 주시하십시오, 그들은 … 때문에 악의적일 수 있습니다”
  • “이 행동은 의심스러워 보이지만, …에 의해 이것이 legitimate한 사용자임을 검증했습니다”
  • “저는 이미 우려를 해결하기 위해 이 사용자와 대화를 나누고 있습니다, 다른 중재자들은 몰려들 필요가 없습니다.”

정보 접근성

Discourse Solved 플러그인은 사이트 방문자가 해결된 문제를 더 쉽게 식별할 수 있도록 해결된 문제를 표시할 뿐만 아니라, google 검색 결과를 우선시할 수도 있다고 이해합니다.

공개 정보는 비공개 정보보다 접근하기 쉽습니다. Maker Forums에서, 우리의 FAQ는 개인 메시지를 강력히劝阻하고 개인 메시지는 실제로 비공개가 아님을 모두에게 상기시킵니다. 그러나, 기본적으로, 사용자는 다음 메시지를 볼 수 있습니다:

당신은 사용자에게 3번 답장했습니다, 대신 개인 메시지를 보낼 수 있다는 것을 알고 계셨나요?

사용자가 개인 메시지로 가도록 정말로 장려하려면, Admin → Customize → Text로 가서 get_a_room 템플릿을 변경하여 쉼표 오류를 수정하십시오.

Maker Forums와 같이 모든 사람이 혜택을 보기 위해 대화를 공개적으로 유지하려면, Admin → Settings → Other → get_a_room_threshold를 1000000과 같이 더 높게 설정할 수 있습니다.

마찬가지로, 도움이 되는 포럼이 있다면, max_replies_in_first_day 기본 10은 도움이 필요한 대화에서 새 사용자가 답장 예산을 소진하면 개인 메시지로 밀어넣을 수 있습니다. 대화를 개인 메시지로 밀어넣는 것을 피하기 위해 이 설정을 증가시키는 것을 고려하십시오.

사용자 연결, 커뮤니티 구축

몇 가지 플러그인이 사용자를 서로 연결하는 데 도움이 될 수 있습니다.

포럼에 동시 사용자가 너무 많지 않다면, 사람들에게 더 많은 연결감을 주기 위해 Who’s Online 플러그인을 고려하십시오. 로그인한 사용자로 표시를 제한하고, 아마도 최소 신뢰 수준 1에 도달한 사용자만 제한하고 싶을 수 있습니다. whose_online_minimum_display를 매우 높게 설정하고 whos_online_hide_below_minimum_display를 true로 설정하여 아바타에 존재 flair (whos_online_avatar_indicator)를 추가하는 데만 사용할 수 있습니다. 이는 사용자가 문제를 해결하는 동안 빠른 질문과 답변을 지원하고 장려하는 데 지원 포럼에 유용할 수 있습니다.

그러나, 존재감은 양날의 검이 될 수 있습니다. 포럼 사용자의 대부분과 다른 시간에 온라인인 사용자는 외로움을 느끼거나, 포럼이 그들에게 "유령 도시"처럼 느껴질 수 있습니다.

많은 국가의 사용자가 있고, 서로가 더 가용할 가능성이 높은 시기에 대한 힌트를 원한다면, National Flags 플러그인을 고려하고, 사용자에게 프로필에서 국기를 설정하도록 장려하십시오.

번역은 까다로운 것입니다. 같은 언어를 구사하지 않을 때 사람들이 소통하는 데 도움이 되는 것이 편리하겠지만, 현재 (이 글 작성 시점) 무료 티어 서비스를 가진 번역 서비스가 없습니다. 번역 서비스에 비용을 지불하기로 선택한다면, Discourse Translator 플러그인으로 번역을 활성화할 수 있습니다.

백업

시스템 파일에 대해, 최소한 다음을 백업하는 것을 고려하십시오:

  • /var/discourse/containers (discourse 구성 세부 사항용)
  • /var/www (오류 페이지용)
  • /etc/ssl (letsencrypt 구성용, 백업을 복원하는 일부로 certbot을 부트스트랩해야 하는 것을 피하기 위해; 그렇지 않으면 부트스트랩하는 동안 nginx 구성의 SSL 부분을 주석 처리해야 합니다; 인증서가 짧은 유효 기간을 가지므로 백업을 최신으로 유지하는 경우에만 작동합니다)
  • /etc/systemd/system/backup-uploads.service (S3로의 이미지 백업을 수행하기 위해)
  • /usr/local/bin/mc (이미지 백업 도구로 minio-client, 선택하여 사용하는 경우)
  • /root/.mc (minio-client로 이미지 백업 구성)
  • /root/.ssh (들어오는 SSH 세션 인증)

이 파일 중 일부는 Git에 체크인하고 사이트 밖 어딘가로 푸시하여 백업할 수 있습니다. Git에 체크인하는 파일에 비밀 (데이터베이스 비밀번호 등)이 포함되어 있다면, 공개 저장소에 푸시하지 마십시오. 또는, 시스템에서 복사하여 제가 제어하는 감독 시스템에서 Git에 체크인하는 것을 스크립트화할 수 있습니다. /etc/ssl 백업을 최신으로 유지하기 위해 충분히 자주 스크립트화하십시오.

목표는 재해의 경우와 실수의 경우 변경에 대한 기록을 갖기 위해 백업을 갖는 것입니다.

이 파일 대부분에 대한 더 나은 대안은 정본 사본을 다른 곳에 유지하고, Ansible과 같은 도구를 사용하여 시스템에서 구성을 유지하는 것입니다. 이는 백업 후 업데이트하는 것과 마찬가지로 쉽게 만듭니다. 하지만 그렇게 할 계획이라면, 제가 말하지 않아도 알아냈을 가능성이 높습니다!

백업을 위한 Discourse 구성

  • include_thumbnails_in_backups로 썸네일을 백업하십시오. 썸네일 없이 복원하면 재생성하는 데 오랜 시간이 걸립니다. 사이트에 그래픽이 많지 않다면, 썸네일은 무시할 수 있는 공간을 차지합니다. 사이트에 그래픽이 풍부하다면, 썸네일 재생성에 며칠이 걸릴 수 있습니다. 썸네일이 재생성되는 동안, 이메일 알림은 비활성화됩니다. 어느 쪽이든, 백업에서 썸네일을 제외하는 것은 의미가 없습니다.

  • 이미지가 많다면 백업에 이미지를 포함하지 마십시오. 이는 백업을 느리고 다루기 어렵게 만듭니다. 별도로 백업하십시오. 데이터베이스 백업 후 이미지를 백업하면, 백업이 일관성이 있게 됩니다.

  • 백업이 어떻게든 사이트 밖으로 가도록 배열하십시오.

이 페이지는 S3 또는 S3와 같은 것으로 데이터베이스 백업을 설정하는 방법을 보여줍니다:

restic로 백업

discourse를 파일 시스템으로 백업하도록 구성하고, restic로 파일 시스템에서 원격 백업 대상으로 백업하십시오. 여기 샘플 레시피가 있습니다.

# dnf install restic
# mkdir /var/restic
# mkdir /opt/backup
# cd /opt/backup
# cat > backup <<EOF
#!/usr/bin/bash

set -e
. /opt/backup/backup-config

restic --cache-dir=/var/restic \
	backup \
	/etc \
	/root \
	/var/discourse \
	/opt/backup \
	--exclude /var/discourse/shared/data/postgres_data

restic --cache-dir=/var/restic forget \
	--prune --keep-hourly 24 --keep-daily 7 --keep-monthly 3
EOF
# chmod +x backup

이러한 세부 사항은 restic 대상(target)에 따라 달라집니다. 모든 대상이 AWS 환경 변수를 사용하는 것은 아니므로 restic 문서를 읽어보시기 바랍니다.

# cat > backup-config <<EOF
### 이 세부 사항은 구성하는 restic 대상에 따라 다르므로 변경하세요
export AWS_ACCESS_KEY_ID=yours-here
export AWS_SECRET_ACCESS_KEY=same
export RESTIC_PASSWORD_FILE=/root/restic-password
export RESTIC_REPOSITORY=see-the-restic-documentation
EOF
# {$EDITOR:-nano} /root/restic-password

/root/restic-password에 입력한 것의 사본을 저장해 두어야 합니다. 그렇지 않으면 백업을 읽을 수 없게 됩니다! 비밀번호 볼트를 사용하세요.

마지막으로, 서비스를 생성하고 restic 저장소를 초기화한 후, 백업이 시작되도록 타이머를 설정합니다.

# cat > /etc/systemd/system/backup.service <<EOF
[Unit]
Description=Back up to remote target
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
StandardOutput=file:/var/log/backup.out
StandardError=file:/var/log/backup.err
WorkingDirectory=/var/discourse
ExecStart=/opt/backup/backup

[Install]
WantedBy=multi-user.target
EOF
# cat > /etc/systemd/system/backup.timer <<EOF
[Unit]
Description=Regular system backups

[Timer]
Persistent=true
OnCalendar=00/4:30:00
Unit=backup.service

[Install]
WantedBy=timers.target
EOF

# systemctl daemon-reload
# . /opt/backup/backup-config
# restic init
# /opt/backup/backup
# systemctl enable backup.timer

이 과정을 거친 후, 초기 백업이 생성되었음을 확인할 수 있어야 합니다.

# restic snapshots

이 방식으로 생성된 restic 백업을 사용하여 restic restore 명령으로 Discourse 서버를 한 시스템에서 다른 시스템으로 성공적으로 이동시킨 경험이 있습니다.

minio 이미지 백업 스트리밍

Restic의 대안으로, 데이터베이스 백업은 S3에 저장할 수 있지만, S3에서 이미지를 서빙하는 것과 별개로 S3 이미지 백업 기능은 없습니다. 대안으로 minio-client를 사용하여 임의의 S3 호환 스토리지로 이미지를 복사할 수 있습니다. 이는 S3와 minio를 포함한 많은 S3 호환 대상이 될 수 있지만, Ceph 파일 시스템 위에 구축되어 S3와 동일한 방식으로 ListObjectsV2 API를 구현하지 않으므로 DigitalOcean Spaces는 포함되지 않습니다.

S3에서 공개 액세스를 차단하는 버킷을 생성하세요. (권한공개 액세스 차단은 AWS에서 이를 올바르게 설정하는 쉬운 방법입니다).

어떤 방식으로든 minio-client(mc)를 설치합니다. 여기에는 하나의 방법이 있습니다.

curl https://dl.min.io/client/mc/release/linux-amd64/mc > /usr/local/bin/mc && chmod +x /usr/local/bin/mc

다음과 같은 명령을 사용하여 backup이라는 별명을 가진 minio-client를 구성합니다:

# mc alias set backup https://s3.amazonaws.com ACCESSKEY SECRETKEY --api S3v4
# mc mirror /var/discourse/shared/standalone/uploads backup/UPLOADS-BACKUP-BUCKET

그런 다음 다음과 같이 /etc/systemd/system/backup-uploads.service 서비스를 생성합니다

[Unit]
Description=Neartime remote backup sync of discourse uploads
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=600
User=root
ExecStart=/usr/local/bin/mc mirror --overwrite -a --watch /var/discourse/shared/app/uploads backup/UPLOADS-BACKUP-BUCKET

[Install]
WantedBy=multi-user.target

여기서 UPLOADS-BACKUP-BUCKET은 discourse가 데이터베이스 백업을 업로드하도록 구성하는 s3_backup_bucket과 다른 버킷이어야 합니다. 또한, 표준 멀티컨테이너 배포를 사용하는 경우 경로는 /var/discourse/shared/web_only/uploads가 됩니다.

# systemctl enable backup-uploads
# systemctl start backup-uploads
# journalctl -fu backup-uploads

테스트 이미지를 업로드하고 원본 이미지와 최적화된 이미지가 성공적으로 백업되었음을 나타내는 줄이 보이는지 확인하세요. Control-C 키를 누르면 journalctl의 follow 모드에서 나옵니다.

복구

이 글을 쓰는 시점까지 이 계획을 테스트해 본 적이 없습니다. 이 요약에는 누락된 부분이 있을 수 있습니다.

  • 백업된 모든 파일을 일반적으로 복구
  • nginx 시작 (이제 유지보수 페이지가 표시됩니다)
  • /var/discourse/containers의 복구된 파일을 사용하여 Discourse의 일반 배포 수행
  • 백업에서 복구하지 않은 경우 /usr/local/bin/mc에 minio-client 설치
  • /root/mc를 백업하지 않은 경우, 백업 별명을 설정합니다: # mc alias set backup https://s3.amazonaws.com ACCESSKEY SECRETKEY --api S3v4
  • # mc cp backup/UPLOADS-BACKUP-BUCKET /var/discourse/shared/app/uploads
  • 최신 데이터베이스 백업을 복구합니다. Restore a backup from the command line 을 권장합니다
  • 사이트가 정상 작동하는 것을 확인한 이후에만, 위에서 문서화된 대로 S3로의 업로드 백업을 다시 구성합니다.

postgresql 백업 스트리밍

미래에, 연속 WAL 아카이빙을 사용하여 minio-client로 postgresql의 archive-command를 통해 거의 즉각적인 Postgres 백업을 스트리밍하도록 하는 구성을 생성하고 테스트하여 제공할 수 있습니다. 이는 업로드 백업 스트리밍과 유사합니다.

성능 모니터링

성능 모니터링에는 최소 두 가지 접근 방식이 있습니다.

Prometheus 컨테이너

Prometheus를 설정하고, 동일한 시스템에서 실행하는 경우 prometheus 로그를 /var/discourse/shared/prometheus에 저장하세요. Prometheus 파일은 크게增长할 수 있으며, 루트 파일 시스템을 가득 채우기를 원하지 않을 것입니다. 또한, 더 새로운 호스트 시스템(더 큰 VM으로 업그레이드하거나 더 새로운 운영체제 설치의 VM)으로 이동할 때 이 파일들을 함께 가져오기를 원할 가능성이 높습니다.

Prometheus를 discourse 시스템(또는 공용 인터넷의 다른 어디든)에 배포하는 경우, 그 앞에 보안을 구성하세요. 그렇게 설치된 경우, 다음과 같은 nginx 구성이 하나의 옵션이 될 수 있습니다:

  location /prometheus/ {
    auth_basic "Prometheus";
    auth_basic_user_file /etc/nginx/prometheus-htpasswd;
    proxy_pass http://localhost:9090/;
  }

Sysstat

Prometheus가 너무 부담스럽다면, 대신 sysstat을 사용하는 것을 고려해 보세요.

  • dnf install sysstat (또는 debian 및 파생 배포판에서는 apt install sysstat)
  • systemctl enable --now sysstat
  • systemctl enable --now sysstat-collect.timer
  • systemctl enable --now sysstat-summary.timer
  • systemctl edit sysstat-collect.timer를 실행하고 OnCalendar=*:00/10OnCalendar=*:00/2로 변경
  • /etc/default/sysstat이 존재하면 falsetrue로 변경

이후 sar 명령을 사용하면 자원이 부족한 경우가 있는지 때때로 알려줄 수 있습니다.

기타 리소스

Discourse를 내부 커뮤니케이션의 주요 형태로 내부적으로 사용하는 것에 대한 보완적이고(더 간결한) 토론입니다.

54개의 좋아요

Hi, thank you for this howto

What are your current CPU (cores) and RAM?
What are your current settings:

  db_shared_buffers: "xGB"
  db_work_mem: "xMB"
  UNICORN_WORKERS:

2 vCPUs (L5640 Xeon), 4GB RAM — but thanks to the massive import we have larger content per simultaneous user than typical Discourse that has been built entirely organically. We rarely have more than 5 simultaneous users.

I have not currently set db_work_mem in data.yml but it looks like it’s set to 10MB in /etc/postgresql/13/main/postgresql.conf. In my data.yml I have set db_shared_buffers: "768MB" but now I see that /etc/postgresql/13/main/postgresql.conf in my data container says shared_buffers = 512MB which surprises me. Apparently I didn’t rebuild my data container after my last change. :roll_eyes: I made the configuration change before adding prometheus (using more memory) so before I change that I will probably move prometheus off that server instance.

In the app, I have set UNICORN_WORKERS: 4

1개의 좋아요

thanks @mcdanlj for all these goodies. Any advice on periodic/scheduled maintenance? like weekly/monthly restarts? or up/down monitoring with automatic restart? Any other periodic manual or automated maintenance?

1개의 좋아요

@jaffadog Watch the release-notes tag (it’s the bell at the upper right; I use “Watching First Post”) and/or add https://meta.discourse.org/tag/release-notes.rss to your RSS feed to know when there are releases. This is typically monthly for the app, which covers monthly restart. I don’t restart more frequently on a schedule. Also:

If you use letsencrypt and mail_receiver, you probably should set it up to restart mail_receiver after getting a new cert.

That’s all that comes to mind at the moment. Thanks for asking, and I incorporated how to watch release-notes and more specifics on restarting into the main text. I think that it’s all now fully covered in the original post.

I augmented the update instructions to pull updates in /var/discourse in order to change the base image version, because in looking at Update base image for polkit vulnerability I realized not being explicit about this step might be misleading. I was previously thinking of it as one of those things that’s part of the baseline documentation. The launcher script contains a specific reference to the base image version, and until you git pull you will be building on top of an older base image, and not be running what has been tested. (Look for image= near the top of the file.)

1개의 좋아요

It’s worse than that: it’s configured by default to delete inactive level 0 accounts after some period. In my case, that was really not what I wanted! Check “clean up inactive users after days” and set it to zero, or a really large number.

2개의 좋아요

I looked at that, but as far I understood it means that they never responded to the “confirm your email address” flow and so wouldn’t be getting emails anyway. I don’t think “inactive” here means “not logging into the site” but would like to know whether I’m wrong.

No, “inactive” does not mean active=false here.

It means users where all of the below apply to:

  • trust level 0
  • no posts
  • not an admin, not a moderator
  • last seen longer than X days ago.

And yes, that wording is indeed confusing, although the setting does explain it somewhat (“trust level 0 without any posts”).

8개의 좋아요

Actually I was confusing different settings and had " clean up unused staged users after days" in mind. I had already set “clean up inactive users after days” to zero in Maker Forums long ago, and had failed to mention it here; I must have missed it in my audit of changed site settings when I was looking for things that might be of general interest. Thanks to both of you, @Ed_S and @RGJ! I have updated the post with another paragraph on enabling lurking. :smiling_face:

4개의 좋아요

Hi @mcdanlj thanks for your excellent insight that you share here. Regarding a 2-container installation, I’m confused about how that gives an advantage in reducing downtime when upgrades are performed. On my test installation, an upgrade via the GUI /admin/upgrade#/upgrade/all interface does take several minutes as you describe, but the site remains operable for users during the whole process.

When you have to rebuild from the command line the two container installation let’s you bootstrap the new image while the old one continues to run.

2개의 좋아요

As usual, @pfaffman is quicker than I am, and knows his stuff. :smiley:

I never do upgrades from the GUI. That way every time I update, I will include any system security updates in the underlying containers on top of which Discourse is running. That’s saying it’s bad to use the GUI, nor is it a recommendation for everyone else. The GUI update have slightly lower downtime (restarting the web container does bip service momentarily, which one of several reasons to consider this together with external nginx) so it’s a trade-off, and I have taken the road less traveled.

A single-container installation has longer outages more often, if you regularly update for security updates as well as the occasional database version updates as Discourse takes advantage of new Postgresql features. Without reviewing actual data, my gut feeling is that there’s a reason to rebuild that 3-4 times per year. If that amount of downtime is OK from your perspective, there’s not a lot of reason to take on the complexity of the 2-container deployment.

4개의 좋아요

That’s very kind, but this is about your opinions. :wink:

Me neither. Except with my dashboard, that has a bunch of extra stuff in the container (like Ansible, and I can’t remember exactly what all), and if someone is running an upgrade using dashboard.literatecomputing.com and then I destroy that container, their rebuild gets terminated, which is potentially problematic. So I’ve been doing a few more docker_manager upgrades lately, and they are pretty darn slick.

Not really. It’s at least mostly the case that if there’s a new base image docker_manager will force you to get it (at least, it’ll try).

That’s about right. FWIW, and I don’t recommend it, but I’ve interacted with a bunch of people who did zero upgrades for years without any problems.

1개의 좋아요

Yeah, there’s no question that the in-container updates are well implemented!

Yes, that’s what I meant. :tada:

2개의 좋아요

Hi again, thanks to both of you for your insight. So I’m still trying to decide what I’m going to do for my production setup. I understand in theory why a 2-container installation would lead to less downtime. But I’m still seeing hardly any downtime with the GUI update mechanism. I just now timed it, I had a docker-manager update and Discourse was 22 commits behind. The whole process took under 5 minutes and during the whole process the forum was fully operable. Granted there were no PostgreSQL updates this time, but if it were necessary to update that too then I would be looking at the maximum amount of downtime even with the 2-container method, correct? So if I’m understanding correctly, the 2-container method only reduces downtime when an SSH login and app container rebuild is necessary due to a configuration change (add/remove plugins, etc.)? I don’t foresee frequent changes to my production configuration, so I don’t see any potential reduction in downtime in my case. Or do I also need to SSH in and rebuild the containers to apply certain kinds of feature/security updates?

That’s fine.

Yes, you have the full downtime every time you update postgresql (every year or two I suppose, plus and security updates to PostgreSQL itself, not that they have been that frequent).

Before I changed, I had a couple of failures doing the GUI updates, but no longer remember the details; ancient history.

The GUI updates do not update the base image, so all security updates in the provided software, like image processing, won’t be applied there.

I like rebuilding the app container fast as my normal mode to consume security updates to the app container when available so that I would never put that off for a 10-minute downtime to rebuild everything. That’s why git pull in the launcher is part of the first part of me applying every update; so that if the base image with things like image processing programs has been updated, they get applied without me even having to think to ask whether there are security updates to apply. :smiling_face:

But ultimately, I personally find the two-containers approach simpler for me, and I absolutely am not trying to talk other people into it. If it doesn’t seem simpler to you based on your knowledge and experience, do not do it just because my personal opinionated guide identifies it as useful in a certain context. :grin:

2개의 좋아요

Gotcha! :wink: I tend to have strong opinions on tech implementation details too, but I appreciate your additional perspective.

Hmm, so is this still the case?

At any rate in my experience with traditional forums installed without containerization on a LAMP/LEMP stack, the typical vulnerability / attack vector that leads to real-world compromised websites is almost always in the web app’s code or in one of its web development frameworks. So I tend to have more of a sense of urgency toward updates to the Discourse codebase, which appear to be handled by the GUI, so I guess I’m leaning that way.


By the way, on the subject of downtime, a quick plug for Clear Linux: I started testing it due to its low-level optimizations at pure number crunching to try to shave a few hours off my huge forum import process. There may indeed be some speed improvements for that case, but in general, holy mackerel, it’s crazy fast at rebooting, especially as a KVM guest. On the cheapest tier VPS I can reboot and log back into SSH in barely 5 seconds. So I’m looking forward to using that when there are important host OS updates.

Yes. But a few times a year you must to a command line rebuild because some component in the container has been updated. And then you’ll have the 5-15 minutes of downtime. I pretty much only do upgrades from the command line (except on my dashboard where I might update just the dashboard plugin several times in a day), and I have a bunch of customers for whom I do those command line updates when they are required (presumably they are doing updates via the web interface, but often they don’t).

2개의 좋아요

Does the Discourse dashboard notify specifically about that sort of required update? Or should I keep an eye on Debian’s PSAs?